uikit-react-public 0.36.1 → 0.37.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.
Files changed (28) hide show
  1. package/dist/components/Table/Table.context.d.ts +6 -0
  2. package/dist/components/Table/Table.d.ts +4 -3
  3. package/dist/components/Table/Table.stories.d.ts +3 -3
  4. package/dist/components/Table/Table.types.d.ts +6 -0
  5. package/dist/components/Table/index.d.ts +1 -1
  6. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +8 -1
  7. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +5 -1
  8. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +3 -2
  9. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +4 -2
  10. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +3 -1
  11. package/dist/components/Table/subcomponents/Row.d.ts +8 -1
  12. package/dist/index.js +6563 -6193
  13. package/lib/components/Table/Table.context.ts +12 -0
  14. package/lib/components/Table/Table.tsx +91 -12
  15. package/lib/components/Table/Table.types.ts +21 -0
  16. package/lib/components/Table/__tests__/Table.test.tsx +95 -1
  17. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +54 -13
  18. package/lib/components/Table/index.ts +6 -1
  19. package/lib/components/Table/subcomponents/Body.tsx +16 -3
  20. package/lib/components/Table/subcomponents/Cell/Cell.tsx +193 -4
  21. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +63 -3
  22. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +15 -10
  23. package/lib/components/Table/subcomponents/Head.tsx +14 -3
  24. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +17 -4
  25. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +3 -3
  26. package/lib/components/Table/subcomponents/Row.tsx +195 -6
  27. package/lib/components/Table/subcomponents/SortIcon.tsx +4 -4
  28. package/package.json +1 -1
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { createRef } from 'react';
2
2
  import { describe, expect, test } from 'vitest';
3
- import { render, screen, within } from '@testing-library/react';
3
+ import { fireEvent, render, screen, within } from '@testing-library/react';
4
4
  import Table from '../Table';
5
5
  import { ThemeContextProvider } from '../../../theme/useTheme';
6
6
 
@@ -137,4 +137,98 @@ describe('Table', () => {
137
137
  screen.getByRole('table', { name: 'User list' })
138
138
  ).toBeInTheDocument();
139
139
  });
140
+
141
+ test('maps mobile column roles and labels from header cells to body cells', () => {
142
+ render(
143
+ <ThemeContextProvider>
144
+ <Table>
145
+ <Table.Head>
146
+ <Table.HeadCell
147
+ variant='checkbox'
148
+ mobileLabel='Select'
149
+ />
150
+ <Table.HeadCell mobileRole='primaryMeta'>Reference</Table.HeadCell>
151
+ <Table.HeadCell mobileRole='primary'>Course</Table.HeadCell>
152
+ <Table.HeadCell mobileRole='primaryStatus'>Status</Table.HeadCell>
153
+ <Table.HeadCell mobileRole='primaryAction'>Action</Table.HeadCell>
154
+ </Table.Head>
155
+ <Table.Body>
156
+ <Table.Row>
157
+ <Table.Cell
158
+ variant='checkbox'
159
+ checkboxProps={{ ariaLabel: 'Select Computer Science' }}
160
+ />
161
+ <Table.Cell>12345678</Table.Cell>
162
+ <Table.Cell>Computer Science BSc</Table.Cell>
163
+ <Table.Cell>Open</Table.Cell>
164
+ <Table.Cell>
165
+ <a href='/courses/computer-science'>View course</a>
166
+ </Table.Cell>
167
+ </Table.Row>
168
+ </Table.Body>
169
+ </Table>
170
+ </ThemeContextProvider>
171
+ );
172
+
173
+ const cells = screen.getAllByTestId('ucl-uikit-table__cell');
174
+
175
+ expect(cells[0]).toHaveAttribute('data-mobile-role', 'selection');
176
+ expect(cells[1]).toHaveAttribute('data-mobile-role', 'primaryMeta');
177
+ expect(cells[2]).toHaveAttribute('data-mobile-role', 'primary');
178
+ expect(cells[2]).toHaveAttribute('data-mobile-label', 'Course');
179
+ expect(cells[3]).toHaveAttribute('data-mobile-role', 'status');
180
+ expect(cells[4]).toHaveAttribute('data-mobile-role', 'primaryAction');
181
+ });
182
+
183
+ test('uses the first field column as the mobile primary when none is marked', () => {
184
+ render(
185
+ <ThemeContextProvider>
186
+ <Table>
187
+ <Table.Head>
188
+ <Table.HeadCell showSortIcon={false}>Name</Table.HeadCell>
189
+ <Table.HeadCell showSortIcon={false}>Email</Table.HeadCell>
190
+ </Table.Head>
191
+ <Table.Body>
192
+ <Table.Row>
193
+ <Table.Cell>Ada Lovelace</Table.Cell>
194
+ <Table.Cell>ada@example.com</Table.Cell>
195
+ </Table.Row>
196
+ </Table.Body>
197
+ </Table>
198
+ </ThemeContextProvider>
199
+ );
200
+
201
+ const cells = screen.getAllByTestId('ucl-uikit-table__cell');
202
+
203
+ expect(cells[0]).toHaveAttribute('data-mobile-role', 'primary');
204
+ expect(cells[1]).toHaveAttribute('data-mobile-role', 'field');
205
+ });
206
+
207
+ test('toggles row mobile expansion from the details disclosure button', () => {
208
+ render(
209
+ <ThemeContextProvider>
210
+ <Table>
211
+ <Table.Head>
212
+ <Table.HeadCell mobileRole='primary'>Name</Table.HeadCell>
213
+ <Table.HeadCell>Email</Table.HeadCell>
214
+ </Table.Head>
215
+ <Table.Body>
216
+ <Table.Row defaultMobileExpanded={false}>
217
+ <Table.Cell>Ada Lovelace</Table.Cell>
218
+ <Table.Cell>ada@example.com</Table.Cell>
219
+ </Table.Row>
220
+ </Table.Body>
221
+ </Table>
222
+ </ThemeContextProvider>
223
+ );
224
+
225
+ const row = screen.getByTestId('ucl-uikit-table__row');
226
+ const button = screen.getByRole('button', { name: 'Show details' });
227
+
228
+ expect(row).toHaveAttribute('data-mobile-expanded', 'false');
229
+
230
+ fireEvent.click(button);
231
+
232
+ expect(row).toHaveAttribute('data-mobile-expanded', 'true');
233
+ });
140
234
  });
@@ -2,18 +2,18 @@
2
2
 
3
3
  exports[`Table > Snapshot: basic table 1`] = `
4
4
  <table
5
- class="css-3bowfr ucl-uikit-table"
5
+ class="css-1mnnjq ucl-uikit-table"
6
6
  data-testid="ucl-uikit-table"
7
7
  >
8
8
  <thead
9
- class="css-3fy35g ucl-uikit-table__head"
9
+ class="css-1ux39q6 ucl-uikit-table__head"
10
10
  data-testid="ucl-uikit-table__head"
11
11
  >
12
12
  <tr
13
- class="css-7kbd2r"
13
+ class="css-1pr7kjy"
14
14
  >
15
15
  <th
16
- class="css-19iwbn3 ucl-uikit-table__head-cell"
16
+ class="css-x1n4gt ucl-uikit-table__head-cell"
17
17
  data-testid="ucl-uikit-table__head-cell"
18
18
  scope="col"
19
19
  >
@@ -64,7 +64,7 @@ exports[`Table > Snapshot: basic table 1`] = `
64
64
  </div>
65
65
  </th>
66
66
  <th
67
- class="css-19iwbn3 ucl-uikit-table__head-cell"
67
+ class="css-x1n4gt ucl-uikit-table__head-cell"
68
68
  data-testid="ucl-uikit-table__head-cell"
69
69
  scope="col"
70
70
  >
@@ -115,7 +115,7 @@ exports[`Table > Snapshot: basic table 1`] = `
115
115
  </div>
116
116
  </th>
117
117
  <th
118
- class="css-19iwbn3 ucl-uikit-table__head-cell"
118
+ class="css-x1n4gt ucl-uikit-table__head-cell"
119
119
  data-testid="ucl-uikit-table__head-cell"
120
120
  scope="col"
121
121
  >
@@ -168,46 +168,87 @@ exports[`Table > Snapshot: basic table 1`] = `
168
168
  </tr>
169
169
  </thead>
170
170
  <tbody
171
+ class="css-13y1iix ucl-uikit-table__body"
171
172
  data-testid="ucl-uikit-table__body"
172
173
  >
173
174
  <tr
174
175
  aria-selected="false"
175
- class="css-jetl3c ucl-uikit-table__row"
176
+ class="css-icxsp9 ucl-uikit-table__row"
177
+ data-mobile-expanded="true"
176
178
  data-testid="ucl-uikit-table__row"
177
179
  >
178
180
  <td
179
- class="css-u1gowl ucl-uikit-table__cell"
181
+ class="ucl-uikit-table__cell css-1bdqdyj"
182
+ data-mobile-label="Header Cell 1"
183
+ data-mobile-role="primary"
180
184
  data-testid="ucl-uikit-table__cell"
181
185
  >
182
186
  <div
183
- class="ucl-uikit-table__cell-content css-1ao6jhw"
187
+ class="ucl-uikit-table__cell-content css-1ugtgeo"
184
188
  data-testid="ucl-uikit-table__cell-content"
185
189
  >
186
190
  Test Cell 1
187
191
  </div>
188
192
  </td>
189
193
  <td
190
- class="css-u1gowl ucl-uikit-table__cell"
194
+ class="ucl-uikit-table__cell css-1yys4um"
195
+ data-mobile-label="Header Cell 2"
196
+ data-mobile-role="field"
191
197
  data-testid="ucl-uikit-table__cell"
192
198
  >
193
199
  <div
194
- class="ucl-uikit-table__cell-content css-1ao6jhw"
200
+ class="ucl-uikit-table__cell-content css-qn93h1"
195
201
  data-testid="ucl-uikit-table__cell-content"
196
202
  >
197
203
  Test Cell 2
198
204
  </div>
199
205
  </td>
200
206
  <td
201
- class="css-u1gowl ucl-uikit-table__cell"
207
+ class="ucl-uikit-table__cell css-501n1z"
208
+ data-mobile-label="Header Cell 3"
209
+ data-mobile-role="field"
202
210
  data-testid="ucl-uikit-table__cell"
203
211
  >
204
212
  <div
205
- class="ucl-uikit-table__cell-content css-1ao6jhw"
213
+ class="ucl-uikit-table__cell-content css-qn93h1"
206
214
  data-testid="ucl-uikit-table__cell-content"
207
215
  >
208
216
  Test Cell 3
209
217
  </div>
210
218
  </td>
219
+ <td
220
+ class="css-1nfo37q"
221
+ data-mobile-role="details"
222
+ >
223
+ <button
224
+ aria-expanded="true"
225
+ aria-label="Hide details"
226
+ class="css-1edts3y"
227
+ type="button"
228
+ >
229
+ <span>
230
+ Details
231
+ </span>
232
+ <svg
233
+ class="ucl-uikit-icon css-148hpxb"
234
+ data-testid="ucl-uikit-icon"
235
+ fill="none"
236
+ focusable="false"
237
+ height="20"
238
+ stroke="currentColor"
239
+ stroke-linecap="round"
240
+ stroke-linejoin="round"
241
+ stroke-width="2"
242
+ viewBox="0 0 24 24"
243
+ width="20"
244
+ xmlns="http://www.w3.org/2000/svg"
245
+ >
246
+ <path
247
+ d="m18 15-6-6-6 6"
248
+ />
249
+ </svg>
250
+ </button>
251
+ </td>
211
252
  </tr>
212
253
  </tbody>
213
254
  </table>
@@ -1,5 +1,10 @@
1
1
  export { default } from './Table';
2
- export type { TableProps, ColumnVariant } from './Table.types';
2
+ export type {
3
+ TableProps,
4
+ ColumnVariant,
5
+ TableMobileRole,
6
+ TableColumnMetadata,
7
+ } from './Table.types';
3
8
  export type {
4
9
  TableHeadProps,
5
10
  TableHeadCellProps,
@@ -1,12 +1,25 @@
1
- export interface TableBodyProps
2
- extends React.HTMLAttributes<HTMLTableSectionElement> {}
1
+ import { css, cx } from '@emotion/css';
2
+ import { useTheme } from '../../../theme';
3
+
4
+ export interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
3
5
 
4
6
  const NAME = 'ucl-uikit-table__body';
5
7
 
6
8
  const Body = ({ className, children, ...props }: TableBodyProps) => {
9
+ const [theme] = useTheme();
10
+
11
+ const baseStyle = css`
12
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
13
+ display: block;
14
+ width: 100%;
15
+ }
16
+ `;
17
+
18
+ const style = cx(baseStyle, className, NAME);
19
+
7
20
  return (
8
21
  <tbody
9
- className={className}
22
+ className={style}
10
23
  data-testid={NAME}
11
24
  {...props}
12
25
  >
@@ -1,7 +1,11 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import CellContent from './CellContent';
3
3
  import { useTheme } from '../../../../theme';
4
- import type { ColumnVariant } from '../../Table.types';
4
+ import { useTableContext } from '../../Table.context';
5
+ import type {
6
+ ColumnVariant,
7
+ NormalizedTableMobileRole,
8
+ } from '../../Table.types';
5
9
  import type { CheckboxProps } from '../../../Checkbox/Checkbox';
6
10
  import type { ButtonProps } from '../../../Button';
7
11
 
@@ -21,10 +25,25 @@ export interface TableCellProps extends React.HTMLAttributes<HTMLTableCellElemen
21
25
  ButtonProps<'button'>,
22
26
  'variant' | 'onClick' | 'className'
23
27
  >;
28
+ /** @internal */
29
+ __mobileColumnIndex?: number;
30
+ __mobileHasSelection?: boolean;
31
+ /** @internal */
32
+ __mobileHasPrimaryMeta?: boolean;
33
+ /** @internal */
34
+ __mobileIsFirstBodyField?: boolean;
24
35
  }
25
36
 
26
37
  const NAME = 'ucl-uikit-table__cell';
27
38
 
39
+ const getDefaultMobileRole = (
40
+ variant: ColumnVariant
41
+ ): NormalizedTableMobileRole => {
42
+ if (variant === 'checkbox') return 'selection';
43
+ if (variant === 'button') return 'primaryAction';
44
+ return 'field';
45
+ };
46
+
28
47
  const Cell = ({
29
48
  variant = 'default',
30
49
  icon,
@@ -36,9 +55,17 @@ const Cell = ({
36
55
  children,
37
56
  checkboxProps,
38
57
  buttonProps,
58
+ __mobileColumnIndex,
59
+ __mobileHasSelection = false,
60
+ __mobileHasPrimaryMeta = false,
61
+ __mobileIsFirstBodyField = false,
39
62
  ...props
40
63
  }: TableCellProps) => {
41
64
  const [theme] = useTheme();
65
+ const { columns } = useTableContext();
66
+ const mobileColumn = columns[__mobileColumnIndex ?? -1];
67
+ const mobileRole = mobileColumn?.mobileRole ?? getDefaultMobileRole(variant);
68
+ const mobileLabel = mobileColumn?.mobileLabel;
42
69
 
43
70
  if (
44
71
  variant === 'checkbox' &&
@@ -63,20 +90,182 @@ const Cell = ({
63
90
  box-sizing: border-box;
64
91
  padding: 0 ${theme.padding.p16};
65
92
  height: 40px;
66
- font-family: ${theme.font.family.primary};
67
- font-size: ${theme.font.size.f14};
93
+ font-family: ${theme.typography.body.sm.fontFamily};
94
+ font-feature-settings: ${theme.typography.body.sm.fontSettings};
95
+ font-size: ${theme.typography.body.sm.fontSize}px;
96
+ font-weight: ${theme.typography.body.sm.fontWeight};
97
+ line-height: ${theme.typography.body.sm.lineHeight}%;
98
+
99
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
100
+ min-width: 0;
101
+ height: auto;
102
+ padding: 0;
103
+ }
104
+ `;
105
+
106
+ const mobileSelectionStyle = css`
107
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
108
+ grid-column: 1;
109
+ grid-row: 1 / span 2;
110
+ order: 0;
111
+ align-self: center;
112
+ padding-top: ${theme.padding.p2};
113
+ }
114
+ `;
115
+
116
+ const mobilePrimaryStyle = css`
117
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
118
+ display: grid;
119
+ grid-template-columns: minmax(0, 1fr) auto;
120
+ align-items: start;
121
+ gap: ${theme.padding.p8};
122
+ min-height: 20px;
123
+ }
124
+ `;
125
+
126
+ const mobilePrimaryAfterSelectionStyle = css`
127
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
128
+ grid-column: 2;
129
+ }
130
+ `;
131
+
132
+ const mobilePrimaryWithoutSelectionStyle = css`
133
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
134
+ grid-column: 1 / 3;
135
+ }
136
+ `;
137
+
138
+ const mobilePrimaryWithPrimaryMetaStyle = css`
139
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
140
+ grid-row: 2;
141
+ order: 2;
142
+ }
143
+ `;
144
+
145
+ const mobilePrimaryWithoutPrimaryMetaStyle = css`
146
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
147
+ grid-row: 1 / span 2;
148
+ order: 1;
149
+ align-self: center;
150
+ }
151
+ `;
152
+
153
+ const mobilePrimaryMetaStyle = css`
154
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
155
+ grid-row: 1;
156
+ order: 1;
157
+ min-height: 18px;
158
+ }
159
+ `;
160
+
161
+ const mobilePrimaryMetaAfterSelectionStyle = css`
162
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
163
+ grid-column: 2;
164
+ }
165
+ `;
166
+
167
+ const mobilePrimaryMetaWithoutSelectionStyle = css`
168
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
169
+ grid-column: 1 / 3;
170
+ }
171
+ `;
172
+
173
+ const mobileStatusStyle = css`
174
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
175
+ grid-column: 3;
176
+ grid-row: 1 / span 2;
177
+ order: 2;
178
+ align-self: center;
179
+ }
180
+ `;
181
+
182
+ const mobileContextMenuStyle = css`
183
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
184
+ grid-column: 4;
185
+ grid-row: 1 / span 2;
186
+ order: 3;
187
+ align-self: center;
188
+ }
189
+ `;
190
+
191
+ const mobileFieldStyle = css`
192
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
193
+ display: grid;
194
+ grid-template-columns: minmax(90px, 32%) minmax(0, 1fr);
195
+ grid-column: 1 / -1;
196
+ order: 20;
197
+ align-items: start;
198
+ column-gap: ${theme.padding.p16};
199
+ padding: ${theme.padding.p6} 0;
200
+
201
+ &::before {
202
+ content: attr(data-mobile-label);
203
+ color: ${theme.colour.text.secondary};
204
+ font-weight: ${theme.typography.body.sm.fontWeight};
205
+ }
206
+ }
207
+ `;
208
+
209
+ const mobileFirstFieldStyle = css`
210
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
211
+ margin-top: ${theme.margin.m16};
212
+ padding-top: ${theme.padding.p12};
213
+ border-top: ${theme.border.b1} solid ${theme.colour.border.default};
214
+ }
215
+ `;
216
+
217
+ const mobilePrimaryActionStyle = css`
218
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
219
+ grid-column: 1 / -1;
220
+ order: 30;
221
+ margin-top: ${theme.margin.m8};
222
+ padding-top: ${theme.padding.p12};
223
+ border-top: ${theme.border.b1} solid ${theme.colour.border.default};
224
+ }
225
+ `;
226
+
227
+ const mobileHiddenStyle = css`
228
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
229
+ display: none;
230
+ }
68
231
  `;
69
232
 
70
233
  const style = cx(baseStyle, className, NAME);
234
+ const mobileStyle = cx({
235
+ [mobileSelectionStyle]: mobileRole === 'selection',
236
+ [mobilePrimaryStyle]: mobileRole === 'primary',
237
+ [mobilePrimaryAfterSelectionStyle]:
238
+ mobileRole === 'primary' && __mobileHasSelection,
239
+ [mobilePrimaryWithoutSelectionStyle]:
240
+ mobileRole === 'primary' && !__mobileHasSelection,
241
+ [mobilePrimaryWithPrimaryMetaStyle]:
242
+ mobileRole === 'primary' && __mobileHasPrimaryMeta,
243
+ [mobilePrimaryWithoutPrimaryMetaStyle]:
244
+ mobileRole === 'primary' && !__mobileHasPrimaryMeta,
245
+ [mobilePrimaryMetaStyle]: mobileRole === 'primaryMeta',
246
+ [mobilePrimaryMetaAfterSelectionStyle]:
247
+ mobileRole === 'primaryMeta' && __mobileHasSelection,
248
+ [mobilePrimaryMetaWithoutSelectionStyle]:
249
+ mobileRole === 'primaryMeta' && !__mobileHasSelection,
250
+ [mobileStatusStyle]: mobileRole === 'status',
251
+ [mobileContextMenuStyle]: mobileRole === 'contextMenu',
252
+ [mobileFieldStyle]: mobileRole === 'field',
253
+ [mobileFirstFieldStyle]: mobileRole === 'field' && __mobileIsFirstBodyField,
254
+ [mobilePrimaryActionStyle]: mobileRole === 'primaryAction',
255
+ [mobileHiddenStyle]: mobileRole === 'hidden',
256
+ });
71
257
 
72
258
  return (
73
259
  <td
74
- className={style}
260
+ className={cx(style, mobileStyle)}
75
261
  data-testid={testId}
262
+ data-mobile-role={mobileRole}
263
+ data-mobile-label={mobileLabel}
76
264
  {...props}
77
265
  >
78
266
  <CellContent
79
267
  variant={variant}
268
+ mobileRole={mobileRole}
80
269
  icon={icon}
81
270
  checked={checked}
82
271
  handleCheckboxChange={handleCheckboxChange}