@apify/ui-library 1.132.1 → 1.133.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +1 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/src/components/link.d.ts +3 -0
- package/dist/src/components/link.d.ts.map +1 -1
- package/dist/src/components/link.js +2 -2
- package/dist/src/components/link.js.map +1 -1
- package/dist/src/components/table/index.d.ts +17 -0
- package/dist/src/components/table/index.d.ts.map +1 -0
- package/dist/src/components/table/index.js +16 -0
- package/dist/src/components/table/index.js.map +1 -0
- package/dist/src/components/table/table.context.d.ts +3 -0
- package/dist/src/components/table/table.context.d.ts.map +1 -0
- package/dist/src/components/table/table.context.js +3 -0
- package/dist/src/components/table/table.context.js.map +1 -0
- package/dist/src/components/table/table.d.ts +3 -0
- package/dist/src/components/table/table.d.ts.map +1 -0
- package/dist/src/components/table/table.js +6 -0
- package/dist/src/components/table/table.js.map +1 -0
- package/dist/src/components/table/table.styled.d.ts +30 -0
- package/dist/src/components/table/table.styled.d.ts.map +1 -0
- package/dist/src/components/table/table.styled.js +194 -0
- package/dist/src/components/table/table.styled.js.map +1 -0
- package/dist/src/components/table/table_body.d.ts +3 -0
- package/dist/src/components/table/table_body.d.ts.map +1 -0
- package/dist/src/components/table/table_body.js +6 -0
- package/dist/src/components/table/table_body.js.map +1 -0
- package/dist/src/components/table/table_cell.d.ts +10 -0
- package/dist/src/components/table/table_cell.d.ts.map +1 -0
- package/dist/src/components/table/table_cell.js +18 -0
- package/dist/src/components/table/table_cell.js.map +1 -0
- package/dist/src/components/table/table_empty_row.d.ts +6 -0
- package/dist/src/components/table/table_empty_row.d.ts.map +1 -0
- package/dist/src/components/table/table_empty_row.js +5 -0
- package/dist/src/components/table/table_empty_row.js.map +1 -0
- package/dist/src/components/table/table_error_row.d.ts +7 -0
- package/dist/src/components/table/table_error_row.d.ts.map +1 -0
- package/dist/src/components/table/table_error_row.js +6 -0
- package/dist/src/components/table/table_error_row.js.map +1 -0
- package/dist/src/components/table/table_expansion_row.d.ts +10 -0
- package/dist/src/components/table/table_expansion_row.d.ts.map +1 -0
- package/dist/src/components/table/table_expansion_row.js +10 -0
- package/dist/src/components/table/table_expansion_row.js.map +1 -0
- package/dist/src/components/table/table_foot.d.ts +3 -0
- package/dist/src/components/table/table_foot.d.ts.map +1 -0
- package/dist/src/components/table/table_foot.js +6 -0
- package/dist/src/components/table/table_foot.js.map +1 -0
- package/dist/src/components/table/table_head.d.ts +3 -0
- package/dist/src/components/table/table_head.d.ts.map +1 -0
- package/dist/src/components/table/table_head.js +6 -0
- package/dist/src/components/table/table_head.js.map +1 -0
- package/dist/src/components/table/table_head_cell.d.ts +3 -0
- package/dist/src/components/table/table_head_cell.d.ts.map +1 -0
- package/dist/src/components/table/table_head_cell.js +6 -0
- package/dist/src/components/table/table_head_cell.js.map +1 -0
- package/dist/src/components/table/table_head_row.d.ts +3 -0
- package/dist/src/components/table/table_head_row.d.ts.map +1 -0
- package/dist/src/components/table/table_head_row.js +6 -0
- package/dist/src/components/table/table_head_row.js.map +1 -0
- package/dist/src/components/table/table_loading_row.d.ts +6 -0
- package/dist/src/components/table/table_loading_row.d.ts.map +1 -0
- package/dist/src/components/table/table_loading_row.js +6 -0
- package/dist/src/components/table/table_loading_row.js.map +1 -0
- package/dist/src/components/table/table_row.d.ts +4 -0
- package/dist/src/components/table/table_row.d.ts.map +1 -0
- package/dist/src/components/table/table_row.js +12 -0
- package/dist/src/components/table/table_row.js.map +1 -0
- package/dist/src/components/table/table_test_ids.d.ts +16 -0
- package/dist/src/components/table/table_test_ids.d.ts.map +1 -0
- package/dist/src/components/table/table_test_ids.js +16 -0
- package/dist/src/components/table/table_test_ids.js.map +1 -0
- package/dist/src/components/table/table_wrapper.d.ts +4 -0
- package/dist/src/components/table/table_wrapper.d.ts.map +1 -0
- package/dist/src/components/table/table_wrapper.js +49 -0
- package/dist/src/components/table/table_wrapper.js.map +1 -0
- package/dist/src/components/table/types.d.ts +21 -0
- package/dist/src/components/table/types.d.ts.map +1 -0
- package/dist/src/components/table/types.js +2 -0
- package/dist/src/components/table/types.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/components/index.ts +1 -0
- package/src/components/link.stories.tsx +12 -0
- package/src/components/link.tsx +9 -0
- package/src/components/table/index.ts +16 -0
- package/src/components/table/table.context.ts +5 -0
- package/src/components/table/table.stories.tsx +258 -0
- package/src/components/table/table.styled.ts +207 -0
- package/src/components/table/table.tsx +9 -0
- package/src/components/table/table_body.tsx +9 -0
- package/src/components/table/table_cell.tsx +28 -0
- package/src/components/table/table_empty_row.tsx +12 -0
- package/src/components/table/table_error_row.tsx +13 -0
- package/src/components/table/table_expansion_row.tsx +24 -0
- package/src/components/table/table_foot.tsx +9 -0
- package/src/components/table/table_head.tsx +9 -0
- package/src/components/table/table_head_cell.tsx +9 -0
- package/src/components/table/table_head_row.tsx +9 -0
- package/src/components/table/table_loading_row.tsx +13 -0
- package/src/components/table/table_row.tsx +30 -0
- package/src/components/table/table_test_ids.ts +15 -0
- package/src/components/table/table_wrapper.tsx +71 -0
- package/src/components/table/types.ts +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apify/ui-library",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.133.0",
|
|
4
4
|
"description": "React UI library used by apify.com",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"postpublish": "npm run clean"
|
|
24
24
|
},
|
|
25
25
|
"//": [
|
|
26
|
-
"Storybook for the components lives in a separate package
|
|
26
|
+
"Storybook for the components lives in a separate package ui-storybook.",
|
|
27
27
|
"It's not nice, but helps us to get around the problem of multiple react instances."
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"src",
|
|
71
71
|
"style"
|
|
72
72
|
],
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "4df09cdffad169b379e062cc4dbc1641d8a6f0c7"
|
|
74
74
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -69,6 +69,18 @@ export default {
|
|
|
69
69
|
control: 'text',
|
|
70
70
|
description: 'The link text or content to display',
|
|
71
71
|
},
|
|
72
|
+
ariaHidden: {
|
|
73
|
+
control: 'boolean',
|
|
74
|
+
description: 'Whether the link should be hidden from screen readers (useful for decorative links)',
|
|
75
|
+
},
|
|
76
|
+
ariaLabel: {
|
|
77
|
+
control: 'text',
|
|
78
|
+
description: 'Accessible label for screen readers when the link text is not descriptive',
|
|
79
|
+
},
|
|
80
|
+
tabIndex: {
|
|
81
|
+
control: 'number',
|
|
82
|
+
description: 'Tab index for keyboard navigation (default is 0)',
|
|
83
|
+
},
|
|
72
84
|
},
|
|
73
85
|
parameters: {
|
|
74
86
|
design: {
|
package/src/components/link.tsx
CHANGED
|
@@ -19,6 +19,9 @@ export interface RegularLinkProps {
|
|
|
19
19
|
target?: string,
|
|
20
20
|
trackingId?: string,
|
|
21
21
|
trackingData?: object,
|
|
22
|
+
tabIndex?: number,
|
|
23
|
+
ariaHidden?: boolean,
|
|
24
|
+
ariaLabel?: string,
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -104,6 +107,9 @@ export const Link = forwardRef<HTMLElement, LinkProps>(({
|
|
|
104
107
|
onClick,
|
|
105
108
|
trackingId,
|
|
106
109
|
trackingData,
|
|
110
|
+
tabIndex,
|
|
111
|
+
ariaHidden,
|
|
112
|
+
ariaLabel,
|
|
107
113
|
...rest
|
|
108
114
|
}, ref) => {
|
|
109
115
|
const {
|
|
@@ -139,6 +145,9 @@ export const Link = forwardRef<HTMLElement, LinkProps>(({
|
|
|
139
145
|
target={target || (isExternal ? '_blank' : '_self')}
|
|
140
146
|
onClick={trackedOnClick}
|
|
141
147
|
ref={ref}
|
|
148
|
+
tabIndex={tabIndex}
|
|
149
|
+
aria-hidden={ariaHidden}
|
|
150
|
+
aria-label={ariaLabel}
|
|
142
151
|
{...rest}
|
|
143
152
|
>
|
|
144
153
|
{children}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { Table } from './table.js';
|
|
2
|
+
export { TableHead } from './table_head.js';
|
|
3
|
+
export { TableBody } from './table_body.js';
|
|
4
|
+
export { TableFoot } from './table_foot.js';
|
|
5
|
+
export { TableHeadRow } from './table_head_row.js';
|
|
6
|
+
export { TableHeadCell } from './table_head_cell.js';
|
|
7
|
+
export { TableRow } from './table_row.js';
|
|
8
|
+
export { TableCell, TableCellLink } from './table_cell.js';
|
|
9
|
+
export { HorizontallyScrollableTableWrapper } from './table_wrapper.js';
|
|
10
|
+
export { TableExpansionRow } from './table_expansion_row.js';
|
|
11
|
+
export { TableEmptyRow } from './table_empty_row.js';
|
|
12
|
+
export { TableLoadingRow } from './table_loading_row.js';
|
|
13
|
+
export { TableErrorRow } from './table_error_row.js';
|
|
14
|
+
export { tableClassNames } from './table.styled.js';
|
|
15
|
+
export { tableTestIds } from './table_test_ids.js';
|
|
16
|
+
export type { HorizontallyScrollableTableWrapperProps, TableExpansion, TableRowProps } from './types.js';
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
|
|
4
|
+
import { Text } from '../text/index.js';
|
|
5
|
+
import { Table } from './table.js';
|
|
6
|
+
import { TableBody } from './table_body.js';
|
|
7
|
+
import { TableCell, TableCellLink } from './table_cell.js';
|
|
8
|
+
import { TableEmptyRow } from './table_empty_row.js';
|
|
9
|
+
import { TableErrorRow } from './table_error_row.js';
|
|
10
|
+
import { TableHead } from './table_head.js';
|
|
11
|
+
import { TableHeadCell } from './table_head_cell.js';
|
|
12
|
+
import { TableHeadRow } from './table_head_row.js';
|
|
13
|
+
import { TableLoadingRow } from './table_loading_row.js';
|
|
14
|
+
import { TableRow } from './table_row.js';
|
|
15
|
+
import { HorizontallyScrollableTableWrapper } from './table_wrapper.js';
|
|
16
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Mock data
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
type MockItem = {
|
|
23
|
+
_id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
status: string;
|
|
26
|
+
email: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const MOCK_DATA: MockItem[] = [
|
|
30
|
+
{ _id: '1', name: 'Web Scraper', status: 'active', email: 'alice@example.com' },
|
|
31
|
+
{ _id: '2', name: 'Data Extractor', status: 'inactive', email: 'bob@example.com' },
|
|
32
|
+
{ _id: '3', name: 'Email Sender', status: 'pending', email: 'carol@example.com' },
|
|
33
|
+
{ _id: '4', name: 'PDF Parser', status: 'active', email: 'dave@example.com' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Storybook meta
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const meta: Meta = {
|
|
41
|
+
title: 'UI-Library/Table',
|
|
42
|
+
tags: ['new'],
|
|
43
|
+
component: Table,
|
|
44
|
+
subcomponents: {
|
|
45
|
+
HorizontallyScrollableTableWrapper,
|
|
46
|
+
TableHead,
|
|
47
|
+
TableHeadRow,
|
|
48
|
+
TableHeadCell,
|
|
49
|
+
TableBody,
|
|
50
|
+
TableRow,
|
|
51
|
+
TableCell,
|
|
52
|
+
TableCellLink,
|
|
53
|
+
TableEmptyRow,
|
|
54
|
+
TableLoadingRow,
|
|
55
|
+
TableErrorRow,
|
|
56
|
+
},
|
|
57
|
+
parameters: {
|
|
58
|
+
docs: {
|
|
59
|
+
description: {
|
|
60
|
+
component: 'Compositional table primitives. Compose together to build any table layout. See subcomponents below.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default meta;
|
|
67
|
+
|
|
68
|
+
type Story = StoryObj;
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Stories
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/** Basic table with primitives only. */
|
|
75
|
+
export const Default: Story = {
|
|
76
|
+
render: () => (
|
|
77
|
+
<HorizontallyScrollableTableWrapper>
|
|
78
|
+
<Table>
|
|
79
|
+
<TableHead>
|
|
80
|
+
<TableHeadRow>
|
|
81
|
+
<TableHeadCell>Name</TableHeadCell>
|
|
82
|
+
<TableHeadCell>Email</TableHeadCell>
|
|
83
|
+
<TableHeadCell>Status</TableHeadCell>
|
|
84
|
+
</TableHeadRow>
|
|
85
|
+
</TableHead>
|
|
86
|
+
<TableBody>
|
|
87
|
+
{MOCK_DATA.map((item) => (
|
|
88
|
+
<TableRow key={item._id}>
|
|
89
|
+
<TableCell>{item.name}</TableCell>
|
|
90
|
+
<TableCell>{item.email}</TableCell>
|
|
91
|
+
<TableCell>{item.status}</TableCell>
|
|
92
|
+
</TableRow>
|
|
93
|
+
))}
|
|
94
|
+
</TableBody>
|
|
95
|
+
</Table>
|
|
96
|
+
</HorizontallyScrollableTableWrapper>
|
|
97
|
+
),
|
|
98
|
+
play: async ({ canvasElement }) => {
|
|
99
|
+
const canvas = within(canvasElement);
|
|
100
|
+
|
|
101
|
+
await expect(canvas.getByTestId(tableTestIds.WRAPPER)).toBeInTheDocument();
|
|
102
|
+
await expect(canvas.getByTestId(tableTestIds.TABLE)).toBeInTheDocument();
|
|
103
|
+
await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(MOCK_DATA.length);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Empty state with TableEmptyRow. */
|
|
108
|
+
export const Empty: Story = {
|
|
109
|
+
render: () => (
|
|
110
|
+
<HorizontallyScrollableTableWrapper>
|
|
111
|
+
<Table>
|
|
112
|
+
<TableHead>
|
|
113
|
+
<TableHeadRow>
|
|
114
|
+
<TableHeadCell>Name</TableHeadCell>
|
|
115
|
+
<TableHeadCell>Email</TableHeadCell>
|
|
116
|
+
<TableHeadCell>Status</TableHeadCell>
|
|
117
|
+
</TableHeadRow>
|
|
118
|
+
</TableHead>
|
|
119
|
+
<TableBody>
|
|
120
|
+
<TableEmptyRow colSpan={3}>No data available</TableEmptyRow>
|
|
121
|
+
</TableBody>
|
|
122
|
+
</Table>
|
|
123
|
+
</HorizontallyScrollableTableWrapper>
|
|
124
|
+
),
|
|
125
|
+
play: async ({ canvasElement }) => {
|
|
126
|
+
const canvas = within(canvasElement);
|
|
127
|
+
|
|
128
|
+
await expect(canvas.getByTestId(tableTestIds.EMPTY_ROW)).toBeInTheDocument();
|
|
129
|
+
await expect(canvas.queryAllByTestId(tableTestIds.ROW).length).toBe(0);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** Loading state with TableLoadingRow. */
|
|
134
|
+
export const Loading: Story = {
|
|
135
|
+
render: () => (
|
|
136
|
+
<HorizontallyScrollableTableWrapper>
|
|
137
|
+
<Table>
|
|
138
|
+
<TableHead>
|
|
139
|
+
<TableHeadRow>
|
|
140
|
+
<TableHeadCell>Name</TableHeadCell>
|
|
141
|
+
<TableHeadCell>Email</TableHeadCell>
|
|
142
|
+
<TableHeadCell>Status</TableHeadCell>
|
|
143
|
+
</TableHeadRow>
|
|
144
|
+
</TableHead>
|
|
145
|
+
<TableBody>
|
|
146
|
+
<TableLoadingRow colSpan={3} />
|
|
147
|
+
</TableBody>
|
|
148
|
+
</Table>
|
|
149
|
+
</HorizontallyScrollableTableWrapper>
|
|
150
|
+
),
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const canvas = within(canvasElement);
|
|
153
|
+
|
|
154
|
+
await expect(canvas.getByTestId(tableTestIds.LOADING_ROW)).toBeInTheDocument();
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** Error state with TableErrorRow. */
|
|
159
|
+
export const WithError: Story = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<HorizontallyScrollableTableWrapper>
|
|
162
|
+
<Table>
|
|
163
|
+
<TableHead>
|
|
164
|
+
<TableHeadRow>
|
|
165
|
+
<TableHeadCell>Name</TableHeadCell>
|
|
166
|
+
<TableHeadCell>Email</TableHeadCell>
|
|
167
|
+
<TableHeadCell>Status</TableHeadCell>
|
|
168
|
+
</TableHeadRow>
|
|
169
|
+
</TableHead>
|
|
170
|
+
<TableBody>
|
|
171
|
+
<TableErrorRow colSpan={3} error={new globalThis.Error('Connection timed out')} />
|
|
172
|
+
</TableBody>
|
|
173
|
+
</Table>
|
|
174
|
+
</HorizontallyScrollableTableWrapper>
|
|
175
|
+
),
|
|
176
|
+
play: async ({ canvasElement }) => {
|
|
177
|
+
const canvas = within(canvasElement);
|
|
178
|
+
|
|
179
|
+
await expect(canvas.getByTestId(tableTestIds.ERROR_ROW)).toBeInTheDocument();
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/** Nested links — row-level link via `to` prop + inner TableCellLink. */
|
|
184
|
+
export const WithNestedLinks: Story = {
|
|
185
|
+
render: () => (
|
|
186
|
+
<HorizontallyScrollableTableWrapper>
|
|
187
|
+
<Table>
|
|
188
|
+
<TableHead>
|
|
189
|
+
<TableHeadRow>
|
|
190
|
+
<TableHeadCell>Name</TableHeadCell>
|
|
191
|
+
<TableHeadCell>Email</TableHeadCell>
|
|
192
|
+
<TableHeadCell>Status</TableHeadCell>
|
|
193
|
+
</TableHeadRow>
|
|
194
|
+
</TableHead>
|
|
195
|
+
<TableBody>
|
|
196
|
+
{MOCK_DATA.map((item) => (
|
|
197
|
+
<TableRow key={item._id} to={`/items/${item._id}`}>
|
|
198
|
+
<TableCell>
|
|
199
|
+
<TableCellLink to={`/actors/${item._id}`}>{item.name}</TableCellLink>
|
|
200
|
+
</TableCell>
|
|
201
|
+
<TableCell>{item.email}</TableCell>
|
|
202
|
+
<TableCell>{item.status}</TableCell>
|
|
203
|
+
</TableRow>
|
|
204
|
+
))}
|
|
205
|
+
</TableBody>
|
|
206
|
+
</Table>
|
|
207
|
+
</HorizontallyScrollableTableWrapper>
|
|
208
|
+
),
|
|
209
|
+
play: async ({ canvasElement }) => {
|
|
210
|
+
const canvas = within(canvasElement);
|
|
211
|
+
|
|
212
|
+
await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(MOCK_DATA.length);
|
|
213
|
+
|
|
214
|
+
// Each cell in a row with `to` should have an overlay link + first cell has inner link
|
|
215
|
+
const cells = canvas.getAllByTestId(tableTestIds.CELL);
|
|
216
|
+
const firstCellLinks = cells[0].querySelectorAll('a');
|
|
217
|
+
await expect(firstCellLinks.length).toBe(2);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** Direct inline data — no hooks, no data fetching. */
|
|
222
|
+
export const DirectData: Story = {
|
|
223
|
+
render: () => {
|
|
224
|
+
const items = [
|
|
225
|
+
{ _id: '1', key: 'API_TOKEN', value: '••••••••', updatedAt: '2025-04-01' },
|
|
226
|
+
{ _id: '2', key: 'BASE_URL', value: 'https://api.example.com', updatedAt: '2025-03-15' },
|
|
227
|
+
{ _id: '3', key: 'TIMEOUT_MS', value: '30000', updatedAt: '2025-02-20' },
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<HorizontallyScrollableTableWrapper>
|
|
232
|
+
<Table>
|
|
233
|
+
<TableHead>
|
|
234
|
+
<TableHeadRow>
|
|
235
|
+
<TableHeadCell>Key</TableHeadCell>
|
|
236
|
+
<TableHeadCell>Value</TableHeadCell>
|
|
237
|
+
<TableHeadCell>Updated</TableHeadCell>
|
|
238
|
+
</TableHeadRow>
|
|
239
|
+
</TableHead>
|
|
240
|
+
<TableBody>
|
|
241
|
+
{items.map((item) => (
|
|
242
|
+
<TableRow key={item._id}>
|
|
243
|
+
<TableCell><Text weight="bold">{item.key}</Text></TableCell>
|
|
244
|
+
<TableCell>{item.value}</TableCell>
|
|
245
|
+
<TableCell>{item.updatedAt}</TableCell>
|
|
246
|
+
</TableRow>
|
|
247
|
+
))}
|
|
248
|
+
</TableBody>
|
|
249
|
+
</Table>
|
|
250
|
+
</HorizontallyScrollableTableWrapper>
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
play: async ({ canvasElement }) => {
|
|
254
|
+
const canvas = within(canvasElement);
|
|
255
|
+
|
|
256
|
+
await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(3);
|
|
257
|
+
},
|
|
258
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { theme } from '../../design_system/theme.js';
|
|
4
|
+
import { Box } from '../box.js';
|
|
5
|
+
|
|
6
|
+
/** z-index for sticky table head cells. Consumers can override via CSS if needed. */
|
|
7
|
+
const STICKY_HEAD_Z_INDEX = 5;
|
|
8
|
+
|
|
9
|
+
export const tableClassNames = {
|
|
10
|
+
TABLE: 'Table',
|
|
11
|
+
HEAD: 'Table-Head',
|
|
12
|
+
BODY: 'Table-Body',
|
|
13
|
+
FOOT: 'Table-Foot',
|
|
14
|
+
|
|
15
|
+
HEAD_ROW: 'Table-Head-Row',
|
|
16
|
+
HEAD_CELL: 'Table-Head-Cell',
|
|
17
|
+
|
|
18
|
+
BODY_ROW: 'Table-Body-Row',
|
|
19
|
+
BODY_ROW_CLICKABLE: 'Table-Body-Row_clickable',
|
|
20
|
+
BODY_ROW_EXPANDED: 'Table-Body-Row_expanded',
|
|
21
|
+
BODY_CELL: 'Table-Body-Cell',
|
|
22
|
+
|
|
23
|
+
FOOT_ROW: 'Table-Foot-Row',
|
|
24
|
+
FOOT_CELL: 'Table-Foot-Cell',
|
|
25
|
+
|
|
26
|
+
ROW_EMPTY: 'Table-Row-Empty',
|
|
27
|
+
ROW_EMPTY_CELL: 'Table-Row-Empty-Cell',
|
|
28
|
+
|
|
29
|
+
ROW_EXPANSION: 'Table-Row-Expansion',
|
|
30
|
+
ROW_EXPANSION_CELL: 'Table-Row-Expansion-Cell',
|
|
31
|
+
|
|
32
|
+
WRAPPER_SCROLLABLE: 'Table-Scrollable-Content',
|
|
33
|
+
WRAPPER_SHADOW_LEFT: 'Table-Scrollable-Shadow-Left',
|
|
34
|
+
WRAPPER_SHADOW_RIGHT: 'Table-Scrollable-Shadow-Right',
|
|
35
|
+
|
|
36
|
+
// Responsive hiding
|
|
37
|
+
HIDDEN_SM: 'Table-Cell_hiddenSM',
|
|
38
|
+
HIDDEN_MD: 'Table-Cell_hiddenMD',
|
|
39
|
+
HIDDEN_LG: 'Table-Cell_hiddenLG',
|
|
40
|
+
|
|
41
|
+
// Nested links
|
|
42
|
+
CELL_OVERLAY_LINK: 'Table-Cell-Overlay-Link',
|
|
43
|
+
CELL_LINK: 'Table-Cell-Link',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const StyledTable = styled.table`
|
|
47
|
+
position: relative;
|
|
48
|
+
border-collapse: separate;
|
|
49
|
+
border-spacing: 0;
|
|
50
|
+
min-width: 100%;
|
|
51
|
+
|
|
52
|
+
/* Head cell rounding */
|
|
53
|
+
.${tableClassNames.HEAD_CELL}:first-child {
|
|
54
|
+
border-top-left-radius: ${theme.radius.radius12};
|
|
55
|
+
}
|
|
56
|
+
.${tableClassNames.HEAD_CELL}:last-child {
|
|
57
|
+
border-top-right-radius: ${theme.radius.radius12};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Generic cell padding */
|
|
61
|
+
.${tableClassNames.HEAD_CELL},
|
|
62
|
+
.${tableClassNames.BODY_CELL},
|
|
63
|
+
.${tableClassNames.ROW_EMPTY_CELL},
|
|
64
|
+
.${tableClassNames.ROW_EXPANSION_CELL} {
|
|
65
|
+
padding: ${theme.space.space8};
|
|
66
|
+
position: relative;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Head styling */
|
|
70
|
+
.${tableClassNames.HEAD_CELL} {
|
|
71
|
+
position: sticky;
|
|
72
|
+
top: 0;
|
|
73
|
+
z-index: ${STICKY_HEAD_Z_INDEX};
|
|
74
|
+
height: 4rem;
|
|
75
|
+
background-color: ${theme.color.neutral.backgroundMuted};
|
|
76
|
+
border-bottom: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
77
|
+
color: ${theme.color.neutral.textMuted};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Body styling */
|
|
81
|
+
.${tableClassNames.BODY_ROW}:hover {
|
|
82
|
+
background-color: ${theme.color.neutral.hover};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.${tableClassNames.BODY_ROW}:not(:first-child) .${tableClassNames.BODY_CELL} {
|
|
86
|
+
border-top: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.${tableClassNames.BODY_ROW_CLICKABLE} {
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Foot styling */
|
|
94
|
+
.${tableClassNames.FOOT_ROW}:not(:first-child) .${tableClassNames.FOOT_CELL} {
|
|
95
|
+
border-top: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Responsive hiding */
|
|
99
|
+
.${tableClassNames.HIDDEN_SM} {
|
|
100
|
+
display: none !important;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media ${theme.device.tablet} {
|
|
104
|
+
.${tableClassNames.HIDDEN_MD} {
|
|
105
|
+
display: none !important;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@media ${theme.device.desktop} {
|
|
110
|
+
.${tableClassNames.HIDDEN_LG} {
|
|
111
|
+
display: none !important;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Expansion row */
|
|
116
|
+
.${tableClassNames.ROW_EXPANSION_CELL} {
|
|
117
|
+
border-top: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
118
|
+
box-shadow: inset 0rem 0.2rem 0.8rem -0.2rem ${theme.color.neutral.separatorSubtle};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Nested links — see https://www.notion.so/apify/How-to-Nested-links-4863b883e9e1498b965531d946721926 */
|
|
122
|
+
.${tableClassNames.CELL_OVERLAY_LINK} {
|
|
123
|
+
position: absolute;
|
|
124
|
+
top: 0;
|
|
125
|
+
left: 0;
|
|
126
|
+
width: 100%;
|
|
127
|
+
height: 100%;
|
|
128
|
+
z-index: 0;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.${tableClassNames.CELL_LINK} {
|
|
133
|
+
position: relative;
|
|
134
|
+
z-index: 1;
|
|
135
|
+
color: ${theme.color.neutral.text};
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
/* Extra padding for easier clicking, compensated by negative margin */
|
|
138
|
+
padding: ${theme.space.space4} ${theme.space.space8};
|
|
139
|
+
margin: -${theme.space.space4} -${theme.space.space8};
|
|
140
|
+
|
|
141
|
+
&:hover {
|
|
142
|
+
z-index: 2;
|
|
143
|
+
color: ${theme.color.primary.text};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Empty row */
|
|
148
|
+
.${tableClassNames.ROW_EMPTY_CELL} {
|
|
149
|
+
text-align: center;
|
|
150
|
+
padding: ${theme.space.space16};
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
export const StyledHorizontallyScrollableTableWrapper = styled(Box)`
|
|
155
|
+
position: relative;
|
|
156
|
+
width: 100%;
|
|
157
|
+
background-color: ${theme.color.neutral.background};
|
|
158
|
+
border: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
159
|
+
border-radius: ${theme.radius.radius12};
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
|
|
162
|
+
.${tableClassNames.WRAPPER_SCROLLABLE} {
|
|
163
|
+
position: relative;
|
|
164
|
+
overflow-x: auto;
|
|
165
|
+
overflow-y: hidden;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&::before,
|
|
169
|
+
&::after {
|
|
170
|
+
content: '';
|
|
171
|
+
position: absolute;
|
|
172
|
+
top: 0;
|
|
173
|
+
bottom: 0;
|
|
174
|
+
width: 1.6rem;
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
opacity: 0;
|
|
177
|
+
transition: opacity 0.3s ease;
|
|
178
|
+
z-index: ${STICKY_HEAD_Z_INDEX + 1};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
&::before {
|
|
182
|
+
left: 0;
|
|
183
|
+
background: linear-gradient(to right, ${theme.color.neutral.separatorSubtle}, transparent);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
&::after {
|
|
187
|
+
right: 0;
|
|
188
|
+
background: linear-gradient(to left, ${theme.color.neutral.separatorSubtle}, transparent);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
&.${tableClassNames.WRAPPER_SHADOW_LEFT}::before {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
&.${tableClassNames.WRAPPER_SHADOW_RIGHT}::after {
|
|
196
|
+
opacity: 1;
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
export const StyledTableFooterWrapper = styled(Box)`
|
|
201
|
+
position: relative;
|
|
202
|
+
width: 100%;
|
|
203
|
+
background-color: ${theme.color.neutral.backgroundMuted};
|
|
204
|
+
border-top: solid 1px ${theme.color.neutral.separatorSubtle};
|
|
205
|
+
overflow-x: auto;
|
|
206
|
+
scrollbar-width: none;
|
|
207
|
+
`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { FC, HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
import { StyledTable, tableClassNames } from './table.styled.js';
|
|
5
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
6
|
+
|
|
7
|
+
export const Table: FC<HTMLAttributes<HTMLTableElement>> = ({ className, ...rest }) => (
|
|
8
|
+
<StyledTable data-test={tableTestIds.TABLE} className={clsx(tableClassNames.TABLE, className)} {...rest} />
|
|
9
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { FC, HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
import { tableClassNames } from './table.styled.js';
|
|
5
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
6
|
+
|
|
7
|
+
export const TableBody: FC<HTMLAttributes<HTMLTableSectionElement>> = ({ className, ...rest }) => (
|
|
8
|
+
<tbody data-test={tableTestIds.BODY} className={clsx(tableClassNames.BODY, className)} {...rest} />
|
|
9
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { FC, TdHTMLAttributes } from 'react';
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Link, type LinkProps } from '../link.js';
|
|
6
|
+
import { TableRowLinkContext } from './table.context.js';
|
|
7
|
+
import { tableClassNames } from './table.styled.js';
|
|
8
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
9
|
+
|
|
10
|
+
export const TableCell: FC<TdHTMLAttributes<HTMLTableCellElement>> = ({ className, children, ...rest }) => {
|
|
11
|
+
const rowLink = useContext(TableRowLinkContext);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<td data-test={tableTestIds.CELL} className={clsx(tableClassNames.BODY_CELL, className)} {...rest}>
|
|
15
|
+
{rowLink && <Link className={tableClassNames.CELL_OVERLAY_LINK} to={rowLink} tabIndex={-1} ariaHidden />}
|
|
16
|
+
{children}
|
|
17
|
+
</td>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Inner link inside a cell whose row has a `to` prop (overlay link).
|
|
23
|
+
* Elevated above the row overlay via `z-index` so it remains clickable and hoverable.
|
|
24
|
+
* Includes extra padding (compensated by negative margin) for easier click targets.
|
|
25
|
+
*/
|
|
26
|
+
export const TableCellLink: FC<LinkProps> = ({ className, ...rest }) => (
|
|
27
|
+
<Link className={clsx(tableClassNames.CELL_LINK, className)} {...rest} />
|
|
28
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { tableClassNames } from './table.styled.js';
|
|
4
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
5
|
+
|
|
6
|
+
export const TableEmptyRow: FC<{ colSpan: number; children: ReactNode }> = ({ colSpan, children }) => (
|
|
7
|
+
<tr data-test={tableTestIds.EMPTY_ROW} className={tableClassNames.ROW_EMPTY}>
|
|
8
|
+
<td colSpan={colSpan} className={tableClassNames.ROW_EMPTY_CELL}>
|
|
9
|
+
{children}
|
|
10
|
+
</td>
|
|
11
|
+
</tr>
|
|
12
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Text } from '../text/index.js';
|
|
4
|
+
import { tableClassNames } from './table.styled.js';
|
|
5
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
6
|
+
|
|
7
|
+
export const TableErrorRow: FC<{ colSpan: number; children?: ReactNode; error?: Error | null }> = ({ colSpan, children, error }) => (
|
|
8
|
+
<tr data-test={tableTestIds.ERROR_ROW} className={tableClassNames.ROW_EMPTY}>
|
|
9
|
+
<td colSpan={colSpan} className={tableClassNames.ROW_EMPTY_CELL}>
|
|
10
|
+
{children ?? <Text color="error">{error?.message ?? 'Failed to load data'}</Text>}
|
|
11
|
+
</td>
|
|
12
|
+
</tr>
|
|
13
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { FC, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { tableClassNames } from './table.styled.js';
|
|
5
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
6
|
+
import type { TableExpansion } from './types.js';
|
|
7
|
+
|
|
8
|
+
export const TableExpansionRow: FC<{
|
|
9
|
+
colSpan: number;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
expansion: TableExpansion;
|
|
12
|
+
itemId: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}> = ({ colSpan, children, expansion, itemId, className }) => {
|
|
15
|
+
if (!expansion.isExpanded(itemId)) return null;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<tr data-test={tableTestIds.EXPANSION_ROW} className={clsx(tableClassNames.ROW_EXPANSION, className)}>
|
|
19
|
+
<td colSpan={colSpan} className={tableClassNames.ROW_EXPANSION_CELL}>
|
|
20
|
+
{children}
|
|
21
|
+
</td>
|
|
22
|
+
</tr>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { FC, HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
import { tableClassNames } from './table.styled.js';
|
|
5
|
+
import { tableTestIds } from './table_test_ids.js';
|
|
6
|
+
|
|
7
|
+
export const TableFoot: FC<HTMLAttributes<HTMLTableSectionElement>> = ({ className, ...rest }) => (
|
|
8
|
+
<tfoot data-test={tableTestIds.FOOT} className={clsx(tableClassNames.FOOT, className)} {...rest} />
|
|
9
|
+
);
|