uikit-react-public 0.27.2 → 0.29.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.
- package/dist/components/BreadcrumbsNew/BaseBreadcrumbsItem.d.ts +12 -0
- package/dist/components/BreadcrumbsNew/Breadcrumbs.d.ts +18 -0
- package/dist/components/BreadcrumbsNew/BreadcrumbsCurrent.d.ts +8 -0
- package/dist/components/BreadcrumbsNew/BreadcrumbsLink.d.ts +7 -0
- package/dist/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.d.ts +1 -0
- package/dist/components/BreadcrumbsNew/index.d.ts +2 -0
- package/dist/components/Chip/Chip.d.ts +11 -0
- package/dist/components/Chip/Chip.stories.d.ts +25 -0
- package/dist/components/Chip/__tests__/Chip.test.d.ts +1 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Link/BaseLink.d.ts +3 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/index.js +5965 -5634
- package/lib/components/BaseCheckbox/BaseCheckbox.tsx +1 -5
- package/lib/components/BreadcrumbsNew/BaseBreadcrumbsItem.tsx +69 -0
- package/lib/components/BreadcrumbsNew/Breadcrumbs.tsx +118 -0
- package/lib/components/BreadcrumbsNew/BreadcrumbsCurrent.tsx +54 -0
- package/lib/components/BreadcrumbsNew/BreadcrumbsLink.tsx +67 -0
- package/lib/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.tsx +53 -0
- package/lib/components/BreadcrumbsNew/index.ts +8 -0
- package/lib/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap +4 -4
- package/lib/components/Chip/Chip.stories.tsx +37 -0
- package/lib/components/Chip/Chip.tsx +191 -0
- package/lib/components/Chip/__tests__/Chip.test.tsx +287 -0
- package/lib/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap +255 -0
- package/lib/components/Chip/index.ts +2 -0
- package/lib/components/Link/BaseLink.tsx +40 -2
- package/lib/components/Link/__tests__/link.test.tsx +58 -1
- package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +1 -1
- package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +1 -1
- package/lib/components/index.ts +12 -0
- package/package.json +1 -1
|
@@ -116,11 +116,7 @@ const BaseCheckbox = ({
|
|
|
116
116
|
grid-area: 1 / 1;
|
|
117
117
|
line-height: 0;
|
|
118
118
|
pointer-events: none;
|
|
119
|
-
|
|
120
|
-
input:focus-visible + & {
|
|
121
|
-
outline: 2px solid currentColor;
|
|
122
|
-
outline-offset: 2px;
|
|
123
|
-
}
|
|
119
|
+
outline: none;
|
|
124
120
|
`;
|
|
125
121
|
|
|
126
122
|
const style = cx(NAME, rootStyle, className);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { LiHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import Icon from '../Icon';
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import type { BreadcrumbsSize } from './Breadcrumbs';
|
|
6
|
+
|
|
7
|
+
export const NAME = 'ucl-uikit-breadcrumbs__item';
|
|
8
|
+
|
|
9
|
+
export interface BaseBreadcrumbsItemProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
10
|
+
separator?: boolean;
|
|
11
|
+
back?: boolean;
|
|
12
|
+
size?: BreadcrumbsSize;
|
|
13
|
+
testId?: string;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BaseBreadcrumbsItem = ({
|
|
18
|
+
separator = false,
|
|
19
|
+
back = false,
|
|
20
|
+
size = 'medium',
|
|
21
|
+
testId = NAME,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: BaseBreadcrumbsItemProps) => {
|
|
26
|
+
const [theme] = useTheme();
|
|
27
|
+
|
|
28
|
+
const style = cx(
|
|
29
|
+
NAME,
|
|
30
|
+
css`
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: ${theme.margin.m8};
|
|
34
|
+
min-width: 0;
|
|
35
|
+
`,
|
|
36
|
+
className
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const iconStyle = css`
|
|
40
|
+
flex: 0 0 auto;
|
|
41
|
+
color: ${theme.colour.icon.brandPrimary};
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<li
|
|
46
|
+
className={style}
|
|
47
|
+
data-testid={testId}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{back && (
|
|
51
|
+
<Icon.ArrowLeft
|
|
52
|
+
aria-hidden
|
|
53
|
+
className={iconStyle}
|
|
54
|
+
size={size === 'small' ? 16 : 20}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
{children}
|
|
58
|
+
{separator && (
|
|
59
|
+
<Icon.ChevronRight
|
|
60
|
+
aria-hidden
|
|
61
|
+
className={iconStyle}
|
|
62
|
+
size={size === 'small' ? 12 : 16}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</li>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default BaseBreadcrumbsItem;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
NamedExoticComponent,
|
|
5
|
+
ReactElement,
|
|
6
|
+
cloneElement,
|
|
7
|
+
isValidElement,
|
|
8
|
+
memo,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { css, cx } from '@emotion/css';
|
|
11
|
+
import { useTheme } from '../../theme';
|
|
12
|
+
import BaseBreadcrumbsItem, {
|
|
13
|
+
BaseBreadcrumbsItemProps,
|
|
14
|
+
} from './BaseBreadcrumbsItem';
|
|
15
|
+
import BreadcrumbsCurrent, {
|
|
16
|
+
BreadcrumbsCurrentProps,
|
|
17
|
+
} from './BreadcrumbsCurrent';
|
|
18
|
+
import BreadcrumbsLink, { BreadcrumbsLinkProps } from './BreadcrumbsLink';
|
|
19
|
+
|
|
20
|
+
export const NAME = 'ucl-uikit-breadcrumbs';
|
|
21
|
+
|
|
22
|
+
export type BreadcrumbsSize = 'small' | 'medium';
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbsProps extends HTMLAttributes<HTMLElement> {
|
|
25
|
+
size?: BreadcrumbsSize;
|
|
26
|
+
testId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type BreadcrumbChild = ReactElement<{
|
|
30
|
+
separator?: boolean;
|
|
31
|
+
back?: boolean;
|
|
32
|
+
size?: BreadcrumbsSize;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
const Breadcrumbs = ({
|
|
36
|
+
size = 'medium',
|
|
37
|
+
testId = NAME,
|
|
38
|
+
className,
|
|
39
|
+
children,
|
|
40
|
+
'aria-label': ariaLabel = 'Breadcrumb',
|
|
41
|
+
...props
|
|
42
|
+
}: BreadcrumbsProps) => {
|
|
43
|
+
const [theme] = useTheme();
|
|
44
|
+
const items = Children.toArray(children).filter(isValidElement);
|
|
45
|
+
const parentItem = items[items.length - 2] as BreadcrumbChild | undefined;
|
|
46
|
+
|
|
47
|
+
const listStyle = css`
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: ${theme.margin.m8};
|
|
51
|
+
margin: 0;
|
|
52
|
+
padding: 0;
|
|
53
|
+
list-style: none;
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const desktopStyle = css`
|
|
57
|
+
display: none;
|
|
58
|
+
|
|
59
|
+
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
60
|
+
display: flex;
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const mobileStyle = css`
|
|
65
|
+
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const style = cx(NAME, className);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<nav
|
|
74
|
+
aria-label={ariaLabel}
|
|
75
|
+
className={style}
|
|
76
|
+
data-testid={testId}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<ol className={cx(listStyle, desktopStyle)}>
|
|
80
|
+
{items.map((item, index) =>
|
|
81
|
+
cloneElement(item as BreadcrumbChild, {
|
|
82
|
+
separator: index < items.length - 1,
|
|
83
|
+
size,
|
|
84
|
+
})
|
|
85
|
+
)}
|
|
86
|
+
</ol>
|
|
87
|
+
{parentItem && (
|
|
88
|
+
<ol className={cx(listStyle, mobileStyle)}>
|
|
89
|
+
{cloneElement(parentItem, {
|
|
90
|
+
back: true,
|
|
91
|
+
separator: false,
|
|
92
|
+
size,
|
|
93
|
+
})}
|
|
94
|
+
</ol>
|
|
95
|
+
)}
|
|
96
|
+
</nav>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
interface BreadcrumbsComponent extends NamedExoticComponent<BreadcrumbsProps> {
|
|
101
|
+
BaseItem: typeof BaseBreadcrumbsItem;
|
|
102
|
+
Link: typeof BreadcrumbsLink;
|
|
103
|
+
Current: typeof BreadcrumbsCurrent;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const MemoBreadcrumbs: BreadcrumbsComponent = Object.assign(memo(Breadcrumbs), {
|
|
107
|
+
BaseItem: BaseBreadcrumbsItem,
|
|
108
|
+
Link: BreadcrumbsLink,
|
|
109
|
+
Current: BreadcrumbsCurrent,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export type {
|
|
113
|
+
BaseBreadcrumbsItemProps,
|
|
114
|
+
BreadcrumbsCurrentProps,
|
|
115
|
+
BreadcrumbsLinkProps,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default MemoBreadcrumbs;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import Text from '../Text';
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import BaseBreadcrumbsItem, {
|
|
6
|
+
BaseBreadcrumbsItemProps,
|
|
7
|
+
} from './BaseBreadcrumbsItem';
|
|
8
|
+
|
|
9
|
+
export const NAME = 'ucl-uikit-breadcrumbs__current';
|
|
10
|
+
|
|
11
|
+
export interface BreadcrumbsCurrentProps
|
|
12
|
+
extends
|
|
13
|
+
HTMLAttributes<HTMLSpanElement>,
|
|
14
|
+
Pick<BaseBreadcrumbsItemProps, 'separator' | 'size'> {
|
|
15
|
+
testId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BreadcrumbsCurrent = ({
|
|
19
|
+
separator,
|
|
20
|
+
size = 'medium',
|
|
21
|
+
testId = NAME,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: BreadcrumbsCurrentProps) => {
|
|
26
|
+
const [theme] = useTheme();
|
|
27
|
+
|
|
28
|
+
const style = cx(
|
|
29
|
+
NAME,
|
|
30
|
+
css`
|
|
31
|
+
color: ${theme.colour.text.secondary};
|
|
32
|
+
`,
|
|
33
|
+
className
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<BaseBreadcrumbsItem
|
|
38
|
+
separator={separator}
|
|
39
|
+
size={size}
|
|
40
|
+
>
|
|
41
|
+
<Text
|
|
42
|
+
aria-current='page'
|
|
43
|
+
className={style}
|
|
44
|
+
data-testid={testId}
|
|
45
|
+
level={size === 'small' ? 'sm-semibold' : 'md-semibold'}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Text>
|
|
50
|
+
</BaseBreadcrumbsItem>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default BreadcrumbsCurrent;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import Link, { LinkProps } from '../Link';
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import BaseBreadcrumbsItem, {
|
|
5
|
+
BaseBreadcrumbsItemProps,
|
|
6
|
+
} from './BaseBreadcrumbsItem';
|
|
7
|
+
|
|
8
|
+
export const NAME = 'ucl-uikit-breadcrumbs__link';
|
|
9
|
+
|
|
10
|
+
type BreadcrumbsLinkOwnProps = Pick<
|
|
11
|
+
BaseBreadcrumbsItemProps,
|
|
12
|
+
'separator' | 'back' | 'size'
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export type BreadcrumbsLinkProps = BreadcrumbsLinkOwnProps &
|
|
16
|
+
Omit<LinkProps, 'size'>;
|
|
17
|
+
|
|
18
|
+
const BreadcrumbsLink = ({
|
|
19
|
+
separator,
|
|
20
|
+
back,
|
|
21
|
+
size = 'medium',
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: BreadcrumbsLinkProps) => {
|
|
26
|
+
const [theme] = useTheme();
|
|
27
|
+
|
|
28
|
+
const linkStyle = cx(
|
|
29
|
+
NAME,
|
|
30
|
+
css`
|
|
31
|
+
color: ${theme.colour.link.primary};
|
|
32
|
+
text-decoration: none;
|
|
33
|
+
|
|
34
|
+
&:visited {
|
|
35
|
+
color: ${theme.colour.link.primary};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
text-decoration: underline;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&:active {
|
|
43
|
+
color: ${theme.colour.link.primaryHover};
|
|
44
|
+
}
|
|
45
|
+
`,
|
|
46
|
+
className
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<BaseBreadcrumbsItem
|
|
51
|
+
separator={separator}
|
|
52
|
+
back={back}
|
|
53
|
+
size={size}
|
|
54
|
+
>
|
|
55
|
+
<Link
|
|
56
|
+
className={linkStyle}
|
|
57
|
+
noVisited
|
|
58
|
+
size={size === 'small' ? 'small' : 'default'}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</Link>
|
|
63
|
+
</BaseBreadcrumbsItem>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default BreadcrumbsLink;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { render, screen, within } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
4
|
+
import Breadcrumbs from '../Breadcrumbs';
|
|
5
|
+
|
|
6
|
+
describe('BreadcrumbsNew', () => {
|
|
7
|
+
test('renders the full trail and a mobile parent link', () => {
|
|
8
|
+
render(
|
|
9
|
+
<ThemeContextProvider>
|
|
10
|
+
<Breadcrumbs>
|
|
11
|
+
<Breadcrumbs.Link asChild>
|
|
12
|
+
<a href='/'>Home</a>
|
|
13
|
+
</Breadcrumbs.Link>
|
|
14
|
+
<Breadcrumbs.Link asChild>
|
|
15
|
+
<a href='/parent'>Parent</a>
|
|
16
|
+
</Breadcrumbs.Link>
|
|
17
|
+
<Breadcrumbs.Current>Current page</Breadcrumbs.Current>
|
|
18
|
+
</Breadcrumbs>
|
|
19
|
+
</ThemeContextProvider>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const lists = screen.getAllByRole('list', { hidden: true });
|
|
23
|
+
|
|
24
|
+
expect(
|
|
25
|
+
within(lists[0]).getAllByRole('listitem', { hidden: true })
|
|
26
|
+
).toHaveLength(3);
|
|
27
|
+
expect(
|
|
28
|
+
within(lists[1]).getAllByRole('listitem', { hidden: true })
|
|
29
|
+
).toHaveLength(1);
|
|
30
|
+
expect(
|
|
31
|
+
within(lists[1]).getByRole('link', { hidden: true })
|
|
32
|
+
).toHaveAttribute('href', '/parent');
|
|
33
|
+
expect(screen.getByText('Current page')).toHaveAttribute(
|
|
34
|
+
'aria-current',
|
|
35
|
+
'page'
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('supports small breadcrumbs', () => {
|
|
40
|
+
render(
|
|
41
|
+
<ThemeContextProvider>
|
|
42
|
+
<Breadcrumbs size='small'>
|
|
43
|
+
<Breadcrumbs.Link asChild>
|
|
44
|
+
<a href='/parent'>Parent</a>
|
|
45
|
+
</Breadcrumbs.Link>
|
|
46
|
+
<Breadcrumbs.Current>Current page</Breadcrumbs.Current>
|
|
47
|
+
</Breadcrumbs>
|
|
48
|
+
</ThemeContextProvider>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText('Current page')).toHaveClass('ucl-uikit-text');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -13,7 +13,7 @@ exports[`Checkbox > snapshot: checked prop 1`] = `
|
|
|
13
13
|
/>
|
|
14
14
|
<span
|
|
15
15
|
aria-hidden="true"
|
|
16
|
-
class="css-
|
|
16
|
+
class="css-g3adpt"
|
|
17
17
|
>
|
|
18
18
|
<svg
|
|
19
19
|
class="ucl-uikit-icon css-1nacdsu"
|
|
@@ -49,7 +49,7 @@ exports[`Checkbox > snapshot: indeterminate prop 1`] = `
|
|
|
49
49
|
/>
|
|
50
50
|
<span
|
|
51
51
|
aria-hidden="true"
|
|
52
|
-
class="css-
|
|
52
|
+
class="css-g3adpt"
|
|
53
53
|
>
|
|
54
54
|
<svg
|
|
55
55
|
class="ucl-uikit-icon css-2i3jn5"
|
|
@@ -87,7 +87,7 @@ exports[`Checkbox > snapshot: no props 1`] = `
|
|
|
87
87
|
/>
|
|
88
88
|
<span
|
|
89
89
|
aria-hidden="true"
|
|
90
|
-
class="css-
|
|
90
|
+
class="css-g3adpt"
|
|
91
91
|
/>
|
|
92
92
|
</span>
|
|
93
93
|
`;
|
|
@@ -104,7 +104,7 @@ exports[`Checkbox > snapshot: testId prop 1`] = `
|
|
|
104
104
|
/>
|
|
105
105
|
<span
|
|
106
106
|
aria-hidden="true"
|
|
107
|
-
class="css-
|
|
107
|
+
class="css-g3adpt"
|
|
108
108
|
/>
|
|
109
109
|
</span>
|
|
110
110
|
`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useArgs } from '@storybook/preview-api';
|
|
3
|
+
|
|
4
|
+
import Chip from './Chip';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Chip',
|
|
8
|
+
component: Chip,
|
|
9
|
+
argTypes: {
|
|
10
|
+
label: { control: { type: 'text' } },
|
|
11
|
+
checked: { control: { type: 'boolean' } },
|
|
12
|
+
disabled: { control: { type: 'boolean' } },
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof Chip>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
label: 'Student',
|
|
22
|
+
checked: false,
|
|
23
|
+
disabled: false,
|
|
24
|
+
},
|
|
25
|
+
render: (args) => {
|
|
26
|
+
const [{ checked }, updateArgs] = useArgs();
|
|
27
|
+
const onChange = () => updateArgs({ checked: !checked });
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Chip
|
|
31
|
+
{...args}
|
|
32
|
+
checked={checked}
|
|
33
|
+
onChange={onChange}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { memo, forwardRef } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import useTheme from '../../theme/useTheme';
|
|
4
|
+
import BaseCheckbox, { BaseCheckboxProps } from '../BaseCheckbox';
|
|
5
|
+
import Icon from '../Icon';
|
|
6
|
+
import Text from '../Text';
|
|
7
|
+
import marginsStyle, { MarginProps } from '../common/marginsStyle';
|
|
8
|
+
|
|
9
|
+
export const NAME = 'ucl-uikit-chip';
|
|
10
|
+
|
|
11
|
+
export interface ChipBaseProps extends Omit<
|
|
12
|
+
BaseCheckboxProps,
|
|
13
|
+
| 'renderVisual'
|
|
14
|
+
| 'checkedComponent'
|
|
15
|
+
| 'uncheckedComponent'
|
|
16
|
+
| 'indeterminateComponent'
|
|
17
|
+
| 'indeterminate'
|
|
18
|
+
> {
|
|
19
|
+
label: string;
|
|
20
|
+
ariaLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ChipProps = ChipBaseProps & MarginProps;
|
|
24
|
+
|
|
25
|
+
export type Ref = HTMLInputElement;
|
|
26
|
+
|
|
27
|
+
const Chip = forwardRef<Ref, ChipProps>(
|
|
28
|
+
(
|
|
29
|
+
{
|
|
30
|
+
label,
|
|
31
|
+
checked,
|
|
32
|
+
defaultChecked,
|
|
33
|
+
disabled,
|
|
34
|
+
testId = NAME,
|
|
35
|
+
ariaLabel,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
},
|
|
39
|
+
ref
|
|
40
|
+
) => {
|
|
41
|
+
const [theme] = useTheme();
|
|
42
|
+
|
|
43
|
+
const baseStyle = css`
|
|
44
|
+
position: relative;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
min-height: 32px;
|
|
49
|
+
width: fit-content;
|
|
50
|
+
padding: ${theme.margin.m4} ${theme.margin.m16};
|
|
51
|
+
border: ${theme.border.b1} solid ${theme.colour.icon.brand};
|
|
52
|
+
border-radius: 16px;
|
|
53
|
+
outline: none;
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
color: ${theme.colour.icon.inverse};
|
|
56
|
+
transition:
|
|
57
|
+
background-color 0.15s ease-out,
|
|
58
|
+
border-color 0.15s ease-out;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
background-color: ${theme.colour.icon.inverse};
|
|
61
|
+
color: ${theme.colour.icon.brand};
|
|
62
|
+
|
|
63
|
+
&:hover {
|
|
64
|
+
background-color: ${theme.colour.fill.brandSubtleHover};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&:has(input:focus-visible) {
|
|
68
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&:has(input:checked) {
|
|
72
|
+
background-color: ${theme.colour.icon.brandSelectedOnBgFill};
|
|
73
|
+
border-color: ${theme.colour.icon.brandSelectedOnBgFill};
|
|
74
|
+
color: ${theme.colour.icon.inverse};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&:has(input:checked):hover {
|
|
78
|
+
background-color: ${theme.colour.icon.brandSelectedOnBgFill};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:has(input:disabled),
|
|
82
|
+
&:has(input:disabled):hover {
|
|
83
|
+
background-color: ${theme.colour.fill.disabled};
|
|
84
|
+
border-color: ${theme.colour.border.disabled};
|
|
85
|
+
color: ${theme.colour.text.disabled};
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const style = cx(NAME, baseStyle, marginsStyle(props, theme), className);
|
|
91
|
+
|
|
92
|
+
const contentStyle = css`
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const iconBaseStyle = css`
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
color: inherit;
|
|
101
|
+
user-select: none;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
transition:
|
|
104
|
+
opacity 0.15s ease-out,
|
|
105
|
+
margin-left 0.15s ease-out,
|
|
106
|
+
margin-right 0.15s ease-out;
|
|
107
|
+
|
|
108
|
+
@media (prefers-reduced-motion: reduce) {
|
|
109
|
+
transition: none;
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const iconVisibleStyle = css`
|
|
114
|
+
opacity: 1;
|
|
115
|
+
margin-left: 0;
|
|
116
|
+
margin-right: 0;
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const iconHiddenStyle = css`
|
|
120
|
+
opacity: 0;
|
|
121
|
+
margin-left: -4px;
|
|
122
|
+
margin-right: 4px;
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
const labelBaseStyle = css`
|
|
126
|
+
color: inherit;
|
|
127
|
+
transition:
|
|
128
|
+
margin-left 0.15s ease-out,
|
|
129
|
+
margin-right 0.15s ease-out;
|
|
130
|
+
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
transition: none;
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const labelCheckedStyle = css`
|
|
137
|
+
margin-left: ${theme.margin.m4};
|
|
138
|
+
margin-right: 0;
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
const labelUncheckedStyle = css`
|
|
142
|
+
margin-left: -6px;
|
|
143
|
+
margin-right: 10px;
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<label className={style}>
|
|
148
|
+
<BaseCheckbox
|
|
149
|
+
ref={ref}
|
|
150
|
+
testId={`${testId}__root`}
|
|
151
|
+
data-testid={testId}
|
|
152
|
+
aria-label={ariaLabel}
|
|
153
|
+
checked={checked}
|
|
154
|
+
defaultChecked={defaultChecked}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
renderVisual={({ checked }) => {
|
|
157
|
+
const iconStyle = cx(
|
|
158
|
+
iconBaseStyle,
|
|
159
|
+
checked && iconVisibleStyle,
|
|
160
|
+
!checked && iconHiddenStyle
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const labelStyle = cx(
|
|
164
|
+
labelBaseStyle,
|
|
165
|
+
checked && labelCheckedStyle,
|
|
166
|
+
!checked && labelUncheckedStyle
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<span className={contentStyle}>
|
|
171
|
+
<Icon.Check
|
|
172
|
+
className={iconStyle}
|
|
173
|
+
size={16}
|
|
174
|
+
/>
|
|
175
|
+
<Text
|
|
176
|
+
level='sm-semibold'
|
|
177
|
+
className={labelStyle}
|
|
178
|
+
>
|
|
179
|
+
{label}
|
|
180
|
+
</Text>
|
|
181
|
+
</span>
|
|
182
|
+
);
|
|
183
|
+
}}
|
|
184
|
+
{...props}
|
|
185
|
+
/>
|
|
186
|
+
</label>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
export default memo(Chip);
|