uikit-react-public 0.21.8 → 0.22.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 (30) hide show
  1. package/dist/components/Heading/Heading.d.ts +6 -4
  2. package/dist/components/Heading/Heading.stories.d.ts +8 -6
  3. package/dist/components/Paragraph/Paragraph.d.ts +5 -2
  4. package/dist/components/Paragraph/Paragraph.stories.d.ts +6 -3
  5. package/dist/components/Spinner/Spinner.d.ts +3 -2
  6. package/dist/components/Spinner/Spinner.stories.d.ts +1 -1
  7. package/dist/components/index.d.ts +6 -4
  8. package/dist/index.js +3712 -3783
  9. package/lib/components/Button/__tests__/__snapshots__/Button.test.tsx.snap +5 -3
  10. package/lib/components/Field/Field.tsx +0 -3
  11. package/lib/components/Heading/Heading.stories.tsx +34 -41
  12. package/lib/components/Heading/Heading.tsx +180 -49
  13. package/lib/components/Heading/__tests__/__snapshots__/Heading.test.tsx.snap +4 -4
  14. package/lib/components/Paragraph/Paragraph.stories.tsx +29 -27
  15. package/lib/components/Paragraph/Paragraph.tsx +212 -81
  16. package/lib/components/Paragraph/__tests__/__snapshots__/Paragraph.test.tsx.snap +5 -5
  17. package/lib/components/Spinner/Spinner.tsx +69 -70
  18. package/lib/components/Spinner/__tests__/Spinner.test.tsx +33 -1
  19. package/lib/components/Spinner/__tests__/__snapshots__/Spinner.test.tsx.snap +20 -12
  20. package/lib/components/Table/improvements-tracking.md +25 -0
  21. package/lib/components/index.ts +7 -4
  22. package/package.json +2 -1
  23. package/dist/components/HeadingNew/Heading.d.ts +0 -13
  24. package/dist/components/HeadingNew/index.d.ts +0 -2
  25. package/dist/components/ParagraphNew/Paragraph.d.ts +0 -13
  26. package/dist/components/ParagraphNew/index.d.ts +0 -4
  27. package/lib/components/HeadingNew/Heading.tsx +0 -208
  28. package/lib/components/HeadingNew/index.ts +0 -2
  29. package/lib/components/ParagraphNew/Paragraph.tsx +0 -200
  30. package/lib/components/ParagraphNew/index.ts +0 -6
@@ -1,95 +1,226 @@
1
- import { memo, HTMLAttributes, forwardRef } from 'react';
1
+ import { memo, HTMLAttributes, JSX } from 'react';
2
2
  import { css, cx } from '@emotion/css';
3
3
  import useTheme from '../../theme/useTheme';
4
4
  import marginsStyle, { MarginProps } from '../common/marginsStyle';
5
5
 
6
6
  export const NAME = 'ucl-uikit-paragrah';
7
7
 
8
- export interface ParagraphBaseProps
9
- extends HTMLAttributes<HTMLParagraphElement> {
10
- size?: 'standfirst' | 'body' | 'small';
11
- emphasis?: 'high' | 'medium';
12
- margins?: boolean; // todo: deprecate
8
+ export type ParagraphLevel =
9
+ | 'lg'
10
+ | 'lg-medium'
11
+ | 'lg-semibold'
12
+ | 'lg-bold'
13
+ | 'md'
14
+ | 'md-medium'
15
+ | 'md-semibold'
16
+ | 'md-semibold'
17
+ | 'md-medium-numeric'
18
+ | 'sm'
19
+ | 'sm-medium'
20
+ | 'sm-semibold'
21
+ | 'xs'
22
+ | 'xs-medium';
23
+
24
+ export interface ParagraphBaseProps extends HTMLAttributes<HTMLParagraphElement> {
25
+ level?: ParagraphLevel;
26
+ size?: 'standfirst' | 'body' | 'small'; // deprecated
27
+ emphasis?: 'high' | 'medium'; // deprecated
28
+ as?: 'p' | 'div' | 'span';
29
+ margins?: boolean; // deprecated
13
30
  testId?: string;
31
+ ref?: React.Ref<HTMLParagraphElement>;
14
32
  }
15
33
 
16
34
  export type ParagraphProps = ParagraphBaseProps & MarginProps;
17
35
 
18
- export type Ref = HTMLParagraphElement;
19
-
20
- const Paragraph = forwardRef<Ref, ParagraphProps>(
21
- (
22
- {
23
- size = 'body',
24
- emphasis = 'high',
25
- margins = true,
26
- testId = NAME,
27
- className,
28
- ...props
36
+ const Paragraph = ({
37
+ level = 'md',
38
+ size = 'body',
39
+ emphasis = 'high',
40
+ as = 'p',
41
+ margins = true,
42
+ testId = NAME,
43
+ className,
44
+ ref,
45
+ noMargins,
46
+ ...props
47
+ }: ParagraphProps) => {
48
+ const [theme] = useTheme();
49
+
50
+ const {
51
+ typography: {
52
+ body: {
53
+ lg,
54
+ lgMedium,
55
+ lgSemibold,
56
+ lgBold,
57
+ md,
58
+ mdMedium,
59
+ mdSemibold,
60
+ mdMediumNumeric,
61
+ sm,
62
+ smMedium,
63
+ smSemibold,
64
+ xs,
65
+ xsMedium,
66
+ },
29
67
  },
30
- ref
31
- ) => {
32
- const [theme] = useTheme();
33
-
34
- const {
35
- font: { size: fontSize, lineHeight },
36
- color: { text },
37
- } = theme;
38
-
39
- const baseStyle = css`
40
- font-family: ${theme.font.family.primary};
41
- color: ${emphasis === 'medium' ? text.secondary : text.primary};
42
- `;
43
-
44
- const standfirstStyle = css`
45
- font-size: ${fontSize.f20};
46
- line-height: ${lineHeight.h140};
47
- @media screen and (min-width: ${theme.breakpoints.desktop}px) {
48
- font-size: ${fontSize.f24};
49
- line-height: ${lineHeight.h150};
50
- }
51
- `;
52
-
53
- const bodyStyle = css`
54
- font-size: ${fontSize.f16};
55
- line-height: ${lineHeight.h150};
56
- @media screen and (min-width: ${theme.breakpoints.desktop}px) {
57
- font-size: ${fontSize.f18};
58
- }
59
- `;
60
-
61
- const smallStyle = css`
62
- font-size: ${fontSize.f14};
63
- line-height: ${lineHeight.h150};
64
- @media screen and (min-width: ${theme.breakpoints.desktop}px) {
65
- font-size: ${fontSize.f14};
66
- }
67
- `;
68
-
69
- const noMarginsStyle = css`
70
- margin: 0;
71
- `;
72
-
73
- const style = cx(
74
- NAME,
75
- baseStyle,
76
- marginsStyle(props, theme),
77
- size === 'standfirst' && standfirstStyle,
78
- size === 'body' && bodyStyle,
79
- size === 'small' && smallStyle,
80
- margins === false && noMarginsStyle,
81
- className
82
- );
83
-
84
- return (
85
- <p
86
- ref={ref}
87
- className={style}
88
- data-testid={testId}
89
- {...props}
90
- />
91
- );
92
- }
93
- );
68
+ } = theme;
69
+
70
+ const mappedLevel: ParagraphLevel =
71
+ level ||
72
+ (size === 'standfirst'
73
+ ? emphasis === 'medium'
74
+ ? 'lg-medium'
75
+ : 'lg'
76
+ : size === 'small'
77
+ ? emphasis === 'medium'
78
+ ? 'sm-medium'
79
+ : 'sm'
80
+ : emphasis === 'medium'
81
+ ? 'md-medium'
82
+ : 'md');
83
+
84
+ const baseStyle = css`
85
+ font-family: ${md.fontFamily};
86
+ font-feature-settings: ${md.fontSettings};
87
+ color: ${emphasis === 'medium'
88
+ ? theme.colour.text.secondary
89
+ : theme.colour.text.default};
90
+ margin: 0;
91
+ `;
92
+
93
+ const lgStyle = css`
94
+ font-size: ${lg.fontSize}px;
95
+ font-weight: ${lg.fontWeight};
96
+ line-height: ${lg.lineHeight}%;
97
+ `;
98
+
99
+ const lgMediumStyle = css`
100
+ font-size: ${lgMedium.fontSize}px;
101
+ font-weight: ${lgMedium.fontWeight};
102
+ line-height: ${lgMedium.lineHeight}%;
103
+ `;
104
+
105
+ const lgSemiboldStyle = css`
106
+ font-size: ${lgSemibold.fontSize}px;
107
+ font-weight: ${lgSemibold.fontWeight};
108
+ line-height: ${lgSemibold.lineHeight}%;
109
+ `;
110
+
111
+ const lgBoldStyle = css`
112
+ font-size: ${lgBold.fontSize}px;
113
+ font-weight: ${lgBold.fontWeight};
114
+ line-height: ${lgBold.lineHeight}%;
115
+ `;
116
+
117
+ const mdStyle = css`
118
+ font-size: ${md.fontSize}px;
119
+ font-weight: ${md.fontWeight};
120
+ line-height: ${md.lineHeight}%;
121
+ `;
122
+
123
+ const mdMediumStyle = css`
124
+ font-size: ${mdMedium.fontSize}px;
125
+ font-weight: ${mdMedium.fontWeight};
126
+ line-height: ${mdMedium.lineHeight}%;
127
+ `;
128
+
129
+ const mdSemiboldStyle = css`
130
+ font-size: ${mdSemibold.fontSize}px;
131
+ font-weight: ${mdSemibold.fontWeight};
132
+ line-height: ${mdSemibold.lineHeight}%;
133
+ `;
134
+
135
+ const mdMediumNumericStyle = css`
136
+ font-size: ${mdMediumNumeric.fontSize}px;
137
+ font-weight: ${mdMediumNumeric.fontWeight};
138
+ line-height: ${mdMediumNumeric.lineHeight}%;
139
+ `;
140
+
141
+ const smStyle = css`
142
+ font-size: ${sm.fontSize}px;
143
+ font-weight: ${sm.fontWeight};
144
+ line-height: ${sm.lineHeight}%;
145
+ `;
146
+
147
+ const smMediumStyle = css`
148
+ font-size: ${smMedium.fontSize}px;
149
+ font-weight: ${smMedium.fontWeight};
150
+ line-height: ${smMedium.lineHeight}%;
151
+ `;
152
+
153
+ const smSemiboldStyle = css`
154
+ font-size: ${smSemibold.fontSize}px;
155
+ font-weight: ${smSemibold.fontWeight};
156
+ line-height: ${smSemibold.lineHeight}%;
157
+ `;
158
+
159
+ const xsStyle = css`
160
+ font-size: ${xs.fontSize}px;
161
+ font-weight: ${xs.fontWeight};
162
+ line-height: ${xs.lineHeight}%;
163
+ `;
164
+
165
+ const xsMediumStyle = css`
166
+ font-size: ${xsMedium.fontSize}px;
167
+ font-weight: ${xsMedium.fontWeight};
168
+ line-height: ${xsMedium.lineHeight}%;
169
+ `;
170
+
171
+ const style = cx(
172
+ NAME,
173
+ baseStyle,
174
+ mappedLevel === 'lg' && lgStyle,
175
+ mappedLevel === 'lg-medium' && lgMediumStyle,
176
+ mappedLevel === 'lg-semibold' && lgSemiboldStyle,
177
+ mappedLevel === 'lg-bold' && lgBoldStyle,
178
+ mappedLevel === 'md' && mdStyle,
179
+ mappedLevel === 'md-medium' && mdMediumStyle,
180
+ mappedLevel === 'md-semibold' && mdSemiboldStyle,
181
+ mappedLevel === 'md-medium-numeric' && mdMediumNumericStyle,
182
+ mappedLevel === 'sm' && smStyle,
183
+ mappedLevel === 'sm-medium' && smMediumStyle,
184
+ mappedLevel === 'sm-semibold' && smSemiboldStyle,
185
+ mappedLevel === 'xs' && xsStyle,
186
+ mappedLevel === 'xs-medium' && xsMediumStyle,
187
+ marginsStyle(
188
+ { ...props, noMargins: noMargins || margins === false },
189
+ theme
190
+ ),
191
+ className
192
+ );
193
+
194
+ const paragraphTag = as as keyof JSX.IntrinsicElements;
195
+
196
+ return (
197
+ <>
198
+ {paragraphTag === 'p' && (
199
+ <p
200
+ ref={ref}
201
+ className={style}
202
+ data-testid={testId}
203
+ {...props}
204
+ />
205
+ )}
206
+ {paragraphTag === 'div' && (
207
+ <div
208
+ ref={ref as React.Ref<HTMLDivElement>}
209
+ className={style}
210
+ data-testid={testId}
211
+ {...props}
212
+ />
213
+ )}
214
+ {paragraphTag === 'span' && (
215
+ <span
216
+ ref={ref as React.Ref<HTMLSpanElement>}
217
+ className={style}
218
+ data-testid={testId}
219
+ {...props}
220
+ />
221
+ )}
222
+ </>
223
+ );
224
+ };
94
225
 
95
226
  export default memo(Paragraph);
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`Paragraph > snapshot: custom test id 1`] = `
4
4
  <p
5
- class="ucl-uikit-paragrah css-1q7fap"
5
+ class="ucl-uikit-paragrah css-h12wmf"
6
6
  data-testid="custom-test-id"
7
7
  >
8
8
  This is a test
@@ -11,7 +11,7 @@ exports[`Paragraph > snapshot: custom test id 1`] = `
11
11
 
12
12
  exports[`Paragraph > snapshot: emphasis 1`] = `
13
13
  <p
14
- class="ucl-uikit-paragrah css-1q7fap"
14
+ class="ucl-uikit-paragrah css-h12wmf"
15
15
  data-testid="ucl-uikit-paragrah"
16
16
  >
17
17
  high emphasis
@@ -20,7 +20,7 @@ exports[`Paragraph > snapshot: emphasis 1`] = `
20
20
 
21
21
  exports[`Paragraph > snapshot: minimal props 1`] = `
22
22
  <p
23
- class="ucl-uikit-paragrah css-1q7fap"
23
+ class="ucl-uikit-paragrah css-h12wmf"
24
24
  data-testid="ucl-uikit-paragrah"
25
25
  >
26
26
  This is a test
@@ -29,7 +29,7 @@ exports[`Paragraph > snapshot: minimal props 1`] = `
29
29
 
30
30
  exports[`Paragraph > snapshot: no margins 1`] = `
31
31
  <p
32
- class="ucl-uikit-paragrah css-1trsbhh"
32
+ class="ucl-uikit-paragrah css-1dosm7d"
33
33
  data-testid="ucl-uikit-paragrah"
34
34
  >
35
35
  no margins
@@ -38,7 +38,7 @@ exports[`Paragraph > snapshot: no margins 1`] = `
38
38
 
39
39
  exports[`Paragraph > snapshot: size 1`] = `
40
40
  <p
41
- class="ucl-uikit-paragrah css-u9mr1g"
41
+ class="ucl-uikit-paragrah css-h12wmf"
42
42
  data-testid="ucl-uikit-paragrah"
43
43
  >
44
44
  standfirst
@@ -1,4 +1,4 @@
1
- import { SVGAttributes, forwardRef, memo } from 'react';
1
+ import { SVGAttributes, Ref, memo } from 'react';
2
2
  import { css, cx } from '@emotion/css';
3
3
  import { useTheme } from '../../theme';
4
4
 
@@ -8,87 +8,86 @@ export interface SpinnerProps extends SVGAttributes<SVGSVGElement> {
8
8
  size?: number;
9
9
  strokeWidth?: number;
10
10
  testId?: string;
11
+ ref?: Ref<SVGSVGElement>;
11
12
  inheritColour?: boolean;
12
13
  }
13
14
 
14
- const Spinner = forwardRef<SVGSVGElement, SpinnerProps>(
15
- (
16
- {
17
- size = 72,
18
- strokeWidth = 4,
19
- className,
20
- testId = NAME,
21
- inheritColour = false,
22
- ...rest
23
- }: SpinnerProps,
24
- ref
25
- ) => {
26
- const [theme] = useTheme();
15
+ const Spinner = ({
16
+ size = 72,
17
+ strokeWidth = 4,
18
+ className,
19
+ testId = NAME,
20
+ inheritColour = false,
21
+ ref,
22
+ ...rest
23
+ }: SpinnerProps) => {
24
+ const [theme] = useTheme();
27
25
 
28
- const rotationDuration = `3s`; // rotation duration
29
- const dashDuration = `2s`; // dash duration
26
+ const rotationDuration = `3s`; // rotation duration
27
+ const dashDuration = `2s`; // dash duration
30
28
 
31
- const baseStyle = css`
32
- color: ${inheritColour ? 'inherit' : theme.colour.icon.brand};
33
- `;
29
+ const baseStyle = css`
30
+ color: ${inheritColour ? 'inherit' : theme.colour.icon.brand};
31
+ `;
34
32
 
35
- const gStyle = css`
36
- transform-origin: 50% 50%;
37
- animation: rotate ${rotationDuration} linear infinite;
33
+ const gStyle = css`
34
+ transform-origin: 50% 50%;
35
+ animation: rotate ${rotationDuration} linear infinite;
38
36
 
39
- @keyframes rotate {
40
- 100% {
41
- transform: rotate(360deg);
42
- }
37
+ @keyframes rotate {
38
+ 100% {
39
+ transform: rotate(360deg);
43
40
  }
44
- `;
41
+ }
42
+ `;
45
43
 
46
- const circleStyle = css`
47
- stroke-linecap: square;
48
- animation: dash ${dashDuration} ease-in-out infinite;
44
+ const circleStyle = css`
45
+ stroke-linecap: square;
46
+ animation: dash ${dashDuration} ease-in-out infinite;
49
47
 
50
- @keyframes dash {
51
- 0% {
52
- stroke-dasharray: 1, 150;
53
- stroke-dashoffset: 0;
54
- }
55
- 50% {
56
- stroke-dasharray: 90, 150;
57
- stroke-dashoffset: -35;
58
- }
59
- 100% {
60
- stroke-dasharray: 90, 150;
61
- stroke-dashoffset: -124;
62
- }
48
+ @keyframes dash {
49
+ 0% {
50
+ stroke-dasharray: 1, 150;
51
+ stroke-dashoffset: 0;
63
52
  }
64
- `;
53
+ 50% {
54
+ stroke-dasharray: 90, 150;
55
+ stroke-dashoffset: -35;
56
+ }
57
+ 100% {
58
+ stroke-dasharray: 90, 150;
59
+ stroke-dashoffset: -124;
60
+ }
61
+ }
62
+ `;
65
63
 
66
- const style = cx(NAME, baseStyle, className);
64
+ const style = cx(NAME, baseStyle, className);
67
65
 
68
- return (
69
- <svg
70
- viewBox='0 0 50 50'
71
- className={style}
72
- data-testid={testId}
73
- ref={ref}
74
- width={size} // Set the width of the SVG element
75
- height={size} // Set the height of the SVG element
76
- {...rest}
77
- >
78
- <g className={gStyle}>
79
- <circle
80
- className={circleStyle}
81
- cx='25'
82
- cy='25'
83
- r='20'
84
- fill='none'
85
- stroke='currentColor'
86
- strokeWidth={strokeWidth}
87
- />
88
- </g>
89
- </svg>
90
- );
91
- }
92
- );
66
+ return (
67
+ <svg
68
+ role='progressbar'
69
+ aria-label='Loading'
70
+ viewBox='0 0 50 50'
71
+ className={style}
72
+ data-testid={testId}
73
+ ref={ref}
74
+ width={size} // Set the width of the SVG element
75
+ height={size} // Set the height of the SVG element
76
+ {...rest}
77
+ >
78
+ <g className={gStyle}>
79
+ <circle
80
+ className={circleStyle}
81
+ cx='25'
82
+ cy='25'
83
+ r='20'
84
+ fill='none'
85
+ stroke='currentColor'
86
+ strokeWidth={strokeWidth}
87
+ />
88
+ </g>
89
+ </svg>
90
+ );
91
+ };
93
92
 
94
93
  export default memo(Spinner);
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'vitest';
2
- import { render } from '@testing-library/react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import { css } from '@emotion/css';
4
4
  import SpinnerSvg from '../Spinner';
5
5
  import { ThemeContextProvider } from '../../..';
@@ -68,4 +68,36 @@ describe('SpinnerSvg', () => {
68
68
  const svg = getByTestId('custom-test-id');
69
69
  expect(svg).toBeDefined();
70
70
  });
71
+
72
+ // Accessibility tests
73
+
74
+ test('has role="progressbar" by default', () => {
75
+ render(
76
+ <ThemeContextProvider>
77
+ <SpinnerSvg />
78
+ </ThemeContextProvider>
79
+ );
80
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
81
+ });
82
+
83
+ test('has aria-label="Loading" by default', () => {
84
+ render(
85
+ <ThemeContextProvider>
86
+ <SpinnerSvg />
87
+ </ThemeContextProvider>
88
+ );
89
+ expect(screen.getByRole('progressbar')).toHaveAccessibleName('Loading');
90
+ });
91
+
92
+ test('aria-label can be overridden via props', () => {
93
+ render(
94
+ <ThemeContextProvider>
95
+ <SpinnerSvg aria-label='Loading visitors' />
96
+ </ThemeContextProvider>
97
+ );
98
+ expect(screen.getByRole('progressbar')).toHaveAccessibleName(
99
+ 'Loading visitors'
100
+ );
101
+ expect(screen.getByRole('progressbar')).not.toHaveAccessibleName('Loading');
102
+ });
71
103
  });
@@ -2,17 +2,19 @@
2
2
 
3
3
  exports[`SpinnerSvg > Snapshot: custom className 1`] = `
4
4
  <svg
5
- class="ucl-uikit-spinner css-g7tq6j"
5
+ aria-label="Loading"
6
+ class="ucl-uikit-spinner css-rppy7j"
6
7
  data-testid="ucl-uikit-spinner"
7
8
  height="72"
9
+ role="progressbar"
8
10
  viewBox="0 0 50 50"
9
11
  width="72"
10
12
  >
11
13
  <g
12
- class="css-ypv7tf"
14
+ class="css-swia5b"
13
15
  >
14
16
  <circle
15
- class="css-1w5ipzg"
17
+ class="css-1iyl2x2"
16
18
  cx="25"
17
19
  cy="25"
18
20
  fill="none"
@@ -26,17 +28,19 @@ exports[`SpinnerSvg > Snapshot: custom className 1`] = `
26
28
 
27
29
  exports[`SpinnerSvg > snapshot: custom size 1`] = `
28
30
  <svg
29
- class="ucl-uikit-spinner css-1g359sh"
31
+ aria-label="Loading"
32
+ class="ucl-uikit-spinner css-1scd17h"
30
33
  data-testid="ucl-uikit-spinner"
31
34
  height="32"
35
+ role="progressbar"
32
36
  viewBox="0 0 50 50"
33
37
  width="32"
34
38
  >
35
39
  <g
36
- class="css-ypv7tf"
40
+ class="css-swia5b"
37
41
  >
38
42
  <circle
39
- class="css-1w5ipzg"
43
+ class="css-1iyl2x2"
40
44
  cx="25"
41
45
  cy="25"
42
46
  fill="none"
@@ -50,17 +54,19 @@ exports[`SpinnerSvg > snapshot: custom size 1`] = `
50
54
 
51
55
  exports[`SpinnerSvg > snapshot: default props 1`] = `
52
56
  <svg
53
- class="ucl-uikit-spinner css-1g359sh"
57
+ aria-label="Loading"
58
+ class="ucl-uikit-spinner css-1scd17h"
54
59
  data-testid="ucl-uikit-spinner"
55
60
  height="72"
61
+ role="progressbar"
56
62
  viewBox="0 0 50 50"
57
63
  width="72"
58
64
  >
59
65
  <g
60
- class="css-ypv7tf"
66
+ class="css-swia5b"
61
67
  >
62
68
  <circle
63
- class="css-1w5ipzg"
69
+ class="css-1iyl2x2"
64
70
  cx="25"
65
71
  cy="25"
66
72
  fill="none"
@@ -74,17 +80,19 @@ exports[`SpinnerSvg > snapshot: default props 1`] = `
74
80
 
75
81
  exports[`SpinnerSvg > snapshot: inherits parent colour when inheritColour is true 1`] = `
76
82
  <svg
77
- class="ucl-uikit-spinner css-1izyex6"
83
+ aria-label="Loading"
84
+ class="ucl-uikit-spinner css-15f359x"
78
85
  data-testid="ucl-uikit-spinner"
79
86
  height="72"
87
+ role="progressbar"
80
88
  viewBox="0 0 50 50"
81
89
  width="72"
82
90
  >
83
91
  <g
84
- class="css-ypv7tf"
92
+ class="css-swia5b"
85
93
  >
86
94
  <circle
87
- class="css-1w5ipzg"
95
+ class="css-1iyl2x2"
88
96
  cx="25"
89
97
  cy="25"
90
98
  fill="none"
@@ -0,0 +1,25 @@
1
+ # Table – Improvements Tracking
2
+
3
+ ## Decision: Table.Cell should not support a `variant='button'` prop
4
+
5
+ **Status:** Decided
6
+ **Date:** 2026-04-13
7
+ **Context:** EVE frontend – My Visitors screen (`MyVisitorsScreen.tsx`)
8
+
9
+ ### Problem
10
+
11
+ We considered two approaches for adding interactive actions (e.g. "Invite again") inside table cells:
12
+
13
+ - **A) `variant='button'` on `Table.Cell`** – The cell itself renders as a button, with props like `buttonProps`, `loading`, and `onClick` forwarded through the cell.
14
+ - **B) `<Button>` as a child of `Table.Cell`** – The cell remains a plain container; a `<Button>` component is composed inside it.
15
+
16
+ ### Decision
17
+
18
+ **Go with B – compose `<Button>` as a child.**
19
+
20
+ ### Rationale
21
+
22
+ - **Composition over configuration.** Adding `variant='button'` conflates layout (cell) and interaction (button) concerns, increasing the API surface of `Table.Cell` with forwarded props (`loading`, `onClick`, `disabled`, `size`, button variant, etc.).
23
+ - **Flexibility.** A child `<Button>` can easily be swapped for an icon button, a link, a menu, or multiple actions — without the cell API needing to accommodate every combination.
24
+ - **No duplicated logic.** `<Button>` already handles loading spinners, disabled states, and accessibility. A `variant='button'` cell would need to re-implement or proxy all of that.
25
+ - **Simpler Table.Cell API.** Fewer props to document, test, and maintain.