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
@@ -5,11 +5,12 @@ exports[`Cell > Snapshot: default 1`] = `
5
5
  <tbody>
6
6
  <tr>
7
7
  <td
8
- class="css-u1gowl ucl-uikit-table__cell"
8
+ class="ucl-uikit-table__cell css-501n1z"
9
+ data-mobile-role="field"
9
10
  data-testid="ucl-uikit-table__cell"
10
11
  >
11
12
  <div
12
- class="ucl-uikit-table__cell-content css-1ao6jhw"
13
+ class="ucl-uikit-table__cell-content css-qn93h1"
13
14
  data-testid="ucl-uikit-table__cell-content"
14
15
  >
15
16
  Default cell
@@ -25,22 +26,24 @@ exports[`Cell > Snapshot: variants 1`] = `
25
26
  <tbody>
26
27
  <tr>
27
28
  <td
28
- class="css-u1gowl ucl-uikit-table__cell"
29
+ class="ucl-uikit-table__cell css-501n1z"
30
+ data-mobile-role="field"
29
31
  data-testid="ucl-uikit-table__cell"
30
32
  >
31
33
  <div
32
- class="ucl-uikit-table__cell-content css-tlcu3i"
34
+ class="ucl-uikit-table__cell-content css-1tff2yh"
33
35
  data-testid="ucl-uikit-table__cell-content"
34
36
  >
35
37
  123
36
38
  </div>
37
39
  </td>
38
40
  <td
39
- class="css-u1gowl ucl-uikit-table__cell"
41
+ class="ucl-uikit-table__cell css-11iy32t"
42
+ data-mobile-role="selection"
40
43
  data-testid="ucl-uikit-table__cell"
41
44
  >
42
45
  <div
43
- class="ucl-uikit-table__cell-content css-1kkenvm"
46
+ class="ucl-uikit-table__cell-content css-e77rql"
44
47
  data-testid="ucl-uikit-table__cell-content"
45
48
  >
46
49
  <span
@@ -60,11 +63,12 @@ exports[`Cell > Snapshot: variants 1`] = `
60
63
  </div>
61
64
  </td>
62
65
  <td
63
- class="css-u1gowl ucl-uikit-table__cell"
66
+ class="ucl-uikit-table__cell css-501n1z"
67
+ data-mobile-role="field"
64
68
  data-testid="ucl-uikit-table__cell"
65
69
  >
66
70
  <div
67
- class="ucl-uikit-table__cell-content css-1ao6jhw"
71
+ class="ucl-uikit-table__cell-content css-qn93h1"
68
72
  data-testid="ucl-uikit-table__cell-content"
69
73
  >
70
74
  <svg
@@ -91,11 +95,12 @@ exports[`Cell > Snapshot: variants 1`] = `
91
95
  </div>
92
96
  </td>
93
97
  <td
94
- class="css-u1gowl ucl-uikit-table__cell"
98
+ class="ucl-uikit-table__cell css-x1o1q5"
99
+ data-mobile-role="primaryAction"
95
100
  data-testid="ucl-uikit-table__cell"
96
101
  >
97
102
  <div
98
- class="css-1fubti ucl-uikit-table__cell-content"
103
+ class="ucl-uikit-table__cell-content css-1d5ok6z"
99
104
  data-testid="ucl-uikit-table__cell-content"
100
105
  >
101
106
  <button
@@ -1,8 +1,7 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import { useTheme } from '../../../theme';
3
3
 
4
- export interface TableHeadProps
5
- extends React.HTMLAttributes<HTMLTableSectionElement> {}
4
+ export interface TableHeadProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
6
5
 
7
6
  const NAME = 'ucl-uikit-table__head';
8
7
 
@@ -12,10 +11,22 @@ const Head = ({ className, children, ...props }: TableHeadProps) => {
12
11
  const baseStyle = css`
13
12
  height: 40px;
14
13
  background-color: #f8f8f8; // TODO: Add a design token
14
+
15
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
16
+ position: absolute;
17
+ width: 1px;
18
+ height: 1px;
19
+ padding: 0;
20
+ margin: -1px;
21
+ overflow: hidden;
22
+ clip: rect(0, 0, 0, 0);
23
+ white-space: nowrap;
24
+ border: 0;
25
+ }
15
26
  `;
16
27
 
17
28
  const rowStyle = css`
18
- border-bottom: 1px solid ${theme.color.neutral.grey20};
29
+ border-bottom: 1px solid ${theme.colour.border.default};
19
30
  `;
20
31
 
21
32
  const style = cx(baseStyle, className, NAME);
@@ -1,11 +1,17 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import { useTheme } from '../../../../theme';
3
3
  import HeadCellContent from './HeadCellContent';
4
- import type { ColumnVariant, SortOrder } from '../../Table.types';
4
+ import type {
5
+ ColumnVariant,
6
+ SortOrder,
7
+ TableMobileRole,
8
+ } from '../../Table.types';
5
9
 
6
10
  export interface TableHeadCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
7
11
  variant?: ColumnVariant;
8
12
  accessor?: string;
13
+ mobileRole?: TableMobileRole;
14
+ mobileLabel?: string;
9
15
  sort?: SortOrder;
10
16
  onSortChange?: (
11
17
  accessor: string,
@@ -27,6 +33,8 @@ const NAME = 'ucl-uikit-table__head-cell';
27
33
  const HeadCell = ({
28
34
  variant = 'default',
29
35
  accessor,
36
+ mobileRole,
37
+ mobileLabel,
30
38
  sort = null,
31
39
  onSortChange,
32
40
  showSortIcon = true,
@@ -73,9 +81,12 @@ const HeadCell = ({
73
81
 
74
82
  const baseStyle = css`
75
83
  padding: ${theme.padding.p8} ${theme.padding.p16};
76
- color: ${theme.color.text.secondary};
77
- font-family: ${theme.font.family.primary};
78
- font-weight: 400;
84
+ color: ${theme.colour.text.secondary};
85
+ font-family: ${theme.typography.body.sm.fontFamily};
86
+ font-feature-settings: ${theme.typography.body.sm.fontSettings};
87
+ font-size: ${theme.typography.body.sm.fontSize}px;
88
+ font-weight: ${theme.typography.body.sm.fontWeight};
89
+ line-height: ${theme.typography.body.sm.lineHeight}%;
79
90
  text-align: left;
80
91
  cursor: ${isSortable ? 'pointer' : 'default'};
81
92
  user-select: none;
@@ -91,6 +102,8 @@ const HeadCell = ({
91
102
  className={style}
92
103
  data-testid={testId}
93
104
  data-accessor={accessor}
105
+ data-mobile-role={mobileRole}
106
+ data-mobile-label={mobileLabel}
94
107
  data-sort={sort}
95
108
  tabIndex={isSortable ? 0 : undefined}
96
109
  aria-sort={
@@ -5,7 +5,7 @@ exports[`HeadCell > Snapshot: checkbox variant 1`] = `
5
5
  <thead>
6
6
  <tr>
7
7
  <th
8
- class="css-19iwbn3 ucl-uikit-table__head-cell"
8
+ class="css-x1n4gt ucl-uikit-table__head-cell"
9
9
  data-testid="ucl-uikit-table__head-cell"
10
10
  scope="col"
11
11
  >
@@ -40,7 +40,7 @@ exports[`HeadCell > Snapshot: default 1`] = `
40
40
  <thead>
41
41
  <tr>
42
42
  <th
43
- class="css-19iwbn3 ucl-uikit-table__head-cell"
43
+ class="css-x1n4gt ucl-uikit-table__head-cell"
44
44
  data-testid="ucl-uikit-table__head-cell"
45
45
  scope="col"
46
46
  >
@@ -100,7 +100,7 @@ exports[`HeadCell > no sorting icon 1`] = `
100
100
  <thead>
101
101
  <tr>
102
102
  <th
103
- class="css-19iwbn3 ucl-uikit-table__head-cell"
103
+ class="css-x1n4gt ucl-uikit-table__head-cell"
104
104
  data-testid="ucl-uikit-table__head-cell"
105
105
  scope="col"
106
106
  >
@@ -1,47 +1,236 @@
1
+ import { Children, cloneElement, isValidElement, useState } from 'react';
1
2
  import { css, cx } from '@emotion/css';
2
3
  import { useTheme } from '../../../theme';
4
+ import { useTableContext } from '../Table.context';
5
+ import Icon from '../../Icon/Icon';
6
+ import type { TableCellProps } from './Cell/Cell';
7
+ import type { NormalizedTableMobileRole } from '../Table.types';
3
8
 
4
9
  export interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
5
10
  selected?: boolean;
11
+ mobileExpanded?: boolean;
12
+ defaultMobileExpanded?: boolean;
13
+ mobileCollapsible?: boolean;
14
+ mobileDetailsLabel?: string;
15
+ mobileExpandLabel?: string;
16
+ mobileCollapseLabel?: string;
17
+ onMobileExpandedChange?: (expanded: boolean) => void;
6
18
  }
7
19
 
8
20
  const NAME = 'ucl-uikit-table__row';
9
21
 
22
+ const isMobileBodyRole = (mobileRole?: NormalizedTableMobileRole) =>
23
+ !mobileRole || mobileRole === 'field';
24
+
25
+ const isMobileRole = (
26
+ mobileRole: NormalizedTableMobileRole | undefined,
27
+ expectedMobileRole: NormalizedTableMobileRole
28
+ ) => mobileRole === expectedMobileRole;
29
+
10
30
  const Row = ({
11
31
  selected = false,
32
+ mobileExpanded,
33
+ defaultMobileExpanded = true,
34
+ mobileCollapsible = true,
35
+ mobileDetailsLabel = 'Details',
36
+ mobileExpandLabel = 'Show details',
37
+ mobileCollapseLabel = 'Hide details',
38
+ onMobileExpandedChange,
12
39
  className,
13
40
  children,
14
41
  ...props
15
42
  }: TableRowProps) => {
16
43
  const [theme] = useTheme();
44
+ const { columns } = useTableContext();
45
+ const [uncontrolledMobileExpanded, setUncontrolledMobileExpanded] = useState(
46
+ defaultMobileExpanded
47
+ );
48
+
49
+ const isMobileExpanded =
50
+ mobileExpanded === undefined ? uncontrolledMobileExpanded : mobileExpanded;
51
+
52
+ const hasMobileBodyFields = Children.toArray(children).some(
53
+ (child, index) =>
54
+ isValidElement(child) && isMobileBodyRole(columns[index]?.mobileRole)
55
+ );
56
+ const hasMobileSelection = Children.toArray(children).some(
57
+ (child, index) =>
58
+ isValidElement(child) &&
59
+ isMobileRole(columns[index]?.mobileRole, 'selection')
60
+ );
61
+ const hasMobilePrimaryMeta = Children.toArray(children).some(
62
+ (child, index) =>
63
+ isValidElement(child) &&
64
+ isMobileRole(columns[index]?.mobileRole, 'primaryMeta')
65
+ );
66
+ const hasMobileContextMenu = Children.toArray(children).some(
67
+ (child, index) =>
68
+ isValidElement(child) &&
69
+ isMobileRole(columns[index]?.mobileRole, 'contextMenu')
70
+ );
71
+ const firstMobileBodyFieldIndex = Children.toArray(children).findIndex(
72
+ (child, index) =>
73
+ isValidElement(child) && isMobileBodyRole(columns[index]?.mobileRole)
74
+ );
75
+
76
+ const canMobileCollapse = mobileCollapsible && hasMobileBodyFields;
77
+ const effectiveMobileExpanded = !canMobileCollapse || isMobileExpanded;
78
+
79
+ const handleMobileExpandedChange = () => {
80
+ const nextMobileExpanded = !isMobileExpanded;
81
+
82
+ if (mobileExpanded === undefined)
83
+ setUncontrolledMobileExpanded(nextMobileExpanded);
84
+
85
+ onMobileExpandedChange?.(nextMobileExpanded);
86
+ };
17
87
 
18
88
  const baseStyle = css`
19
89
  height: 40px;
20
- border-bottom: 1px solid ${theme.color.neutral.grey20};
90
+ border-bottom: 1px solid ${theme.colour.border.default};
21
91
 
22
92
  &:hover {
23
- background-color: ${theme.color.neutral.grey5};
93
+ background-color: ${theme.colour.surface.secondary};
94
+ }
95
+
96
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
97
+ display: grid;
98
+ grid-template-columns: auto minmax(0, 1fr) auto;
99
+ align-items: start;
100
+ column-gap: ${theme.padding.p16};
101
+ row-gap: 0;
102
+ height: auto;
103
+ padding: ${theme.padding.p16};
104
+ margin-bottom: ${theme.margin.m16};
105
+ border: ${theme.border.b1} solid ${theme.colour.border.default};
106
+ border-radius: ${theme.radius.r4};
107
+ background-color: ${theme.colour.surface.default};
108
+
109
+ &:hover {
110
+ background-color: ${theme.colour.surface.default};
111
+ }
112
+
113
+ &[data-mobile-expanded='false'] > [data-mobile-role='field'] {
114
+ display: none;
115
+ }
116
+
117
+ &[data-mobile-expanded='false'] > [data-mobile-role='primaryAction'] {
118
+ display: none;
119
+ }
24
120
  }
25
121
  `;
26
122
 
27
123
  const highlightedStyle = css`
28
- background-color: ${theme.color.interaction.blue5};
124
+ background-color: ${theme.colour.surface.brandSecondary};
29
125
 
30
126
  &:hover {
31
- background-color: ${theme.color.interaction.blue5};
127
+ background-color: ${theme.colour.surface.brandSecondary};
128
+ }
129
+
130
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
131
+ background-color: ${theme.colour.surface.brandSecondary};
132
+ border-color: ${theme.colour.border.brandSubtle};
133
+
134
+ &:hover {
135
+ background-color: ${theme.colour.surface.brandSecondary};
136
+ }
137
+ }
138
+ `;
139
+
140
+ const mobileContextMenuGridStyle = css`
141
+ @media screen and (max-width: ${theme.breakpoints.tablet - 1}px) {
142
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
32
143
  }
33
144
  `;
34
145
 
35
- const style = cx(baseStyle, selected && highlightedStyle, className, NAME);
146
+ const style = cx(
147
+ baseStyle,
148
+ selected && highlightedStyle,
149
+ hasMobileContextMenu && mobileContextMenuGridStyle,
150
+ className,
151
+ NAME
152
+ );
153
+
154
+ const mobileDetailsCellStyle = css`
155
+ display: block;
156
+ grid-column: 1 / -1;
157
+ order: 10;
158
+ margin-top: ${theme.margin.m8};
159
+
160
+ @media screen and (min-width: ${theme.breakpoints.tablet}px) {
161
+ display: none;
162
+ }
163
+ `;
164
+
165
+ const mobileDetailsButtonStyle = css`
166
+ all: unset;
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: space-between;
170
+ box-sizing: border-box;
171
+ width: 100%;
172
+ min-height: ${theme.height.h24};
173
+ color: ${theme.colour.text.secondary};
174
+ font-family: ${theme.typography.body.sm.fontFamily};
175
+ font-feature-settings: ${theme.typography.body.sm.fontSettings};
176
+ font-size: ${theme.typography.body.sm.fontSize}px;
177
+ font-weight: ${theme.typography.body.sm.fontWeight};
178
+ line-height: ${theme.typography.body.sm.lineHeight}%;
179
+ cursor: pointer;
180
+
181
+ &:focus-visible {
182
+ box-shadow: ${theme.boxShadow.focus};
183
+ }
184
+ `;
185
+
186
+ let columnIndex = -1;
187
+ const mobileChildren = Children.map(children, (child) => {
188
+ if (!isValidElement<TableCellProps>(child)) return child;
189
+
190
+ columnIndex += 1;
191
+
192
+ return cloneElement(child, {
193
+ __mobileColumnIndex: columnIndex,
194
+ __mobileHasSelection: hasMobileSelection,
195
+ __mobileHasPrimaryMeta: hasMobilePrimaryMeta,
196
+ __mobileIsFirstBodyField: columnIndex === firstMobileBodyFieldIndex,
197
+ });
198
+ });
199
+
200
+ const mobileDetailsAriaLabel = isMobileExpanded
201
+ ? mobileCollapseLabel
202
+ : mobileExpandLabel;
36
203
 
37
204
  return (
38
205
  <tr
39
206
  className={style}
40
207
  data-testid={NAME}
41
208
  aria-selected={selected}
209
+ data-mobile-expanded={effectiveMobileExpanded}
42
210
  {...props}
43
211
  >
44
- {children}
212
+ {mobileChildren}
213
+ {canMobileCollapse && (
214
+ <td
215
+ className={mobileDetailsCellStyle}
216
+ data-mobile-role='details'
217
+ >
218
+ <button
219
+ type='button'
220
+ className={mobileDetailsButtonStyle}
221
+ aria-label={mobileDetailsAriaLabel}
222
+ aria-expanded={isMobileExpanded}
223
+ onClick={handleMobileExpandedChange}
224
+ >
225
+ <span>{mobileDetailsLabel}</span>
226
+ {isMobileExpanded ? (
227
+ <Icon.ChevronUp size={20} />
228
+ ) : (
229
+ <Icon.ChevronDown size={20} />
230
+ )}
231
+ </button>
232
+ </td>
233
+ )}
45
234
  </tr>
46
235
  );
47
236
  };
@@ -11,12 +11,12 @@ const SortingIcon = ({ sortOrder, size = 16 }: SortingIconProps) => {
11
11
 
12
12
  const leftArrowColour =
13
13
  sortOrder === 'asc'
14
- ? theme.color.neutral.black
15
- : theme.color.neutral.grey20;
14
+ ? theme.colour.icon.default
15
+ : theme.colour.icon.disabled;
16
16
  const rightArrowColour =
17
17
  sortOrder === 'desc'
18
- ? theme.color.neutral.black
19
- : theme.color.neutral.grey20;
18
+ ? theme.colour.icon.default
19
+ : theme.colour.icon.disabled;
20
20
 
21
21
  return (
22
22
  <svg
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "uikit-react-public",
3
3
  "private": false,
4
4
  "license": "UNLICENSED",
5
- "version": "0.36.1",
5
+ "version": "0.37.2",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -55,11 +55,10 @@
55
55
  "@eslint/compat": "^1.2.7",
56
56
  "@eslint/eslintrc": "^3.3.0",
57
57
  "@eslint/js": "^9.22.0",
58
+ "@storybook/addon-docs": "10.4.6",
58
59
  "@storybook/addon-links": "10.4.6",
59
60
  "@storybook/addon-onboarding": "10.4.6",
60
61
  "@storybook/react-vite": "10.4.6",
61
- "@storybook/addon-docs": "10.4.6",
62
- "eslint-plugin-storybook": "10.4.6",
63
62
  "@testing-library/jest-dom": "^6.6.3",
64
63
  "@testing-library/react": "^16.2.0",
65
64
  "@testing-library/user-event": "^14.6.1",
@@ -77,9 +76,10 @@
77
76
  "eslint-plugin-jest": "^28.11.0",
78
77
  "eslint-plugin-no-for-of-loops": "^1.0.1",
79
78
  "eslint-plugin-no-function-declare-after-return": "^1.1.0",
80
- "eslint-plugin-react": "^7.37.4",
79
+ "eslint-plugin-react": "^7.37.5",
81
80
  "eslint-plugin-react-hooks": "^5.2.0",
82
81
  "eslint-plugin-react-refresh": "^0.4.19",
82
+ "eslint-plugin-storybook": "10.4.6",
83
83
  "gh-pages": "^6.3.0",
84
84
  "globals": "^16.0.0",
85
85
  "husky": "^9.1.7",
@@ -88,7 +88,7 @@
88
88
  "prettier": "^3.5.3",
89
89
  "react": "^19.1.0",
90
90
  "react-dom": "^19.1.0",
91
- "react-router": "^7.13.1",
91
+ "react-router": "^8.3.0",
92
92
  "storybook": "10.4.6",
93
93
  "typescript": "^5.8.2",
94
94
  "typescript-eslint": "^8.26.1",