Skip to content

Commit e8b3e14

Browse files
authored
fix(AnalyticalTable): allow focusing "no-data" & placeholder components (#7286)
Fixes #7282
1 parent 9c675d5 commit e8b3e14

File tree

8 files changed

+129
-44
lines changed

8 files changed

+129
-44
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ Here you can find an example using a `MultiComboBox` with multiple values as fil
499499
500500
<details>
501501
502-
<summary>Show Code</summary>
502+
<summary>Show static code</summary>
503503
504504
```jsx
505505
const filterFn = (rows, accessor, filterValue) => {
@@ -562,6 +562,10 @@ const TableComponent = () => {
562562
563563
</details>
564564
565+
## Table Without Data
566+
567+
<Canvas of={ComponentStories.NoData} />
568+
565569
## Kitchen Sink
566570
567571
<Canvas of={ComponentStories.KitchenSink} />

packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,14 @@
243243
}
244244

245245
.noDataContainer {
246+
box-sizing: border-box;
247+
&:focus {
248+
outline-offset: -3px;
249+
outline: var(--sapContent_FocusColor) solid 0.125rem;
250+
}
251+
}
252+
253+
.noData {
246254
display: flex;
247255
justify-content: center;
248256
align-items: center;

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react';
44
import '@ui5/webcomponents-icons/dist/delete.js';
55
import '@ui5/webcomponents-icons/dist/edit.js';
66
import '@ui5/webcomponents-icons/dist/settings.js';
7+
import NoDataIllustration from '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
78
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
89
import {
910
AnalyticalTablePopinDisplay,
@@ -17,10 +18,14 @@ import {
1718
TextAlign
1819
} from '../../enums/index.js';
1920
import { Button } from '../../webComponents/Button/index.js';
21+
import { IllustratedMessage } from '../../webComponents/IllustratedMessage/index.js';
2022
import { Label } from '../../webComponents/Label/index.js';
2123
import { MultiComboBox } from '../../webComponents/MultiComboBox/index.js';
2224
import { MultiComboBoxItem } from '../../webComponents/MultiComboBoxItem/index.js';
2325
import { Option } from '../../webComponents/Option/index.js';
26+
import type { SegmentedButtonPropTypes } from '../../webComponents/SegmentedButton/index.js';
27+
import { SegmentedButton } from '../../webComponents/SegmentedButton/index.js';
28+
import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/index.js';
2429
import { Select } from '../../webComponents/Select/index.js';
2530
import { Tag } from '../../webComponents/Tag/index.js';
2631
import { Text } from '../../webComponents/Text/index.js';
@@ -572,6 +577,49 @@ export const CustomFilter: Story = {
572577
}
573578
};
574579

580+
export const NoData: Story = {
581+
args: { visibleRows: 5 },
582+
render(args, context) {
583+
const [selected, setSelected] = useState('noData');
584+
const handleChange: SegmentedButtonPropTypes['onSelectionChange'] = (e) => {
585+
setSelected(e.detail.selectedItems[0].dataset.key);
586+
};
587+
588+
return (
589+
<>
590+
<SegmentedButton onSelectionChange={handleChange} accessibleName="Select data view mode">
591+
<SegmentedButtonItem selected={selected === 'noData'} data-key="noData">
592+
Default NoData Component
593+
</SegmentedButtonItem>
594+
<SegmentedButtonItem selected={selected === 'illustratedMessage'} data-key="illustratedMessage">
595+
IllustratedMessage NoData Component
596+
</SegmentedButtonItem>
597+
<SegmentedButtonItem selected={selected === 'data'} data-key="data">
598+
With Data
599+
</SegmentedButtonItem>
600+
</SegmentedButton>
601+
{context.viewMode === 'story' ? (
602+
<AnalyticalTable
603+
{...args}
604+
data={selected === 'data' ? args.data : []}
605+
NoDataComponent={
606+
selected === 'noData' ? undefined : () => <IllustratedMessage role="gridcell" name={NoDataIllustration} />
607+
}
608+
/>
609+
) : (
610+
<ToggleableTable
611+
{...args}
612+
data={selected === 'data' ? args.data : []}
613+
NoDataComponent={
614+
selected === 'noData' ? undefined : () => <IllustratedMessage role="gridcell" name={NoDataIllustration} />
615+
}
616+
/>
617+
)}
618+
</>
619+
);
620+
}
621+
};
622+
575623
export const KitchenSink: Story = {
576624
args: kitchenSinkArgs,
577625
render(args, context) {

packages/main/src/components/AnalyticalTable/defaults/LoadingComponent/TablePlaceholder.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
}
88
}
99

10+
.container {
11+
background: var(--sapList_Background);
12+
}
13+
1014
.animation {
1115
animation-duration: 2s;
1216
animation-fill-mode: forwards;

packages/main/src/components/AnalyticalTable/defaults/LoadingComponent/TablePlaceholder.tsx

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,52 @@
1-
import { ThemingParameters, useStylesheet } from '@ui5/webcomponents-react-base';
1+
import { useStylesheet } from '@ui5/webcomponents-react-base';
22
import { clsx } from 'clsx';
33
import type { CSSProperties, FC } from 'react';
4+
import type { ColumnType } from '../../types/index.js';
45
import { resolveCellAlignment } from '../../util/index.js';
5-
import { classNames, styleData } from './TablePlaceholder.module.css.js';
6+
import { classNames, content } from './TablePlaceholder.module.css.js';
67

7-
const getArrayOfLength = (len) => Array.from(Array(len).keys());
8+
const getArrayOfLength = (len: number) => Array.from(Array(len).keys());
89

910
interface TablePlaceholderPropTypes {
10-
columns: any[];
11-
rows: number;
1211
style: CSSProperties;
12+
columns: ColumnType[];
13+
rows: number;
14+
pleaseWaitText: string;
1315
}
1416

1517
export const TablePlaceholder: FC<TablePlaceholderPropTypes> = (props) => {
16-
const { columns, rows = 5, style } = props;
18+
const { columns, rows = 5, style, pleaseWaitText } = props;
1719

18-
useStylesheet(styleData, TablePlaceholder.displayName);
20+
useStylesheet(content, TablePlaceholder.displayName);
1921

2022
return (
21-
<div
22-
style={{
23-
backgroundColor: ThemingParameters.sapList_Background,
24-
width: '100%',
25-
...style
26-
}}
27-
data-component-name="AnalyticalTableLoadingPlaceholder"
28-
>
29-
{getArrayOfLength(rows).map((_, index) => {
30-
return (
31-
<div className={classNames.row} key={`row-${index}`}>
32-
{columns.map((col) => {
33-
return (
34-
<div
35-
key={`row${index}-${col.id}`}
36-
className={classNames.cellContainer}
37-
style={{ width: col.totalWidth, ...resolveCellAlignment(col) }}
38-
>
39-
<div className={clsx(classNames.cell, classNames.animation)} />
40-
</div>
41-
);
42-
})}
43-
</div>
44-
);
45-
})}
23+
<div role="gridcell">
24+
<div
25+
style={style}
26+
className={classNames.container}
27+
title={pleaseWaitText}
28+
role="progressbar"
29+
aria-valuetext="Busy"
30+
data-component-name="AnalyticalTableLoadingPlaceholder"
31+
>
32+
{getArrayOfLength(rows).map((_, index) => {
33+
return (
34+
<div className={classNames.row} key={`row-${index}`}>
35+
{columns.map((col) => {
36+
return (
37+
<div
38+
key={`row${index}-${col.id}`}
39+
className={classNames.cellContainer}
40+
style={{ width: col.totalWidth, ...resolveCellAlignment(col) }}
41+
>
42+
<div className={clsx(classNames.cell, classNames.animation)} />
43+
</div>
44+
);
45+
})}
46+
</div>
47+
);
48+
})}
49+
</div>
4650
</div>
4751
);
4852
};

packages/main/src/components/AnalyticalTable/defaults/NoDataComponent/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
export const DefaultNoDataComponent = ({ noDataText, className, style }) => {
1+
interface NoDataComponentProps {
2+
noDataText: string;
3+
className: string;
4+
}
5+
6+
export const DefaultNoDataComponent = ({ noDataText, className }: NoDataComponentProps) => {
27
return (
3-
<div className={className} style={style}>
8+
<div className={className} data-component-name="AnalyticalTableNoData" role="gridcell">
49
{noDataText}
510
</div>
611
);

packages/main/src/components/AnalyticalTable/hooks/useKeyboardNavigation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ const useGetTableProps = (
106106
Object.prototype.hasOwnProperty.call(dataset, 'subcomponentActiveElement') ||
107107
// todo: with the new popover API of ui5wc this might not be necessary anymore
108108
dataset.componentName === 'ATHeaderPopoverList' ||
109-
dataset.componentName === 'ATHeaderPopover'
109+
dataset.componentName === 'ATHeaderPopover' ||
110+
dataset.componentName === 'AnalyticalTableNoDataContainer'
110111
) {
111112
return;
112113
}

packages/main/src/components/AnalyticalTable/index.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
INVALID_TABLE,
4444
LIST_NO_DATA,
4545
NO_DATA_FILTERED,
46+
PLEASE_WAIT,
4647
ROW_COLLAPSED,
4748
ROW_EXPANDED,
4849
SELECT_ALL,
@@ -797,15 +798,25 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
797798
)
798799
);
799800
})}
800-
{loading && rows?.length === 0 && (
801-
<TablePlaceholder columns={visibleColumns} rows={minRows} style={noDataStyles} />
802-
)}
803-
{!loading && rows?.length === 0 && (
804-
<NoDataComponent
805-
noDataText={noDataTextLocal}
806-
className={classNames.noDataContainer}
801+
{rows?.length === 0 && (
802+
<div
807803
style={noDataStyles}
808-
/>
804+
data-component-name="AnalyticalTableNoDataContainer"
805+
role="row"
806+
tabIndex={0}
807+
className={classNames.noDataContainer}
808+
>
809+
{loading ? (
810+
<TablePlaceholder
811+
columns={visibleColumns}
812+
rows={minRows}
813+
style={noDataStyles}
814+
pleaseWaitText={i18nBundle.getText(PLEASE_WAIT)}
815+
/>
816+
) : (
817+
<NoDataComponent noDataText={noDataTextLocal} className={classNames.noData} />
818+
)}
819+
</div>
809820
)}
810821
{rows?.length > 0 && tableRef.current && (
811822
<VirtualTableBodyContainer

0 commit comments

Comments
 (0)