uikit-react-public 0.21.9 → 0.22.1

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.
@@ -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"
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`Link > snapshot: no props 1`] = `
4
4
  <a
5
- class="ucl-uikit-base-link ucl-uikit-standalone-link css-1hrm145"
5
+ class="ucl-uikit-base-link ucl-uikit-standalone-link css-zj0pf"
6
6
  data-testid="ucl-uikit-standalone-link"
7
7
  >
8
8
  test
@@ -11,7 +11,7 @@ exports[`Link > snapshot: no props 1`] = `
11
11
 
12
12
  exports[`Link > snapshot: testId prop 1`] = `
13
13
  <a
14
- class="ucl-uikit-base-link ucl-uikit-standalone-link css-1hrm145"
14
+ class="ucl-uikit-base-link ucl-uikit-standalone-link css-zj0pf"
15
15
  data-testid="test123"
16
16
  >
17
17
  testidlink
@@ -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.
@@ -31,6 +31,9 @@ export type { SnackbarProps } from './Snackbar';
31
31
  export { default as Avatar } from './Avatar';
32
32
  export type { AvatarProps } from './Avatar';
33
33
 
34
+ export { default as Badge } from './Badge';
35
+ export type { BadgeProps } from './Badge';
36
+
34
37
  export { default as Spinner } from './Spinner';
35
38
  export type { SpinnerProps } from './Spinner';
36
39
 
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.21.9",
5
+ "version": "0.22.1",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "dev": "vite",
18
18
  "dev_expose": "vite --host",
19
19
  "build": "tsc --p ./tsconfig-build.json && vite build",
20
+ "build:watch": "tsc --p ./tsconfig-build.json --watch --preserveWatchOutput & vite build --watch",
20
21
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
21
22
  "pre-commit": "lint-staged",
22
23
  "format": "prettier \"./**/*.{ts,tsx}\" --write --config ./prettierrc",