Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dataSmall from '@sb/mockData/Friends50.json';
import dataLarge from '@sb/mockData/Friends500.json';
import type { Meta, StoryObj } from '@storybook/react-vite';
import NoDataIllustration from '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
import '@ui5/webcomponents-icons/dist/sort-ascending.js';
import '@ui5/webcomponents-icons/dist/sort-descending.js';
import '@ui5/webcomponents-icons/dist/reset.js';
Expand All @@ -18,6 +19,7 @@ import {
} from '../../../../enums/index.js';
import { Button } from '../../../../webComponents/Button/index.js';
import { Dialog } from '../../../../webComponents/Dialog/index.js';
import { IllustratedMessage } from '../../../../webComponents/IllustratedMessage/index.js';
import { Input } from '../../../../webComponents/Input/index.js';
import { MultiComboBox } from '../../../../webComponents/MultiComboBox/index.js';
import { MultiComboBoxItem } from '../../../../webComponents/MultiComboBoxItem/index.js';
Expand All @@ -29,7 +31,7 @@ import { Text } from '../../../../webComponents/Text/index.js';
import { ToggleButton } from '../../../../webComponents/ToggleButton/index.js';
import { FlexBox } from '../../../FlexBox/index.js';
import { ObjectStatus } from '../../../ObjectStatus/index.js';
import type { AnalyticalTableColumnDefinition } from '../../index.js';
import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from '../../index.js';
import { AnalyticalTable } from '../../index.js';
import meta from '../AnalyticalTable.stories.js';

Expand Down Expand Up @@ -456,9 +458,16 @@ export const LoadingStates: Story = {
const [loading, setLoading] = useState(false);
const [showOverlay, setShowOverlay] = useState(false);
const [alwaysShowBusyIndicator, setAlwaysShowBusyIndicator] = useState(false);
const [customNoData, setCustomNoData] = useState(false);

const data = useMemo(() => (hasData ? dataSmall : []), [hasData]);

const NoDataComponent: AnalyticalTablePropTypes['NoDataComponent'] = !customNoData
? undefined
: loading
? undefined
: (props) => <IllustratedMessage role={props.accessibleRole} name={NoDataIllustration} />;

return (
<>
<FlexBox style={{ gap: '0.5rem', marginBlockEnd: '0.5rem' }} wrap={FlexBoxWrap.Wrap}>
Expand All @@ -474,13 +483,17 @@ export const LoadingStates: Story = {
<ToggleButton pressed={!hasData} onClick={() => setHasData((prev) => !prev)}>
Empty data
</ToggleButton>
<ToggleButton pressed={customNoData} onClick={() => setCustomNoData((prev) => !prev)}>
Custom NoDataComponent
</ToggleButton>
</FlexBox>
<AnalyticalTable
{...args}
data={data}
loading={loading}
showOverlay={showOverlay}
alwaysShowBusyIndicator={alwaysShowBusyIndicator}
NoDataComponent={NoDataComponent}
loadingDelay={500}
header={`loading: ${loading} | showOverlay: ${showOverlay} | alwaysShowBusyIndicator: ${alwaysShowBusyIndicator} | data: ${hasData ? '50 rows' : 'empty'}`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,20 @@ const ServerSideTable = () => {

The table supports multiple loading indicators: a skeleton placeholder (when `loading` is true and data is empty), a `BusyIndicator` overlay (when `loading` is true and data exists), and `showOverlay` which displays a semi-transparent overlay without a loading indicator. When `loading` is active, user interactions are blocked as well. Set `alwaysShowBusyIndicator` to always show the BusyIndicator instead of the skeleton.

**Recommended:** Set `alwaysShowBusyIndicator` to `true` in most cases. The default skeleton loading indicator is only sufficient when loading times exceed 1 second. See [Fiori Skeleton Loading](https://www.sap.com/design-system/fiori-design-ios/ui-elements/patterns/skeleton-loading/?external) for design guidance.
**Recommended:** Set `alwaysShowBusyIndicator` to `true` in most cases. The default skeleton loading indicator should only be used when loading times exceed 1 second. See [Fiori Skeleton Loading](https://www.sap.com/design-system/fiori-design-ios/v26-4/components/progress-indicators/skeleton-loading) for design guidance.

The following table summarizes which indicator is rendered for the empty and non-empty states:

| `loading` | `data` | `alwaysShowBusyIndicator` | Result |
| --------- | ------ | ------------------------- | ------------------------------------------------------------------------- |
| `true` | filled | `false` | `BusyIndicator` overlay on top of the existing rows |
| `true` | empty | `false` | Skeleton loader (`TablePlaceholder`) — `NoDataComponent` is **not** shown |
| `true` | empty | `true` | `BusyIndicator` overlay on top of the `NoDataComponent` |
| `false` | empty | any | `NoDataComponent` only |

The `BusyIndicator` is an overlay: it is layered on top of the table and dims the content beneath it, but it does not replace the normal render logic. This is why, with `alwaysShowBusyIndicator` and empty data, the `NoDataComponent` is rendered underneath the overlay while loading — there is no skeleton loader in this mode by design.

For the default text `NoDataComponent` this is unobtrusive, but a richer custom empty state — like an `IllustratedMessage` — would flash beneath the overlay during the initial load, before any data has arrived. To avoid this, render the custom component only once loading has finished and fall back to the default "No data" placeholder in the meantime by returning `undefined`. Toggle **Custom NoDataComponent**, **loading**, and **alwaysShowBusyIndicator** in the example below to compare the patterns.

<Canvas sourceState="none" of={FeatureStories.LoadingStates} />

Expand All @@ -396,6 +409,13 @@ The table supports multiple loading indicators: a skeleton placeholder (when `lo

// Overlay without loading indicator
<AnalyticalTable data={data} columns={columns} showOverlay />

// Custom NoDataComponent (e.g. IllustratedMessage) with alwaysShowBusyIndicator:
// guard it so the illustration doesn't flash beneath the overlay while loading.
const NoDataComponent = loading
? undefined // fall back to the default "No data" placeholder during loading
: (props) => <IllustratedMessage role={props.accessibleRole} name={NoDataIllustration} />;
<AnalyticalTable data={[]} columns={columns} loading alwaysShowBusyIndicator NoDataComponent={NoDataComponent} />
```

## Controlled Selection
Expand Down
Loading