@indico-data/design-system 3.22.1 → 3.23.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 (39) hide show
  1. package/AGENTS.md +110 -0
  2. package/CLAUDE.md +1 -0
  3. package/lib/components/alert/Alert.d.ts +2 -0
  4. package/lib/components/alert/Alert.stories.d.ts +15 -0
  5. package/lib/components/alert/__tests__/Alert.test.d.ts +1 -0
  6. package/lib/components/alert/index.d.ts +2 -0
  7. package/lib/components/alert/types.d.ts +26 -0
  8. package/lib/components/index.d.ts +1 -0
  9. package/lib/components/pill/Pill.d.ts +1 -1
  10. package/lib/components/pill/Pill.stories.d.ts +3 -0
  11. package/lib/components/pill/types.d.ts +4 -0
  12. package/lib/index.css +214 -30
  13. package/lib/index.d.ts +34 -2
  14. package/lib/index.esm.css +214 -30
  15. package/lib/index.esm.js +41 -5
  16. package/lib/index.esm.js.map +1 -1
  17. package/lib/index.js +40 -3
  18. package/lib/index.js.map +1 -1
  19. package/lib/types.d.ts +2 -0
  20. package/package.json +1 -1
  21. package/src/components/alert/Alert.mdx +65 -0
  22. package/src/components/alert/Alert.stories.tsx +162 -0
  23. package/src/components/alert/Alert.tsx +68 -0
  24. package/src/components/alert/__tests__/Alert.test.tsx +52 -0
  25. package/src/components/alert/index.ts +2 -0
  26. package/src/components/alert/styles/Alert.scss +139 -0
  27. package/src/components/alert/styles/_tokens.scss +71 -0
  28. package/src/components/alert/types.ts +28 -0
  29. package/src/components/index.ts +1 -0
  30. package/src/components/pill/Pill.mdx +27 -0
  31. package/src/components/pill/Pill.stories.tsx +87 -0
  32. package/src/components/pill/Pill.tsx +36 -0
  33. package/src/components/pill/__tests__/Pill.test.tsx +93 -0
  34. package/src/components/pill/styles/Pill.scss +15 -2
  35. package/src/components/pill/types.ts +4 -0
  36. package/src/index.ts +1 -0
  37. package/src/setup/setupIcons.ts +8 -0
  38. package/src/styles/index.scss +2 -1
  39. package/src/types.ts +2 -0
@@ -65,6 +65,8 @@ const meta: Meta<typeof Pill> = {
65
65
  iconLeft: { control: 'text' },
66
66
  iconRight: { control: 'text' },
67
67
  dot: { control: 'boolean' },
68
+ shade: { control: { type: 'number' } },
69
+ shadeCount: { control: { type: 'number' } },
68
70
  onClose: { control: false },
69
71
  children: { control: 'text' },
70
72
  },
@@ -274,6 +276,91 @@ export const CloseableMatrix: Story = {
274
276
  ),
275
277
  };
276
278
 
279
+ export const Shaded: Story = {
280
+ args: {
281
+ children: 'Medium',
282
+ color: 'green',
283
+ shade: 3,
284
+ shadeCount: 5,
285
+ },
286
+ argTypes: {
287
+ shade: { control: { type: 'range', min: 1, max: 10, step: 1 } },
288
+ shadeCount: { control: { type: 'range', min: 2, max: 10, step: 1 } },
289
+ },
290
+ };
291
+
292
+ export const ShadedSpectrum: Story = {
293
+ render: () => {
294
+ const labels = ['Very Low', 'Low', 'Medium', 'High', 'Very High'];
295
+ return (
296
+ <>
297
+ {(['solid', 'outline'] as const).map((variant) => (
298
+ <div key={variant} style={section}>
299
+ <h4 style={{ marginBottom: 8, textTransform: 'capitalize' }}>{variant}</h4>
300
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
301
+ {COLORS.filter((c) => c !== 'soft').map((color) => (
302
+ <div key={color} style={row}>
303
+ <span
304
+ style={{
305
+ width: 60,
306
+ fontSize: 12,
307
+ fontWeight: 500,
308
+ textTransform: 'capitalize',
309
+ }}
310
+ >
311
+ {color}
312
+ </span>
313
+ {labels.map((label, i) => (
314
+ <Pill
315
+ key={label}
316
+ color={color}
317
+ variant={variant}
318
+ size="md"
319
+ shade={i + 1}
320
+ shadeCount={labels.length}
321
+ >
322
+ {label}
323
+ </Pill>
324
+ ))}
325
+ </div>
326
+ ))}
327
+ </div>
328
+ </div>
329
+ ))}
330
+ </>
331
+ );
332
+ },
333
+ };
334
+
335
+ export const ShadedDynamicCount: Story = {
336
+ render: () => {
337
+ const counts = [3, 5, 7];
338
+ return (
339
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
340
+ {counts.map((count) => (
341
+ <div key={count}>
342
+ <h4 style={{ marginBottom: 8 }}>{count} shades</h4>
343
+ <div style={row}>
344
+ {Array.from({ length: count }, (_, i) => (
345
+ <Pill
346
+ key={i}
347
+ color="blue"
348
+ variant="solid"
349
+ size="md"
350
+ shade={i + 1}
351
+ shadeCount={count}
352
+ >
353
+ {i + 1} of {count}
354
+ </Pill>
355
+ ))}
356
+ </div>
357
+ </div>
358
+ ))}
359
+ </div>
360
+ );
361
+ },
362
+ };
363
+
277
364
  const th: React.CSSProperties = {
278
365
  padding: '6px 10px',
279
366
  fontSize: 11,
@@ -19,11 +19,46 @@ export const Pill = ({
19
19
  iconLeft,
20
20
  iconRight,
21
21
  dot,
22
+ shade,
23
+ shadeCount,
22
24
  onClose,
23
25
  closeAriaLabel = 'Remove',
24
26
  className,
27
+ style,
25
28
  ...rest
26
29
  }: PillProps) => {
30
+ const shadeStyle = (() => {
31
+ if (
32
+ shade === undefined ||
33
+ shade === null ||
34
+ shadeCount === undefined ||
35
+ shadeCount === null ||
36
+ shadeCount <= 1
37
+ )
38
+ return undefined;
39
+
40
+ const s = Math.max(1, Math.min(shadeCount, Math.round(shade)));
41
+ const midpoint = (shadeCount + 1) / 2;
42
+
43
+ if (s < midpoint) {
44
+ const distance = (midpoint - s) / (midpoint - 1);
45
+ return {
46
+ '--pill-shade-mix': `${Math.round(100 - distance * 30)}%`,
47
+ '--pill-shade-target': 'white',
48
+ } as React.CSSProperties;
49
+ }
50
+
51
+ if (s > midpoint) {
52
+ const distance = (s - midpoint) / (shadeCount - midpoint);
53
+ return {
54
+ '--pill-shade-mix': `${Math.round(100 - distance * 30)}%`,
55
+ '--pill-shade-target': 'black',
56
+ } as React.CSSProperties;
57
+ }
58
+
59
+ return undefined;
60
+ })();
61
+
27
62
  return (
28
63
  <div
29
64
  className={classNames(
@@ -37,6 +72,7 @@ export const Pill = ({
37
72
  'pill--closeable': !!onClose,
38
73
  },
39
74
  )}
75
+ style={shadeStyle ? { ...shadeStyle, ...style } : style}
40
76
  {...rest}
41
77
  >
42
78
  {dot && <span className="pill__dot" />}
@@ -122,4 +122,97 @@ describe('Pill', () => {
122
122
  expect(container.querySelector('.pill__close')).toBeInTheDocument();
123
123
  expect(screen.getByText('All Features')).toBeInTheDocument();
124
124
  });
125
+
126
+ describe('shade', () => {
127
+ const getStyle = (container: HTMLElement) => (container.firstChild as HTMLElement).style;
128
+
129
+ it('sets no shade vars when both shade and shadeCount are omitted', () => {
130
+ const { container } = render(<Pill>Plain</Pill>);
131
+ const style = getStyle(container);
132
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('');
133
+ expect(style.getPropertyValue('--pill-shade-target')).toBe('');
134
+ });
135
+
136
+ it('sets no shade vars when only shade is provided', () => {
137
+ const { container } = render(<Pill shade={2}>Shade Only</Pill>);
138
+ const style = getStyle(container);
139
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('');
140
+ });
141
+
142
+ it('sets no shade vars when only shadeCount is provided', () => {
143
+ const { container } = render(<Pill shadeCount={5}>Count Only</Pill>);
144
+ const style = getStyle(container);
145
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('');
146
+ });
147
+
148
+ it('mixes toward white for shades below midpoint', () => {
149
+ const { container } = render(
150
+ <Pill shade={1} shadeCount={5}>
151
+ Lightest
152
+ </Pill>,
153
+ );
154
+ const style = getStyle(container);
155
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('70%');
156
+ expect(style.getPropertyValue('--pill-shade-target')).toBe('white');
157
+ });
158
+
159
+ it('mixes toward black for shades above midpoint', () => {
160
+ const { container } = render(
161
+ <Pill shade={5} shadeCount={5}>
162
+ Darkest
163
+ </Pill>,
164
+ );
165
+ const style = getStyle(container);
166
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('70%');
167
+ expect(style.getPropertyValue('--pill-shade-target')).toBe('black');
168
+ });
169
+
170
+ it('sets no shade vars at the exact midpoint (base color)', () => {
171
+ const { container } = render(
172
+ <Pill shade={3} shadeCount={5}>
173
+ Middle
174
+ </Pill>,
175
+ );
176
+ const style = getStyle(container);
177
+ expect(style.getPropertyValue('--pill-shade-mix')).toBe('');
178
+ expect(style.getPropertyValue('--pill-shade-target')).toBe('');
179
+ });
180
+
181
+ it('handles even shadeCount (no exact midpoint)', () => {
182
+ const { container: c1 } = render(
183
+ <Pill shade={2} shadeCount={4}>
184
+ Below
185
+ </Pill>,
186
+ );
187
+ const { container: c2 } = render(
188
+ <Pill shade={3} shadeCount={4}>
189
+ Above
190
+ </Pill>,
191
+ );
192
+ expect(getStyle(c1).getPropertyValue('--pill-shade-target')).toBe('white');
193
+ expect(getStyle(c2).getPropertyValue('--pill-shade-target')).toBe('black');
194
+ });
195
+
196
+ it('ignores shade when shadeCount is 1 or missing', () => {
197
+ const { container: c1 } = render(
198
+ <Pill shade={1} shadeCount={1}>
199
+ Single
200
+ </Pill>,
201
+ );
202
+ const { container: c2 } = render(<Pill shade={2}>No Count</Pill>);
203
+ expect(getStyle(c1).getPropertyValue('--pill-shade-mix')).toBe('');
204
+ expect(getStyle(c2).getPropertyValue('--pill-shade-mix')).toBe('');
205
+ });
206
+
207
+ it('preserves consumer style prop alongside shade vars', () => {
208
+ const { container } = render(
209
+ <Pill shade={1} shadeCount={3} style={{ maxWidth: 200 }}>
210
+ Styled
211
+ </Pill>,
212
+ );
213
+ const style = getStyle(container);
214
+ expect(style.getPropertyValue('--pill-shade-mix')).not.toBe('');
215
+ expect(style.maxWidth).toBe('200px');
216
+ });
217
+ });
125
218
  });
@@ -112,11 +112,24 @@ $pill-variants: 'solid', 'outline';
112
112
  @each $variant in $pill-variants {
113
113
  @each $color in $pill-colors {
114
114
  &--#{$variant}-#{$color} {
115
- background-color: var(--pf-pill-#{$variant}-#{$color}-bg);
115
+ background-color: color-mix(
116
+ in srgb,
117
+ var(--pf-pill-#{$variant}-#{$color}-bg) var(--pill-shade-mix, 100%),
118
+ var(--pill-shade-target, white)
119
+ );
116
120
  color: var(--pf-pill-#{$variant}-#{$color}-text);
117
121
 
118
122
  @if $variant == 'outline' {
119
- box-shadow: inset 0 0 0 1px var(--pf-pill-#{$variant}-#{$color}-border);
123
+ box-shadow: inset
124
+ 0
125
+ 0
126
+ 0
127
+ 1px
128
+ color-mix(
129
+ in srgb,
130
+ var(--pf-pill-#{$variant}-#{$color}-border) var(--pill-shade-mix, 100%),
131
+ var(--pill-shade-target, white)
132
+ );
120
133
  }
121
134
 
122
135
  > .icon {
@@ -25,6 +25,10 @@ export interface PillProps extends React.HTMLAttributes<HTMLDivElement> {
25
25
  iconRight?: IconName;
26
26
  /** When true, renders a small colored dot indicator before the content */
27
27
  dot?: boolean;
28
+ /** 1-based shade index within a group. Used with shadeCount to vary background lightness via color-mix(). */
29
+ shade?: number;
30
+ /** Total shades in the group. Background blends lighter (white) below midpoint, darker (black) above. */
31
+ shadeCount?: number;
28
32
  /** When provided, renders a close button that calls this handler */
29
33
  onClose?: () => void;
30
34
  /** Accessible label for the close button (for i18n) */
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ export { Card } from './components/card';
27
27
  export { FloatUI } from './components/floatUI';
28
28
  export { Menu } from './components/menu';
29
29
  export { Pill } from './components/pill';
30
+ export { Alert } from './components/alert';
30
31
  export { Badge } from './components/badge';
31
32
  export { Modal, ConfirmationModal } from './components/modal';
32
33
  export { TanstackTable } from './components/tanstackTable';
@@ -6,9 +6,13 @@ import {
6
6
  faArrowLeft,
7
7
  faCalculator,
8
8
  faCheck,
9
+ faCircleCheck,
10
+ faCircleExclamation,
11
+ faCircleInfo,
9
12
  faCircleNotch,
10
13
  faMountainSun,
11
14
  faRocket,
15
+ faTriangleExclamation,
12
16
  faWind,
13
17
  faEyeSlash,
14
18
  faEye,
@@ -40,9 +44,13 @@ registerFontAwesomeIcons(
40
44
  faArrowLeft,
41
45
  faCalculator,
42
46
  faCheck,
47
+ faCircleCheck,
48
+ faCircleExclamation,
49
+ faCircleInfo,
43
50
  faCircleNotch,
44
51
  faMountainSun,
45
52
  faRocket,
53
+ faTriangleExclamation,
46
54
  faWind,
47
55
  faEyeSlash,
48
56
  faEye,
@@ -3,7 +3,6 @@
3
3
  @import 'variables/index.scss';
4
4
  @import 'primitives/index.scss';
5
5
  @import 'tokens/semantic-tokens';
6
-
7
6
  @import 'utilities';
8
7
  @import 'typography';
9
8
  @import 'colors';
@@ -36,6 +35,8 @@
36
35
  @import '../components/tanstackTable/styles/table.scss';
37
36
  @import '../components/pill/styles/tokens';
38
37
  @import '../components/pill/styles/Pill.scss';
38
+ @import '../components/alert/styles/tokens';
39
+ @import '../components/alert/styles/Alert.scss';
39
40
  @import '../components/loading-indicators/BarSpinner/styles/BarSpinner.scss';
40
41
  @import '../components/loading-indicators/CirclePulse/CirclePulse.scss';
41
42
  @import '../components/truncate/styles/Truncate.scss';
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type AlertType, type AlertSize, type AlertVariant } from './components/alert/types';
1
2
  import { type SelectOption } from './components/forms/select/types';
2
3
  import { type IconSizes, type IconName } from './components/icons/types';
3
4
  import {
@@ -40,3 +41,4 @@ export type SemanticColor = 'neutral' | 'info' | 'warning' | 'error' | 'success'
40
41
  export type { SelectOption };
41
42
  export type { TableProps, TableColumn, Direction, Alignment };
42
43
  export type { PillColor, PillSize, PillVariant, PillType };
44
+ export type { AlertType, AlertSize, AlertVariant };