@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.
- package/AGENTS.md +110 -0
- package/CLAUDE.md +1 -0
- package/lib/components/alert/Alert.d.ts +2 -0
- package/lib/components/alert/Alert.stories.d.ts +15 -0
- package/lib/components/alert/__tests__/Alert.test.d.ts +1 -0
- package/lib/components/alert/index.d.ts +2 -0
- package/lib/components/alert/types.d.ts +26 -0
- 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 +3 -0
- package/lib/components/pill/types.d.ts +4 -0
- package/lib/index.css +214 -30
- package/lib/index.d.ts +34 -2
- package/lib/index.esm.css +214 -30
- package/lib/index.esm.js +41 -5
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +40 -3
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/alert/Alert.mdx +65 -0
- package/src/components/alert/Alert.stories.tsx +162 -0
- package/src/components/alert/Alert.tsx +68 -0
- package/src/components/alert/__tests__/Alert.test.tsx +52 -0
- package/src/components/alert/index.ts +2 -0
- package/src/components/alert/styles/Alert.scss +139 -0
- package/src/components/alert/styles/_tokens.scss +71 -0
- package/src/components/alert/types.ts +28 -0
- package/src/components/index.ts +1 -0
- package/src/components/pill/Pill.mdx +27 -0
- package/src/components/pill/Pill.stories.tsx +87 -0
- package/src/components/pill/Pill.tsx +36 -0
- package/src/components/pill/__tests__/Pill.test.tsx +93 -0
- package/src/components/pill/styles/Pill.scss +15 -2
- package/src/components/pill/types.ts +4 -0
- package/src/index.ts +1 -0
- package/src/setup/setupIcons.ts +8 -0
- package/src/styles/index.scss +2 -1
- package/src/types.ts +2 -0
package/lib/types.d.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 { type PillColor, type PillSize, type PillVariant, type PillType } from './components/pill/types';
|
|
@@ -15,3 +16,4 @@ export type SemanticColor = 'neutral' | 'info' | 'warning' | 'error' | 'success'
|
|
|
15
16
|
export type { SelectOption };
|
|
16
17
|
export type { TableProps, TableColumn, Direction, Alignment };
|
|
17
18
|
export type { PillColor, PillSize, PillVariant, PillType };
|
|
19
|
+
export type { AlertType, AlertSize, AlertVariant };
|
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls, Story } from '@storybook/blocks';
|
|
2
|
+
import * as Alert from './Alert.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Components/Alert" name="Alert" of={Alert} />
|
|
5
|
+
|
|
6
|
+
# Alert
|
|
7
|
+
|
|
8
|
+
Inline alerts notify users of a status or action result. They appear at the top
|
|
9
|
+
of a content area or near the item that needs attention.
|
|
10
|
+
|
|
11
|
+
Use `type` to set the semantic meaning (warning, info, success, error) — the
|
|
12
|
+
icon and accent color are chosen automatically. `variant` controls the visual
|
|
13
|
+
style: **soft** (subtle background) or **bordered** (colored border).
|
|
14
|
+
|
|
15
|
+
`size` controls density: **large** (default, banner-style) or **small**
|
|
16
|
+
(compact inline). When `tinted` is true on a **bordered** alert, the background
|
|
17
|
+
swaps from neutral to a type-specific tinted color.
|
|
18
|
+
|
|
19
|
+
Pass `title` to add a heading above the message. Add `actionText` + `onAction`
|
|
20
|
+
for an inline action button, and `onClose` to make it dismissable.
|
|
21
|
+
|
|
22
|
+
<Canvas of={Alert.Default} />
|
|
23
|
+
|
|
24
|
+
<Controls of={Alert.Default} />
|
|
25
|
+
|
|
26
|
+
## Types
|
|
27
|
+
|
|
28
|
+
<Canvas of={Alert.Types} />
|
|
29
|
+
|
|
30
|
+
## Variants
|
|
31
|
+
|
|
32
|
+
<Canvas of={Alert.Variants} />
|
|
33
|
+
|
|
34
|
+
## Small Bordered
|
|
35
|
+
|
|
36
|
+
Compact inline alerts without tinted backgrounds.
|
|
37
|
+
|
|
38
|
+
<Canvas of={Alert.SmallBordered} />
|
|
39
|
+
|
|
40
|
+
## Small Bordered Tinted
|
|
41
|
+
|
|
42
|
+
Compact inline alerts with type-specific tinted backgrounds — useful when
|
|
43
|
+
alerts sit alongside content and need stronger visual distinction.
|
|
44
|
+
|
|
45
|
+
<Canvas of={Alert.SmallBorderedTinted} />
|
|
46
|
+
|
|
47
|
+
## Small Tinted with Close
|
|
48
|
+
|
|
49
|
+
<Canvas of={Alert.SmallTintedWithClose} />
|
|
50
|
+
|
|
51
|
+
## With Title
|
|
52
|
+
|
|
53
|
+
<Canvas of={Alert.WithTitle} />
|
|
54
|
+
|
|
55
|
+
## With Action
|
|
56
|
+
|
|
57
|
+
<Canvas of={Alert.WithAction} />
|
|
58
|
+
|
|
59
|
+
## Closeable
|
|
60
|
+
|
|
61
|
+
<Canvas of={Alert.Closeable} />
|
|
62
|
+
|
|
63
|
+
## Combined
|
|
64
|
+
|
|
65
|
+
<Canvas of={Alert.Combined} />
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
faTriangleExclamation,
|
|
3
|
+
faCircleInfo,
|
|
4
|
+
faCircleCheck,
|
|
5
|
+
faCircleExclamation,
|
|
6
|
+
faXmark,
|
|
7
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
8
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
9
|
+
import { fn } from '@storybook/test';
|
|
10
|
+
|
|
11
|
+
import { registerFontAwesomeIcons } from '@/setup/setupIcons';
|
|
12
|
+
|
|
13
|
+
import { Alert } from './Alert';
|
|
14
|
+
import { type AlertSize, type AlertType, type AlertVariant } from './types';
|
|
15
|
+
|
|
16
|
+
registerFontAwesomeIcons(
|
|
17
|
+
faTriangleExclamation,
|
|
18
|
+
faCircleInfo,
|
|
19
|
+
faCircleCheck,
|
|
20
|
+
faCircleExclamation,
|
|
21
|
+
faXmark,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const TYPES: AlertType[] = ['warning', 'info', 'success', 'error'];
|
|
25
|
+
const VARIANTS: AlertVariant[] = ['soft', 'bordered'];
|
|
26
|
+
const SIZES: AlertSize[] = ['large', 'small'];
|
|
27
|
+
|
|
28
|
+
const meta: Meta<typeof Alert> = {
|
|
29
|
+
title: 'Components/Alert',
|
|
30
|
+
component: Alert,
|
|
31
|
+
decorators: [
|
|
32
|
+
(Story) => (
|
|
33
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 700 }}>
|
|
34
|
+
<Story />
|
|
35
|
+
</div>
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
argTypes: {
|
|
39
|
+
type: { control: 'select', options: TYPES },
|
|
40
|
+
variant: { control: 'select', options: VARIANTS },
|
|
41
|
+
size: { control: 'select', options: SIZES },
|
|
42
|
+
tinted: { control: 'boolean' },
|
|
43
|
+
title: { control: 'text' },
|
|
44
|
+
actionText: { control: 'text' },
|
|
45
|
+
onAction: { control: false },
|
|
46
|
+
onClose: { control: false },
|
|
47
|
+
children: { control: 'text' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default meta;
|
|
52
|
+
|
|
53
|
+
type Story = StoryObj<typeof Alert>;
|
|
54
|
+
|
|
55
|
+
export const Default: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
type: 'warning',
|
|
58
|
+
variant: 'soft',
|
|
59
|
+
children:
|
|
60
|
+
'This preset may incur additional charges depending on your submission size and usage.',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const Types: Story = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<>
|
|
67
|
+
{TYPES.map((type) => (
|
|
68
|
+
<Alert key={type} type={type}>
|
|
69
|
+
This is a {type} alert.
|
|
70
|
+
</Alert>
|
|
71
|
+
))}
|
|
72
|
+
</>
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const Variants: Story = {
|
|
77
|
+
render: () => (
|
|
78
|
+
<>
|
|
79
|
+
{VARIANTS.map((variant) => (
|
|
80
|
+
<Alert key={variant} type="info" variant={variant}>
|
|
81
|
+
This is the {variant} variant.
|
|
82
|
+
</Alert>
|
|
83
|
+
))}
|
|
84
|
+
</>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const WithTitle: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
type: 'info',
|
|
91
|
+
title: 'Info Title',
|
|
92
|
+
children: (
|
|
93
|
+
<>
|
|
94
|
+
An amazing message giving more context. A <a href="#">link</a> can be used in this content.
|
|
95
|
+
</>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const WithAction: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
type: 'warning',
|
|
103
|
+
actionText: 'Action',
|
|
104
|
+
onAction: fn(),
|
|
105
|
+
children: 'This preset may incur additional charges.',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const Closeable: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
type: 'success',
|
|
112
|
+
onClose: fn(),
|
|
113
|
+
children: 'Your changes have been saved.',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const Combined: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
type: 'error',
|
|
120
|
+
variant: 'bordered',
|
|
121
|
+
title: 'Upload Failed',
|
|
122
|
+
actionText: 'Retry',
|
|
123
|
+
onAction: fn(),
|
|
124
|
+
onClose: fn(),
|
|
125
|
+
children: 'The file could not be uploaded. Please check your connection and try again.',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const SmallBordered: Story = {
|
|
130
|
+
render: () => (
|
|
131
|
+
<>
|
|
132
|
+
{TYPES.map((type) => (
|
|
133
|
+
<Alert key={type} type={type} variant="bordered" size="small">
|
|
134
|
+
This is a small bordered {type} alert.
|
|
135
|
+
</Alert>
|
|
136
|
+
))}
|
|
137
|
+
</>
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const SmallBorderedTinted: Story = {
|
|
142
|
+
render: () => (
|
|
143
|
+
<>
|
|
144
|
+
{TYPES.map((type) => (
|
|
145
|
+
<Alert key={type} type={type} variant="bordered" size="small" tinted>
|
|
146
|
+
This is a small bordered tinted {type} alert.
|
|
147
|
+
</Alert>
|
|
148
|
+
))}
|
|
149
|
+
</>
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const SmallTintedWithClose: Story = {
|
|
154
|
+
args: {
|
|
155
|
+
type: 'info',
|
|
156
|
+
variant: 'bordered',
|
|
157
|
+
size: 'small',
|
|
158
|
+
tinted: true,
|
|
159
|
+
onClose: fn(),
|
|
160
|
+
children: 'New correspondence has been added',
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { type AlertProps, type AlertType } from './types';
|
|
4
|
+
import { Icon } from '../icons/Icon';
|
|
5
|
+
import { type IconName } from '../icons/types';
|
|
6
|
+
|
|
7
|
+
const ALERT_ICON: Record<AlertType, IconName> = {
|
|
8
|
+
warning: 'fa-triangle-exclamation',
|
|
9
|
+
info: 'fa-circle-info',
|
|
10
|
+
success: 'fa-circle-check',
|
|
11
|
+
error: 'fa-circle-exclamation',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Alert = ({
|
|
15
|
+
type,
|
|
16
|
+
variant = 'soft',
|
|
17
|
+
size = 'large',
|
|
18
|
+
tinted = false,
|
|
19
|
+
title,
|
|
20
|
+
children,
|
|
21
|
+
actionText,
|
|
22
|
+
onAction,
|
|
23
|
+
onClose,
|
|
24
|
+
closeAriaLabel = 'Dismiss',
|
|
25
|
+
className,
|
|
26
|
+
...rest
|
|
27
|
+
}: AlertProps) => {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
role="alert"
|
|
31
|
+
className={classNames(
|
|
32
|
+
'alert',
|
|
33
|
+
`alert--${variant}`,
|
|
34
|
+
`alert--${variant}-${type}`,
|
|
35
|
+
{ 'alert--small': size === 'small', 'alert--tinted': tinted, 'alert--has-title': !!title },
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
<Icon name={ALERT_ICON[type]} size="sm" />
|
|
41
|
+
|
|
42
|
+
<div className="alert__body">
|
|
43
|
+
{title && <div className="alert__title">{title}</div>}
|
|
44
|
+
<div className="alert__message">{children}</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{((actionText && onAction) || onClose) && (
|
|
48
|
+
<div className="alert__actions">
|
|
49
|
+
{actionText && onAction && (
|
|
50
|
+
<button type="button" className="alert__action" onClick={onAction}>
|
|
51
|
+
{actionText}
|
|
52
|
+
</button>
|
|
53
|
+
)}
|
|
54
|
+
{onClose && (
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
className="alert__close"
|
|
58
|
+
onClick={onClose}
|
|
59
|
+
aria-label={closeAriaLabel}
|
|
60
|
+
>
|
|
61
|
+
<Icon name="fa-xmark" size="xs" />
|
|
62
|
+
</button>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
|
|
4
|
+
import { Alert } from '@/components/alert/Alert';
|
|
5
|
+
|
|
6
|
+
describe('Alert', () => {
|
|
7
|
+
describe('action button', () => {
|
|
8
|
+
it('renders action button and calls onAction when clicked', async () => {
|
|
9
|
+
const user = userEvent.setup();
|
|
10
|
+
const handleAction = jest.fn();
|
|
11
|
+
render(
|
|
12
|
+
<Alert type="info" actionText="Retry" onAction={handleAction}>
|
|
13
|
+
Something failed
|
|
14
|
+
</Alert>,
|
|
15
|
+
);
|
|
16
|
+
await user.click(screen.getByRole('button', { name: 'Retry' }));
|
|
17
|
+
expect(handleAction).toHaveBeenCalledTimes(1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('does not render action button when actionText is provided without onAction', () => {
|
|
21
|
+
render(
|
|
22
|
+
<Alert type="info" actionText="Orphan">
|
|
23
|
+
msg
|
|
24
|
+
</Alert>,
|
|
25
|
+
);
|
|
26
|
+
expect(screen.queryByRole('button', { name: 'Orphan' })).not.toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('close button', () => {
|
|
31
|
+
it('calls onClose when close button is clicked', async () => {
|
|
32
|
+
const user = userEvent.setup();
|
|
33
|
+
const handleClose = jest.fn();
|
|
34
|
+
render(
|
|
35
|
+
<Alert type="warning" onClose={handleClose}>
|
|
36
|
+
dismissable
|
|
37
|
+
</Alert>,
|
|
38
|
+
);
|
|
39
|
+
await user.click(screen.getByRole('button', { name: 'Dismiss' }));
|
|
40
|
+
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('uses custom closeAriaLabel', () => {
|
|
44
|
+
render(
|
|
45
|
+
<Alert type="info" onClose={() => {}} closeAriaLabel="Fermer">
|
|
46
|
+
msg
|
|
47
|
+
</Alert>,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByRole('button', { name: 'Fermer' })).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
$alert-types: 'warning', 'info', 'success', 'error';
|
|
2
|
+
|
|
3
|
+
.alert {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: flex-start;
|
|
6
|
+
gap: var(--pf-spacing-md);
|
|
7
|
+
padding: var(--pf-spacing-2xl);
|
|
8
|
+
border-radius: var(--pf-border-radius-md);
|
|
9
|
+
background-color: var(--pf-semantic-background-secondary);
|
|
10
|
+
font-family: var(--pf-font-family);
|
|
11
|
+
box-shadow:
|
|
12
|
+
0 4px 6px -4px var(--pf-semantic-elevation-1),
|
|
13
|
+
0 10px 15px -3px var(--pf-semantic-elevation-1);
|
|
14
|
+
|
|
15
|
+
// ------- Size: small (compact inline) -------
|
|
16
|
+
&--small {
|
|
17
|
+
padding: var(--pf-spacing-sm);
|
|
18
|
+
align-items: center;
|
|
19
|
+
box-shadow: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ------- Variant: bordered -------
|
|
23
|
+
&--bordered {
|
|
24
|
+
border: 1px solid var(--alert-border-color);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ------- Type-specific icon colors (soft uses semantic utility tokens) -------
|
|
28
|
+
&--soft-warning > .icon {
|
|
29
|
+
color: var(--pf-semantic-utility-warning-default);
|
|
30
|
+
}
|
|
31
|
+
&--soft-info > .icon {
|
|
32
|
+
color: var(--pf-semantic-utility-info-default);
|
|
33
|
+
}
|
|
34
|
+
&--soft-success > .icon {
|
|
35
|
+
color: var(--pf-semantic-utility-success-default);
|
|
36
|
+
}
|
|
37
|
+
&--soft-error > .icon {
|
|
38
|
+
color: var(--pf-semantic-utility-error-default);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ------- Type-specific bordered colors (from component tokens) -------
|
|
42
|
+
@each $type in $alert-types {
|
|
43
|
+
&--bordered-#{$type} {
|
|
44
|
+
--alert-border-color: var(--pf-alert-bordered-#{$type}-border);
|
|
45
|
+
|
|
46
|
+
> .icon {
|
|
47
|
+
color: var(--pf-alert-bordered-#{$type}-icon);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.alert__title {
|
|
51
|
+
color: var(--pf-alert-bordered-#{$type}-text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&--tinted#{&}--bordered-#{$type} {
|
|
56
|
+
background-color: var(--pf-alert-bordered-#{$type}-bg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ------- Sub-elements -------
|
|
61
|
+
> .icon {
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&__body {
|
|
66
|
+
flex: 1;
|
|
67
|
+
min-width: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&__title {
|
|
71
|
+
font-size: var(--pf-font-size-md);
|
|
72
|
+
font-weight: var(--pf-font-weight-semibold);
|
|
73
|
+
line-height: 20px;
|
|
74
|
+
color: var(--pf-semantic-font-regular);
|
|
75
|
+
margin-bottom: var(--pf-spacing-xxs);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&__message {
|
|
79
|
+
font-size: var(--pf-font-size-md);
|
|
80
|
+
font-weight: var(--pf-font-weight-regular);
|
|
81
|
+
line-height: 20px;
|
|
82
|
+
color: var(--pf-semantic-font-regular);
|
|
83
|
+
|
|
84
|
+
a {
|
|
85
|
+
color: var(--pf-semantic-font-link);
|
|
86
|
+
text-decoration: underline;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&--has-title .alert__message {
|
|
91
|
+
color: var(--pf-semantic-font-soft);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&__actions {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: var(--pf-spacing-sm);
|
|
98
|
+
flex-shrink: 0;
|
|
99
|
+
margin-left: auto;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&__action {
|
|
103
|
+
background: none;
|
|
104
|
+
border: none;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
font-family: var(--pf-font-family);
|
|
107
|
+
font-size: var(--pf-font-size-md);
|
|
108
|
+
font-weight: var(--pf-font-weight-medium);
|
|
109
|
+
line-height: 20px;
|
|
110
|
+
color: var(--pf-semantic-font-link);
|
|
111
|
+
padding: 0;
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
|
|
114
|
+
&:hover {
|
|
115
|
+
text-decoration: underline;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&__close {
|
|
120
|
+
display: inline-flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
border: none;
|
|
126
|
+
background: transparent;
|
|
127
|
+
padding: var(--pf-spacing-xxs);
|
|
128
|
+
border-radius: var(--pf-border-radius-xs);
|
|
129
|
+
color: var(--pf-semantic-font-soft);
|
|
130
|
+
line-height: 1;
|
|
131
|
+
transition:
|
|
132
|
+
color 0.15s ease,
|
|
133
|
+
background-color 0.15s ease;
|
|
134
|
+
|
|
135
|
+
&:hover {
|
|
136
|
+
color: var(--pf-semantic-font-regular);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ALERT COMPONENT TOKENS (internal)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// These CSS custom properties are implementation details of the Alert component.
|
|
5
|
+
// They provide light/dark theme values for the bordered variant's per-type
|
|
6
|
+
// accent colors (border, title text, icon). The soft variant uses semantic
|
|
7
|
+
// utility tokens directly and doesn't need component-level overrides.
|
|
8
|
+
//
|
|
9
|
+
// Consumers should style alerts through the component props, not by
|
|
10
|
+
// overriding these variables.
|
|
11
|
+
//
|
|
12
|
+
// Aligned with Figma's "Alert" variable group.
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Bordered variant - Light Theme
|
|
16
|
+
// ============================================================================
|
|
17
|
+
:root,
|
|
18
|
+
:root [data-theme='light'] {
|
|
19
|
+
// Bordered / Warning
|
|
20
|
+
--pf-alert-bordered-warning-bg: var(--pf-yellow-color-150);
|
|
21
|
+
--pf-alert-bordered-warning-border: var(--pf-yellow-color-400);
|
|
22
|
+
--pf-alert-bordered-warning-text: var(--pf-yellow-color-600);
|
|
23
|
+
--pf-alert-bordered-warning-icon: var(--pf-yellow-color-400);
|
|
24
|
+
|
|
25
|
+
// Bordered / Info
|
|
26
|
+
--pf-alert-bordered-info-bg: var(--pf-blue-color-150);
|
|
27
|
+
--pf-alert-bordered-info-border: var(--pf-secondary-color-400);
|
|
28
|
+
--pf-alert-bordered-info-text: var(--pf-secondary-color-500);
|
|
29
|
+
--pf-alert-bordered-info-icon: var(--pf-secondary-color-400);
|
|
30
|
+
|
|
31
|
+
// Bordered / Success
|
|
32
|
+
--pf-alert-bordered-success-bg: var(--pf-green-color-150);
|
|
33
|
+
--pf-alert-bordered-success-border: var(--pf-green-color-450);
|
|
34
|
+
--pf-alert-bordered-success-text: var(--pf-green-color-600);
|
|
35
|
+
--pf-alert-bordered-success-icon: var(--pf-green-color-500);
|
|
36
|
+
|
|
37
|
+
// Bordered / Error
|
|
38
|
+
--pf-alert-bordered-error-bg: var(--pf-red-color-150);
|
|
39
|
+
--pf-alert-bordered-error-border: var(--pf-red-color-250);
|
|
40
|
+
--pf-alert-bordered-error-text: var(--pf-red-color-500);
|
|
41
|
+
--pf-alert-bordered-error-icon: var(--pf-red-color-450);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Bordered variant - Dark Theme
|
|
46
|
+
// ============================================================================
|
|
47
|
+
:root [data-theme='dark'] {
|
|
48
|
+
// Bordered / Warning
|
|
49
|
+
--pf-alert-bordered-warning-bg: var(--pf-yellow-color-800);
|
|
50
|
+
--pf-alert-bordered-warning-border: var(--pf-yellow-color-500);
|
|
51
|
+
--pf-alert-bordered-warning-text: var(--pf-yellow-color-300);
|
|
52
|
+
--pf-alert-bordered-warning-icon: var(--pf-yellow-color-300);
|
|
53
|
+
|
|
54
|
+
// Bordered / Info
|
|
55
|
+
--pf-alert-bordered-info-bg: var(--pf-blue-color-700);
|
|
56
|
+
--pf-alert-bordered-info-border: var(--pf-secondary-color-500);
|
|
57
|
+
--pf-alert-bordered-info-text: var(--pf-secondary-color-250);
|
|
58
|
+
--pf-alert-bordered-info-icon: var(--pf-secondary-color-250);
|
|
59
|
+
|
|
60
|
+
// Bordered / Success
|
|
61
|
+
--pf-alert-bordered-success-bg: var(--pf-green-color-800);
|
|
62
|
+
--pf-alert-bordered-success-border: var(--pf-green-color-600);
|
|
63
|
+
--pf-alert-bordered-success-text: var(--pf-green-color-300);
|
|
64
|
+
--pf-alert-bordered-success-icon: var(--pf-green-color-300);
|
|
65
|
+
|
|
66
|
+
// Bordered / Error
|
|
67
|
+
--pf-alert-bordered-error-bg: var(--pf-red-color-700);
|
|
68
|
+
--pf-alert-bordered-error-border: var(--pf-red-color-500);
|
|
69
|
+
--pf-alert-bordered-error-text: var(--pf-red-color-300);
|
|
70
|
+
--pf-alert-bordered-error-icon: var(--pf-red-color-300);
|
|
71
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
export type AlertType = 'warning' | 'info' | 'success' | 'error';
|
|
4
|
+
export type AlertVariant = 'soft' | 'bordered';
|
|
5
|
+
export type AlertSize = 'small' | 'large';
|
|
6
|
+
|
|
7
|
+
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Semantic type — controls the icon and accent color */
|
|
9
|
+
type: AlertType;
|
|
10
|
+
/** Visual style: soft (subtle bg) or bordered (colored border) */
|
|
11
|
+
variant?: AlertVariant;
|
|
12
|
+
/** Size: large (banner) or small (compact inline) */
|
|
13
|
+
size?: AlertSize;
|
|
14
|
+
/** When true, the bordered variant uses a type-specific tinted background instead of surface-secondary */
|
|
15
|
+
tinted?: boolean;
|
|
16
|
+
/** Optional title — when provided, renders above the message */
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Alert body / message content */
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
/** Optional action button label */
|
|
21
|
+
actionText?: string;
|
|
22
|
+
/** Called when the action button is clicked */
|
|
23
|
+
onAction?: () => void;
|
|
24
|
+
/** Called when the close button is clicked — omit to hide the close button */
|
|
25
|
+
onClose?: () => void;
|
|
26
|
+
/** Accessible label for the close button (for i18n) */
|
|
27
|
+
closeAriaLabel?: string;
|
|
28
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { InputDateRangePicker } from './forms/date/inputDateRangePicker';
|
|
|
21
21
|
export { Modal, ConfirmationModal } from './modal';
|
|
22
22
|
export { Badge } from './badge';
|
|
23
23
|
export { Pill } from './pill';
|
|
24
|
+
export { Alert } from './alert';
|
|
24
25
|
export { Pagination } from './pagination';
|
|
25
26
|
export { TanstackTable } from './tanstackTable';
|
|
26
27
|
export { Tooltip } from './tooltip';
|
|
@@ -68,6 +68,33 @@ Multiple features can be combined freely.
|
|
|
68
68
|
|
|
69
69
|
<Canvas of={Pill.Combined} />
|
|
70
70
|
|
|
71
|
+
## Shading
|
|
72
|
+
|
|
73
|
+
Use `shade` and `shadeCount` to visually differentiate pills within a single color.
|
|
74
|
+
The middle shade renders the base color; shades below the midpoint blend toward white
|
|
75
|
+
(up to 30% lighter), shades above blend toward black (up to 30% darker). Text and icons
|
|
76
|
+
stay at full strength throughout.
|
|
77
|
+
|
|
78
|
+
Pass both props together — `shade` is the 1-based index of this pill, `shadeCount` is
|
|
79
|
+
the total number of pills in the group. When either prop is omitted the pill renders
|
|
80
|
+
at full color as usual.
|
|
81
|
+
|
|
82
|
+
<Canvas of={Pill.Shaded} />
|
|
83
|
+
|
|
84
|
+
<Controls of={Pill.Shaded} />
|
|
85
|
+
|
|
86
|
+
### Spectrum across colors
|
|
87
|
+
|
|
88
|
+
Every chromatic color works with shading, in both solid and outline variants.
|
|
89
|
+
|
|
90
|
+
<Canvas of={Pill.ShadedSpectrum} />
|
|
91
|
+
|
|
92
|
+
### Dynamic shade counts
|
|
93
|
+
|
|
94
|
+
The distribution adapts to any group size — no fixed token set required.
|
|
95
|
+
|
|
96
|
+
<Canvas of={Pill.ShadedDynamicCount} />
|
|
97
|
+
|
|
71
98
|
## Full Color × Variant Matrix
|
|
72
99
|
|
|
73
100
|
<Canvas of={Pill.FullMatrix} />
|