@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
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, createContext, useContext, ReactNode, useCallback, useMemo } from 'react';
1
+ import { useState, useEffect, createContext, useContext, ReactNode, useCallback, useMemo, useRef } from 'react';
2
2
  import css from './Toast.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
 
@@ -18,6 +18,7 @@ const ToastContext = createContext<ToastContextType | undefined>(undefined);
18
18
 
19
19
  export function ToastProvider({ children }: { children: ReactNode }) {
20
20
  const [toasts, setToasts] = useState<Toast[]>([]);
21
+ const containerRef = useRef<HTMLDivElement>(null);
21
22
 
22
23
  const showToast = useCallback((message: string, variant: ToastVariant) => {
23
24
  const id = Math.random().toString(36).substring(2, 9);
@@ -28,12 +29,31 @@ export function ToastProvider({ children }: { children: ReactNode }) {
28
29
  setToasts(prev => prev.filter(toast => toast.id !== id));
29
30
  }, []);
30
31
 
32
+ // Sync the popover's top-layer visibility with the toast queue.
33
+ // showPopover() is a no-op when already open (safe to call multiple times).
34
+ // hidePopover() throws InvalidStateError when already hidden, so catch silently.
35
+ useEffect(() => {
36
+ const container = containerRef.current;
37
+ if (!container) return;
38
+ if (toasts.length > 0) {
39
+ container.showPopover?.();
40
+ } else {
41
+ try { container.hidePopover?.(); } catch { /* already hidden on initial mount */ }
42
+ }
43
+ }, [toasts.length]);
44
+
31
45
  const value = useMemo(() => ({ showToast }), [showToast]);
32
46
 
33
47
  return (
34
48
  <ToastContext.Provider value={value}>
35
49
  {children}
36
- <div className={css.toastContainer}>
50
+ <div
51
+ ref={containerRef}
52
+ popover="manual"
53
+ role="region"
54
+ aria-label="Notifications"
55
+ className={css.toastContainer}
56
+ >
37
57
  {toasts.map(toast => (
38
58
  <ToastItem
39
59
  key={toast.id}
@@ -44,8 +44,8 @@ Binary toggle for opt-in/opt-out choices. Renders a styled native `<input type="
44
44
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
45
45
  </thead>
46
46
  <tbody>
47
- <tr><td>`--checkbox_color-active`</td><td>`var(--bds-active)`</td><td>Checked background</td></tr>
48
- <tr><td>`--checkbox_color-on-active`</td><td>`var(--bds-on-active)`</td><td>Checkmark colour</td></tr>
47
+ <tr><td>`--checkbox_color-active`</td><td>`var(--bdc-color_active)`</td><td>Checked background</td></tr>
48
+ <tr><td>`--checkbox_color-on-active`</td><td>`var(--bdc-color_on-active)`</td><td>Checkmark colour</td></tr>
49
49
  </tbody>
50
50
  </table>
51
51
 
@@ -14,8 +14,8 @@
14
14
 
15
15
  .checkbox {
16
16
  margin-block-start: 0.25em;
17
- width: var(--inputSize);
18
- height: var(--inputSize);
17
+ inline-size: var(--inputSize);
18
+ block-size: var(--inputSize);
19
19
  color: var(--checkbox_color, var(--bds-color_on-bg));
20
20
  border: var(--checkbox_border, none);
21
21
  --bdc_color: currentcolor;
@@ -33,18 +33,18 @@
33
33
  }
34
34
 
35
35
  .checkbox:checked {
36
- background-color: var(--checkbox_color-active, var(--bds-color_green));
37
- --bdc_color: var(--checkbox_color-active, var(--bds-color_green));
36
+ background-color: var(--checkbox_color-active, var(--bdc-color_active));
37
+ --bdc_color: var(--checkbox_color-active, var(--bdc-color_active));
38
38
  }
39
39
 
40
40
  .checkbox:checked::after {
41
41
  content: '';
42
42
  position: absolute;
43
- left: 0.6em;
44
- top: 0.2em;
45
- width: 0.5em;
46
- height: 1em;
47
- border: solid var(--checkbox_color-on-active, var(--bds-color_on-green));
43
+ inset-inline-start: 0.6em;
44
+ inset-block-start: 0.2em;
45
+ inline-size: 0.5em;
46
+ block-size: 1em;
47
+ border: solid var(--checkbox_color-on-active, var(--bdc-color_on-active));
48
48
  border-width: 0 2px 2px 0;
49
49
  transform: rotate(45deg);
50
50
  }
@@ -0,0 +1,74 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Checkbox.native.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Checkbox (React Native)
7
+
8
+ React Native checkbox component with label, optional hint, and error state. Fully controlled — manage checked state via `checked` + `onChange`.
9
+
10
+ > **Status: alpha** — API may change before stable release.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Checkbox } from '@boostdev/components';
16
+ import { useState } from 'react';
17
+
18
+ function Example() {
19
+ const [checked, setChecked] = useState(false);
20
+ return (
21
+ <Checkbox
22
+ name="terms"
23
+ label="Accept terms and conditions"
24
+ checked={checked}
25
+ onChange={setChecked}
26
+ />
27
+ );
28
+ }
29
+ ```
30
+
31
+ ## Examples
32
+
33
+ ### Interactive
34
+ <Canvas of={Stories.Interactive} />
35
+
36
+ ### Unchecked
37
+ <Canvas of={Stories.Unchecked} />
38
+
39
+ ### Checked
40
+ <Canvas of={Stories.Checked} />
41
+
42
+ ### With hint
43
+ <Canvas of={Stories.WithHint} />
44
+
45
+ ### With error
46
+ <Canvas of={Stories.WithError} />
47
+
48
+ ### Disabled
49
+ <Canvas of={Stories.Disabled} />
50
+
51
+ ### Disabled + checked
52
+ <Canvas of={Stories.DisabledChecked} />
53
+
54
+ ## Props
55
+
56
+ <ArgTypes of={Stories} />
57
+
58
+ ## Props reference
59
+
60
+ | Prop | Type | Default | Description |
61
+ |------|------|---------|-------------|
62
+ | `name` | string | — | Field name (used for accessibility grouping) |
63
+ | `label` | string | — | Visible label text |
64
+ | `checked` | boolean | `false` | Controlled checked state |
65
+ | `onChange` | `(checked: boolean) => void` | — | Called with the new value on press |
66
+ | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
67
+ | `hint` | string | — | Helper text below the label |
68
+ | `error` | string | — | Error message; shown instead of hint when set |
69
+
70
+ ## Accessibility
71
+
72
+ - The label, hint, and error are announced together by screen readers
73
+ - `disabled` communicates the disabled state via `accessibilityState`
74
+ - Group related checkboxes using a wrapping `View` with `accessibilityRole="radiogroup"` or a labelled `fieldset` equivalent
@@ -0,0 +1,42 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../../native/ThemeContext';
3
+ import { Checkbox } from './Checkbox.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Checkbox (native)', () => {
10
+ it('has checkbox role with label', () => {
11
+ renderWithTheme(<Checkbox label="Accept terms" name="terms" />);
12
+ expect(screen.getByRole('checkbox', { name: 'Accept terms' })).toBeInTheDocument();
13
+ });
14
+
15
+ it('shows the checkmark when checked', () => {
16
+ renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
17
+ expect(screen.getByText('✓')).toBeInTheDocument();
18
+ });
19
+
20
+ it('hides the checkmark when unchecked', () => {
21
+ renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={false} />);
22
+ expect(screen.queryByText('✓')).not.toBeInTheDocument();
23
+ });
24
+
25
+ it('checkmark uses theme color (contrast regression: was hardcoded #fff)', () => {
26
+ // Previously the checkmark had color: '#fff' hardcoded in the StyleSheet.
27
+ // Now it uses colors.colorOnInteractive from the theme, which is #000 in dark
28
+ // mode — fixing the 2.52:1 contrast failure on the dark interactive blue.
29
+ renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
30
+ const checkmark = screen.getByText('✓');
31
+ // The color must be applied as an inline style (overrides StyleSheet), not absent.
32
+ // In light mode colorOnInteractive=#fff; in dark mode it is #000.
33
+ expect(checkmark.style.color).toBeTruthy();
34
+ });
35
+
36
+ it('renders with error message', () => {
37
+ renderWithTheme(
38
+ <Checkbox label="Accept terms" name="terms" error="You must accept." />,
39
+ );
40
+ expect(screen.getByRole('alert')).toHaveTextContent('You must accept.');
41
+ });
42
+ });
@@ -0,0 +1,36 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../../native/ThemeContext';
4
+ import { Checkbox } from './Checkbox.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/Form/Checkbox',
9
+ component: Checkbox,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'padded' },
12
+ } satisfies Meta<typeof Checkbox>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Unchecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: false } };
18
+ export const Checked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true } };
19
+ export const Disabled: Story = { args: { label: 'Accept terms', name: 'terms', disabled: true } };
20
+ export const DisabledChecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true, disabled: true } };
21
+ export const WithError: Story = {
22
+ args: { label: 'Accept terms', name: 'terms', error: 'You must accept the terms.' },
23
+ };
24
+ export const WithHint: Story = {
25
+ args: { label: 'Subscribe to newsletter', name: 'newsletter', hint: 'Max one email per week.' },
26
+ };
27
+ export const Interactive: Story = {
28
+ render: () => {
29
+ const [checked, setChecked] = useState(false);
30
+ return (
31
+ <ThemeProvider>
32
+ <Checkbox label="Accept terms and conditions" name="terms" checked={checked} onChange={setChecked} />
33
+ </ThemeProvider>
34
+ );
35
+ },
36
+ };
@@ -0,0 +1,80 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
3
+ import { spacing, border } from '../../../../native/tokens';
4
+ import { useTheme } from '../../../../native/ThemeContext';
5
+ import { Label } from '../atoms/Label.native';
6
+ import { Message } from '../atoms/Message.native';
7
+
8
+ interface CheckboxProps {
9
+ label: ReactNode;
10
+ name: string;
11
+ checked?: boolean;
12
+ onChange?: (checked: boolean) => void;
13
+ disabled?: boolean;
14
+ error?: string;
15
+ hint?: string;
16
+ style?: StyleProp<ViewStyle>;
17
+ }
18
+
19
+ const BOX = 22;
20
+
21
+ const styles = StyleSheet.create({
22
+ container: {
23
+ gap: spacing.xxs,
24
+ },
25
+ row: {
26
+ flexDirection: 'row',
27
+ alignItems: 'center',
28
+ gap: spacing.xs,
29
+ minHeight: 44,
30
+ },
31
+ box: {
32
+ width: BOX,
33
+ height: BOX,
34
+ borderRadius: border.radius.xs,
35
+ borderWidth: 2,
36
+ alignItems: 'center',
37
+ justifyContent: 'center',
38
+ },
39
+ checkmark: {
40
+ fontSize: 14,
41
+ fontWeight: '700',
42
+ lineHeight: 16,
43
+ },
44
+ });
45
+
46
+ export function Checkbox({
47
+ label,
48
+ name,
49
+ checked = false,
50
+ onChange,
51
+ disabled = false,
52
+ error,
53
+ hint,
54
+ style,
55
+ }: CheckboxProps) {
56
+ const { colors } = useTheme();
57
+
58
+ const borderColor = error ? colors.colorError : checked ? colors.colorInteractive : colors.colorBgSubtle;
59
+ const bgColor = checked ? colors.colorInteractive : 'transparent';
60
+
61
+ return (
62
+ <View style={[styles.container, style]}>
63
+ <Pressable
64
+ onPress={() => !disabled && onChange?.(!checked)}
65
+ accessibilityRole="checkbox"
66
+ accessibilityLabel={typeof label === 'string' ? label : name}
67
+ accessibilityState={{ checked, disabled }}
68
+ disabled={disabled}
69
+ style={({ pressed }) => [styles.row, pressed && { opacity: 0.7 }, disabled && { opacity: 0.4 }]}
70
+ >
71
+ <View style={[styles.box, { borderColor, backgroundColor: bgColor }]}>
72
+ {checked && <Label label="✓" style={[styles.checkmark, { color: colors.colorOnInteractive }]} />}
73
+ </View>
74
+ <Label label={label} />
75
+ </Pressable>
76
+ <Message type="error" message={error} />
77
+ <Message type="hint" message={hint} />
78
+ </View>
79
+ );
80
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Checkbox } from './Checkbox';
3
3
 
4
4
  const meta = {
5
- title: 'Form/Checkbox',
5
+ title: 'React/Form/Checkbox',
6
6
  component: Checkbox,
7
7
  } satisfies Meta<typeof Checkbox>;
8
8
 
@@ -4,13 +4,13 @@ import { Message } from '../atoms/Message';
4
4
  import { Label } from '../atoms/Label';
5
5
  import { cn } from '@boostdev/design-system-foundation';
6
6
  import { InputContainer } from '../atoms/InputContainer';
7
+ import type { WithClassName } from '../../../../types';
7
8
 
8
- interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
+ interface CheckboxProps extends WithClassName, Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
10
  label: ReactNode;
10
11
  name: string;
11
12
  error?: string;
12
13
  hint?: string;
13
- className?: string;
14
14
  }
15
15
 
16
16
  export function Checkbox({ label, name, error, hint, className, ...props }: CheckboxProps) {
@@ -0,0 +1,44 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './CheckboxGroup.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # CheckboxGroup
7
+
8
+ Groups multiple `Checkbox` inputs under a shared label. Renders a `<fieldset>` with a `<legend>` for accessible grouping.
9
+
10
+ ## When to use
11
+ - Multi-select option sets where several choices can be active simultaneously
12
+ - Any time two or more checkboxes share a common label or question
13
+
14
+ ## When not to use
15
+ - A single independent toggle — use `Checkbox` on its own
16
+ - Mutually exclusive choices — use `RadioGroup`
17
+
18
+ ## Examples
19
+
20
+ ### Default
21
+ <Canvas of={Stories.Default} />
22
+
23
+ ### With error
24
+ <Canvas of={Stories.WithError} />
25
+
26
+ ### With hint
27
+ <Canvas of={Stories.WithHint} />
28
+
29
+ ### Required
30
+ <Canvas of={Stories.Required} />
31
+
32
+ ### Disabled
33
+ <Canvas of={Stories.Disabled} />
34
+
35
+ ## Props
36
+
37
+ <ArgTypes of={Stories} />
38
+
39
+ ## Accessibility
40
+
41
+ - Wraps checkboxes in a `<fieldset>` with a `<legend>` — screen readers announce the group label before each option
42
+ - `disabled` on the group sets the HTML `disabled` attribute on the `<fieldset>`, which disables all child inputs
43
+ - `aria-required` is set on the `<fieldset>` when `required` is true
44
+ - Error and hint messages are linked via `aria-describedby`
@@ -3,7 +3,7 @@ import { Checkbox } from '../Checkbox';
3
3
  import { CheckboxGroup } from './CheckboxGroup';
4
4
 
5
5
  const meta = {
6
- title: 'Form/CheckboxGroup',
6
+ title: 'React/Form/CheckboxGroup',
7
7
  component: CheckboxGroup,
8
8
  } satisfies Meta<typeof CheckboxGroup>;
9
9
 
@@ -2,15 +2,15 @@ import { ReactNode, useId } from 'react';
2
2
  import css from './CheckboxGroup.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { cn } from '@boostdev/design-system-foundation';
5
+ import type { WithClassName } from '../../../../types';
5
6
 
6
- export interface CheckboxGroupProps {
7
+ export interface CheckboxGroupProps extends WithClassName {
7
8
  legend: string;
8
9
  children: ReactNode;
9
10
  error?: string;
10
11
  hint?: string;
11
12
  required?: boolean;
12
13
  disabled?: boolean;
13
- className?: string;
14
14
  }
15
15
 
16
16
  export function CheckboxGroup({
@@ -29,6 +29,27 @@ Searchable select with type-ahead filtering. Combines a text input with a dropdo
29
29
 
30
30
  <ArgTypes of={Stories} />
31
31
 
32
+ ## CSS variables
33
+
34
+
35
+ <table>
36
+ <thead>
37
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
38
+ </thead>
39
+ <tbody>
40
+ <tr><td>`--combobox_color_bg`</td><td>`var(--bds-color_bg)`</td><td>Input background colour</td></tr>
41
+ <tr><td>`--combobox_color`</td><td>`var(--bds-color_on-bg)`</td><td>Input text colour</td></tr>
42
+ <tr><td>`--combobox_border`</td><td>`none`</td><td>Input border</td></tr>
43
+ <tr><td>`--combobox_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Input corner radius</td></tr>
44
+ <tr><td>`--combobox_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Input box shadow</td></tr>
45
+ <tr><td>`--combobox_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Dropdown list text colour</td></tr>
46
+ <tr><td>`--combobox_listbox-border`</td><td>`none`</td><td>Dropdown list border</td></tr>
47
+ <tr><td>`--combobox_listbox-radius`</td><td>`var(--bdc-outline_radius)`</td><td>Dropdown list corner radius</td></tr>
48
+ <tr><td>`--combobox_listbox-shadow`</td><td>`var(--bds-shadow_m)`</td><td>Dropdown list box shadow</td></tr>
49
+ </tbody>
50
+ </table>
51
+
52
+
32
53
  ## Accessibility
33
54
 
34
55
  - Input uses `role="combobox"` with `aria-autocomplete="list"` and `aria-controls`
@@ -12,7 +12,7 @@
12
12
 
13
13
  .input {
14
14
  appearance: none;
15
- width: 100%;
15
+ inline-size: 100%;
16
16
  font-family: var(--bds-font_family--body);
17
17
  font-size: var(--bds-font_size--body);
18
18
  padding: var(--bds-space_s);
@@ -59,18 +59,17 @@
59
59
  }
60
60
 
61
61
  .chevron svg {
62
- width: 1rem;
63
- height: 1rem;
62
+ inline-size: 1rem;
63
+ block-size: 1rem;
64
64
  }
65
65
 
66
66
  .listbox {
67
67
  position: absolute;
68
- top: calc(100% + var(--bds-space_xxs));
69
- left: 0;
70
- right: 0;
68
+ inset-block-start: calc(100% + var(--bds-space_xxs));
69
+ inset-inline: 0;
71
70
  z-index: var(--bds-z-index_dropdown);
72
- max-height: 15rem;
73
- overflow-y: auto;
71
+ max-block-size: 15rem;
72
+ overflow-block: auto;
74
73
  list-style: none;
75
74
  margin: 0;
76
75
  padding: var(--bds-space_xxs) 0;
@@ -98,7 +97,7 @@
98
97
 
99
98
  .option.--highlighted {
100
99
  background-color: var(--bds-color_bg--subtle);
101
- color: var(--bds-color_interactive);
100
+ color: var(--bds-color_interactive_on-bg);
102
101
  }
103
102
 
104
103
  .option.--selected {
@@ -12,7 +12,7 @@ const options = [
12
12
  ];
13
13
 
14
14
  const meta = {
15
- title: 'Form/Combobox',
15
+ title: 'React/Form/Combobox',
16
16
  component: Combobox,
17
17
  } satisfies Meta<typeof Combobox>;
18
18
 
@@ -13,6 +13,7 @@ import { cn } from '@boostdev/design-system-foundation';
13
13
  import { InputContainer } from '../atoms/InputContainer';
14
14
  import { Label } from '../atoms/Label';
15
15
  import { Message } from '../atoms/Message';
16
+ import type { WithClassName } from '../../../../types';
16
17
 
17
18
  export interface ComboboxOption {
18
19
  value: string;
@@ -20,7 +21,7 @@ export interface ComboboxOption {
20
21
  disabled?: boolean;
21
22
  }
22
23
 
23
- interface ComboboxProps {
24
+ interface ComboboxProps extends WithClassName {
24
25
  label: ReactNode;
25
26
  name: string;
26
27
  options: ComboboxOption[];
@@ -30,7 +31,6 @@ interface ComboboxProps {
30
31
  disabled?: boolean;
31
32
  error?: string;
32
33
  hint?: string;
33
- className?: string;
34
34
  }
35
35
 
36
36
  export function Combobox({
@@ -31,6 +31,20 @@ File upload input with styled trigger button, hint, and error support.
31
31
 
32
32
  <ArgTypes of={Stories} />
33
33
 
34
+ ## CSS variables
35
+
36
+
37
+ <table>
38
+ <thead>
39
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr><td>`--fileInput_color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
43
+ <tr><td>`--fileInput_border-color`</td><td>`currentcolor`</td><td>Drop zone border colour</td></tr>
44
+ </tbody>
45
+ </table>
46
+
47
+
34
48
  ## Accessibility
35
49
 
36
50
  - Native `<input type="file">` — opens the OS file picker
@@ -48,8 +48,8 @@
48
48
  }
49
49
 
50
50
  .icon {
51
- width: 2rem;
52
- height: 2rem;
51
+ inline-size: 2rem;
52
+ block-size: 2rem;
53
53
  flex-shrink: 0;
54
54
  }
55
55
 
@@ -59,7 +59,7 @@
59
59
  }
60
60
 
61
61
  .prompt strong {
62
- color: var(--bds-color_interactive);
62
+ color: var(--bds-color_interactive_on-bg);
63
63
  font-weight: var(--bds-font_weight--semibold);
64
64
  }
65
65
 
@@ -70,8 +70,8 @@
70
70
 
71
71
  .hiddenInput {
72
72
  position: absolute;
73
- width: 1px;
74
- height: 1px;
73
+ inline-size: 1px;
74
+ block-size: 1px;
75
75
  overflow: hidden;
76
76
  clip: rect(0, 0, 0, 0);
77
77
  white-space: nowrap;
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { FileInput } from './FileInput';
3
3
 
4
4
  const meta = {
5
- title: 'Form/FileInput',
5
+ title: 'React/Form/FileInput',
6
6
  component: FileInput,
7
7
  } satisfies Meta<typeof FileInput>;
8
8
 
@@ -3,8 +3,9 @@ import css from './FileInput.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { InputContainer } from '../atoms/InputContainer';
5
5
  import { cn } from '@boostdev/design-system-foundation';
6
+ import type { WithClassName } from '../../../../types';
6
7
 
7
- interface FileInputProps {
8
+ interface FileInputProps extends WithClassName {
8
9
  label: string;
9
10
  name: string;
10
11
  accept?: string;
@@ -14,7 +15,6 @@ interface FileInputProps {
14
15
  error?: string;
15
16
  hint?: string;
16
17
  onChange?: (files: FileList | null) => void;
17
- className?: string;
18
18
  }
19
19
 
20
20
  export function FileInput({
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { FormInput } from './FormInput';
3
3
 
4
4
  const meta = {
5
- title: 'Form/FormInput',
5
+ title: 'React/Form/FormInput',
6
6
  component: FormInput,
7
7
  } satisfies Meta<typeof FormInput>;
8
8
 
@@ -4,15 +4,15 @@ import { Label } from '../atoms/Label';
4
4
  import { Message } from '../atoms/Message';
5
5
  import { cn } from '@boostdev/design-system-foundation';
6
6
  import { InputContainer } from '../atoms/InputContainer';
7
+ import type { WithClassName } from '../../../../types';
7
8
 
8
- interface FormInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
+ interface FormInputProps extends WithClassName, Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
10
  label: ReactNode;
10
11
  name: string;
11
12
  type?: Exclude<HTMLInputTypeAttribute, 'checkbox' | 'radio' | 'file'>;
12
13
  ariaLabel?: string;
13
14
  error?: string;
14
15
  hint?: string;
15
- className?: string;
16
16
  }
17
17
 
18
18
  export function FormInput({