@boostdev/design-system-components 1.0.2 → 1.1.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 +20 -18
- package/dist/client.cjs +236 -153
- package/dist/client.css +785 -702
- package/dist/client.d.cts +53 -94
- package/dist/client.d.ts +53 -94
- package/dist/client.js +237 -163
- package/dist/index.cjs +236 -153
- package/dist/index.css +785 -702
- package/dist/index.d.cts +53 -94
- package/dist/index.d.ts +53 -94
- package/dist/index.js +237 -163
- package/dist/native/index.cjs +991 -0
- package/dist/native/index.d.cts +208 -0
- package/dist/native/index.d.ts +208 -0
- package/dist/native/index.js +968 -0
- package/dist/web-components.d.ts +303 -0
- package/dist/web-components.js +1968 -0
- package/package.json +29 -7
- package/src/components/interaction/Button/Button.module.css +14 -11
- package/src/components/interaction/Button/Button.native.spec.tsx +35 -0
- package/src/components/interaction/Button/Button.native.stories.tsx +42 -0
- package/src/components/interaction/Button/Button.native.tsx +95 -0
- package/src/components/interaction/Button/Button.stories.tsx +1 -1
- package/src/components/interaction/Button/Button.tsx +2 -2
- package/src/components/interaction/Command/Command.mdx +16 -0
- package/src/components/interaction/Command/Command.module.css +12 -12
- package/src/components/interaction/Command/Command.stories.tsx +1 -1
- package/src/components/interaction/Command/Command.tsx +2 -2
- package/src/components/interaction/Dialog/Dialog.module.css +3 -3
- package/src/components/interaction/Dialog/Dialog.stories.tsx +1 -1
- package/src/components/interaction/Dialog/Dialog.tsx +2 -2
- package/src/components/interaction/Drawer/Drawer.mdx +14 -0
- package/src/components/interaction/Drawer/Drawer.module.css +3 -3
- package/src/components/interaction/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/interaction/Drawer/Drawer.tsx +2 -2
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +3 -3
- package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +1 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/components/interaction/Popover/Popover.mdx +17 -0
- package/src/components/interaction/Popover/Popover.module.css +72 -15
- package/src/components/interaction/Popover/Popover.spec.tsx +68 -4
- package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
- package/src/components/interaction/Popover/Popover.tsx +76 -20
- package/src/components/interaction/Rating/Rating.module.css +2 -2
- package/src/components/interaction/Rating/Rating.stories.tsx +1 -1
- package/src/components/interaction/Rating/Rating.tsx +2 -2
- package/src/components/interaction/Toast/Toast.module.css +16 -11
- package/src/components/interaction/Toast/Toast.spec.tsx +21 -0
- package/src/components/interaction/Toast/Toast.stories.tsx +1 -1
- package/src/components/interaction/Toast/Toast.tsx +22 -2
- package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +5 -5
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +42 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +36 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +80 -0
- package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +44 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +2 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -2
- package/src/components/interaction/form/Combobox/Combobox.mdx +21 -0
- package/src/components/interaction/form/Combobox/Combobox.module.css +8 -8
- package/src/components/interaction/form/Combobox/Combobox.stories.tsx +1 -1
- package/src/components/interaction/form/Combobox/Combobox.tsx +2 -2
- package/src/components/interaction/form/FileInput/FileInput.mdx +14 -0
- package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
- package/src/components/interaction/form/FileInput/FileInput.stories.tsx +1 -1
- package/src/components/interaction/form/FileInput/FileInput.tsx +2 -2
- package/src/components/interaction/form/FormInput/FormInput.module.css +2 -2
- package/src/components/interaction/form/FormInput/FormInput.stories.tsx +1 -1
- package/src/components/interaction/form/FormInput/FormInput.tsx +2 -2
- package/src/components/interaction/form/NumberInput/NumberInput.mdx +16 -0
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +4 -4
- package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -2
- package/src/components/interaction/form/Radio/Radio.mdx +3 -3
- package/src/components/interaction/form/Radio/Radio.module.css +6 -6
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +46 -0
- package/src/components/interaction/form/Radio/Radio.native.tsx +79 -0
- package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
- package/src/components/interaction/form/Radio/Radio.tsx +2 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +45 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +2 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -2
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -5
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +1 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +2 -2
- package/src/components/interaction/form/Select/Select.module.css +3 -3
- package/src/components/interaction/form/Select/Select.stories.tsx +1 -1
- package/src/components/interaction/form/Select/Select.tsx +2 -2
- package/src/components/interaction/form/Slider/Slider.mdx +1 -1
- package/src/components/interaction/form/Slider/Slider.module.css +14 -14
- package/src/components/interaction/form/Slider/Slider.stories.tsx +1 -1
- package/src/components/interaction/form/Slider/Slider.tsx +2 -2
- package/src/components/interaction/form/Switch/Switch.mdx +4 -4
- package/src/components/interaction/form/Switch/Switch.module.css +8 -8
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +60 -0
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +35 -0
- package/src/components/interaction/form/Switch/Switch.native.tsx +59 -0
- package/src/components/interaction/form/Switch/Switch.stories.tsx +1 -1
- package/src/components/interaction/form/Switch/Switch.tsx +2 -2
- package/src/components/interaction/form/Textarea/Textarea.mdx +17 -0
- package/src/components/interaction/form/Textarea/Textarea.module.css +2 -2
- package/src/components/interaction/form/Textarea/Textarea.stories.tsx +1 -1
- package/src/components/interaction/form/Textarea/Textarea.tsx +2 -2
- package/src/components/interaction/form/atoms/InputContainer.tsx +2 -1
- package/src/components/interaction/form/atoms/Label.module.css +1 -1
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +18 -0
- package/src/components/interaction/form/atoms/Label.native.tsx +29 -0
- package/src/components/interaction/form/atoms/Label.tsx +5 -3
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +30 -0
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +22 -0
- package/src/components/interaction/form/atoms/Message.native.tsx +34 -0
- package/src/components/interaction/form/atoms/Message.tsx +5 -3
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +50 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +34 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +2 -2
- package/src/components/layout/Card/Card.module.css +2 -2
- package/src/components/layout/Card/Card.native.stories.tsx +53 -0
- package/src/components/layout/Card/Card.native.tsx +89 -0
- package/src/components/layout/Card/Card.stories.tsx +1 -1
- package/src/components/layout/Card/Card.tsx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.module.css +3 -3
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +39 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +41 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +20 -0
- package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.tsx +2 -2
- package/src/components/layout/SectionHeader/SectionHeader.mdx +13 -0
- package/src/components/layout/SectionHeader/SectionHeader.module.css +2 -2
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +38 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +79 -0
- package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +1 -1
- package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
- package/src/components/ui/Accordion/Accordion.mdx +14 -0
- package/src/components/ui/Accordion/Accordion.module.css +5 -5
- package/src/components/ui/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/ui/Accordion/Accordion.tsx +2 -2
- package/src/components/ui/Alert/Alert.module.css +15 -15
- package/src/components/ui/Alert/Alert.native.stories.tsx +43 -0
- package/src/components/ui/Alert/Alert.native.tsx +94 -0
- package/src/components/ui/Alert/Alert.stories.tsx +1 -1
- package/src/components/ui/Alert/Alert.tsx +2 -2
- package/src/components/ui/Avatar/Avatar.module.css +2 -2
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +47 -0
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +34 -0
- package/src/components/ui/Avatar/Avatar.native.tsx +85 -0
- package/src/components/ui/Avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/Avatar/Avatar.tsx +2 -2
- package/src/components/ui/Badge/Badge.mdx +2 -2
- package/src/components/ui/Badge/Badge.module.css +12 -12
- package/src/components/ui/Badge/Badge.native.stories.tsx +36 -0
- package/src/components/ui/Badge/Badge.native.tsx +50 -0
- package/src/components/ui/Badge/Badge.stories.tsx +1 -1
- package/src/components/ui/Badge/Badge.tsx +2 -2
- package/src/components/ui/Breadcrumb/Breadcrumb.module.css +2 -2
- package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +1 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/ui/Calendar/Calendar.mdx +16 -0
- package/src/components/ui/Calendar/Calendar.module.css +11 -11
- package/src/components/ui/Calendar/Calendar.stories.tsx +1 -1
- package/src/components/ui/Calendar/Calendar.tsx +2 -2
- package/src/components/ui/Carousel/Carousel.module.css +4 -4
- package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
- package/src/components/ui/Carousel/Carousel.tsx +2 -2
- package/src/components/ui/Collapsible/Collapsible.module.css +6 -6
- package/src/components/ui/Collapsible/Collapsible.stories.tsx +1 -1
- package/src/components/ui/Collapsible/Collapsible.tsx +2 -2
- package/src/components/ui/DescriptionList/DescriptionList.module.css +3 -3
- package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +1 -1
- package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -2
- package/src/components/ui/Link/Link.mdx +14 -0
- package/src/components/ui/Link/Link.module.css +4 -4
- package/src/components/ui/Link/Link.stories.tsx +1 -1
- package/src/components/ui/Link/Link.tsx +2 -2
- package/src/components/ui/Loading/Loading.module.css +2 -2
- package/src/components/ui/Loading/Loading.native.spec.tsx +24 -0
- package/src/components/ui/Loading/Loading.native.stories.tsx +33 -0
- package/src/components/ui/Loading/Loading.native.tsx +29 -0
- package/src/components/ui/Loading/Loading.stories.tsx +1 -1
- package/src/components/ui/Loading/Loading.tsx +2 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +15 -15
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +39 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +76 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +1 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -2
- package/src/components/ui/Pagination/Pagination.module.css +8 -8
- package/src/components/ui/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/ui/Pagination/Pagination.tsx +2 -2
- package/src/components/ui/Progress/Progress.mdx +1 -1
- package/src/components/ui/Progress/Progress.module.css +3 -3
- package/src/components/ui/Progress/Progress.native.stories.tsx +34 -0
- package/src/components/ui/Progress/Progress.native.tsx +84 -0
- package/src/components/ui/Progress/Progress.stories.tsx +1 -1
- package/src/components/ui/Progress/Progress.tsx +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +3 -3
- package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -2
- package/src/components/ui/Separator/Separator.mdx +14 -0
- package/src/components/ui/Separator/Separator.module.css +1 -1
- package/src/components/ui/Separator/Separator.native.stories.tsx +42 -0
- package/src/components/ui/Separator/Separator.native.tsx +32 -0
- package/src/components/ui/Separator/Separator.stories.tsx +1 -1
- package/src/components/ui/Separator/Separator.tsx +2 -2
- package/src/components/ui/Skeleton/Skeleton.module.css +3 -3
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +33 -0
- package/src/components/ui/Skeleton/Skeleton.native.tsx +41 -0
- package/src/components/ui/Skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/Skeleton/Skeleton.tsx +2 -3
- package/src/components/ui/SkipLink/SkipLink.module.css +3 -3
- package/src/components/ui/SkipLink/SkipLink.stories.tsx +1 -1
- package/src/components/ui/SkipLink/SkipLink.tsx +5 -3
- package/src/components/ui/Table/Table.mdx +14 -0
- package/src/components/ui/Table/Table.module.css +11 -11
- package/src/components/ui/Table/Table.stories.tsx +1 -1
- package/src/components/ui/Table/Table.tsx +2 -2
- package/src/components/ui/Tabs/Tabs.module.css +3 -3
- package/src/components/ui/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/Tabs/Tabs.tsx +2 -2
- package/src/components/ui/Tooltip/Tooltip.mdx +14 -0
- package/src/components/ui/Tooltip/Tooltip.module.css +2 -2
- package/src/components/ui/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/ui/Tooltip/Tooltip.tsx +2 -2
- package/src/components/ui/Typography/Typography.mdx +13 -0
- package/src/components/ui/Typography/Typography.module.css +1 -1
- package/src/components/ui/Typography/Typography.native.stories.tsx +38 -0
- package/src/components/ui/Typography/Typography.native.tsx +65 -0
- package/src/components/ui/Typography/Typography.stories.tsx +1 -1
- package/src/components/ui/Typography/Typography.tsx +2 -2
- package/src/css/bdc.css +10 -2
- package/src/index.ts +3 -0
- package/src/native/ThemeContext.tsx +28 -0
- package/src/native/tokens.ts +13 -0
- package/src/native.ts +39 -0
- package/src/react-augment.d.ts +13 -0
- package/src/stories/DesignSystem/Borders.mdx +7 -7
- package/src/stories/DesignSystem/Colors.mdx +28 -29
- package/src/stories/DesignSystem/DarkMode.mdx +130 -0
- package/src/stories/DesignSystem/Elevation.mdx +4 -4
- package/src/stories/DesignSystem/Grid.mdx +5 -5
- package/src/stories/DesignSystem/Motion.mdx +2 -2
- package/src/stories/DesignSystem/Overview.mdx +1 -1
- package/src/stories/DesignSystem/Spacing.mdx +3 -3
- package/src/stories/DesignSystem/Typography.mdx +6 -6
- package/src/types.ts +2 -0
- package/src/typings.d.ts +3 -0
- package/src/web-components/globals.ts +61 -0
- package/src/web-components/index.ts +12 -0
- package/src/web-components/interaction/BdsButton.stories.tsx +60 -0
- package/src/web-components/interaction/BdsPopover.stories.tsx +70 -0
- package/src/web-components/interaction/BdsToastProvider.stories.tsx +73 -0
- package/src/web-components/interaction/bds-button.spec.ts +95 -0
- package/src/web-components/interaction/bds-button.ts +293 -0
- package/src/web-components/interaction/bds-popover.spec.ts +126 -0
- package/src/web-components/interaction/bds-popover.ts +217 -0
- package/src/web-components/interaction/bds-toast-provider.spec.ts +122 -0
- package/src/web-components/interaction/bds-toast-provider.ts +211 -0
- package/src/web-components/test/helpers.ts +14 -0
- package/src/web-components/ui/BdsAlert.stories.tsx +60 -0
- package/src/web-components/ui/BdsBadge.stories.tsx +37 -0
- package/src/web-components/ui/bds-alert.spec.ts +109 -0
- package/src/web-components/ui/bds-alert.ts +209 -0
- package/src/web-components/ui/bds-badge.spec.ts +51 -0
- package/src/web-components/ui/bds-badge.ts +88 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Design System/Dark Mode" />
|
|
4
|
+
|
|
5
|
+
# Dark Mode
|
|
6
|
+
|
|
7
|
+
All components adapt to dark mode automatically. No extra CSS imports or component changes are needed — switching the theme updates every token at once.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
The design system foundation ships two sets of semantic tokens: one for light mode and one for dark mode. When dark mode is active, the `--bds-color_*` tokens remap to their dark counterparts. Because every component is built on these tokens, the entire UI responds in a single cascade update.
|
|
12
|
+
|
|
13
|
+
## Enabling dark mode
|
|
14
|
+
|
|
15
|
+
### Via `data-theme` attribute (recommended)
|
|
16
|
+
|
|
17
|
+
Set `data-theme="dark"` on a container element — typically `<html>` for a global switch, or any wrapper for a scoped one.
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<!-- global dark mode -->
|
|
21
|
+
<html data-theme="dark">
|
|
22
|
+
|
|
23
|
+
<!-- scoped dark mode (only this subtree) -->
|
|
24
|
+
<div data-theme="dark">
|
|
25
|
+
<Button>Dark button</Button>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Remove the attribute (or set `data-theme="light"`) to return to light mode.
|
|
30
|
+
|
|
31
|
+
### Via `prefers-color-scheme` (automatic)
|
|
32
|
+
|
|
33
|
+
No attribute needed. When a user has dark mode enabled at the OS level, the design system tokens switch automatically:
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
/* this is built into @boostdev/design-system-foundation — nothing to add */
|
|
37
|
+
@media (prefers-color-scheme: dark) {
|
|
38
|
+
:root { /* dark token values */ }
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`data-theme` always takes precedence over `prefers-color-scheme`, so you can override the system preference.
|
|
43
|
+
|
|
44
|
+
## Building a theme toggle
|
|
45
|
+
|
|
46
|
+
A minimal React hook that syncs with the system preference and lets the user override it:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useEffect, useState } from 'react';
|
|
50
|
+
|
|
51
|
+
type Theme = 'light' | 'dark';
|
|
52
|
+
|
|
53
|
+
function getSystemTheme(): Theme {
|
|
54
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useTheme() {
|
|
58
|
+
const [theme, setTheme] = useState<Theme>(
|
|
59
|
+
() => (localStorage.getItem('theme') as Theme) ?? getSystemTheme()
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
64
|
+
localStorage.setItem('theme', theme);
|
|
65
|
+
}, [theme]);
|
|
66
|
+
|
|
67
|
+
const toggle = () => setTheme(t => (t === 'dark' ? 'light' : 'dark'));
|
|
68
|
+
|
|
69
|
+
return { theme, toggle };
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Usage:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function ThemeToggle() {
|
|
77
|
+
const { theme, toggle } = useTheme();
|
|
78
|
+
return (
|
|
79
|
+
<Button onClick={toggle}>
|
|
80
|
+
{theme === 'dark' ? 'Switch to light' : 'Switch to dark'}
|
|
81
|
+
</Button>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Customising tokens in dark mode
|
|
87
|
+
|
|
88
|
+
Override any token in `@layer tokens.override` — the rule applies regardless of the active theme unless you scope it:
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
@import "@boostdev/design-system-foundation/css";
|
|
92
|
+
|
|
93
|
+
/* override for all themes */
|
|
94
|
+
@layer tokens.override {
|
|
95
|
+
:root {
|
|
96
|
+
--bds-color_green: oklch(60% 0.15 145);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* override for dark mode only */
|
|
101
|
+
@layer tokens.override {
|
|
102
|
+
[data-theme="dark"],
|
|
103
|
+
@media (prefers-color-scheme: dark) {
|
|
104
|
+
:root:not([data-theme="light"]) {
|
|
105
|
+
--bds-color_green: oklch(75% 0.15 145);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Component-level dark mode overrides
|
|
112
|
+
|
|
113
|
+
Use component CSS variables to restyle a specific component in dark mode without touching global tokens:
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
/* make all Tooltips use a high-contrast dark style in both themes */
|
|
117
|
+
.my-app {
|
|
118
|
+
--tooltip_bg: #1a1a1a;
|
|
119
|
+
--tooltip_text: #f5f5f5;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* only override in dark mode */
|
|
123
|
+
[data-theme="dark"] .my-card {
|
|
124
|
+
--card_color: #1e1e1e;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Testing dark mode in Storybook
|
|
129
|
+
|
|
130
|
+
Use the **Theme** toolbar toggle in the top bar to switch between light and dark mode while browsing stories. This sets `data-theme` on the story canvas root.
|
|
@@ -7,7 +7,7 @@ export const ShadowCard = ({ token, label }) => (
|
|
|
7
7
|
<div style={{
|
|
8
8
|
width: '8rem',
|
|
9
9
|
height: '5rem',
|
|
10
|
-
background: 'var(--bds-
|
|
10
|
+
background: 'var(--bds-color_bg)',
|
|
11
11
|
borderRadius: '0.75rem',
|
|
12
12
|
boxShadow: `var(${token})`,
|
|
13
13
|
}} />
|
|
@@ -23,7 +23,7 @@ export const RadiusBox = ({ token, label, value }) => (
|
|
|
23
23
|
<div style={{
|
|
24
24
|
width: '4rem',
|
|
25
25
|
height: '4rem',
|
|
26
|
-
background: 'var(--bds-
|
|
26
|
+
background: 'var(--bds-color_interactive)',
|
|
27
27
|
borderRadius: `var(${token})`,
|
|
28
28
|
opacity: 0.8,
|
|
29
29
|
}} />
|
|
@@ -49,7 +49,7 @@ export const ZRow = ({ token, value, description }) => (
|
|
|
49
49
|
|
|
50
50
|
Five shadow levels from subtle to dramatic. All values adapt to dark mode (higher opacity for visibility against dark surfaces).
|
|
51
51
|
|
|
52
|
-
<div style={{ display: 'flex', gap: '2.5rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-
|
|
52
|
+
<div style={{ display: 'flex', gap: '2.5rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-color_bg--subtle)', borderRadius: '0.75rem', marginBottom: '2rem' }}>
|
|
53
53
|
<ShadowCard token="--bds-shadow_s" label="Small" />
|
|
54
54
|
<ShadowCard token="--bds-shadow_m" label="Medium" />
|
|
55
55
|
<ShadowCard token="--bds-shadow_l" label="Large" />
|
|
@@ -69,7 +69,7 @@ Five shadow levels from subtle to dramatic. All values adapt to dark mode (highe
|
|
|
69
69
|
|
|
70
70
|
## Border radius
|
|
71
71
|
|
|
72
|
-
<div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-
|
|
72
|
+
<div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-color_bg--subtle)', borderRadius: '0.75rem', marginBottom: '2rem' }}>
|
|
73
73
|
<RadiusBox token="--bds-border_radius--xs" label="X-Small" value="0.25rem / 4px" />
|
|
74
74
|
<RadiusBox token="--bds-border_radius--s" label="Small" value="0.75rem / 12px" />
|
|
75
75
|
<RadiusBox token="--bds-border_radius--m" label="Medium" value="1rem / 16px" />
|
|
@@ -2,11 +2,11 @@ import { Meta } from '@storybook/blocks';
|
|
|
2
2
|
|
|
3
3
|
<Meta title="Design System/Grid" />
|
|
4
4
|
|
|
5
|
-
export const GridCell = ({ span, label, color = 'var(--bds-
|
|
5
|
+
export const GridCell = ({ span, label, color = 'var(--bds-color_interactive)' }) => (
|
|
6
6
|
<div style={{
|
|
7
7
|
gridColumn: span,
|
|
8
8
|
background: color,
|
|
9
|
-
color: 'var(--bds-
|
|
9
|
+
color: 'var(--bds-color_on-interactive)',
|
|
10
10
|
borderRadius: '0.25rem',
|
|
11
11
|
padding: '0.5rem 0.75rem',
|
|
12
12
|
fontSize: '0.75rem',
|
|
@@ -23,7 +23,7 @@ export const GridDemo = ({ children }) => (
|
|
|
23
23
|
gridTemplateColumns: 'repeat(12, 1fr)',
|
|
24
24
|
gap: '0.5rem',
|
|
25
25
|
padding: '1rem',
|
|
26
|
-
background: 'var(--bds-
|
|
26
|
+
background: 'var(--bds-color_bg--subtle)',
|
|
27
27
|
borderRadius: '0.5rem',
|
|
28
28
|
marginBottom: '1.5rem',
|
|
29
29
|
}}>
|
|
@@ -69,14 +69,14 @@ The grid system is responsive and built entirely on CSS custom properties. It ad
|
|
|
69
69
|
|
|
70
70
|
<GridDemo>
|
|
71
71
|
<GridCell span="span 9" label="span 75%" />
|
|
72
|
-
<GridCell span="span 3" label="25%" color="var(--bds-
|
|
72
|
+
<GridCell span="span 3" label="25%" color="var(--bds-color_grey)" />
|
|
73
73
|
</GridDemo>
|
|
74
74
|
|
|
75
75
|
### Two thirds — `--bds-grid_span-66`
|
|
76
76
|
|
|
77
77
|
<GridDemo>
|
|
78
78
|
<GridCell span="span 8" label="span 66%" />
|
|
79
|
-
<GridCell span="span 4" label="33%" color="var(--bds-
|
|
79
|
+
<GridCell span="span 4" label="33%" color="var(--bds-color_grey)" />
|
|
80
80
|
</GridDemo>
|
|
81
81
|
|
|
82
82
|
### Half and half — `--bds-grid_span-50`
|
|
@@ -7,7 +7,7 @@ export const AnimDemo = ({ label, token, animationStyle }) => (
|
|
|
7
7
|
<div style={{
|
|
8
8
|
width: '3rem',
|
|
9
9
|
height: '3rem',
|
|
10
|
-
background: 'var(--bds-
|
|
10
|
+
background: 'var(--bds-color_interactive)',
|
|
11
11
|
borderRadius: '0.5rem',
|
|
12
12
|
animation: animationStyle,
|
|
13
13
|
opacity: 0.9,
|
|
@@ -47,7 +47,7 @@ export const AnimDemo = ({ label, token, animationStyle }) => (
|
|
|
47
47
|
|
|
48
48
|
The foundation ships six named keyframes.
|
|
49
49
|
|
|
50
|
-
<div style={{ display: 'flex', gap: '3rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-
|
|
50
|
+
<div style={{ display: 'flex', gap: '3rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-color_bg--subtle)', borderRadius: '0.75rem', marginBottom: '2rem' }}>
|
|
51
51
|
<AnimDemo label="bounce" animationStyle="bounce 1s ease-out infinite" />
|
|
52
52
|
<AnimDemo label="rotate" animationStyle="rotate 2s linear infinite" />
|
|
53
53
|
<AnimDemo label="fade-zoom" animationStyle="fade-zoom 2s ease-in-out infinite" />
|
|
@@ -3,10 +3,10 @@ import { Meta } from '@storybook/blocks';
|
|
|
3
3
|
<Meta title="Design System/Spacing" />
|
|
4
4
|
|
|
5
5
|
export const SpaceStep = ({ token, mobile, tablet, desktop }) => (
|
|
6
|
-
<div style={{ display: 'grid', gridTemplateColumns: '10rem auto 1fr', gap: '0 1.5rem', alignItems: 'center', padding: '0.625rem 0', borderBottom: '1px solid var(--bds-
|
|
6
|
+
<div style={{ display: 'grid', gridTemplateColumns: '10rem auto 1fr', gap: '0 1.5rem', alignItems: 'center', padding: '0.625rem 0', borderBottom: '1px solid var(--bds-color_grey--subtle)' }}>
|
|
7
7
|
<code style={{ fontSize: '0.75rem' }}>{token}</code>
|
|
8
|
-
<div style={{ width: `var(${token})`, height: '1.25rem', background: 'var(--bds-
|
|
9
|
-
<div style={{ fontSize: '0.75rem', color: 'var(--bds-
|
|
8
|
+
<div style={{ width: `var(${token})`, height: '1.25rem', background: 'var(--bds-color_interactive)', borderRadius: '0.125rem', minWidth: '2px' }} />
|
|
9
|
+
<div style={{ fontSize: '0.75rem', color: 'var(--bds-color_grey)', fontFamily: 'monospace' }}>
|
|
10
10
|
{mobile}{tablet !== mobile ? ` → ${tablet}` : ''}{desktop !== tablet ? ` → ${desktop}` : ''}
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
@@ -3,8 +3,8 @@ import { Meta } from '@storybook/blocks';
|
|
|
3
3
|
<Meta title="Design System/Typography" />
|
|
4
4
|
|
|
5
5
|
export const TypeScale = ({ token, label, style = {} }) => (
|
|
6
|
-
<div style={{ display: 'grid', gridTemplateColumns: '10rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-
|
|
7
|
-
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-on-bg--muted, var(--bds-
|
|
6
|
+
<div style={{ display: 'grid', gridTemplateColumns: '10rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-color_grey--subtle)' }}>
|
|
7
|
+
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-on-bg--muted, var(--bds-color_grey))', lineHeight: 1.4 }}>
|
|
8
8
|
{token}
|
|
9
9
|
</div>
|
|
10
10
|
<div style={{ fontFamily: 'var(--bds-font_family--body)', fontSize: `var(${token})`, ...style }}>
|
|
@@ -14,8 +14,8 @@ export const TypeScale = ({ token, label, style = {} }) => (
|
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
export const WeightSample = ({ weight, token, label }) => (
|
|
17
|
-
<div style={{ display: 'grid', gridTemplateColumns: '12rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-
|
|
18
|
-
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-
|
|
17
|
+
<div style={{ display: 'grid', gridTemplateColumns: '12rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-color_grey--subtle)' }}>
|
|
18
|
+
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-color_grey)' }}>
|
|
19
19
|
<div>{token}</div>
|
|
20
20
|
<div style={{ opacity: 0.7 }}>{weight}</div>
|
|
21
21
|
</div>
|
|
@@ -26,8 +26,8 @@ export const WeightSample = ({ weight, token, label }) => (
|
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
export const TrackingSample = ({ token, label, value }) => (
|
|
29
|
-
<div style={{ display: 'grid', gridTemplateColumns: '12rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-
|
|
30
|
-
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-
|
|
29
|
+
<div style={{ display: 'grid', gridTemplateColumns: '12rem 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline', padding: '0.75rem 0', borderBottom: '1px solid var(--bds-color_grey--subtle)' }}>
|
|
30
|
+
<div style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--bds-color_grey)' }}>
|
|
31
31
|
<div>{token}</div>
|
|
32
32
|
<div style={{ opacity: 0.7 }}>{value}</div>
|
|
33
33
|
</div>
|
package/src/types.ts
ADDED
package/src/typings.d.ts
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects the bdc border-decoration @property rules into the document's adopted stylesheets.
|
|
3
|
+
*
|
|
4
|
+
* @property declarations must live at the document level — they cannot be scoped to a Shadow DOM.
|
|
5
|
+
* This module is imported once by src/web-components/index.ts and runs as a side effect.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const bdcCss = `
|
|
9
|
+
@property --bdc_color {
|
|
10
|
+
syntax: '<color>';
|
|
11
|
+
inherits: false;
|
|
12
|
+
initial-value: currentcolor;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@property --bdc-outline_width {
|
|
16
|
+
syntax: '<length>';
|
|
17
|
+
inherits: false;
|
|
18
|
+
initial-value: 1px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@property --bdc-outline_offset {
|
|
22
|
+
syntax: '<length>';
|
|
23
|
+
inherits: false;
|
|
24
|
+
initial-value: -1px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@property --bdc-outline_radius {
|
|
28
|
+
syntax: '<length-percentage>';
|
|
29
|
+
inherits: false;
|
|
30
|
+
initial-value: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@property --bdc-outline_shadow {
|
|
34
|
+
syntax: '*';
|
|
35
|
+
inherits: false;
|
|
36
|
+
initial-value: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@layer component {
|
|
40
|
+
:root {
|
|
41
|
+
--bdc-outline_border: none;
|
|
42
|
+
|
|
43
|
+
--bdc_color: currentcolor;
|
|
44
|
+
--bdc_color--focus: var(--bds-color_interactive);
|
|
45
|
+
--bdc_color--error: var(--bds-color_error);
|
|
46
|
+
|
|
47
|
+
--bdc-outline_width: 1px;
|
|
48
|
+
--bdc-outline_offset: -1px;
|
|
49
|
+
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
50
|
+
--bdc-outline_shadow: none;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
// Guard against double-injection during hot-reload or test re-evaluation.
|
|
56
|
+
if (!('__bdcInjected' in document)) {
|
|
57
|
+
Object.defineProperty(document, '__bdcInjected', { value: true });
|
|
58
|
+
const sheet = new CSSStyleSheet();
|
|
59
|
+
sheet.replaceSync(bdcCss);
|
|
60
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Inject global bdc @property declarations into the document.
|
|
2
|
+
// Must run before any component that uses --bdc_color / --bdc-outline_* tokens.
|
|
3
|
+
import './globals';
|
|
4
|
+
|
|
5
|
+
// Phase 1 — proof of concept components
|
|
6
|
+
export { BdsBadge } from './ui/bds-badge';
|
|
7
|
+
export { BdsButton } from './interaction/bds-button';
|
|
8
|
+
export { BdsAlert } from './ui/bds-alert';
|
|
9
|
+
|
|
10
|
+
// Phase 2 — layout-driven components
|
|
11
|
+
export { BdsPopover } from './interaction/bds-popover';
|
|
12
|
+
export { BdsToastProvider } from './interaction/bds-toast-provider';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import type { ButtonVariant, ButtonSize } from './bds-button';
|
|
4
|
+
import '../index'; // auto-registers all custom elements
|
|
5
|
+
|
|
6
|
+
// Thin React wrapper so Storybook controls work with the custom element's props
|
|
7
|
+
function BdsButton({
|
|
8
|
+
variant = 'default',
|
|
9
|
+
size = 'medium',
|
|
10
|
+
disabled,
|
|
11
|
+
href,
|
|
12
|
+
hasPulse,
|
|
13
|
+
children,
|
|
14
|
+
}: {
|
|
15
|
+
variant?: ButtonVariant;
|
|
16
|
+
size?: ButtonSize;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
href?: string;
|
|
19
|
+
hasPulse?: boolean;
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}) {
|
|
22
|
+
return React.createElement(
|
|
23
|
+
'bds-button',
|
|
24
|
+
{ variant, size, disabled: disabled || undefined, href, 'has-pulse': hasPulse || undefined },
|
|
25
|
+
children,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const meta = {
|
|
30
|
+
title: 'Web Components/Interaction/Button',
|
|
31
|
+
component: BdsButton,
|
|
32
|
+
tags: ['!stable', 'experimental'],
|
|
33
|
+
parameters: { layout: 'centered' },
|
|
34
|
+
argTypes: {
|
|
35
|
+
variant: { control: 'select', options: ['default', 'ghost'] },
|
|
36
|
+
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
37
|
+
},
|
|
38
|
+
} satisfies Meta<typeof BdsButton>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof meta>;
|
|
42
|
+
|
|
43
|
+
export const Default: Story = { args: { children: 'Default', variant: 'default' } };
|
|
44
|
+
export const Ghost: Story = { args: { children: 'Ghost', variant: 'ghost' } };
|
|
45
|
+
export const Small: Story = { args: { children: 'Small', size: 'small' } };
|
|
46
|
+
export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
|
|
47
|
+
export const Large: Story = { args: { children: 'Large', size: 'large' } };
|
|
48
|
+
export const WithPulse: Story = { args: { children: 'Pulsing', hasPulse: true } };
|
|
49
|
+
export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
|
|
50
|
+
export const AllVariants: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'center' }}>
|
|
53
|
+
{(['default', 'ghost'] as const).flatMap(v =>
|
|
54
|
+
(['small', 'medium', 'large'] as const).map(s => (
|
|
55
|
+
<BdsButton key={`${v}-${s}`} variant={v} size={s}>{v} {s}</BdsButton>
|
|
56
|
+
)),
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import type { PopoverPlacement } from './bds-popover';
|
|
4
|
+
import '../index'; // auto-registers all custom elements
|
|
5
|
+
|
|
6
|
+
// Thin React wrapper so Storybook controls work with the custom element's props
|
|
7
|
+
function BdsPopover({
|
|
8
|
+
placement = 'bottom',
|
|
9
|
+
children,
|
|
10
|
+
trigger,
|
|
11
|
+
}: {
|
|
12
|
+
placement?: PopoverPlacement;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
trigger?: React.ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
return React.createElement(
|
|
17
|
+
'bds-popover',
|
|
18
|
+
{ placement },
|
|
19
|
+
trigger ?? React.createElement('button', { slot: 'trigger' }, 'Open popover'),
|
|
20
|
+
children ?? React.createElement('p', { style: { margin: 0 } }, 'Popover content'),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const meta = {
|
|
25
|
+
title: 'Web Components/Interaction/Popover',
|
|
26
|
+
component: BdsPopover,
|
|
27
|
+
tags: ['!stable', 'experimental'],
|
|
28
|
+
parameters: { layout: 'centered' },
|
|
29
|
+
argTypes: {
|
|
30
|
+
placement: { control: 'select', options: ['bottom', 'top', 'left', 'right'] },
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<typeof BdsPopover>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
export const Default: Story = { args: { placement: 'bottom' } };
|
|
38
|
+
export const Top: Story = { args: { placement: 'top' } };
|
|
39
|
+
export const Right: Story = { args: { placement: 'right' } };
|
|
40
|
+
export const Left: Story = { args: { placement: 'left' } };
|
|
41
|
+
|
|
42
|
+
export const WithRichContent: Story = {
|
|
43
|
+
args: { placement: 'bottom' },
|
|
44
|
+
render: (args) => (
|
|
45
|
+
<BdsPopover {...args}>
|
|
46
|
+
{React.createElement(
|
|
47
|
+
'div',
|
|
48
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' } },
|
|
49
|
+
React.createElement('strong', null, 'Actions'),
|
|
50
|
+
React.createElement('button', null, 'Edit'),
|
|
51
|
+
React.createElement('button', null, 'Duplicate'),
|
|
52
|
+
React.createElement('button', null, 'Delete'),
|
|
53
|
+
)}
|
|
54
|
+
</BdsPopover>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const AllPlacements: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, auto)', gap: '3rem', padding: '4rem' }}>
|
|
61
|
+
{(['bottom', 'top', 'right', 'left'] as const).map(p => (
|
|
62
|
+
<div key={p} style={{ display: 'flex', justifyContent: 'center' }}>
|
|
63
|
+
<BdsPopover placement={p}>
|
|
64
|
+
{React.createElement('p', { style: { margin: 0 } }, `Placed ${p}`)}
|
|
65
|
+
</BdsPopover>
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import type { ToastVariant } from './bds-toast-provider';
|
|
4
|
+
import { BdsToastProvider } from './bds-toast-provider';
|
|
5
|
+
import '../index'; // auto-registers all custom elements
|
|
6
|
+
|
|
7
|
+
// Render the bds-toast-provider as a wrapper around demo content.
|
|
8
|
+
// Trigger buttons call BdsToastProvider.show() — the static convenience API.
|
|
9
|
+
function ToastDemo() {
|
|
10
|
+
const variants: { label: string; variant: ToastVariant }[] = [
|
|
11
|
+
{ label: 'Info', variant: 'info' },
|
|
12
|
+
{ label: 'Success', variant: 'success' },
|
|
13
|
+
{ label: 'Warning', variant: 'warning' },
|
|
14
|
+
{ label: 'Error', variant: 'error' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return React.createElement(
|
|
18
|
+
'bds-toast-provider',
|
|
19
|
+
null,
|
|
20
|
+
React.createElement(
|
|
21
|
+
'div',
|
|
22
|
+
{ style: { display: 'flex', gap: '0.75rem', flexWrap: 'wrap' } },
|
|
23
|
+
...variants.map(({ label, variant }) =>
|
|
24
|
+
React.createElement(
|
|
25
|
+
'button',
|
|
26
|
+
{
|
|
27
|
+
key: variant,
|
|
28
|
+
onClick: () => BdsToastProvider.show(`This is a ${variant} toast`, variant),
|
|
29
|
+
},
|
|
30
|
+
`Show ${label}`,
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const meta = {
|
|
38
|
+
title: 'Web Components/Interaction/ToastProvider',
|
|
39
|
+
component: ToastDemo,
|
|
40
|
+
tags: ['!stable', 'experimental'],
|
|
41
|
+
parameters: { layout: 'centered' },
|
|
42
|
+
} satisfies Meta<typeof ToastDemo>;
|
|
43
|
+
|
|
44
|
+
export default meta;
|
|
45
|
+
type Story = StoryObj<typeof meta>;
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {};
|
|
48
|
+
|
|
49
|
+
export const EventAPI: Story = {
|
|
50
|
+
render: () =>
|
|
51
|
+
React.createElement(
|
|
52
|
+
'bds-toast-provider',
|
|
53
|
+
null,
|
|
54
|
+
React.createElement(
|
|
55
|
+
'div',
|
|
56
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' } },
|
|
57
|
+
React.createElement('p', { style: { margin: 0, fontSize: '0.875rem', color: '#666' } },
|
|
58
|
+
'Uses the document event API — no import required.'),
|
|
59
|
+
React.createElement(
|
|
60
|
+
'button',
|
|
61
|
+
{
|
|
62
|
+
onClick: () =>
|
|
63
|
+
document.dispatchEvent(
|
|
64
|
+
new CustomEvent('bds-show-toast', {
|
|
65
|
+
detail: { message: 'Fired via bds-show-toast event', variant: 'info' },
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
'Dispatch bds-show-toast',
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsButton } from './bds-button';
|
|
3
|
+
|
|
4
|
+
describe('bds-button', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-button')).toBe(BdsButton);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a <button> by default', async () => {
|
|
10
|
+
const el = await fixture('<bds-button>Click</bds-button>');
|
|
11
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
12
|
+
expect(btn).not.toBeNull();
|
|
13
|
+
cleanup(el);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('renders an <a> when href is set', async () => {
|
|
17
|
+
const el = await fixture('<bds-button href="/home">Home</bds-button>');
|
|
18
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
19
|
+
expect(a).not.toBeNull();
|
|
20
|
+
expect(a!.getAttribute('href')).toBe('/home');
|
|
21
|
+
cleanup(el);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('defaults to variant="default" and size="medium"', async () => {
|
|
25
|
+
const el = await fixture('<bds-button>Click</bds-button>');
|
|
26
|
+
expect(el.getAttribute('variant')).toBe('default');
|
|
27
|
+
expect(el.getAttribute('size')).toBe('medium');
|
|
28
|
+
cleanup(el);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('reflects variant attribute', async () => {
|
|
32
|
+
const el = await fixture('<bds-button variant="ghost">Click</bds-button>');
|
|
33
|
+
expect(el.getAttribute('variant')).toBe('ghost');
|
|
34
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
35
|
+
expect(btn!.className).toContain('ghost');
|
|
36
|
+
cleanup(el);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('reflects size attribute', async () => {
|
|
40
|
+
const el = await fixture('<bds-button size="small">Click</bds-button>');
|
|
41
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
42
|
+
expect(btn!.className).toContain('small');
|
|
43
|
+
cleanup(el);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('sets disabled on the inner button', async () => {
|
|
47
|
+
const el = await fixture('<bds-button disabled>Click</bds-button>');
|
|
48
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
49
|
+
expect(btn!.disabled).toBe(true);
|
|
50
|
+
cleanup(el);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('sets aria-disabled and removes href when disabled + href', async () => {
|
|
54
|
+
const el = await fixture('<bds-button href="/home" disabled>Home</bds-button>');
|
|
55
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
56
|
+
expect(a!.getAttribute('aria-disabled')).toBe('true');
|
|
57
|
+
expect(a!.hasAttribute('href')).toBe(false);
|
|
58
|
+
cleanup(el);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('applies has-pulse class when has-pulse is set', async () => {
|
|
62
|
+
const el = await fixture('<bds-button has-pulse>Click</bds-button>');
|
|
63
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
64
|
+
expect(btn!.className).toContain('has-pulse');
|
|
65
|
+
cleanup(el);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders default slot content in light DOM', async () => {
|
|
69
|
+
const el = await fixture('<bds-button>Save</bds-button>');
|
|
70
|
+
expect(el.textContent?.trim()).toBe('Save');
|
|
71
|
+
cleanup(el);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('renders icon-start slot when content is provided', async () => {
|
|
75
|
+
const el = await fixture(`
|
|
76
|
+
<bds-button>
|
|
77
|
+
<svg slot="icon-start" data-testid="icon"></svg>
|
|
78
|
+
Label
|
|
79
|
+
</bds-button>
|
|
80
|
+
`);
|
|
81
|
+
await (el as unknown as { updateComplete: Promise<void> }).updateComplete;
|
|
82
|
+
// Icon span should be present in shadow DOM
|
|
83
|
+
const iconStart = el.shadowRoot!.querySelector('.icon-start');
|
|
84
|
+
expect(iconStart).not.toBeNull();
|
|
85
|
+
cleanup(el);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('forwards target and rel to anchor', async () => {
|
|
89
|
+
const el = await fixture('<bds-button href="/ext" target="_blank" rel="noopener">Ext</bds-button>');
|
|
90
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
91
|
+
expect(a!.getAttribute('target')).toBe('_blank');
|
|
92
|
+
expect(a!.getAttribute('rel')).toBe('noopener');
|
|
93
|
+
cleanup(el);
|
|
94
|
+
});
|
|
95
|
+
});
|