@boostdev/design-system-components 1.0.3 → 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.
Files changed (225) hide show
  1. package/AGENTS.md +5 -5
  2. package/dist/client.cjs +236 -153
  3. package/dist/client.css +547 -483
  4. package/dist/client.d.cts +53 -94
  5. package/dist/client.d.ts +53 -94
  6. package/dist/client.js +237 -163
  7. package/dist/index.cjs +236 -153
  8. package/dist/index.css +547 -483
  9. package/dist/index.d.cts +53 -94
  10. package/dist/index.d.ts +53 -94
  11. package/dist/index.js +237 -163
  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 +303 -0
  17. package/dist/web-components.js +1968 -0
  18. package/package.json +27 -5
  19. package/src/components/interaction/Button/Button.module.css +10 -7
  20. package/src/components/interaction/Button/Button.native.spec.tsx +35 -0
  21. package/src/components/interaction/Button/Button.native.stories.tsx +42 -0
  22. package/src/components/interaction/Button/Button.native.tsx +95 -0
  23. package/src/components/interaction/Button/Button.stories.tsx +1 -1
  24. package/src/components/interaction/Button/Button.tsx +2 -2
  25. package/src/components/interaction/Command/Command.mdx +16 -0
  26. package/src/components/interaction/Command/Command.stories.tsx +1 -1
  27. package/src/components/interaction/Command/Command.tsx +2 -2
  28. package/src/components/interaction/Dialog/Dialog.stories.tsx +1 -1
  29. package/src/components/interaction/Dialog/Dialog.tsx +2 -2
  30. package/src/components/interaction/Drawer/Drawer.mdx +14 -0
  31. package/src/components/interaction/Drawer/Drawer.stories.tsx +1 -1
  32. package/src/components/interaction/Drawer/Drawer.tsx +2 -2
  33. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +1 -1
  34. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -2
  35. package/src/components/interaction/Popover/Popover.mdx +17 -0
  36. package/src/components/interaction/Popover/Popover.module.css +70 -13
  37. package/src/components/interaction/Popover/Popover.spec.tsx +68 -4
  38. package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
  39. package/src/components/interaction/Popover/Popover.tsx +76 -20
  40. package/src/components/interaction/Rating/Rating.stories.tsx +1 -1
  41. package/src/components/interaction/Rating/Rating.tsx +2 -2
  42. package/src/components/interaction/Toast/Toast.module.css +6 -1
  43. package/src/components/interaction/Toast/Toast.spec.tsx +21 -0
  44. package/src/components/interaction/Toast/Toast.stories.tsx +1 -1
  45. package/src/components/interaction/Toast/Toast.tsx +22 -2
  46. package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
  47. package/src/components/interaction/form/Checkbox/Checkbox.module.css +3 -3
  48. package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +42 -0
  49. package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +36 -0
  50. package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +80 -0
  51. package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +1 -1
  52. package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
  53. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +44 -0
  54. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +1 -1
  55. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -2
  56. package/src/components/interaction/form/Combobox/Combobox.mdx +21 -0
  57. package/src/components/interaction/form/Combobox/Combobox.module.css +1 -1
  58. package/src/components/interaction/form/Combobox/Combobox.stories.tsx +1 -1
  59. package/src/components/interaction/form/Combobox/Combobox.tsx +2 -2
  60. package/src/components/interaction/form/FileInput/FileInput.mdx +14 -0
  61. package/src/components/interaction/form/FileInput/FileInput.module.css +1 -1
  62. package/src/components/interaction/form/FileInput/FileInput.stories.tsx +1 -1
  63. package/src/components/interaction/form/FileInput/FileInput.tsx +2 -2
  64. package/src/components/interaction/form/FormInput/FormInput.stories.tsx +1 -1
  65. package/src/components/interaction/form/FormInput/FormInput.tsx +2 -2
  66. package/src/components/interaction/form/NumberInput/NumberInput.mdx +16 -0
  67. package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +1 -1
  68. package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -2
  69. package/src/components/interaction/form/Radio/Radio.mdx +3 -3
  70. package/src/components/interaction/form/Radio/Radio.module.css +3 -3
  71. package/src/components/interaction/form/Radio/Radio.native.stories.tsx +46 -0
  72. package/src/components/interaction/form/Radio/Radio.native.tsx +79 -0
  73. package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
  74. package/src/components/interaction/form/Radio/Radio.tsx +2 -2
  75. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +45 -0
  76. package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +1 -1
  77. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -2
  78. package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +1 -1
  79. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +2 -2
  80. package/src/components/interaction/form/Select/Select.stories.tsx +1 -1
  81. package/src/components/interaction/form/Select/Select.tsx +2 -2
  82. package/src/components/interaction/form/Slider/Slider.mdx +1 -1
  83. package/src/components/interaction/form/Slider/Slider.module.css +4 -4
  84. package/src/components/interaction/form/Slider/Slider.stories.tsx +1 -1
  85. package/src/components/interaction/form/Slider/Slider.tsx +2 -2
  86. package/src/components/interaction/form/Switch/Switch.mdx +4 -4
  87. package/src/components/interaction/form/Switch/Switch.module.css +3 -3
  88. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +60 -0
  89. package/src/components/interaction/form/Switch/Switch.native.stories.tsx +35 -0
  90. package/src/components/interaction/form/Switch/Switch.native.tsx +59 -0
  91. package/src/components/interaction/form/Switch/Switch.stories.tsx +1 -1
  92. package/src/components/interaction/form/Switch/Switch.tsx +2 -2
  93. package/src/components/interaction/form/Textarea/Textarea.mdx +17 -0
  94. package/src/components/interaction/form/Textarea/Textarea.stories.tsx +1 -1
  95. package/src/components/interaction/form/Textarea/Textarea.tsx +2 -2
  96. package/src/components/interaction/form/atoms/InputContainer.tsx +2 -1
  97. package/src/components/interaction/form/atoms/Label.native.stories.tsx +18 -0
  98. package/src/components/interaction/form/atoms/Label.native.tsx +29 -0
  99. package/src/components/interaction/form/atoms/Label.tsx +5 -3
  100. package/src/components/interaction/form/atoms/Message.native.spec.tsx +30 -0
  101. package/src/components/interaction/form/atoms/Message.native.stories.tsx +22 -0
  102. package/src/components/interaction/form/atoms/Message.native.tsx +34 -0
  103. package/src/components/interaction/form/atoms/Message.tsx +5 -3
  104. package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +50 -0
  105. package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +34 -0
  106. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -1
  107. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +2 -2
  108. package/src/components/layout/Card/Card.native.stories.tsx +53 -0
  109. package/src/components/layout/Card/Card.native.tsx +89 -0
  110. package/src/components/layout/Card/Card.stories.tsx +1 -1
  111. package/src/components/layout/Card/Card.tsx +2 -2
  112. package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
  113. package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +39 -0
  114. package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +41 -0
  115. package/src/components/layout/IconWrapper/IconWrapper.native.tsx +20 -0
  116. package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +1 -1
  117. package/src/components/layout/IconWrapper/IconWrapper.tsx +2 -2
  118. package/src/components/layout/SectionHeader/SectionHeader.mdx +13 -0
  119. package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +38 -0
  120. package/src/components/layout/SectionHeader/SectionHeader.native.tsx +79 -0
  121. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +1 -1
  122. package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
  123. package/src/components/ui/Accordion/Accordion.mdx +14 -0
  124. package/src/components/ui/Accordion/Accordion.stories.tsx +1 -1
  125. package/src/components/ui/Accordion/Accordion.tsx +2 -2
  126. package/src/components/ui/Alert/Alert.native.stories.tsx +43 -0
  127. package/src/components/ui/Alert/Alert.native.tsx +94 -0
  128. package/src/components/ui/Alert/Alert.stories.tsx +1 -1
  129. package/src/components/ui/Alert/Alert.tsx +2 -2
  130. package/src/components/ui/Avatar/Avatar.native.spec.tsx +47 -0
  131. package/src/components/ui/Avatar/Avatar.native.stories.tsx +34 -0
  132. package/src/components/ui/Avatar/Avatar.native.tsx +85 -0
  133. package/src/components/ui/Avatar/Avatar.stories.tsx +1 -1
  134. package/src/components/ui/Avatar/Avatar.tsx +2 -2
  135. package/src/components/ui/Badge/Badge.mdx +2 -2
  136. package/src/components/ui/Badge/Badge.native.stories.tsx +36 -0
  137. package/src/components/ui/Badge/Badge.native.tsx +50 -0
  138. package/src/components/ui/Badge/Badge.stories.tsx +1 -1
  139. package/src/components/ui/Badge/Badge.tsx +2 -2
  140. package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  141. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -2
  142. package/src/components/ui/Calendar/Calendar.mdx +16 -0
  143. package/src/components/ui/Calendar/Calendar.stories.tsx +1 -1
  144. package/src/components/ui/Calendar/Calendar.tsx +2 -2
  145. package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
  146. package/src/components/ui/Carousel/Carousel.tsx +2 -2
  147. package/src/components/ui/Collapsible/Collapsible.stories.tsx +1 -1
  148. package/src/components/ui/Collapsible/Collapsible.tsx +2 -2
  149. package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +1 -1
  150. package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -2
  151. package/src/components/ui/Link/Link.mdx +14 -0
  152. package/src/components/ui/Link/Link.stories.tsx +1 -1
  153. package/src/components/ui/Link/Link.tsx +2 -2
  154. package/src/components/ui/Loading/Loading.native.spec.tsx +24 -0
  155. package/src/components/ui/Loading/Loading.native.stories.tsx +33 -0
  156. package/src/components/ui/Loading/Loading.native.tsx +29 -0
  157. package/src/components/ui/Loading/Loading.stories.tsx +1 -1
  158. package/src/components/ui/Loading/Loading.tsx +2 -2
  159. package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +39 -0
  160. package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +76 -0
  161. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +1 -1
  162. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -2
  163. package/src/components/ui/Pagination/Pagination.module.css +1 -1
  164. package/src/components/ui/Pagination/Pagination.stories.tsx +1 -1
  165. package/src/components/ui/Pagination/Pagination.tsx +2 -2
  166. package/src/components/ui/Progress/Progress.mdx +1 -1
  167. package/src/components/ui/Progress/Progress.module.css +1 -1
  168. package/src/components/ui/Progress/Progress.native.stories.tsx +34 -0
  169. package/src/components/ui/Progress/Progress.native.tsx +84 -0
  170. package/src/components/ui/Progress/Progress.stories.tsx +1 -1
  171. package/src/components/ui/Progress/Progress.tsx +2 -2
  172. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
  173. package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +1 -1
  174. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -2
  175. package/src/components/ui/Separator/Separator.mdx +14 -0
  176. package/src/components/ui/Separator/Separator.native.stories.tsx +42 -0
  177. package/src/components/ui/Separator/Separator.native.tsx +32 -0
  178. package/src/components/ui/Separator/Separator.stories.tsx +1 -1
  179. package/src/components/ui/Separator/Separator.tsx +2 -2
  180. package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +33 -0
  181. package/src/components/ui/Skeleton/Skeleton.native.tsx +41 -0
  182. package/src/components/ui/Skeleton/Skeleton.stories.tsx +1 -1
  183. package/src/components/ui/Skeleton/Skeleton.tsx +2 -3
  184. package/src/components/ui/SkipLink/SkipLink.stories.tsx +1 -1
  185. package/src/components/ui/SkipLink/SkipLink.tsx +5 -3
  186. package/src/components/ui/Table/Table.mdx +14 -0
  187. package/src/components/ui/Table/Table.stories.tsx +1 -1
  188. package/src/components/ui/Table/Table.tsx +2 -2
  189. package/src/components/ui/Tabs/Tabs.stories.tsx +1 -1
  190. package/src/components/ui/Tabs/Tabs.tsx +2 -2
  191. package/src/components/ui/Tooltip/Tooltip.mdx +14 -0
  192. package/src/components/ui/Tooltip/Tooltip.stories.tsx +1 -1
  193. package/src/components/ui/Tooltip/Tooltip.tsx +2 -2
  194. package/src/components/ui/Typography/Typography.mdx +13 -0
  195. package/src/components/ui/Typography/Typography.native.stories.tsx +38 -0
  196. package/src/components/ui/Typography/Typography.native.tsx +65 -0
  197. package/src/components/ui/Typography/Typography.stories.tsx +1 -1
  198. package/src/components/ui/Typography/Typography.tsx +2 -2
  199. package/src/css/bdc.css +8 -0
  200. package/src/index.ts +1 -0
  201. package/src/native/ThemeContext.tsx +28 -0
  202. package/src/native/tokens.ts +13 -0
  203. package/src/native.ts +39 -0
  204. package/src/react-augment.d.ts +13 -0
  205. package/src/stories/DesignSystem/DarkMode.mdx +130 -0
  206. package/src/types.ts +2 -0
  207. package/src/typings.d.ts +3 -0
  208. package/src/web-components/globals.ts +61 -0
  209. package/src/web-components/index.ts +12 -0
  210. package/src/web-components/interaction/BdsButton.stories.tsx +60 -0
  211. package/src/web-components/interaction/BdsPopover.stories.tsx +70 -0
  212. package/src/web-components/interaction/BdsToastProvider.stories.tsx +73 -0
  213. package/src/web-components/interaction/bds-button.spec.ts +95 -0
  214. package/src/web-components/interaction/bds-button.ts +293 -0
  215. package/src/web-components/interaction/bds-popover.spec.ts +126 -0
  216. package/src/web-components/interaction/bds-popover.ts +217 -0
  217. package/src/web-components/interaction/bds-toast-provider.spec.ts +122 -0
  218. package/src/web-components/interaction/bds-toast-provider.ts +211 -0
  219. package/src/web-components/test/helpers.ts +14 -0
  220. package/src/web-components/ui/BdsAlert.stories.tsx +60 -0
  221. package/src/web-components/ui/BdsBadge.stories.tsx +37 -0
  222. package/src/web-components/ui/bds-alert.spec.ts +109 -0
  223. package/src/web-components/ui/bds-alert.ts +209 -0
  224. package/src/web-components/ui/bds-badge.spec.ts +51 -0
  225. package/src/web-components/ui/bds-badge.ts +88 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
5
  "keywords": [
6
6
  "React",
@@ -26,6 +26,7 @@
26
26
  "type": "module",
27
27
  "exports": {
28
28
  ".": {
29
+ "react-native": "./dist/native/index.js",
29
30
  "types": "./dist/index.d.ts",
30
31
  "import": "./dist/index.js",
31
32
  "require": "./dist/index.cjs"
@@ -35,6 +36,10 @@
35
36
  "import": "./dist/client.js",
36
37
  "require": "./dist/client.cjs"
37
38
  },
39
+ "./web-components": {
40
+ "types": "./dist/web-components.d.ts",
41
+ "import": "./dist/web-components.js"
42
+ },
38
43
  "./css": "./dist/index.css",
39
44
  "./css/bdc": "./src/css/bdc.css",
40
45
  "./css/accordion": "./src/components/ui/Accordion/Accordion.module.css",
@@ -89,6 +94,7 @@
89
94
  "./css/section-header": "./src/components/layout/SectionHeader/SectionHeader.module.css"
90
95
  },
91
96
  "main": "./dist/index.js",
97
+ "react-native": "./dist/native/index.js",
92
98
  "types": "./dist/index.d.ts",
93
99
  "files": [
94
100
  "dist",
@@ -121,15 +127,19 @@
121
127
  "@testing-library/react": "^16.3.2",
122
128
  "@testing-library/user-event": "^14.6.1",
123
129
  "@types/node": "^25.5.0",
124
- "@types/react": "^18",
125
- "@types/react-dom": "^18",
130
+ "@types/react": "^19",
131
+ "@types/react-dom": "^19",
132
+ "@types/react-native": "^0.73.0",
126
133
  "@vitejs/plugin-react": "^5.1.4",
127
134
  "eslint": "^9",
128
135
  "git-cliff": "^2.12.0",
129
136
  "identity-obj-proxy": "^3.0.0",
130
137
  "jsdom": "^28.1.0",
138
+ "lit": "^3.3.2",
139
+ "react-native-web": "^0.21.2",
131
140
  "remark-gfm": "^4.0.1",
132
141
  "storybook": "^8.6.18",
142
+ "storybook-addon-tag-badges": "^1.4.0",
133
143
  "stylelint": "^16",
134
144
  "stylelint-config-standard": "^37",
135
145
  "tsup": "^8",
@@ -142,7 +152,16 @@
142
152
  "peerDependencies": {
143
153
  "@boostdev/design-system-foundation": ">=1.0.4",
144
154
  "react": ">=18",
145
- "react-dom": ">=18"
155
+ "react-dom": ">=18",
156
+ "react-native": ">=0.70"
157
+ },
158
+ "peerDependenciesMeta": {
159
+ "react-dom": {
160
+ "optional": true
161
+ },
162
+ "react-native": {
163
+ "optional": true
164
+ }
146
165
  },
147
166
  "publishConfig": {
148
167
  "access": "public",
@@ -150,5 +169,8 @@
150
169
  },
151
170
  "sideEffects": [
152
171
  "src/css/**/*.css"
153
- ]
172
+ ],
173
+ "dependencies": {
174
+ "@oddbird/css-anchor-positioning": "^0.9.0"
175
+ }
154
176
  }
@@ -47,8 +47,11 @@
47
47
  line-height: 1;
48
48
 
49
49
  /* Colour model: override --button_color + --button_on-color to re-theme */
50
- --button_color: var(--bds-color_green);
51
- --button_on-color: var(--bds-color_on-green);
50
+ --button_color: var(--bds-color_green);
51
+ --button_on-color: var(--bds-color_on-green);
52
+
53
+ /* Text-safe variant of --button_color for use on page background (ghost text/border) */
54
+ --button_color--on-bg: var(--bds-color_green--strong);
52
55
 
53
56
  background-color: var(--button_bg, var(--button_color));
54
57
  color: var(--button_text, var(--button_on-color));
@@ -63,11 +66,11 @@
63
66
  /* Default: filled with --button_color, no visible border */
64
67
  .button.--default { /* inherits base — no overrides needed */ }
65
68
 
66
- /* Ghost: transparent background, border and text use --button_color */
69
+ /* Ghost: transparent background, border and text use text-safe --button_color--on-bg */
67
70
  .button.--ghost {
68
71
  --button_bg: transparent;
69
- --button_text: var(--button_color);
70
- --bdc_color: var(--button_color);
72
+ --button_text: var(--button_color--on-bg);
73
+ --bdc_color: var(--button_color--on-bg);
71
74
  }
72
75
 
73
76
  .button.--size_small {
@@ -141,8 +144,8 @@
141
144
  @media (hover: hover) and (pointer: fine) {
142
145
  .button.--default:hover {
143
146
  --button_bg: var(--bds-color_bg);
144
- --button_text: var(--bds-color_interactive);
145
- --button_pulse-color: var(--bds-color_interactive);
147
+ --button_text: var(--bds-color_interactive_on-bg);
148
+ --button_pulse-color: var(--bds-color_interactive_on-bg);
146
149
  --bdc_color: currentcolor;
147
150
  }
148
151
  }
@@ -0,0 +1,35 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Button } from './Button.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Button (native)', () => {
10
+ it('has button role', () => {
11
+ renderWithTheme(<Button>Save</Button>);
12
+ expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
13
+ });
14
+
15
+ it('exposes disabled state', () => {
16
+ renderWithTheme(<Button disabled>Save</Button>);
17
+ expect(screen.getByRole('button', { name: 'Save' })).toHaveAttribute('aria-disabled', 'true');
18
+ });
19
+
20
+ it('renders with icon-start without throwing', () => {
21
+ // Icon wrappers have importantForAccessibility="no-hide-descendants" (Android)
22
+ // and accessibilityElementsHidden={true} (iOS) so decorative icons are hidden on device.
23
+ const icon = <span data-testid="icon">★</span>;
24
+ renderWithTheme(<Button iconStart={icon}>Save</Button>);
25
+ expect(screen.getByRole('button')).toBeInTheDocument();
26
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
27
+ });
28
+
29
+ it('renders with icon-end without throwing', () => {
30
+ const icon = <span data-testid="icon-end">→</span>;
31
+ renderWithTheme(<Button iconEnd={icon}>Next</Button>);
32
+ expect(screen.getByRole('button')).toBeInTheDocument();
33
+ expect(screen.getByTestId('icon-end')).toBeInTheDocument();
34
+ });
35
+ });
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Button } from './Button.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/Interaction/Button',
9
+ component: Button,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'centered' },
12
+ argTypes: {
13
+ variant: { control: 'select', options: ['default', 'ghost'] },
14
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
15
+ onPress: { action: 'pressed' },
16
+ },
17
+ } satisfies Meta<typeof Button>;
18
+
19
+ export default meta;
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ export const Default: Story = { args: { children: 'Default', variant: 'default' } };
23
+ export const Ghost: Story = { args: { children: 'Ghost', variant: 'ghost' } };
24
+ export const Small: Story = { args: { children: 'Small', size: 'small' } };
25
+ export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
26
+ export const Large: Story = { args: { children: 'Large', size: 'large' } };
27
+ export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
28
+ export const AllVariants: Story = {
29
+ render: () => (
30
+ <ThemeProvider>
31
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
32
+ {(['default', 'ghost'] as const).map(v => (
33
+ <div key={v} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
34
+ {(['small', 'medium', 'large'] as const).map(s => (
35
+ <Button key={s} variant={v} size={s}>{v} {s}</Button>
36
+ ))}
37
+ </div>
38
+ ))}
39
+ </div>
40
+ </ThemeProvider>
41
+ ),
42
+ };
@@ -0,0 +1,95 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleSheet, Pressable, Text, View, StyleProp, ViewStyle } from 'react-native';
3
+ import { spacing, font, border } from '../../../native/tokens';
4
+ import { useTheme } from '../../../native/ThemeContext';
5
+
6
+ type ButtonVariant = 'default' | 'ghost';
7
+ type ButtonSize = 'small' | 'medium' | 'large';
8
+
9
+ export interface ButtonProps {
10
+ children?: ReactNode;
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ iconStart?: ReactNode;
14
+ iconEnd?: ReactNode;
15
+ disabled?: boolean;
16
+ onPress?: () => void;
17
+ accessibilityLabel?: string;
18
+ style?: StyleProp<ViewStyle>;
19
+ }
20
+
21
+ const sizeTokens: Record<ButtonSize, { height: number; px: number; fontSize: number; radius: number }> = {
22
+ small: { height: 36, px: spacing.m, fontSize: font.size.bodyS, radius: border.radius.xs },
23
+ medium: { height: 48, px: spacing.m, fontSize: font.size.body, radius: border.radius.s },
24
+ large: { height: 56, px: spacing.xxl, fontSize: font.size.heading3, radius: border.radius.m },
25
+ };
26
+
27
+ const styles = StyleSheet.create({
28
+ pressable: {
29
+ flexDirection: 'row',
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ gap: spacing.xs,
33
+ alignSelf: 'flex-start',
34
+ },
35
+ label: {
36
+ fontFamily: font.family.body,
37
+ fontWeight: font.weight.semibold,
38
+ includeFontPadding: false,
39
+ },
40
+ });
41
+
42
+ export function Button({
43
+ children,
44
+ variant = 'default',
45
+ size = 'medium',
46
+ iconStart,
47
+ iconEnd,
48
+ disabled = false,
49
+ onPress,
50
+ accessibilityLabel,
51
+ style,
52
+ }: Readonly<ButtonProps>) {
53
+ const { colors } = useTheme();
54
+ const { height, px, fontSize, radius } = sizeTokens[size];
55
+
56
+ // Color model: filled = green bg, ghost = transparent bg + darker green border/text
57
+ const primaryColor = colors.colorGreen;
58
+ const onPrimaryColor = colors.colorOnGreen;
59
+ // Ghost uses colorGreenStrong for text/border — colorGreen (#00c950) fails 3:1 on white (2.15:1)
60
+ const ghostTextColor = colors.colorGreenStrong;
61
+
62
+ const containerStyle: ViewStyle = {
63
+ height,
64
+ paddingHorizontal: px,
65
+ borderRadius: radius,
66
+ opacity: disabled ? 0.4 : 1,
67
+ ...(variant === 'default' && { backgroundColor: primaryColor }),
68
+ ...(variant === 'ghost' && {
69
+ backgroundColor: 'transparent',
70
+ borderWidth: 1,
71
+ borderColor: ghostTextColor,
72
+ }),
73
+ };
74
+
75
+ const textColor = variant === 'default' ? onPrimaryColor : ghostTextColor;
76
+
77
+ return (
78
+ <Pressable
79
+ onPress={onPress}
80
+ disabled={disabled}
81
+ accessibilityRole="button"
82
+ accessibilityLabel={accessibilityLabel}
83
+ accessibilityState={{ disabled }}
84
+ style={({ pressed }) => [styles.pressable, containerStyle, pressed && { opacity: 0.75 }, style]}
85
+ >
86
+ {iconStart && <View importantForAccessibility="no-hide-descendants" accessibilityElementsHidden={true}>{iconStart}</View>}
87
+ {children && (
88
+ <Text style={[styles.label, { fontSize, color: textColor }]}>
89
+ {children}
90
+ </Text>
91
+ )}
92
+ {iconEnd && <View importantForAccessibility="no-hide-descendants">{iconEnd}</View>}
93
+ </Pressable>
94
+ );
95
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Button } from './Button';
3
3
 
4
4
  const meta = {
5
- title: 'Interaction/Button',
5
+ title: 'React/Interaction/Button',
6
6
  component: Button,
7
7
  argTypes: {
8
8
  variant: { control: 'select', options: ['default', 'ghost'] },
@@ -1,8 +1,9 @@
1
1
  import {ButtonHTMLAttributes, MouseEventHandler, ReactNode} from 'react';
2
2
  import css from './Button.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
6
+ export interface ButtonProps extends WithClassName, ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
6
7
  href?: string;
7
8
  variant?: 'default' | 'ghost';
8
9
  type?: 'button' | 'submit' | 'reset';
@@ -10,7 +11,6 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HT
10
11
  iconStart?: ReactNode;
11
12
  iconEnd?: ReactNode;
12
13
  children?: ReactNode;
13
- className?: string;
14
14
  disabled?: boolean;
15
15
  hasPulse?: boolean;
16
16
  onClick?: MouseEventHandler<HTMLElement>;
@@ -20,6 +20,22 @@ Keyboard-first command palette for searching and executing actions. Commonly tri
20
20
 
21
21
  <ArgTypes of={Stories} />
22
22
 
23
+ ## CSS variables
24
+
25
+
26
+ <table>
27
+ <thead>
28
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
29
+ </thead>
30
+ <tbody>
31
+ <tr><td>`--command_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
32
+ <tr><td>`--command_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text and icon colour</td></tr>
33
+ <tr><td>`--command_shadow`</td><td>`var(--bds-shadow_xl)`</td><td>Panel box shadow</td></tr>
34
+ <tr><td>`--command_border`</td><td>`none`</td><td>Panel border</td></tr>
35
+ </tbody>
36
+ </table>
37
+
38
+
23
39
  ## Accessibility
24
40
 
25
41
  - Input has `role="combobox"` with `aria-controls` pointing to the results list
@@ -5,7 +5,7 @@ import type { CommandItem } from './Command';
5
5
  import { Button } from '../Button/Button';
6
6
 
7
7
  const meta = {
8
- title: 'Interaction/Command',
8
+ title: 'React/Interaction/Command',
9
9
  component: Command,
10
10
  } satisfies Meta<typeof Command>;
11
11
 
@@ -1,6 +1,7 @@
1
1
  import { useState, useEffect, useRef, useId, KeyboardEvent, useMemo } from 'react';
2
2
  import css from './Command.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
6
  export interface CommandItem {
6
7
  id: string;
@@ -11,12 +12,11 @@ export interface CommandItem {
11
12
  onSelect: () => void;
12
13
  }
13
14
 
14
- interface CommandProps {
15
+ interface CommandProps extends WithClassName {
15
16
  isOpen: boolean;
16
17
  onClose: () => void;
17
18
  items: CommandItem[];
18
19
  placeholder?: string;
19
- className?: string;
20
20
  }
21
21
 
22
22
  export function Command({
@@ -4,7 +4,7 @@ import { Dialog } from './Dialog';
4
4
  import { Button } from '../Button/Button';
5
5
 
6
6
  const meta = {
7
- title: 'Interaction/Dialog',
7
+ title: 'React/Interaction/Dialog',
8
8
  component: Dialog,
9
9
  } satisfies Meta<typeof Dialog>;
10
10
 
@@ -3,10 +3,10 @@ import { ReactNode, useEffect, useRef } from 'react';
3
3
  const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
4
4
  import css from './Dialog.module.css';
5
5
  import { cn } from '@boostdev/design-system-foundation';
6
+ import type { WithClassName } from '../../../types';
6
7
 
7
- interface DialogProps {
8
+ interface DialogProps extends WithClassName {
8
9
  children: ReactNode;
9
- className?: string;
10
10
  isOpen?: boolean;
11
11
  onClose?: () => void;
12
12
  }
@@ -28,6 +28,20 @@ Slide-in panel from the left or right edge of the viewport. Use for navigation,
28
28
 
29
29
  <ArgTypes of={Stories} />
30
30
 
31
+ ## CSS variables
32
+
33
+
34
+ <table>
35
+ <thead>
36
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
37
+ </thead>
38
+ <tbody>
39
+ <tr><td>`--drawer_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
40
+ <tr><td>`--drawer_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
41
+ </tbody>
42
+ </table>
43
+
44
+
31
45
  ## Accessibility
32
46
 
33
47
  - Uses `<dialog>` internally — provides native focus trapping and `Escape` to close
@@ -4,7 +4,7 @@ import { Drawer } from './Drawer';
4
4
  import { Button } from '../Button/Button';
5
5
 
6
6
  const meta = {
7
- title: 'Interaction/Drawer',
7
+ title: 'React/Interaction/Drawer',
8
8
  component: Drawer,
9
9
  } satisfies Meta<typeof Drawer>;
10
10
 
@@ -1,14 +1,14 @@
1
1
  import { ReactNode, useEffect, useRef } from 'react';
2
2
  import css from './Drawer.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- interface DrawerProps {
6
+ interface DrawerProps extends WithClassName {
6
7
  isOpen: boolean;
7
8
  onClose: () => void;
8
9
  title: ReactNode;
9
10
  children: ReactNode;
10
11
  side?: 'left' | 'right';
11
- className?: string;
12
12
  ariaLabel?: string;
13
13
  }
14
14
 
@@ -3,7 +3,7 @@ import { DropdownMenu } from './DropdownMenu';
3
3
  import {Button} from "../Button";
4
4
 
5
5
  const meta = {
6
- title: 'Interaction/DropdownMenu',
6
+ title: 'React/Interaction/DropdownMenu',
7
7
  component: DropdownMenu,
8
8
  decorators: [
9
9
  Story => (
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react';
11
11
  import css from './DropdownMenu.module.css';
12
12
  import { cn } from '@boostdev/design-system-foundation';
13
+ import type { WithClassName } from '../../../types';
13
14
 
14
15
  export interface DropdownMenuItem {
15
16
  id: string;
@@ -20,11 +21,10 @@ export interface DropdownMenuItem {
20
21
  separator?: boolean;
21
22
  }
22
23
 
23
- interface DropdownMenuProps {
24
+ interface DropdownMenuProps extends WithClassName {
24
25
  trigger: ReactElement;
25
26
  items: DropdownMenuItem[];
26
27
  placement?: 'bottom-start' | 'bottom-end';
27
- className?: string;
28
28
  }
29
29
 
30
30
  export function DropdownMenu({
@@ -27,6 +27,23 @@ Floating panel anchored to a trigger element. Unlike `Tooltip`, popovers can con
27
27
 
28
28
  <ArgTypes of={Stories} />
29
29
 
30
+ ## CSS variables
31
+
32
+
33
+ <table>
34
+ <thead>
35
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
36
+ </thead>
37
+ <tbody>
38
+ <tr><td>`--popover_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
39
+ <tr><td>`--popover_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
40
+ <tr><td>`--popover_shadow`</td><td>`var(--bds-shadow_m)`</td><td>Panel box shadow</td></tr>
41
+ <tr><td>`--popover_border`</td><td>`none`</td><td>Panel border</td></tr>
42
+ <tr><td>`--popover_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Panel corner radius</td></tr>
43
+ </tbody>
44
+ </table>
45
+
46
+
30
47
  ## Accessibility
31
48
 
32
49
  - Trigger element gets `aria-haspopup="true"` and `aria-expanded` toggled on open/close
@@ -5,8 +5,13 @@
5
5
  }
6
6
 
7
7
  .panel {
8
+ /* Absolute positioning keeps the panel anchored to the wrapper/trigger and
9
+ scrolls with the page. CSS anchor positioning sets the exact placement;
10
+ @position-try fallbacks handle automatic repositioning on overflow. */
8
11
  position: absolute;
9
- z-index: var(--bds-z-index_popover);
12
+ margin: 0;
13
+ overflow-y: auto;
14
+ z-index: var(--popover_z-index, 100);
10
15
  min-width: 12rem;
11
16
  padding: var(--bds-space_m);
12
17
  border: var(--popover_border, none);
@@ -22,23 +27,75 @@
22
27
  font-size: var(--bds-font_size--body);
23
28
  }
24
29
 
25
- .--placement_bottom {
26
- top: calc(100% + var(--bds-space_xs));
27
- left: 0;
30
+ /* Primary placements via CSS anchor positioning.
31
+ anchor() resolves relative to the element tagged with the matching anchor-name.
32
+ @position-try fallbacks are tried in order when the primary placement overflows. */
33
+
34
+ .panel[data-placement='bottom'] {
35
+ top: anchor(bottom);
36
+ left: anchor(left);
37
+ margin-top: var(--bds-space_xs, 8px);
38
+ position-try-fallbacks: --popover-top, --popover-right, --popover-left;
39
+ }
40
+
41
+ .panel[data-placement='top'] {
42
+ bottom: anchor(top);
43
+ left: anchor(left);
44
+ margin-bottom: var(--bds-space_xs, 8px);
45
+ position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
46
+ }
47
+
48
+ .panel[data-placement='right'] {
49
+ top: anchor(top);
50
+ left: anchor(right);
51
+ margin-left: var(--bds-space_xs, 8px);
52
+ position-try-fallbacks: --popover-left, --popover-bottom, --popover-top;
53
+ }
54
+
55
+ .panel[data-placement='left'] {
56
+ top: anchor(top);
57
+ right: anchor(left);
58
+ margin-right: var(--bds-space_xs, 8px);
59
+ position-try-fallbacks: --popover-right, --popover-bottom, --popover-top;
60
+ }
61
+
62
+ /* Fallback placements — used by the browser (or polyfill) when the primary
63
+ placement would overflow the viewport. Each block resets all inset/margin
64
+ properties so nothing bleeds in from the primary placement. */
65
+
66
+ @position-try --popover-bottom {
67
+ top: anchor(bottom);
68
+ right: unset;
69
+ bottom: unset;
70
+ left: anchor(left);
71
+ margin: 0;
72
+ margin-top: var(--bds-space_xs, 8px);
28
73
  }
29
74
 
30
- .--placement_top {
31
- bottom: calc(100% + var(--bds-space_xs));
32
- left: 0;
75
+ @position-try --popover-top {
76
+ top: unset;
77
+ right: unset;
78
+ bottom: anchor(top);
79
+ left: anchor(left);
80
+ margin: 0;
81
+ margin-bottom: var(--bds-space_xs, 8px);
33
82
  }
34
83
 
35
- .--placement_right {
36
- left: calc(100% + var(--bds-space_xs));
37
- top: 0;
84
+ @position-try --popover-right {
85
+ top: anchor(top);
86
+ right: unset;
87
+ bottom: unset;
88
+ left: anchor(right);
89
+ margin: 0;
90
+ margin-left: var(--bds-space_xs, 8px);
38
91
  }
39
92
 
40
- .--placement_left {
41
- right: calc(100% + var(--bds-space_xs));
42
- top: 0;
93
+ @position-try --popover-left {
94
+ top: anchor(top);
95
+ right: anchor(left);
96
+ bottom: unset;
97
+ left: unset;
98
+ margin: 0;
99
+ margin-right: var(--bds-space_xs, 8px);
43
100
  }
44
101
  }