@indico-data/design-system 3.20.0 → 3.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.
- package/lib/components/index.d.ts +1 -0
- package/lib/components/pill/Pill.d.ts +1 -1
- package/lib/components/pill/Pill.stories.d.ts +13 -3
- package/lib/components/pill/types.d.ts +22 -9
- package/lib/index.css +638 -2733
- package/lib/index.d.ts +22 -10
- package/lib/index.esm.css +638 -2733
- package/lib/index.esm.js +10 -7
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +10 -7
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/forms/form/styles/Form.scss +1 -1
- package/src/components/forms/select/styles/Select.scss +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/pill/Pill.mdx +66 -7
- package/src/components/pill/Pill.stories.tsx +384 -116
- package/src/components/pill/Pill.tsx +39 -10
- package/src/components/pill/__tests__/Pill.test.tsx +104 -40
- package/src/components/pill/styles/Pill.scss +118 -21
- package/src/components/pill/styles/_tokens.scss +408 -0
- package/src/components/pill/types.ts +22 -10
- package/src/styles/index.scss +2 -0
- package/src/styles/primitives/_colors.scss +11 -9
- package/src/styles/tokens/_semantic-tokens.scss +9 -3
- package/src/types.ts +7 -2
|
@@ -1,24 +1,53 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
|
|
3
|
-
import { type PillProps } from './types';
|
|
3
|
+
import { type PillProps, type PillSize } from './types';
|
|
4
|
+
import { Icon } from '../icons/Icon';
|
|
5
|
+
import { type IconSizes } from '../icons/types';
|
|
6
|
+
|
|
7
|
+
const PILL_ICON_SIZE: Record<PillSize, IconSizes> = {
|
|
8
|
+
sm: 'xxs',
|
|
9
|
+
md: 'xs',
|
|
10
|
+
lg: 'xs',
|
|
11
|
+
};
|
|
4
12
|
|
|
5
13
|
export const Pill = ({
|
|
6
14
|
children,
|
|
7
|
-
className,
|
|
8
15
|
color = 'gray',
|
|
9
16
|
size = 'sm',
|
|
10
|
-
|
|
17
|
+
variant = 'solid',
|
|
18
|
+
type = 'pill',
|
|
19
|
+
iconLeft,
|
|
20
|
+
iconRight,
|
|
21
|
+
dot,
|
|
22
|
+
onClose,
|
|
23
|
+
closeAriaLabel = 'Remove',
|
|
24
|
+
className,
|
|
11
25
|
...rest
|
|
12
26
|
}: PillProps) => {
|
|
13
|
-
const pillClasses = classNames('pill', className, {
|
|
14
|
-
[`pill--${color}`]: color && !shade,
|
|
15
|
-
[`pill--${color}-${shade}`]: color && shade,
|
|
16
|
-
[`pill--${size}`]: size,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
27
|
return (
|
|
20
|
-
<div
|
|
28
|
+
<div
|
|
29
|
+
className={classNames(
|
|
30
|
+
'pill',
|
|
31
|
+
`pill--${type}`,
|
|
32
|
+
`pill--${size}`,
|
|
33
|
+
`pill--${variant}-${color}`,
|
|
34
|
+
className,
|
|
35
|
+
{
|
|
36
|
+
'pill--icon-only': !children && (iconLeft || iconRight),
|
|
37
|
+
'pill--closeable': !!onClose,
|
|
38
|
+
},
|
|
39
|
+
)}
|
|
40
|
+
{...rest}
|
|
41
|
+
>
|
|
42
|
+
{dot && <span className="pill__dot" />}
|
|
43
|
+
{iconLeft && <Icon name={iconLeft} size={PILL_ICON_SIZE[size]} />}
|
|
21
44
|
{children}
|
|
45
|
+
{iconRight && <Icon name={iconRight} size={PILL_ICON_SIZE[size]} />}
|
|
46
|
+
{onClose && (
|
|
47
|
+
<button type="button" className="pill__close" onClick={onClose} aria-label={closeAriaLabel}>
|
|
48
|
+
<Icon name="fa-xmark" size="xs" />
|
|
49
|
+
</button>
|
|
50
|
+
)}
|
|
22
51
|
</div>
|
|
23
52
|
);
|
|
24
53
|
};
|
|
@@ -1,61 +1,125 @@
|
|
|
1
|
+
import { faCheck, faArrowRight } from '@fortawesome/free-solid-svg-icons';
|
|
1
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
2
4
|
|
|
3
5
|
import { Pill } from '@/components/pill/Pill';
|
|
6
|
+
import { registerFontAwesomeIcons } from '@/setup/setupIcons';
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
registerFontAwesomeIcons(faCheck, faArrowRight);
|
|
6
9
|
|
|
10
|
+
describe('Pill', () => {
|
|
11
|
+
it('renders children', () => {
|
|
12
|
+
render(<Pill>Hello</Pill>);
|
|
13
|
+
expect(screen.getByText('Hello')).toBeInTheDocument();
|
|
14
|
+
});
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'yellow',
|
|
14
|
-
'gray',
|
|
15
|
-
'green',
|
|
16
|
-
'pink',
|
|
17
|
-
'orange',
|
|
18
|
-
'teal',
|
|
19
|
-
] as PillColor[];
|
|
16
|
+
it('applies default classes (gray, sm, solid, pill)', () => {
|
|
17
|
+
const { container } = render(<Pill>Default</Pill>);
|
|
18
|
+
const el = container.firstChild as HTMLElement;
|
|
19
|
+
expect(el).toHaveClass('pill', 'pill--pill', 'pill--sm', 'pill--solid-gray');
|
|
20
|
+
});
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
it('applies size class for non-default size', () => {
|
|
23
|
+
const { container } = render(<Pill size="lg">Large</Pill>);
|
|
24
|
+
expect(container.firstChild).toHaveClass('pill--lg');
|
|
25
|
+
});
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
it('applies badge type class', () => {
|
|
28
|
+
const { container } = render(<Pill type="badge">Badge</Pill>);
|
|
29
|
+
expect(container.firstChild).toHaveClass('pill--badge');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('colors x variants', () => {
|
|
33
|
+
it('applies solid color class', () => {
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<Pill color="blue" variant="solid">
|
|
36
|
+
Status
|
|
29
37
|
</Pill>,
|
|
30
38
|
);
|
|
31
|
-
|
|
32
|
-
expect(pill).toHaveClass(`pill pill--blue pill--${size}`);
|
|
39
|
+
expect(container.firstChild).toHaveClass('pill--solid-blue');
|
|
33
40
|
});
|
|
34
|
-
});
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
it('applies outline color class', () => {
|
|
43
|
+
const { container } = render(
|
|
44
|
+
<Pill color="red" variant="outline">
|
|
45
|
+
Status
|
|
46
|
+
</Pill>,
|
|
47
|
+
);
|
|
48
|
+
expect(container.firstChild).toHaveClass('pill--outline-red');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('applies soft color class', () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<Pill color="soft" variant="solid">
|
|
40
54
|
Status
|
|
41
55
|
</Pill>,
|
|
42
56
|
);
|
|
43
|
-
|
|
44
|
-
expect(pill).toHaveClass(`pill pill--${color} pill--md`);
|
|
57
|
+
expect(container.firstChild).toHaveClass('pill--solid-soft');
|
|
45
58
|
});
|
|
46
59
|
});
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
it('renders a leading icon and sizes it based on pill size', () => {
|
|
62
|
+
const { container } = render(
|
|
63
|
+
<Pill iconLeft="fa-check" size="md">
|
|
64
|
+
With Icon
|
|
65
|
+
</Pill>,
|
|
66
|
+
);
|
|
67
|
+
expect(container.querySelector('.icon--xs')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders both leading and trailing icons', () => {
|
|
71
|
+
const { container } = render(
|
|
72
|
+
<Pill iconLeft="fa-check" iconRight="fa-arrow-right">
|
|
73
|
+
Both
|
|
74
|
+
</Pill>,
|
|
75
|
+
);
|
|
76
|
+
expect(container.querySelectorAll('.icon')).toHaveLength(2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders a dot element when dot is true', () => {
|
|
80
|
+
const { container } = render(<Pill dot>Dotted</Pill>);
|
|
81
|
+
expect(container.querySelector('.pill__dot')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('applies pill--icon-only when no children and icon is provided', () => {
|
|
85
|
+
const { container } = render(<Pill iconLeft="fa-check" />);
|
|
86
|
+
expect(container.firstChild).toHaveClass('pill--icon-only');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('onClose', () => {
|
|
90
|
+
it('renders close button and applies closeable class', () => {
|
|
91
|
+
const { container } = render(<Pill onClose={() => {}}>Closeable</Pill>);
|
|
92
|
+
expect(container.querySelector('.pill__close')).toBeInTheDocument();
|
|
93
|
+
expect(container.firstChild).toHaveClass('pill--closeable');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('calls onClose when close button is clicked', async () => {
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
const handleClose = jest.fn();
|
|
99
|
+
render(<Pill onClose={handleClose}>Close Me</Pill>);
|
|
100
|
+
await user.click(screen.getByRole('button', { name: 'Remove' }));
|
|
101
|
+
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('uses custom closeAriaLabel when provided', () => {
|
|
105
|
+
render(
|
|
106
|
+
<Pill onClose={() => {}} closeAriaLabel="Supprimer">
|
|
107
|
+
Close Me
|
|
108
|
+
</Pill>,
|
|
109
|
+
);
|
|
110
|
+
expect(screen.getByRole('button', { name: 'Supprimer' })).toBeInTheDocument();
|
|
59
111
|
});
|
|
60
112
|
});
|
|
113
|
+
|
|
114
|
+
it('renders dot + iconLeft + close together', () => {
|
|
115
|
+
const { container } = render(
|
|
116
|
+
<Pill dot iconLeft="fa-check" onClose={() => {}}>
|
|
117
|
+
All Features
|
|
118
|
+
</Pill>,
|
|
119
|
+
);
|
|
120
|
+
expect(container.querySelector('.pill__dot')).toBeInTheDocument();
|
|
121
|
+
expect(container.querySelector('.icon')).toBeInTheDocument();
|
|
122
|
+
expect(container.querySelector('.pill__close')).toBeInTheDocument();
|
|
123
|
+
expect(screen.getByText('All Features')).toBeInTheDocument();
|
|
124
|
+
});
|
|
61
125
|
});
|
|
@@ -1,41 +1,138 @@
|
|
|
1
|
-
|
|
1
|
+
$pill-colors: 'red', 'purple', 'yellow', 'blue', 'green', 'gray', 'pink', 'orange', 'teal', 'soft';
|
|
2
|
+
$pill-variants: 'solid', 'outline';
|
|
2
3
|
|
|
3
4
|
.pill {
|
|
4
|
-
display: inline-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
font-size: var(--pf-font-size-overline);
|
|
8
|
-
font-weight: var(--pf-font-weight-medium);
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
9
8
|
white-space: nowrap;
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
border: none;
|
|
10
|
+
font-family: var(--pf-font-family);
|
|
12
11
|
|
|
12
|
+
// ------- Types (border-radius) -------
|
|
13
|
+
&--badge {
|
|
14
|
+
border-radius: var(--pf-border-radius-sm);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&--pill {
|
|
18
|
+
border-radius: var(--pf-border-radius-full);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ------- Sizes -------
|
|
13
22
|
&--sm {
|
|
14
23
|
padding: var(--pf-spacing-xxs) var(--pf-spacing-sm);
|
|
15
|
-
font-size: var(--pf-font-size-
|
|
24
|
+
font-size: var(--pf-font-size-sm);
|
|
25
|
+
font-weight: var(--pf-font-weight-semibold);
|
|
26
|
+
line-height: 16px;
|
|
27
|
+
gap: var(--pf-spacing-xxs);
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
&--md {
|
|
19
|
-
padding: var(--pf-spacing-
|
|
20
|
-
font-size: var(--pf-font-size-
|
|
31
|
+
padding: var(--pf-spacing-xxs) var(--pf-spacing-md);
|
|
32
|
+
font-size: var(--pf-font-size-md);
|
|
33
|
+
font-weight: var(--pf-font-weight-medium);
|
|
34
|
+
line-height: 20px;
|
|
35
|
+
gap: var(--pf-spacing-xs);
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
&--lg {
|
|
24
|
-
padding: var(--pf-spacing-
|
|
25
|
-
font-size: var(--pf-font-size-
|
|
39
|
+
padding: var(--pf-spacing-xs) var(--pf-spacing-lg);
|
|
40
|
+
font-size: var(--pf-font-size-md);
|
|
41
|
+
font-weight: var(--pf-font-weight-medium);
|
|
42
|
+
line-height: 20px;
|
|
43
|
+
gap: var(--pf-spacing-xs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// LG badge uses border-md (8px) instead of border-sm (6px)
|
|
47
|
+
&--lg#{&}--badge {
|
|
48
|
+
border-radius: var(--pf-border-radius-md);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ------- Icon-only (special padding) -------
|
|
52
|
+
&--icon-only {
|
|
53
|
+
&.pill--sm {
|
|
54
|
+
padding: var(--pf-spacing-xxs);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.pill--md {
|
|
58
|
+
padding: var(--pf-spacing-xs);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&.pill--lg {
|
|
62
|
+
padding: var(--pf-spacing-sm);
|
|
63
|
+
}
|
|
26
64
|
}
|
|
27
65
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
66
|
+
// ------- Closeable (asymmetric padding) -------
|
|
67
|
+
&--closeable {
|
|
68
|
+
&.pill--sm {
|
|
69
|
+
padding: var(--pf-spacing-xxs) var(--pf-spacing-xs) var(--pf-spacing-xxs) var(--pf-spacing-sm);
|
|
32
70
|
}
|
|
33
71
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
&.pill--md {
|
|
73
|
+
padding: var(--pf-spacing-xxs) var(--pf-spacing-xs) var(--pf-spacing-xxs) var(--pf-spacing-md);
|
|
74
|
+
gap: var(--pf-spacing-xxs);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&.pill--lg {
|
|
78
|
+
padding: var(--pf-spacing-xs) var(--pf-spacing-sm) var(--pf-spacing-xs) var(--pf-spacing-lg);
|
|
79
|
+
gap: var(--pf-spacing-xxs);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ------- Color × Variant -------
|
|
84
|
+
@each $variant in $pill-variants {
|
|
85
|
+
@each $color in $pill-colors {
|
|
86
|
+
&--#{$variant}-#{$color} {
|
|
87
|
+
background-color: var(--pf-pill-#{$variant}-#{$color}-bg);
|
|
88
|
+
color: var(--pf-pill-#{$variant}-#{$color}-text);
|
|
89
|
+
|
|
90
|
+
@if $variant == 'outline' {
|
|
91
|
+
box-shadow: inset 0 0 0 1px var(--pf-pill-#{$variant}-#{$color}-border);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
> .icon {
|
|
95
|
+
color: var(--pf-pill-#{$variant}-#{$color}-icon);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.pill__dot {
|
|
99
|
+
background-color: var(--pf-pill-#{$variant}-#{$color}-dot);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.pill__close {
|
|
103
|
+
color: var(--pf-pill-#{$variant}-#{$color}-close);
|
|
104
|
+
|
|
105
|
+
&:hover {
|
|
106
|
+
color: var(--pf-pill-#{$variant}-#{$color}-close-hover);
|
|
107
|
+
background-color: var(--pf-pill-#{$variant}-#{$color}-close-hover-bg);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
38
110
|
}
|
|
39
111
|
}
|
|
40
112
|
}
|
|
113
|
+
|
|
114
|
+
// ------- Sub-elements -------
|
|
115
|
+
&__dot {
|
|
116
|
+
width: 6px;
|
|
117
|
+
height: 6px;
|
|
118
|
+
border-radius: 50%;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&__close {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
flex-shrink: 0;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
border: none;
|
|
129
|
+
background: transparent;
|
|
130
|
+
padding: var(--pf-spacing-micro);
|
|
131
|
+
border-radius: var(--pf-border-radius-xs);
|
|
132
|
+
line-height: 1;
|
|
133
|
+
aspect-ratio: 1;
|
|
134
|
+
transition:
|
|
135
|
+
color 0.15s ease,
|
|
136
|
+
background-color 0.15s ease;
|
|
137
|
+
}
|
|
41
138
|
}
|