uikit-react-public 0.36.1 → 0.37.2

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.
Files changed (40) hide show
  1. package/dist/components/Pagination/Pagination.stories.d.ts +1 -0
  2. package/dist/components/Pagination/PaginationControls.d.ts +2 -1
  3. package/dist/components/Pagination/__tests__/PaginationControls.test.d.ts +1 -0
  4. package/dist/components/Pagination/subcomponents/PaginationCompactControls.d.ts +7 -0
  5. package/dist/components/Pagination/subcomponents/PaginationStandardControls.d.ts +8 -0
  6. package/dist/components/Pagination/subcomponents/index.d.ts +2 -0
  7. package/dist/components/Table/Table.context.d.ts +6 -0
  8. package/dist/components/Table/Table.d.ts +4 -3
  9. package/dist/components/Table/Table.stories.d.ts +3 -3
  10. package/dist/components/Table/Table.types.d.ts +6 -0
  11. package/dist/components/Table/index.d.ts +1 -1
  12. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +8 -1
  13. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +5 -1
  14. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +3 -2
  15. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +4 -2
  16. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +3 -1
  17. package/dist/components/Table/subcomponents/Row.d.ts +8 -1
  18. package/dist/index.js +7219 -6668
  19. package/lib/components/Pagination/Pagination.stories.tsx +9 -0
  20. package/lib/components/Pagination/PaginationControls.tsx +17 -245
  21. package/lib/components/Pagination/__tests__/PaginationControls.test.tsx +213 -0
  22. package/lib/components/Pagination/subcomponents/PaginationCompactControls.tsx +266 -0
  23. package/lib/components/Pagination/subcomponents/PaginationStandardControls.tsx +246 -0
  24. package/lib/components/Pagination/subcomponents/index.ts +2 -0
  25. package/lib/components/Table/Table.context.ts +12 -0
  26. package/lib/components/Table/Table.tsx +91 -12
  27. package/lib/components/Table/Table.types.ts +21 -0
  28. package/lib/components/Table/__tests__/Table.test.tsx +95 -1
  29. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +54 -13
  30. package/lib/components/Table/index.ts +6 -1
  31. package/lib/components/Table/subcomponents/Body.tsx +16 -3
  32. package/lib/components/Table/subcomponents/Cell/Cell.tsx +193 -4
  33. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +63 -3
  34. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +15 -10
  35. package/lib/components/Table/subcomponents/Head.tsx +14 -3
  36. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +17 -4
  37. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +3 -3
  38. package/lib/components/Table/subcomponents/Row.tsx +195 -6
  39. package/lib/components/Table/subcomponents/SortIcon.tsx +4 -4
  40. package/package.json +5 -5
@@ -0,0 +1,266 @@
1
+ import {
2
+ ChangeEvent,
3
+ HTMLAttributes,
4
+ KeyboardEvent,
5
+ memo,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useState,
10
+ } from 'react';
11
+ import { css, cx } from '@emotion/css';
12
+ import useTheme from '../../../theme/useTheme';
13
+ import { PaginationContext } from '../Pagination';
14
+ import Button from '../../Button';
15
+ import Icon from '../../Icon';
16
+ import Input from '../../Input';
17
+ import Text from '../../Text';
18
+ import announce from '../../../utils/announce';
19
+
20
+ export interface PaginationCompactControlsProps extends HTMLAttributes<HTMLDivElement> {
21
+ name: string;
22
+ testId: string;
23
+ }
24
+
25
+ const PaginationCompactControls = ({
26
+ name,
27
+ testId,
28
+ className,
29
+ ...props
30
+ }: PaginationCompactControlsProps) => {
31
+ const [theme] = useTheme();
32
+ const contextValue = useContext(PaginationContext);
33
+ const [pageInputValue, setPageInputValue] = useState(
34
+ String(contextValue?.currentPage ?? 1)
35
+ );
36
+ const [pageInputError, setPageInputError] = useState('');
37
+ const pageInputDescriptionId = useId();
38
+
39
+ useEffect(() => {
40
+ setPageInputValue(String(contextValue?.currentPage ?? 1));
41
+ setPageInputError('');
42
+ }, [contextValue?.currentPage]);
43
+
44
+ if (!contextValue) {
45
+ return null;
46
+ }
47
+
48
+ const {
49
+ mode,
50
+ limit,
51
+ total,
52
+ currentPage,
53
+ trackedMaxPage,
54
+ hasPreviousPage,
55
+ hasNextPage,
56
+ onPageChange,
57
+ onNextPage,
58
+ onPreviousPage,
59
+ } = contextValue;
60
+
61
+ const totalPages =
62
+ typeof total === 'number' ? Math.ceil(total / limit) : undefined;
63
+ const lastPageNumber = totalPages ?? trackedMaxPage;
64
+ const displayedCurrentPage = totalPages === 0 ? 0 : currentPage;
65
+
66
+ const handlePageChange = (newPage: number) => {
67
+ if (mode === 'tracked') {
68
+ if (newPage < 1 || newPage > trackedMaxPage || newPage === currentPage) {
69
+ return;
70
+ }
71
+ onPageChange?.(newPage);
72
+ return;
73
+ }
74
+
75
+ onPageChange?.((newPage - 1) * limit);
76
+ };
77
+
78
+ const handlePreviousPage = () => {
79
+ if (mode === 'tracked' && onPreviousPage) {
80
+ onPreviousPage();
81
+ return;
82
+ }
83
+ handlePageChange(currentPage - 1);
84
+ };
85
+
86
+ const handleNextPage = () => {
87
+ if (mode === 'tracked') {
88
+ onNextPage?.();
89
+ return;
90
+ }
91
+ handlePageChange(currentPage + 1);
92
+ };
93
+
94
+ const handlePageInputChange = (event: ChangeEvent<HTMLInputElement>) => {
95
+ setPageInputValue(event.target.value);
96
+ setPageInputError('');
97
+ };
98
+
99
+ const resetPageInput = () => {
100
+ setPageInputValue(String(currentPage));
101
+ setPageInputError('');
102
+ };
103
+
104
+ const handlePageInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
105
+ if (event.key === 'Escape') {
106
+ resetPageInput();
107
+ event.currentTarget.blur();
108
+ return;
109
+ }
110
+
111
+ if (event.key !== 'Enter') {
112
+ return;
113
+ }
114
+
115
+ event.preventDefault();
116
+
117
+ const isDigitsOnly = /^\d+$/.test(pageInputValue);
118
+ const newPageNumber = Number(pageInputValue);
119
+
120
+ if (
121
+ !isDigitsOnly ||
122
+ !Number.isInteger(newPageNumber) ||
123
+ newPageNumber < 1 ||
124
+ newPageNumber > lastPageNumber
125
+ ) {
126
+ const errorMessage = `Enter a page number from 1 to ${lastPageNumber}.`;
127
+ setPageInputError(errorMessage);
128
+ announce(errorMessage, true);
129
+ return;
130
+ }
131
+
132
+ setPageInputError('');
133
+ handlePageChange(newPageNumber);
134
+ event.currentTarget.blur();
135
+ };
136
+
137
+ const controlsListStyle = css`
138
+ margin: 0;
139
+ padding: 0;
140
+ display: flex;
141
+ justify-content: center;
142
+ align-items: center;
143
+ list-style: none;
144
+ gap: ${theme.margin.m8};
145
+ `;
146
+
147
+ const navigationButtonStyle = css`
148
+ gap: ${theme.margin.m8};
149
+ `;
150
+
151
+ const pageInputStyle = css`
152
+ height: 48px;
153
+ width: 48px;
154
+ box-sizing: border-box;
155
+ border: ${theme.thickness.t2} solid ${theme.colour.border.default};
156
+ border-radius: ${theme.radius.r4};
157
+ padding: 0;
158
+ text-align: center;
159
+ color: ${theme.colour.text.default};
160
+ font-family: ${theme.typography.body.md.fontFamily};
161
+ font-size: ${theme.typography.body.md.fontSize};
162
+
163
+ &[aria-invalid='true'] {
164
+ border-color: ${theme.colour.border.critical};
165
+ }
166
+ `;
167
+
168
+ const totalPagesLabelStyle = css`
169
+ color: ${theme.colour.text.secondary};
170
+ white-space: nowrap;
171
+ `;
172
+
173
+ const visuallyHiddenStyle = css`
174
+ position: absolute;
175
+ width: 1px;
176
+ height: 1px;
177
+ padding: 0;
178
+ margin: -1px;
179
+ overflow: hidden;
180
+ clip: rect(0, 0, 0, 0);
181
+ white-space: nowrap;
182
+ border: 0;
183
+ `;
184
+
185
+ const pageInputDescription =
186
+ typeof totalPages === 'number'
187
+ ? `Page ${displayedCurrentPage} of ${totalPages}.${
188
+ totalPages === 0 ? '' : ' Enter a page number and press Enter.'
189
+ }`
190
+ : `Page ${currentPage}. Enter a page number and press Enter.`;
191
+
192
+ const style = cx(name, className);
193
+
194
+ return (
195
+ <nav
196
+ {...props}
197
+ className={style}
198
+ aria-label={
199
+ typeof totalPages === 'number'
200
+ ? `Pagination, ${totalPages} pages total`
201
+ : 'Pagination'
202
+ }
203
+ data-testid={testId}
204
+ >
205
+ <ul className={controlsListStyle}>
206
+ <li>
207
+ <Button
208
+ className={navigationButtonStyle}
209
+ variant='tertiary'
210
+ icon={<Icon.ChevronLeft />}
211
+ aria-label='Go to previous page'
212
+ onClick={handlePreviousPage}
213
+ disabled={!hasPreviousPage}
214
+ >
215
+ Previous
216
+ </Button>
217
+ </li>
218
+ <li>
219
+ <Input
220
+ inputClassName={pageInputStyle}
221
+ type='text'
222
+ inputMode='numeric'
223
+ pattern='[0-9]*'
224
+ value={
225
+ totalPages === 0 ? String(displayedCurrentPage) : pageInputValue
226
+ }
227
+ aria-label='Page number'
228
+ aria-current={totalPages === 0 ? undefined : 'page'}
229
+ aria-describedby={pageInputDescriptionId}
230
+ aria-invalid={pageInputError ? true : undefined}
231
+ disabled={totalPages === 0}
232
+ onChange={handlePageInputChange}
233
+ onBlur={resetPageInput}
234
+ onKeyDown={handlePageInputKeyDown}
235
+ />
236
+ <span
237
+ id={pageInputDescriptionId}
238
+ className={visuallyHiddenStyle}
239
+ >
240
+ {pageInputDescription}
241
+ </span>
242
+ </li>
243
+ {typeof totalPages === 'number' && (
244
+ <li>
245
+ <Text className={totalPagesLabelStyle}>of {totalPages}</Text>
246
+ </li>
247
+ )}
248
+ <li>
249
+ <Button
250
+ className={navigationButtonStyle}
251
+ variant='tertiary'
252
+ icon={<Icon.ChevronRight />}
253
+ iconPosition='right'
254
+ aria-label='Go to next page'
255
+ onClick={handleNextPage}
256
+ disabled={!hasNextPage}
257
+ >
258
+ Next
259
+ </Button>
260
+ </li>
261
+ </ul>
262
+ </nav>
263
+ );
264
+ };
265
+
266
+ export default memo(PaginationCompactControls);
@@ -0,0 +1,246 @@
1
+ import { HTMLAttributes, memo, useContext } from 'react';
2
+ import { css, cx } from '@emotion/css';
3
+ import useTheme from '../../../theme/useTheme';
4
+ import { PaginationContext } from '../Pagination';
5
+ import Button from '../../Button';
6
+ import getPaginationButtons from '../getPaginationButtons';
7
+ import { useMediaQuery } from '../../../hooks';
8
+
9
+ export interface PaginationStandardControlsProps extends HTMLAttributes<HTMLDivElement> {
10
+ maxNumberButtons: number;
11
+ name: string;
12
+ testId: string;
13
+ }
14
+
15
+ const PaginationStandardControls = ({
16
+ maxNumberButtons,
17
+ name,
18
+ testId,
19
+ className,
20
+ ...props
21
+ }: PaginationStandardControlsProps) => {
22
+ const [theme] = useTheme();
23
+
24
+ const isTabletPlus = useMediaQuery(
25
+ `(min-width: ${theme.breakpoints.tablet}px)`
26
+ );
27
+
28
+ const contextValue = useContext(PaginationContext);
29
+
30
+ if (!contextValue) {
31
+ return null;
32
+ }
33
+
34
+ const {
35
+ mode,
36
+ limit,
37
+ total,
38
+ currentPage,
39
+ trackedMaxPage,
40
+ hasPreviousPage,
41
+ hasNextPage,
42
+ onPageChange,
43
+ onNextPage,
44
+ onPreviousPage,
45
+ } = contextValue;
46
+
47
+ const handlePageChange = (newPage: number) => {
48
+ if (mode === 'tracked') {
49
+ if (newPage < 1 || newPage > trackedMaxPage || newPage === currentPage) {
50
+ return;
51
+ }
52
+ onPageChange?.(newPage);
53
+ return;
54
+ }
55
+
56
+ onPageChange?.((newPage - 1) * limit);
57
+ };
58
+
59
+ const totalPages =
60
+ typeof total === 'number' ? Math.ceil(total / limit) : undefined;
61
+
62
+ const paginationButtons =
63
+ mode === 'tracked'
64
+ ? Array.from({ length: trackedMaxPage }, (_, i) => i + 1)
65
+ : getPaginationButtons(
66
+ currentPage,
67
+ totalPages ?? 0,
68
+ isTabletPlus ? maxNumberButtons : 5
69
+ );
70
+
71
+ const style = cx(name, className);
72
+
73
+ const listStyle = css`
74
+ margin: 0;
75
+ padding: 0;
76
+ display: flex;
77
+ justify-content: center;
78
+ align-items: center;
79
+ list-style: none;
80
+ gap: ${theme.margin.m4};
81
+ flex-wrap: nowrap;
82
+
83
+ @media (min-width: ${theme.breakpoints.tablet}px) {
84
+ gap: ${theme.margin.m8};
85
+ }
86
+ `;
87
+
88
+ const buttonBaseStyle = css`
89
+ @media (max-width: ${theme.breakpoints.tablet}px) {
90
+ gap: ${theme.margin.m4};
91
+ }
92
+ `;
93
+
94
+ const previousButtonStyle = cx(
95
+ buttonBaseStyle,
96
+ css`
97
+ margin-right: ${theme.margin.m16};
98
+ display: none;
99
+
100
+ @media (min-width: ${theme.breakpoints.tablet}px) {
101
+ display: inline-block;
102
+ }
103
+ `
104
+ );
105
+
106
+ const nextButtonStyle = cx(
107
+ buttonBaseStyle,
108
+ css`
109
+ margin-left: ${theme.margin.m16};
110
+ display: none;
111
+
112
+ @media (min-width: ${theme.breakpoints.tablet}px) {
113
+ display: inline-block;
114
+ }
115
+ `
116
+ );
117
+
118
+ const pageNumberButtonBaseStyle = cx(
119
+ buttonBaseStyle,
120
+ css`
121
+ height: 40px;
122
+ min-width: 40px;
123
+ padding: 0;
124
+
125
+ @media (min-width: ${theme.breakpoints.tablet}px) {
126
+ height: 48px;
127
+ min-width: 48px;
128
+ }
129
+ `
130
+ );
131
+
132
+ const pageNumberButtonStyle = cx(
133
+ pageNumberButtonBaseStyle,
134
+ css`
135
+ border: none;
136
+ color: ${theme.colour.text.secondary};
137
+ `
138
+ );
139
+
140
+ const currentPageNumberButtonStyle = cx(
141
+ pageNumberButtonBaseStyle,
142
+ css`
143
+ color: ${theme.colour.text.brand};
144
+ border-color: ${theme.colour.border.brand};
145
+ `
146
+ );
147
+
148
+ const buttonNumberLabelStyle = css`
149
+ padding: 0 ${theme.padding.p8};
150
+ `;
151
+
152
+ return (
153
+ <nav
154
+ {...props}
155
+ className={style}
156
+ aria-label={
157
+ typeof totalPages === 'number'
158
+ ? `Pagination, ${totalPages} pages total`
159
+ : 'Pagination'
160
+ }
161
+ data-testid={testId}
162
+ >
163
+ <ul className={listStyle}>
164
+ <li>
165
+ <Button
166
+ className={previousButtonStyle}
167
+ variant='tertiary'
168
+ size={isTabletPlus ? 'default' : 'small'}
169
+ aria-label='Go to previous page'
170
+ onClick={() => {
171
+ if (mode === 'tracked') {
172
+ if (onPreviousPage) {
173
+ onPreviousPage();
174
+ } else {
175
+ handlePageChange(currentPage - 1);
176
+ }
177
+ return;
178
+ }
179
+ handlePageChange(currentPage - 1);
180
+ }}
181
+ disabled={!hasPreviousPage}
182
+ >
183
+ Previous
184
+ </Button>
185
+ </li>
186
+
187
+ {paginationButtons.map((page) => (
188
+ <li key={page}>
189
+ {page === '...1' || page === '...2' ? (
190
+ <span aria-hidden>...</span>
191
+ ) : (
192
+ <>
193
+ {page !== currentPage && (
194
+ <Button
195
+ className={pageNumberButtonStyle}
196
+ variant='secondary'
197
+ size={isTabletPlus ? 'default' : 'small'}
198
+ aria-label={`Page ${page}`}
199
+ onClick={() => handlePageChange(page)}
200
+ >
201
+ <span className={buttonNumberLabelStyle}>{page}</span>
202
+ </Button>
203
+ )}
204
+ {page === currentPage && (
205
+ <Button
206
+ className={currentPageNumberButtonStyle}
207
+ variant='secondary'
208
+ size={isTabletPlus ? 'default' : 'small'}
209
+ aria-label={`Page ${page}, current page`}
210
+ aria-current='page'
211
+ disabled
212
+ onClick={() => handlePageChange(page)}
213
+ >
214
+ <span className={buttonNumberLabelStyle}>{page}</span>
215
+ </Button>
216
+ )}
217
+ </>
218
+ )}
219
+ </li>
220
+ ))}
221
+
222
+ <li>
223
+ <Button
224
+ className={nextButtonStyle}
225
+ variant='tertiary'
226
+ aria-label='Go to next page'
227
+ onClick={() => {
228
+ if (mode === 'tracked') {
229
+ if (onNextPage) {
230
+ onNextPage();
231
+ }
232
+ return;
233
+ }
234
+ handlePageChange(currentPage + 1);
235
+ }}
236
+ disabled={!hasNextPage}
237
+ >
238
+ Next
239
+ </Button>
240
+ </li>
241
+ </ul>
242
+ </nav>
243
+ );
244
+ };
245
+
246
+ export default memo(PaginationStandardControls);
@@ -0,0 +1,2 @@
1
+ export { default as PaginationCompactControls } from './PaginationCompactControls';
2
+ export { default as PaginationStandardControls } from './PaginationStandardControls';
@@ -0,0 +1,12 @@
1
+ import { createContext, use } from 'react';
2
+ import type { TableColumnMetadata } from './Table.types';
3
+
4
+ export type TableContextValue = {
5
+ columns: TableColumnMetadata[];
6
+ };
7
+
8
+ const TableContext = createContext<TableContextValue>({ columns: [] });
9
+
10
+ export const TableContextProvider = TableContext.Provider;
11
+
12
+ export const useTableContext = () => use(TableContext);
@@ -1,10 +1,77 @@
1
+ import { Children, isValidElement } from 'react';
1
2
  import { css, cx } from '@emotion/css';
2
3
  import { Head, HeadCell, Body, Row, Cell } from './subcomponents';
3
4
  import { useTheme } from '../../theme';
4
- import type { TableProps } from './Table.types';
5
+ import { TableContextProvider } from './Table.context';
6
+ import type {
7
+ ColumnVariant,
8
+ NormalizedTableMobileRole,
9
+ TableColumnMetadata,
10
+ TableMobileRole,
11
+ TableProps,
12
+ } from './Table.types';
13
+ import type { TableHeadCellProps } from './subcomponents';
5
14
 
6
15
  const NAME = 'ucl-uikit-table';
7
16
 
17
+ const getTextFromChildren = (children: React.ReactNode): string => {
18
+ if (typeof children === 'string' || typeof children === 'number')
19
+ return String(children);
20
+
21
+ if (Array.isArray(children))
22
+ return children.map(getTextFromChildren).join('').trim();
23
+
24
+ if (isValidElement<{ children?: React.ReactNode }>(children))
25
+ return getTextFromChildren(children.props.children);
26
+
27
+ return '';
28
+ };
29
+
30
+ const normalizeMobileRole = (
31
+ mobileRole: TableMobileRole | undefined,
32
+ variant: ColumnVariant | undefined
33
+ ): NormalizedTableMobileRole => {
34
+ if (mobileRole === 'primaryStatus') return 'status';
35
+ if (mobileRole) return mobileRole;
36
+ if (variant === 'checkbox') return 'selection';
37
+ return 'field';
38
+ };
39
+
40
+ const getColumnsFromChildren = (
41
+ children: React.ReactNode
42
+ ): TableColumnMetadata[] => {
43
+ const head = Children.toArray(children).find(
44
+ (child) => isValidElement(child) && child.type === Head
45
+ );
46
+
47
+ if (!isValidElement<{ children?: React.ReactNode }>(head)) return [];
48
+
49
+ const columns = Children.toArray(head.props.children)
50
+ .filter(isValidElement<TableHeadCellProps>)
51
+ .map((cell) => ({
52
+ mobileRole: normalizeMobileRole(
53
+ cell.props.mobileRole,
54
+ cell.props.variant
55
+ ),
56
+ mobileLabel:
57
+ cell.props.mobileLabel ?? getTextFromChildren(cell.props.children),
58
+ }));
59
+
60
+ if (columns.some((column) => column.mobileRole === 'primary')) return columns;
61
+
62
+ const fallbackPrimaryIndex = columns.findIndex(
63
+ (column) => column.mobileRole === 'field'
64
+ );
65
+
66
+ if (fallbackPrimaryIndex === -1) return columns;
67
+
68
+ return columns.map((column, index) =>
69
+ index === fallbackPrimaryIndex
70
+ ? { ...column, mobileRole: 'primary' }
71
+ : column
72
+ );
73
+ };
74
+
8
75
  const Table = ({
9
76
  testId = NAME,
10
77
  className,
@@ -14,25 +81,37 @@ const Table = ({
14
81
  ...props
15
82
  }: TableProps) => {
16
83
  const [theme] = useTheme();
84
+ const columns = getColumnsFromChildren(children);
17
85
 
18
86
  const baseStyle = css`
19
- font-family: ${theme.font.family.primary};
20
- font-size: ${theme.font.size.f14};
87
+ width: 100%;
88
+ font-family: ${theme.typography.body.sm.fontFamily};
89
+ font-feature-settings: ${theme.typography.body.sm.fontSettings};
90
+ font-size: ${theme.typography.body.sm.fontSize}px;
91
+ font-weight: ${theme.typography.body.sm.fontWeight};
92
+ line-height: ${theme.typography.body.sm.lineHeight}%;
21
93
  border-collapse: collapse;
94
+
95
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
96
+ display: block;
97
+ border-collapse: separate;
98
+ }
22
99
  `;
23
100
 
24
101
  const style = cx(baseStyle, className, NAME);
25
102
 
26
103
  return (
27
- <table
28
- className={style}
29
- data-testid={testId}
30
- aria-label={ariaLabel}
31
- ref={ref}
32
- {...props}
33
- >
34
- {children}
35
- </table>
104
+ <TableContextProvider value={{ columns }}>
105
+ <table
106
+ className={style}
107
+ data-testid={testId}
108
+ aria-label={ariaLabel}
109
+ ref={ref}
110
+ {...props}
111
+ >
112
+ {children}
113
+ </table>
114
+ </TableContextProvider>
36
115
  );
37
116
  };
38
117
 
@@ -2,6 +2,27 @@ export type ColumnVariant = 'default' | 'numeric' | 'checkbox' | 'button';
2
2
 
3
3
  export type SortOrder = 'asc' | 'desc' | null;
4
4
 
5
+ export type TableMobileRole =
6
+ | 'selection'
7
+ | 'primaryMeta'
8
+ | 'primary'
9
+ | 'status'
10
+ | 'primaryStatus'
11
+ | 'field'
12
+ | 'primaryAction'
13
+ | 'contextMenu'
14
+ | 'hidden';
15
+
16
+ export type NormalizedTableMobileRole = Exclude<
17
+ TableMobileRole,
18
+ 'primaryStatus'
19
+ >;
20
+
21
+ export type TableColumnMetadata = {
22
+ mobileRole: NormalizedTableMobileRole;
23
+ mobileLabel?: string;
24
+ };
25
+
5
26
  export type SortPattern = {
6
27
  accessor: string | null;
7
28
  order: SortOrder;