@boostdev/design-system-components 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/AGENTS.md +51 -10
  2. package/dist/client.cjs +350 -174
  3. package/dist/client.css +751 -647
  4. package/dist/client.d.cts +62 -97
  5. package/dist/client.d.ts +62 -97
  6. package/dist/client.js +377 -211
  7. package/dist/index.cjs +350 -174
  8. package/dist/index.css +751 -647
  9. package/dist/index.d.cts +62 -97
  10. package/dist/index.d.ts +62 -97
  11. package/dist/index.js +377 -211
  12. package/dist/native/index.cjs +991 -0
  13. package/dist/native/index.d.cts +208 -0
  14. package/dist/native/index.d.ts +208 -0
  15. package/dist/native/index.js +968 -0
  16. package/dist/web-components.d.ts +304 -0
  17. package/dist/web-components.js +1978 -0
  18. package/package.json +27 -5
  19. package/src/components/interaction/Button/Button.module.css +11 -8
  20. package/src/components/interaction/Button/Button.native.mdx +72 -0
  21. package/src/components/interaction/Button/Button.native.spec.tsx +35 -0
  22. package/src/components/interaction/Button/Button.native.stories.tsx +42 -0
  23. package/src/components/interaction/Button/Button.native.tsx +95 -0
  24. package/src/components/interaction/Button/Button.stories.tsx +1 -1
  25. package/src/components/interaction/Button/Button.tsx +2 -2
  26. package/src/components/interaction/Command/Command.mdx +16 -0
  27. package/src/components/interaction/Command/Command.module.css +7 -7
  28. package/src/components/interaction/Command/Command.spec.tsx +37 -0
  29. package/src/components/interaction/Command/Command.stories.tsx +1 -1
  30. package/src/components/interaction/Command/Command.tsx +39 -4
  31. package/src/components/interaction/Dialog/Dialog.module.css +10 -13
  32. package/src/components/interaction/Dialog/Dialog.spec.tsx +53 -1
  33. package/src/components/interaction/Dialog/Dialog.stories.tsx +18 -1
  34. package/src/components/interaction/Dialog/Dialog.tsx +52 -18
  35. package/src/components/interaction/Drawer/Drawer.mdx +14 -0
  36. package/src/components/interaction/Drawer/Drawer.module.css +11 -11
  37. package/src/components/interaction/Drawer/Drawer.spec.tsx +48 -1
  38. package/src/components/interaction/Drawer/Drawer.stories.tsx +1 -1
  39. package/src/components/interaction/Drawer/Drawer.tsx +40 -6
  40. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
  41. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +1 -1
  42. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -2
  43. package/src/components/interaction/Popover/Popover.mdx +17 -0
  44. package/src/components/interaction/Popover/Popover.module.css +132 -16
  45. package/src/components/interaction/Popover/Popover.spec.tsx +119 -20
  46. package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
  47. package/src/components/interaction/Popover/Popover.tsx +69 -29
  48. package/src/components/interaction/Rating/Rating.stories.tsx +1 -1
  49. package/src/components/interaction/Rating/Rating.tsx +2 -2
  50. package/src/components/interaction/Toast/Toast.module.css +13 -8
  51. package/src/components/interaction/Toast/Toast.spec.tsx +21 -0
  52. package/src/components/interaction/Toast/Toast.stories.tsx +1 -1
  53. package/src/components/interaction/Toast/Toast.tsx +22 -2
  54. package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
  55. package/src/components/interaction/form/Checkbox/Checkbox.module.css +9 -9
  56. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
  57. package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +42 -0
  58. package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +36 -0
  59. package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +80 -0
  60. package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +1 -1
  61. package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
  62. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +44 -0
  63. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +1 -1
  64. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -2
  65. package/src/components/interaction/form/Combobox/Combobox.mdx +21 -0
  66. package/src/components/interaction/form/Combobox/Combobox.module.css +8 -9
  67. package/src/components/interaction/form/Combobox/Combobox.stories.tsx +1 -1
  68. package/src/components/interaction/form/Combobox/Combobox.tsx +2 -2
  69. package/src/components/interaction/form/FileInput/FileInput.mdx +14 -0
  70. package/src/components/interaction/form/FileInput/FileInput.module.css +5 -5
  71. package/src/components/interaction/form/FileInput/FileInput.stories.tsx +1 -1
  72. package/src/components/interaction/form/FileInput/FileInput.tsx +2 -2
  73. package/src/components/interaction/form/FormInput/FormInput.stories.tsx +1 -1
  74. package/src/components/interaction/form/FormInput/FormInput.tsx +2 -2
  75. package/src/components/interaction/form/NumberInput/NumberInput.mdx +16 -0
  76. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  77. package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +1 -1
  78. package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -2
  79. package/src/components/interaction/form/Radio/Radio.mdx +3 -3
  80. package/src/components/interaction/form/Radio/Radio.module.css +9 -9
  81. package/src/components/interaction/form/Radio/Radio.native.stories.tsx +46 -0
  82. package/src/components/interaction/form/Radio/Radio.native.tsx +79 -0
  83. package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
  84. package/src/components/interaction/form/Radio/Radio.tsx +2 -2
  85. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +45 -0
  86. package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +1 -1
  87. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -2
  88. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
  89. package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +1 -1
  90. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +2 -2
  91. package/src/components/interaction/form/Select/Select.module.css +3 -3
  92. package/src/components/interaction/form/Select/Select.stories.tsx +1 -1
  93. package/src/components/interaction/form/Select/Select.tsx +2 -2
  94. package/src/components/interaction/form/Slider/Slider.mdx +1 -1
  95. package/src/components/interaction/form/Slider/Slider.module.css +10 -10
  96. package/src/components/interaction/form/Slider/Slider.stories.tsx +1 -1
  97. package/src/components/interaction/form/Slider/Slider.tsx +2 -2
  98. package/src/components/interaction/form/Switch/Switch.mdx +4 -4
  99. package/src/components/interaction/form/Switch/Switch.module.css +11 -11
  100. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +60 -0
  101. package/src/components/interaction/form/Switch/Switch.native.stories.tsx +35 -0
  102. package/src/components/interaction/form/Switch/Switch.native.tsx +59 -0
  103. package/src/components/interaction/form/Switch/Switch.stories.tsx +1 -1
  104. package/src/components/interaction/form/Switch/Switch.tsx +2 -2
  105. package/src/components/interaction/form/Textarea/Textarea.mdx +17 -0
  106. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
  107. package/src/components/interaction/form/Textarea/Textarea.stories.tsx +1 -1
  108. package/src/components/interaction/form/Textarea/Textarea.tsx +2 -2
  109. package/src/components/interaction/form/atoms/InputContainer.tsx +2 -1
  110. package/src/components/interaction/form/atoms/Label.native.stories.tsx +18 -0
  111. package/src/components/interaction/form/atoms/Label.native.tsx +29 -0
  112. package/src/components/interaction/form/atoms/Label.tsx +5 -3
  113. package/src/components/interaction/form/atoms/Message.native.spec.tsx +30 -0
  114. package/src/components/interaction/form/atoms/Message.native.stories.tsx +22 -0
  115. package/src/components/interaction/form/atoms/Message.native.tsx +34 -0
  116. package/src/components/interaction/form/atoms/Message.tsx +5 -3
  117. package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +50 -0
  118. package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +34 -0
  119. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -1
  120. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +2 -2
  121. package/src/components/layout/Card/Card.native.stories.tsx +53 -0
  122. package/src/components/layout/Card/Card.native.tsx +89 -0
  123. package/src/components/layout/Card/Card.stories.tsx +1 -1
  124. package/src/components/layout/Card/Card.tsx +2 -2
  125. package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
  126. package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
  127. package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +39 -0
  128. package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +41 -0
  129. package/src/components/layout/IconWrapper/IconWrapper.native.tsx +20 -0
  130. package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +1 -1
  131. package/src/components/layout/IconWrapper/IconWrapper.tsx +2 -2
  132. package/src/components/layout/SectionHeader/SectionHeader.mdx +13 -0
  133. package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +38 -0
  134. package/src/components/layout/SectionHeader/SectionHeader.native.tsx +79 -0
  135. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +1 -1
  136. package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
  137. package/src/components/ui/Accordion/Accordion.mdx +14 -0
  138. package/src/components/ui/Accordion/Accordion.module.css +5 -5
  139. package/src/components/ui/Accordion/Accordion.stories.tsx +1 -1
  140. package/src/components/ui/Accordion/Accordion.tsx +2 -2
  141. package/src/components/ui/Alert/Alert.native.mdx +62 -0
  142. package/src/components/ui/Alert/Alert.native.stories.tsx +43 -0
  143. package/src/components/ui/Alert/Alert.native.tsx +94 -0
  144. package/src/components/ui/Alert/Alert.stories.tsx +1 -1
  145. package/src/components/ui/Alert/Alert.tsx +2 -2
  146. package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
  147. package/src/components/ui/Avatar/Avatar.native.spec.tsx +47 -0
  148. package/src/components/ui/Avatar/Avatar.native.stories.tsx +34 -0
  149. package/src/components/ui/Avatar/Avatar.native.tsx +85 -0
  150. package/src/components/ui/Avatar/Avatar.stories.tsx +1 -1
  151. package/src/components/ui/Avatar/Avatar.tsx +2 -2
  152. package/src/components/ui/Badge/Badge.mdx +2 -2
  153. package/src/components/ui/Badge/Badge.native.mdx +54 -0
  154. package/src/components/ui/Badge/Badge.native.stories.tsx +36 -0
  155. package/src/components/ui/Badge/Badge.native.tsx +50 -0
  156. package/src/components/ui/Badge/Badge.stories.tsx +1 -1
  157. package/src/components/ui/Badge/Badge.tsx +2 -2
  158. package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  159. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -2
  160. package/src/components/ui/Calendar/Calendar.mdx +16 -0
  161. package/src/components/ui/Calendar/Calendar.module.css +7 -7
  162. package/src/components/ui/Calendar/Calendar.stories.tsx +1 -1
  163. package/src/components/ui/Calendar/Calendar.tsx +2 -2
  164. package/src/components/ui/Carousel/Carousel.module.css +5 -5
  165. package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
  166. package/src/components/ui/Carousel/Carousel.tsx +2 -2
  167. package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
  168. package/src/components/ui/Collapsible/Collapsible.stories.tsx +1 -1
  169. package/src/components/ui/Collapsible/Collapsible.tsx +2 -2
  170. package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +1 -1
  171. package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -2
  172. package/src/components/ui/Link/Link.mdx +14 -0
  173. package/src/components/ui/Link/Link.module.css +2 -2
  174. package/src/components/ui/Link/Link.stories.tsx +1 -1
  175. package/src/components/ui/Link/Link.tsx +2 -2
  176. package/src/components/ui/Loading/Loading.module.css +7 -7
  177. package/src/components/ui/Loading/Loading.native.spec.tsx +24 -0
  178. package/src/components/ui/Loading/Loading.native.stories.tsx +33 -0
  179. package/src/components/ui/Loading/Loading.native.tsx +29 -0
  180. package/src/components/ui/Loading/Loading.stories.tsx +1 -1
  181. package/src/components/ui/Loading/Loading.tsx +2 -2
  182. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
  183. package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +39 -0
  184. package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +76 -0
  185. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +1 -1
  186. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -2
  187. package/src/components/ui/Pagination/Pagination.module.css +7 -7
  188. package/src/components/ui/Pagination/Pagination.stories.tsx +1 -1
  189. package/src/components/ui/Pagination/Pagination.tsx +2 -2
  190. package/src/components/ui/Progress/Progress.mdx +1 -1
  191. package/src/components/ui/Progress/Progress.module.css +6 -6
  192. package/src/components/ui/Progress/Progress.native.stories.tsx +34 -0
  193. package/src/components/ui/Progress/Progress.native.tsx +84 -0
  194. package/src/components/ui/Progress/Progress.stories.tsx +1 -1
  195. package/src/components/ui/Progress/Progress.tsx +2 -2
  196. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
  197. package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +1 -1
  198. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -2
  199. package/src/components/ui/Separator/Separator.mdx +14 -0
  200. package/src/components/ui/Separator/Separator.module.css +3 -3
  201. package/src/components/ui/Separator/Separator.native.stories.tsx +42 -0
  202. package/src/components/ui/Separator/Separator.native.tsx +32 -0
  203. package/src/components/ui/Separator/Separator.stories.tsx +1 -1
  204. package/src/components/ui/Separator/Separator.tsx +2 -2
  205. package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
  206. package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +33 -0
  207. package/src/components/ui/Skeleton/Skeleton.native.tsx +41 -0
  208. package/src/components/ui/Skeleton/Skeleton.stories.tsx +1 -1
  209. package/src/components/ui/Skeleton/Skeleton.tsx +2 -3
  210. package/src/components/ui/SkipLink/SkipLink.stories.tsx +1 -1
  211. package/src/components/ui/SkipLink/SkipLink.tsx +5 -3
  212. package/src/components/ui/Table/Table.mdx +14 -0
  213. package/src/components/ui/Table/Table.module.css +9 -9
  214. package/src/components/ui/Table/Table.stories.tsx +1 -1
  215. package/src/components/ui/Table/Table.tsx +2 -2
  216. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  217. package/src/components/ui/Tabs/Tabs.stories.tsx +1 -1
  218. package/src/components/ui/Tabs/Tabs.tsx +2 -2
  219. package/src/components/ui/Tooltip/Tooltip.mdx +14 -0
  220. package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
  221. package/src/components/ui/Tooltip/Tooltip.stories.tsx +1 -1
  222. package/src/components/ui/Tooltip/Tooltip.tsx +2 -2
  223. package/src/components/ui/Typography/Typography.mdx +13 -0
  224. package/src/components/ui/Typography/Typography.native.mdx +56 -0
  225. package/src/components/ui/Typography/Typography.native.stories.tsx +38 -0
  226. package/src/components/ui/Typography/Typography.native.tsx +65 -0
  227. package/src/components/ui/Typography/Typography.stories.tsx +1 -1
  228. package/src/components/ui/Typography/Typography.tsx +2 -2
  229. package/src/css/bdc.css +8 -0
  230. package/src/index.ts +1 -0
  231. package/src/native/ThemeContext.tsx +28 -0
  232. package/src/native/tokens.ts +13 -0
  233. package/src/native.ts +39 -0
  234. package/src/polyfill-invoker-commands.ts +68 -0
  235. package/src/react-augment.d.ts +32 -0
  236. package/src/stories/DesignSystem/DarkMode.mdx +130 -0
  237. package/src/stories/ReactNative.mdx +121 -0
  238. package/src/types.ts +2 -0
  239. package/src/typings.d.ts +3 -0
  240. package/src/web-components/globals.ts +61 -0
  241. package/src/web-components/index.ts +12 -0
  242. package/src/web-components/interaction/BdsButton.mdx +106 -0
  243. package/src/web-components/interaction/BdsButton.stories.tsx +60 -0
  244. package/src/web-components/interaction/BdsPopover.mdx +120 -0
  245. package/src/web-components/interaction/BdsPopover.stories.tsx +70 -0
  246. package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
  247. package/src/web-components/interaction/BdsToastProvider.stories.tsx +73 -0
  248. package/src/web-components/interaction/bds-button.spec.ts +95 -0
  249. package/src/web-components/interaction/bds-button.ts +293 -0
  250. package/src/web-components/interaction/bds-popover.spec.ts +174 -0
  251. package/src/web-components/interaction/bds-popover.ts +229 -0
  252. package/src/web-components/interaction/bds-toast-provider.spec.ts +122 -0
  253. package/src/web-components/interaction/bds-toast-provider.ts +211 -0
  254. package/src/web-components/test/helpers.ts +22 -0
  255. package/src/web-components/ui/BdsAlert.mdx +90 -0
  256. package/src/web-components/ui/BdsAlert.stories.tsx +60 -0
  257. package/src/web-components/ui/BdsBadge.mdx +74 -0
  258. package/src/web-components/ui/BdsBadge.stories.tsx +37 -0
  259. package/src/web-components/ui/bds-alert.spec.ts +109 -0
  260. package/src/web-components/ui/bds-alert.ts +209 -0
  261. package/src/web-components/ui/bds-badge.spec.ts +51 -0
  262. package/src/web-components/ui/bds-badge.ts +88 -0
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import type { AlertVariant } from './bds-alert';
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 BdsAlert({
8
+ variant = 'info',
9
+ title,
10
+ dismissible,
11
+ children,
12
+ onDismiss,
13
+ }: {
14
+ variant?: AlertVariant;
15
+ title?: string;
16
+ dismissible?: boolean;
17
+ children?: React.ReactNode;
18
+ onDismiss?: () => void;
19
+ }) {
20
+ return React.createElement(
21
+ 'bds-alert',
22
+ { variant, title, dismissible: dismissible || undefined, onbdsdismiss: onDismiss },
23
+ children,
24
+ );
25
+ }
26
+
27
+ const meta = {
28
+ title: 'Web Components/UI/Alert',
29
+ component: BdsAlert,
30
+ tags: ['!stable', 'experimental'],
31
+ parameters: { layout: 'padded' },
32
+ argTypes: {
33
+ variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
34
+ },
35
+ } satisfies Meta<typeof BdsAlert>;
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Info: Story = { args: { children: 'This is an informational message.', variant: 'info' } };
41
+ export const Success: Story = { args: { children: 'Your changes have been saved.', variant: 'success' } };
42
+ export const Warning: Story = { args: { children: 'Please review before continuing.', variant: 'warning' } };
43
+ export const Error: Story = { args: { children: 'Something went wrong.', variant: 'error' } };
44
+ export const WithTitle: Story = {
45
+ args: { children: 'More details about the alert.', variant: 'info', title: 'Heads up' },
46
+ };
47
+ export const Dismissible: Story = {
48
+ args: { children: 'You can dismiss this alert.', variant: 'success', dismissible: true },
49
+ };
50
+ export const AllVariants: Story = {
51
+ render: () => (
52
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: 500 }}>
53
+ {(['info', 'success', 'warning', 'error'] as const).map(v => (
54
+ <BdsAlert key={v} variant={v} title={v.charAt(0).toUpperCase() + v.slice(1)}>
55
+ This is a {v} alert.
56
+ </BdsAlert>
57
+ ))}
58
+ </div>
59
+ ),
60
+ };
@@ -0,0 +1,74 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsBadge.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-badge&gt;
7
+
8
+ Framework-agnostic badge custom element for short status labels and counts. Five semantic variants covering the common status palette.
9
+
10
+ > **Status: experimental** — API may change before stable release.
11
+
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ ## Examples
19
+
20
+ ### All variants
21
+ <Canvas of={Stories.AllVariants} />
22
+
23
+ ### Primary
24
+ <Canvas of={Stories.Primary} />
25
+
26
+ ### Secondary
27
+ <Canvas of={Stories.Secondary} />
28
+
29
+ ### Success
30
+ <Canvas of={Stories.Success} />
31
+
32
+ ### Error
33
+ <Canvas of={Stories.Error} />
34
+
35
+ ### Warning
36
+ <Canvas of={Stories.Warning} />
37
+
38
+ ## Props
39
+
40
+ <ArgTypes of={Stories} />
41
+
42
+ ## Attributes
43
+
44
+ | Attribute | Type | Default | Description |
45
+ |-----------|------|---------|-------------|
46
+ | `variant` | `"primary" \| "secondary" \| "success" \| "error" \| "warning"` | `"primary"` | Semantic colour |
47
+
48
+ ## Slots
49
+
50
+ | Slot | Description |
51
+ |------|-------------|
52
+ | `(default)` | Badge label text |
53
+
54
+ ## CSS Custom Properties
55
+
56
+ | Property | Description |
57
+ |----------|-------------|
58
+ | `--badge_bg` | Badge background colour |
59
+ | `--badge_text` | Badge text colour |
60
+ | `--badge_font-size` | Font size |
61
+ | `--badge_radius` | Border radius |
62
+
63
+ ## Usage in plain HTML
64
+
65
+ ```html
66
+ <bds-badge variant="success">Active</bds-badge>
67
+ <bds-badge variant="error">3</bds-badge>
68
+ <bds-badge variant="warning">Pending</bds-badge>
69
+ ```
70
+
71
+ ## Accessibility
72
+
73
+ - Badge is a purely visual element — wrap in an element with `aria-label` when the colour alone conveys meaning (e.g. status indicators)
74
+ - For count badges next to icon buttons, use `aria-label` on the parent: `aria-label="3 notifications"`
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import type { BadgeVariant } from './bds-badge';
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 BdsBadge({ variant = 'primary', children }: { variant?: BadgeVariant; children?: React.ReactNode }) {
8
+ return React.createElement('bds-badge', { variant }, children);
9
+ }
10
+
11
+ const meta = {
12
+ title: 'Web Components/UI/Badge',
13
+ component: BdsBadge,
14
+ tags: ['!stable', 'experimental'],
15
+ parameters: { layout: 'centered' },
16
+ argTypes: {
17
+ variant: { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'] },
18
+ },
19
+ } satisfies Meta<typeof BdsBadge>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Primary: Story = { args: { children: 'Primary', variant: 'primary' } };
25
+ export const Secondary: Story = { args: { children: 'Secondary', variant: 'secondary' } };
26
+ export const Success: Story = { args: { children: 'Success', variant: 'success' } };
27
+ export const Error: Story = { args: { children: 'Error', variant: 'error' } };
28
+ export const Warning: Story = { args: { children: 'Warning', variant: 'warning' } };
29
+ export const AllVariants: Story = {
30
+ render: () => (
31
+ <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
32
+ {(['primary', 'secondary', 'success', 'error', 'warning'] as const).map(v => (
33
+ <BdsBadge key={v} variant={v}>{v}</BdsBadge>
34
+ ))}
35
+ </div>
36
+ ),
37
+ };
@@ -0,0 +1,109 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsAlert } from './bds-alert';
3
+
4
+ describe('bds-alert', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-alert')).toBe(BdsAlert);
7
+ });
8
+
9
+ it('renders the alert container in shadow DOM', async () => {
10
+ const el = await fixture('<bds-alert>Message</bds-alert>');
11
+ const div = el.shadowRoot!.querySelector('.alert');
12
+ expect(div).not.toBeNull();
13
+ cleanup(el);
14
+ });
15
+
16
+ it('defaults to variant="info"', async () => {
17
+ const el = await fixture('<bds-alert>Message</bds-alert>');
18
+ expect(el.getAttribute('variant')).toBe('info');
19
+ cleanup(el);
20
+ });
21
+
22
+ it('sets role="status" and aria-live="polite" for info variant', async () => {
23
+ const el = await fixture('<bds-alert variant="info">Info</bds-alert>');
24
+ const div = el.shadowRoot!.querySelector('.alert');
25
+ expect(div!.getAttribute('role')).toBe('status');
26
+ expect(div!.getAttribute('aria-live')).toBe('polite');
27
+ cleanup(el);
28
+ });
29
+
30
+ it('sets role="status" and aria-live="polite" for success variant', async () => {
31
+ const el = await fixture('<bds-alert variant="success">Done</bds-alert>');
32
+ const div = el.shadowRoot!.querySelector('.alert');
33
+ expect(div!.getAttribute('role')).toBe('status');
34
+ expect(div!.getAttribute('aria-live')).toBe('polite');
35
+ cleanup(el);
36
+ });
37
+
38
+ it('sets role="alert" and aria-live="assertive" for error variant', async () => {
39
+ const el = await fixture('<bds-alert variant="error">Error</bds-alert>');
40
+ const div = el.shadowRoot!.querySelector('.alert');
41
+ expect(div!.getAttribute('role')).toBe('alert');
42
+ expect(div!.getAttribute('aria-live')).toBe('assertive');
43
+ cleanup(el);
44
+ });
45
+
46
+ it('sets role="alert" and aria-live="assertive" for warning variant', async () => {
47
+ const el = await fixture('<bds-alert variant="warning">Warning</bds-alert>');
48
+ const div = el.shadowRoot!.querySelector('.alert');
49
+ expect(div!.getAttribute('role')).toBe('alert');
50
+ expect(div!.getAttribute('aria-live')).toBe('assertive');
51
+ cleanup(el);
52
+ });
53
+
54
+ it('renders title when title attribute is set', async () => {
55
+ const el = await fixture('<bds-alert title="Heads up">Message</bds-alert>');
56
+ const strong = el.shadowRoot!.querySelector('strong.title');
57
+ expect(strong).not.toBeNull();
58
+ expect(strong!.textContent).toBe('Heads up');
59
+ cleanup(el);
60
+ });
61
+
62
+ it('does not render title element when title is not set', async () => {
63
+ const el = await fixture('<bds-alert>Message</bds-alert>');
64
+ const strong = el.shadowRoot!.querySelector('strong.title');
65
+ expect(strong).toBeNull();
66
+ cleanup(el);
67
+ });
68
+
69
+ it('does not render dismiss button by default', async () => {
70
+ const el = await fixture('<bds-alert>Message</bds-alert>');
71
+ const btn = el.shadowRoot!.querySelector('button.dismiss');
72
+ expect(btn).toBeNull();
73
+ cleanup(el);
74
+ });
75
+
76
+ it('renders dismiss button when dismissible is set', async () => {
77
+ const el = await fixture('<bds-alert dismissible>Message</bds-alert>');
78
+ const btn = el.shadowRoot!.querySelector('button.dismiss');
79
+ expect(btn).not.toBeNull();
80
+ expect(btn!.getAttribute('aria-label')).toBe('Dismiss alert');
81
+ cleanup(el);
82
+ });
83
+
84
+ it('fires bds-dismiss event when dismiss button is clicked', async () => {
85
+ const el = await fixture('<bds-alert dismissible>Message</bds-alert>');
86
+ const btn = el.shadowRoot!.querySelector('button.dismiss')!;
87
+ const handler = vi.fn();
88
+ el.addEventListener('bds-dismiss', handler);
89
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
90
+ expect(handler).toHaveBeenCalledOnce();
91
+ cleanup(el);
92
+ });
93
+
94
+ it('renders slotted body content in light DOM', async () => {
95
+ const el = await fixture('<bds-alert>Something went wrong</bds-alert>');
96
+ expect(el.textContent?.trim()).toBe('Something went wrong');
97
+ cleanup(el);
98
+ });
99
+
100
+ it('reflects variant property updates', async () => {
101
+ const el = await fixture('<bds-alert>Message</bds-alert>') as BdsAlert;
102
+ el.variant = 'warning';
103
+ await el.updateComplete;
104
+ expect(el.getAttribute('variant')).toBe('warning');
105
+ const div = el.shadowRoot!.querySelector('.alert');
106
+ expect(div!.getAttribute('role')).toBe('alert');
107
+ cleanup(el);
108
+ });
109
+ });
@@ -0,0 +1,209 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
4
+
5
+ /**
6
+ * `<bds-alert>` — framework-agnostic Alert custom element.
7
+ *
8
+ * Attributes:
9
+ * variant — "info" (default) | "success" | "warning" | "error"
10
+ * title — optional heading above the body text
11
+ * dismissible — boolean; shows a dismiss button when present
12
+ *
13
+ * Slots:
14
+ * (default) — alert body content
15
+ * icon — optional icon displayed to the left of the content
16
+ *
17
+ * Events:
18
+ * bds-dismiss — fired (bubbles, composed) when the dismiss button is clicked
19
+ *
20
+ * Accessibility:
21
+ * role="alert" + aria-live="assertive" for error/warning variants,
22
+ * role="status" + aria-live="polite" for info/success variants.
23
+ *
24
+ * @example
25
+ * <bds-alert variant="success" title="Saved!">Your changes have been saved.</bds-alert>
26
+ *
27
+ * <bds-alert variant="error" dismissible>
28
+ * <svg slot="icon">…</svg>
29
+ * Something went wrong.
30
+ * </bds-alert>
31
+ */
32
+ export class BdsAlert extends LitElement {
33
+ static styles = css`
34
+ :host {
35
+ display: block;
36
+ }
37
+
38
+ .alert {
39
+ display: flex;
40
+ align-items: flex-start;
41
+ gap: var(--bds-space_s);
42
+ padding: var(--bds-space_m);
43
+ border-radius: var(--bds-border_radius--s);
44
+ border-inline-start: var(--bds-space_s) solid var(--alert_border, var(--bds-color_blue));
45
+ background-color: var(--alert_bg, var(--bds-color_blue--subtle));
46
+ color: var(--alert_text, var(--bds-color_on-blue--subtle));
47
+ font-size: var(--bds-font_size--body);
48
+ line-height: var(--bds-font_line-height--body);
49
+ }
50
+
51
+ /* Variant colours */
52
+ :host([variant='info']) .alert {
53
+ --alert_bg: var(--bds-color_blue--subtle);
54
+ --alert_text: var(--bds-color_on-blue--subtle);
55
+ --alert_border: var(--bds-color_blue);
56
+ }
57
+
58
+ :host([variant='success']) .alert {
59
+ --alert_bg: var(--bds-color_success--subtle);
60
+ --alert_text: var(--bds-color_on-success--subtle);
61
+ --alert_border: var(--bds-color_success);
62
+ }
63
+
64
+ :host([variant='warning']) .alert {
65
+ --alert_bg: var(--bds-color_orange--subtle);
66
+ --alert_text: var(--bds-color_on-orange--subtle);
67
+ --alert_border: var(--bds-color_warning);
68
+ }
69
+
70
+ :host([variant='error']) .alert {
71
+ --alert_bg: rgb(from var(--bds-color_error) r g b / 12%);
72
+ --alert_text: var(--bds-color_on-bg);
73
+ --alert_border: var(--bds-color_error);
74
+ }
75
+
76
+ /* Icon slot wrapper — hidden unless slot has content */
77
+ .icon {
78
+ display: none;
79
+ align-items: center;
80
+ flex-shrink: 0;
81
+ margin-block-start: 0.1em;
82
+ }
83
+
84
+ .icon.has-content {
85
+ display: flex;
86
+ }
87
+
88
+ /* Content area */
89
+ .content {
90
+ flex: 1;
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: var(--bds-space_xxs);
94
+ }
95
+
96
+ .title {
97
+ font-weight: var(--bds-font_weight--semibold);
98
+ font-size: var(--bds-font_size--body);
99
+ }
100
+
101
+ /* Dismiss button */
102
+ .dismiss {
103
+ all: unset;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ flex-shrink: 0;
108
+ cursor: pointer;
109
+ color: currentcolor;
110
+ transition: var(--bds-animation_transition);
111
+ border-radius: 50%;
112
+ --bdc_color: currentcolor;
113
+ outline: var(--bdc-outline_width) solid var(--bdc_color);
114
+ outline-offset: var(--bdc-outline_offset);
115
+ }
116
+
117
+ .dismiss svg {
118
+ width: 1rem;
119
+ height: 1rem;
120
+ }
121
+
122
+ .dismiss:focus-visible {
123
+ outline: var(--bds-outline_default);
124
+ outline-offset: var(--bds-outline_offset);
125
+ }
126
+
127
+ @media (hover: hover) and (pointer: fine) {
128
+ .dismiss:hover {
129
+ opacity: 0.7;
130
+ }
131
+ }
132
+ `;
133
+
134
+ static properties = {
135
+ variant: { type: String, reflect: true },
136
+ title: { type: String },
137
+ dismissible: { type: Boolean, reflect: true },
138
+ _iconFilled: { state: true },
139
+ };
140
+
141
+ // `declare` annotates the type without emitting a class field initializer
142
+ // that would shadow Lit's reactive accessors on the prototype.
143
+ declare variant: AlertVariant;
144
+ declare title: string;
145
+ declare dismissible: boolean;
146
+ declare private _iconFilled: boolean;
147
+
148
+ constructor() {
149
+ super();
150
+ this.variant = 'info';
151
+ this.title = '';
152
+ this.dismissible = false;
153
+ this._iconFilled = false;
154
+ }
155
+
156
+ private _onIconSlotChange(e: Event) {
157
+ const slot = e.target as HTMLSlotElement;
158
+ this._iconFilled = slot.assignedNodes({ flatten: true }).length > 0;
159
+ }
160
+
161
+ private get _isUrgent() {
162
+ return this.variant === 'error' || this.variant === 'warning';
163
+ }
164
+
165
+ private _handleDismiss() {
166
+ this.dispatchEvent(new CustomEvent('bds-dismiss', { bubbles: true, composed: true }));
167
+ }
168
+
169
+ render() {
170
+ return html`
171
+ <div
172
+ class="alert"
173
+ role=${this._isUrgent ? 'alert' : 'status'}
174
+ aria-live=${this._isUrgent ? 'assertive' : 'polite'}
175
+ aria-atomic="true"
176
+ >
177
+ <span class="icon ${this._iconFilled ? 'has-content' : ''}" aria-hidden="true">
178
+ <slot name="icon" @slotchange=${this._onIconSlotChange}></slot>
179
+ </span>
180
+ <div class="content">
181
+ ${this.title ? html`<strong class="title">${this.title}</strong>` : nothing}
182
+ <div><slot></slot></div>
183
+ </div>
184
+ ${this.dismissible
185
+ ? html`
186
+ <button
187
+ type="button"
188
+ class="dismiss"
189
+ @click=${this._handleDismiss}
190
+ aria-label="Dismiss alert"
191
+ >
192
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
193
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
194
+ </svg>
195
+ </button>
196
+ `
197
+ : nothing}
198
+ </div>
199
+ `;
200
+ }
201
+ }
202
+
203
+ customElements.define('bds-alert', BdsAlert);
204
+
205
+ declare global {
206
+ interface HTMLElementTagNameMap {
207
+ 'bds-alert': BdsAlert;
208
+ }
209
+ }
@@ -0,0 +1,51 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsBadge } from './bds-badge';
3
+
4
+ describe('bds-badge', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-badge')).toBe(BdsBadge);
7
+ });
8
+
9
+ it('renders a span.badge in shadow DOM', async () => {
10
+ const el = await fixture('<bds-badge>New</bds-badge>');
11
+ const span = el.shadowRoot!.querySelector('.badge');
12
+ expect(span).not.toBeNull();
13
+ cleanup(el);
14
+ });
15
+
16
+ it('defaults to variant="primary"', async () => {
17
+ const el = await fixture('<bds-badge>New</bds-badge>');
18
+ expect(el.getAttribute('variant')).toBe('primary');
19
+ cleanup(el);
20
+ });
21
+
22
+ it('reflects variant attribute', async () => {
23
+ const el = await fixture('<bds-badge variant="success">OK</bds-badge>');
24
+ expect(el.getAttribute('variant')).toBe('success');
25
+ cleanup(el);
26
+ });
27
+
28
+ it('updates variant property', async () => {
29
+ const el = await fixture('<bds-badge>Label</bds-badge>') as BdsBadge;
30
+ el.variant = 'error';
31
+ await el.updateComplete;
32
+ expect(el.getAttribute('variant')).toBe('error');
33
+ cleanup(el);
34
+ });
35
+
36
+ it('renders slotted text content', async () => {
37
+ const el = await fixture('<bds-badge>Hello</bds-badge>');
38
+ // Slotted content lives in light DOM
39
+ expect(el.textContent?.trim()).toBe('Hello');
40
+ cleanup(el);
41
+ });
42
+
43
+ it('accepts all valid variant values', async () => {
44
+ const variants = ['primary', 'secondary', 'success', 'error', 'warning'] as const;
45
+ for (const variant of variants) {
46
+ const el = await fixture(`<bds-badge variant="${variant}">Label</bds-badge>`);
47
+ expect(el.getAttribute('variant')).toBe(variant);
48
+ cleanup(el);
49
+ }
50
+ });
51
+ });
@@ -0,0 +1,88 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
4
+
5
+ /**
6
+ * `<bds-badge>` — framework-agnostic Badge custom element.
7
+ *
8
+ * Attributes:
9
+ * variant — "primary" (default) | "secondary" | "success" | "error" | "warning"
10
+ *
11
+ * Slots:
12
+ * (default) — badge label text / content
13
+ *
14
+ * @example
15
+ * <bds-badge variant="success">New</bds-badge>
16
+ */
17
+ export class BdsBadge extends LitElement {
18
+ static styles = css`
19
+ :host {
20
+ display: inline-flex;
21
+ }
22
+
23
+ .badge {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ padding: var(--bds-space_xxs) var(--bds-space_xs);
28
+ border-radius: var(--badge_radius, 2em);
29
+ font-size: var(--badge_font-size, var(--bds-font_size--body--s));
30
+ font-weight: var(--bds-font_weight--semibold);
31
+ line-height: 1;
32
+ white-space: nowrap;
33
+ background-color: var(--badge_bg, var(--bds-color_blue));
34
+ color: var(--badge_text, var(--bds-color_on-blue));
35
+ }
36
+
37
+ /* Variant overrides */
38
+ :host([variant='primary']) .badge {
39
+ --badge_bg: var(--bds-color_interactive);
40
+ --badge_text: var(--bds-color_on-interactive);
41
+ }
42
+
43
+ :host([variant='secondary']) .badge {
44
+ --badge_bg: var(--bds-color_bg);
45
+ --badge_text: var(--bds-color_on-bg);
46
+ }
47
+
48
+ :host([variant='success']) .badge {
49
+ --badge_bg: var(--bds-color_success);
50
+ --badge_text: var(--bds-color_on-success);
51
+ }
52
+
53
+ :host([variant='error']) .badge {
54
+ --badge_bg: var(--bds-color_error);
55
+ --badge_text: var(--bds-color_on-error);
56
+ }
57
+
58
+ :host([variant='warning']) .badge {
59
+ --badge_bg: var(--bds-color_warning);
60
+ --badge_text: var(--bds-color_on-warning);
61
+ }
62
+ `;
63
+
64
+ static properties = {
65
+ variant: { type: String, reflect: true },
66
+ };
67
+
68
+ // `declare` gives TypeScript the type without emitting a class field initializer
69
+ // that would shadow Lit's reactive accessor on the prototype.
70
+ declare variant: BadgeVariant;
71
+
72
+ constructor() {
73
+ super();
74
+ this.variant = 'primary';
75
+ }
76
+
77
+ render() {
78
+ return html`<span class="badge"><slot></slot></span>`;
79
+ }
80
+ }
81
+
82
+ customElements.define('bds-badge', BdsBadge);
83
+
84
+ declare global {
85
+ interface HTMLElementTagNameMap {
86
+ 'bds-badge': BdsBadge;
87
+ }
88
+ }