uikit-react-public 0.36.0 → 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 (32) 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 +6564 -6194
  13. package/lib/components/Snackbar/Snackbar.tsx +2 -3
  14. package/lib/components/Snackbar/__tests__/__snapshots__/Snackbar.test.tsx.snap +2 -2
  15. package/lib/components/Table/Table.context.ts +12 -0
  16. package/lib/components/Table/Table.tsx +91 -12
  17. package/lib/components/Table/Table.types.ts +21 -0
  18. package/lib/components/Table/__tests__/Table.test.tsx +95 -1
  19. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +54 -13
  20. package/lib/components/Table/index.ts +6 -1
  21. package/lib/components/Table/subcomponents/Body.tsx +16 -3
  22. package/lib/components/Table/subcomponents/Cell/Cell.tsx +193 -4
  23. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +63 -3
  24. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +15 -10
  25. package/lib/components/Table/subcomponents/Head.tsx +14 -3
  26. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +17 -4
  27. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +3 -3
  28. package/lib/components/Table/subcomponents/Row.tsx +195 -6
  29. package/lib/components/Table/subcomponents/SortIcon.tsx +4 -4
  30. package/lib/theme/__tests__/useTheme.test.tsx +2 -2
  31. package/lib/theme/useTheme.tsx +1 -1
  32. package/package.json +1 -1
@@ -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
@@ -100,14 +100,14 @@ describe('useTheme', () => {
100
100
  );
101
101
 
102
102
  expect(document.body).toHaveStyle({
103
- backgroundColor: lightTheme.colour.bg.brand,
103
+ backgroundColor: lightTheme.colour.bg.default,
104
104
  color: lightTheme.colour.text.default,
105
105
  });
106
106
 
107
107
  fireEvent.click(screen.getByTestId('set-dark'));
108
108
 
109
109
  expect(document.body).toHaveStyle({
110
- backgroundColor: darkTheme.colour.bg.brand,
110
+ backgroundColor: darkTheme.colour.bg.default,
111
111
  color: darkTheme.colour.text.default,
112
112
  });
113
113
  });
@@ -96,7 +96,7 @@ const globalThemeStyles = (activeTheme: ThemeType) => css`
96
96
  html,
97
97
  body,
98
98
  #root {
99
- background-color: ${activeTheme.colour.bg.brand};
99
+ background-color: ${activeTheme.colour.bg.default};
100
100
  color: ${activeTheme.colour.text.default};
101
101
  }
102
102
  `;
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.0",
5
+ "version": "0.37.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",