@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
@@ -0,0 +1,89 @@
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
+
6
+ type CardVariant = 'default' | 'elevated' | 'outlined';
7
+ type CardPadding = 'none' | 'small' | 'medium' | 'large';
8
+ type CardAlign = 'start' | 'center' | 'end';
9
+
10
+ interface CardProps {
11
+ children: ReactNode;
12
+ variant?: CardVariant;
13
+ padding?: CardPadding;
14
+ textAlign?: CardAlign;
15
+ style?: StyleProp<ViewStyle>;
16
+ onPress?: () => void;
17
+ accessibilityLabel?: string;
18
+ }
19
+
20
+ const paddingMap: Record<CardPadding, number> = {
21
+ none: 0,
22
+ small: spacing.s,
23
+ medium: spacing.m,
24
+ large: spacing.xxl,
25
+ };
26
+
27
+ const alignMap: Record<CardAlign, 'flex-start' | 'center' | 'flex-end'> = {
28
+ start: 'flex-start',
29
+ center: 'center',
30
+ end: 'flex-end',
31
+ };
32
+
33
+ const styles = StyleSheet.create({
34
+ card: {
35
+ borderRadius: border.radius.s,
36
+ overflow: 'hidden',
37
+ },
38
+ });
39
+
40
+ export function Card({
41
+ children,
42
+ variant = 'default',
43
+ padding = 'medium',
44
+ textAlign = 'start',
45
+ style,
46
+ onPress,
47
+ accessibilityLabel,
48
+ }: CardProps) {
49
+ const { colors } = useTheme();
50
+
51
+ const variantStyle: ViewStyle = {
52
+ backgroundColor: colors.colorBg,
53
+ ...(variant === 'elevated' && {
54
+ shadowColor: '#000',
55
+ shadowOffset: { width: 0, height: 2 },
56
+ shadowOpacity: 0.1,
57
+ shadowRadius: 8,
58
+ elevation: 4,
59
+ }),
60
+ ...(variant === 'outlined' && {
61
+ borderWidth: 1,
62
+ borderColor: colors.colorBgSubtle,
63
+ }),
64
+ };
65
+
66
+ const inner: ViewStyle = {
67
+ padding: paddingMap[padding],
68
+ alignItems: alignMap[textAlign],
69
+ };
70
+
71
+ if (onPress) {
72
+ return (
73
+ <Pressable
74
+ onPress={onPress}
75
+ accessibilityRole="button"
76
+ accessibilityLabel={accessibilityLabel}
77
+ style={({ pressed }) => [styles.card, variantStyle, inner, pressed && { opacity: 0.8 }, style]}
78
+ >
79
+ {children}
80
+ </Pressable>
81
+ );
82
+ }
83
+
84
+ return (
85
+ <View style={[styles.card, variantStyle, inner, style]}>
86
+ {children}
87
+ </View>
88
+ );
89
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Card } from './Card';
3
3
 
4
4
  const meta = {
5
- title: 'Layout/Card',
5
+ title: 'React/Layout/Card',
6
6
  component: Card,
7
7
  argTypes: {
8
8
  variant: { control: 'select', options: ['default', 'elevated', 'outlined'] },
@@ -1,10 +1,10 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
2
  import css from './Card.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- interface CardProps {
6
+ interface CardProps extends WithClassName {
6
7
  children: ReactNode;
7
- className?: string | undefined;
8
8
  variant?: 'default' | 'elevated' | 'outlined';
9
9
  padding?: 'none' | 'small' | 'medium' | 'large';
10
10
  textAlign?: 'start' | 'center' | 'end';
@@ -28,8 +28,8 @@ Circular container that sizes to its own `font-size`. Use to give an SVG icon a
28
28
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
29
29
  </thead>
30
30
  <tbody>
31
- <tr><td>`--icon-wrapper_color`</td><td>`var(--bds-cta--subtle)`</td><td>Background colour</td></tr>
32
- <tr><td>`--icon-wrapper_on-color`</td><td>`var(--bds-on-cta--subtle)`</td><td>Icon colour</td></tr>
31
+ <tr><td>`--icon-wrapper-color`</td><td>`var(--bds-color_bg)`</td><td>Background colour</td></tr>
32
+ <tr><td>`--bds-on-icon-wrapper-color`</td><td>`var(--bds-color_on-bg)`</td><td>Icon colour</td></tr>
33
33
  </tbody>
34
34
  </table>
35
35
 
@@ -0,0 +1,39 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { IconWrapper } from './IconWrapper.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('IconWrapper (native)', () => {
10
+ it('renders children', () => {
11
+ renderWithTheme(
12
+ <IconWrapper>
13
+ <span data-testid="icon">★</span>
14
+ </IconWrapper>,
15
+ );
16
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
17
+ });
18
+
19
+ it('renders without error when accessibilityElementsHidden is true', () => {
20
+ // accessibilityElementsHidden={true} hides the element from iOS VoiceOver.
21
+ // importantForAccessibility="no-hide-descendants" hides it from Android TalkBack.
22
+ // Both are applied when this prop is set.
23
+ renderWithTheme(
24
+ <IconWrapper accessibilityElementsHidden>
25
+ <span data-testid="icon">★</span>
26
+ </IconWrapper>,
27
+ );
28
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
29
+ });
30
+
31
+ it('renders without error when accessibilityElementsHidden is false', () => {
32
+ renderWithTheme(
33
+ <IconWrapper accessibilityElementsHidden={false}>
34
+ <span data-testid="icon">★</span>
35
+ </IconWrapper>,
36
+ );
37
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
38
+ });
39
+ });
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { IconWrapper } from './IconWrapper.native';
5
+
6
+ // Simple SVG icon for demonstration
7
+ const StarIcon = () => (
8
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
9
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
10
+ </svg>
11
+ );
12
+
13
+ const meta = {
14
+ tags: ['!stable', 'alpha'],
15
+ title: 'React Native/Layout/IconWrapper',
16
+ component: IconWrapper,
17
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
18
+ parameters: { layout: 'centered' },
19
+ } satisfies Meta<typeof IconWrapper>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: (args) => (
26
+ <ThemeProvider>
27
+ <IconWrapper {...args}>
28
+ <StarIcon />
29
+ </IconWrapper>
30
+ </ThemeProvider>
31
+ ),
32
+ };
33
+ export const HiddenFromAccessibility: Story = {
34
+ render: (args) => (
35
+ <ThemeProvider>
36
+ <IconWrapper {...args} accessibilityElementsHidden>
37
+ <StarIcon />
38
+ </IconWrapper>
39
+ </ThemeProvider>
40
+ ),
41
+ };
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react';
2
+ import { View, StyleProp, ViewStyle } from 'react-native';
3
+
4
+ export interface IconWrapperProps {
5
+ children: ReactNode;
6
+ style?: StyleProp<ViewStyle>;
7
+ accessibilityElementsHidden?: boolean;
8
+ }
9
+
10
+ export function IconWrapper({ children, style, accessibilityElementsHidden }: IconWrapperProps) {
11
+ return (
12
+ <View
13
+ style={[{ alignItems: 'center', justifyContent: 'center' }, style]}
14
+ importantForAccessibility={accessibilityElementsHidden ? 'no-hide-descendants' : 'auto'}
15
+ accessibilityElementsHidden={accessibilityElementsHidden}
16
+ >
17
+ {children}
18
+ </View>
19
+ );
20
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { IconWrapper } from './IconWrapper';
3
3
 
4
4
  const meta = {
5
- title: 'Layout/IconWrapper',
5
+ title: 'React/Layout/IconWrapper',
6
6
  component: IconWrapper,
7
7
  } satisfies Meta<typeof IconWrapper>;
8
8
 
@@ -1,10 +1,10 @@
1
1
  import { ReactNode } from 'react';
2
2
  import css from './IconWrapper.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- export interface IconWrapperProps {
6
+ export interface IconWrapperProps extends WithClassName {
6
7
  children: ReactNode;
7
- className?: string;
8
8
  'aria-hidden'?: boolean | 'true' | 'false';
9
9
  }
10
10
 
@@ -32,6 +32,19 @@ Heading block with optional subtitle for introducing a content section.
32
32
 
33
33
  <ArgTypes of={Stories} />
34
34
 
35
+ ## CSS variables
36
+
37
+
38
+ <table>
39
+ <thead>
40
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
41
+ </thead>
42
+ <tbody>
43
+ <tr><td>`--sectionHeader_subtitle-color`</td><td>`var(--bds-color_grey--strong)`</td><td>Subtitle text colour</td></tr>
44
+ </tbody>
45
+ </table>
46
+
47
+
35
48
  ## Accessibility
36
49
 
37
50
  - Title renders as `<h2>` by default — override with `titleAs` to fit document outline
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { SectionHeader } from './SectionHeader.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/Layout/SectionHeader',
9
+ component: SectionHeader,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'padded' },
12
+ argTypes: {
13
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
14
+ alignment: { control: 'select', options: ['start', 'center', 'end'] },
15
+ },
16
+ } satisfies Meta<typeof SectionHeader>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Default: Story = { args: { title: 'Section Title' } };
22
+ export const WithSubtitle: Story = { args: { title: 'Section Title', subtitle: 'Optional subtitle text.' } };
23
+ export const Small: Story = { args: { title: 'Small Section', size: 'small' } };
24
+ export const Large: Story = { args: { title: 'Large Section', size: 'large' } };
25
+ export const Centered: Story = {
26
+ args: { title: 'Centered Header', subtitle: 'Centered subtitle text.', alignment: 'center' },
27
+ };
28
+ export const AllSizes: Story = {
29
+ render: () => (
30
+ <ThemeProvider>
31
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
32
+ <SectionHeader size="large" title="Large Heading" subtitle="Large section subtitle." />
33
+ <SectionHeader size="medium" title="Medium Heading" subtitle="Medium section subtitle." />
34
+ <SectionHeader size="small" title="Small Heading" subtitle="Small section subtitle." />
35
+ </div>
36
+ </ThemeProvider>
37
+ ),
38
+ };
@@ -0,0 +1,79 @@
1
+ import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
2
+ import { spacing, font, lineHeight } from '../../../native/tokens';
3
+ import { useTheme } from '../../../native/ThemeContext';
4
+
5
+ type SectionHeaderAlignment = 'start' | 'center' | 'end';
6
+ type SectionHeaderSize = 'small' | 'medium' | 'large';
7
+
8
+ interface SectionHeaderProps {
9
+ title: string;
10
+ subtitle?: string;
11
+ alignment?: SectionHeaderAlignment;
12
+ size?: SectionHeaderSize;
13
+ style?: StyleProp<ViewStyle>;
14
+ }
15
+
16
+ const alignMap: Record<SectionHeaderAlignment, 'flex-start' | 'center' | 'flex-end'> = {
17
+ start: 'flex-start',
18
+ center: 'center',
19
+ end: 'flex-end',
20
+ };
21
+
22
+ const titleStyles = StyleSheet.create({
23
+ small: {
24
+ fontSize: font.size.heading3,
25
+ fontFamily: font.family.heading,
26
+ fontWeight: font.weight.heading,
27
+ lineHeight: lineHeight.heading3,
28
+ },
29
+ medium: {
30
+ fontSize: font.size.heading2,
31
+ fontFamily: font.family.heading,
32
+ fontWeight: font.weight.heading,
33
+ lineHeight: lineHeight.heading2,
34
+ },
35
+ large: {
36
+ fontSize: font.size.heading1,
37
+ fontFamily: font.family.heading,
38
+ fontWeight: font.weight.heading,
39
+ lineHeight: lineHeight.heading1,
40
+ },
41
+ });
42
+
43
+ const styles = StyleSheet.create({
44
+ container: {
45
+ gap: spacing.xxs,
46
+ },
47
+ subtitle: {
48
+ fontSize: font.size.bodyS,
49
+ fontFamily: font.family.body,
50
+ lineHeight: lineHeight.bodyS,
51
+ },
52
+ });
53
+
54
+ export function SectionHeader({
55
+ title,
56
+ subtitle,
57
+ alignment = 'start',
58
+ size = 'medium',
59
+ style,
60
+ }: Readonly<SectionHeaderProps>) {
61
+ const { colors } = useTheme();
62
+ const align = alignMap[alignment];
63
+
64
+ return (
65
+ <View style={[styles.container, { alignItems: align }, style]}>
66
+ <Text
67
+ accessibilityRole="header"
68
+ style={[titleStyles[size], { color: colors.colorOnBg }]}
69
+ >
70
+ {title}
71
+ </Text>
72
+ {subtitle && (
73
+ <Text style={[styles.subtitle, { color: colors.colorOnBgSubtle }]}>
74
+ {subtitle}
75
+ </Text>
76
+ )}
77
+ </View>
78
+ );
79
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { SectionHeader } from './SectionHeader';
3
3
 
4
4
  const meta = {
5
- title: 'Layout/SectionHeader',
5
+ title: 'React/Layout/SectionHeader',
6
6
  component: SectionHeader,
7
7
  argTypes: {
8
8
  alignment: { control: 'select', options: ['start', 'center', 'end'] },
@@ -1,13 +1,13 @@
1
1
  import type { JSX } from 'react';
2
2
  import css from './SectionHeader.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
6
  type IntrinsicElement = keyof JSX.IntrinsicElements;
6
7
 
7
- type SectionHeaderProps = {
8
+ type SectionHeaderProps = WithClassName & {
8
9
  title: string;
9
10
  subtitle?: string;
10
- className?: string;
11
11
  alignment?: 'start' | 'center' | 'end';
12
12
  size?: 'small' | 'medium' | 'large';
13
13
  titleAs?: IntrinsicElement;
@@ -33,6 +33,20 @@ Vertically stacked sections that can be expanded and collapsed independently.
33
33
 
34
34
  <ArgTypes of={Stories} />
35
35
 
36
+ ## CSS variables
37
+
38
+
39
+ <table>
40
+ <thead>
41
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr><td>`--accordion_border`</td><td>`none`</td><td>Container border</td></tr>
45
+ <tr><td>`--accordion_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Container corner radius</td></tr>
46
+ </tbody>
47
+ </table>
48
+
49
+
36
50
  ## Accessibility
37
51
 
38
52
  - Implements ARIA disclosure pattern: trigger buttons use `aria-expanded` and `aria-controls`
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Accordion } from './Accordion';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Accordion',
5
+ title: 'React/UI/Accordion',
6
6
  component: Accordion,
7
7
  } satisfies Meta<typeof Accordion>;
8
8
 
@@ -1,6 +1,7 @@
1
1
  import { ReactNode, useId, useState } from 'react';
2
2
  import css from './Accordion.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
6
  export interface AccordionItem {
6
7
  id: string;
@@ -9,11 +10,10 @@ export interface AccordionItem {
9
10
  disabled?: boolean;
10
11
  }
11
12
 
12
- interface AccordionProps {
13
+ interface AccordionProps extends WithClassName {
13
14
  items: AccordionItem[];
14
15
  allowMultiple?: boolean;
15
16
  defaultOpen?: string[];
16
- className?: string;
17
17
  }
18
18
 
19
19
  export function Accordion({
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Alert } from './Alert.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/UI/Alert',
9
+ component: Alert,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'padded' },
12
+ argTypes: {
13
+ variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
14
+ },
15
+ } satisfies Meta<typeof Alert>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Info: Story = { args: { children: 'This is an informational message.', variant: 'info' } };
21
+ export const Success: Story = { args: { children: 'Your changes have been saved.', variant: 'success' } };
22
+ export const Warning: Story = { args: { children: 'Please review before continuing.', variant: 'warning' } };
23
+ export const Error: Story = { args: { children: 'Something went wrong. Please try again.', variant: 'error' } };
24
+ export const WithTitle: Story = {
25
+ args: { children: 'More details about the alert.', variant: 'info', title: 'Heads up' },
26
+ };
27
+ export const Dismissible: Story = {
28
+ args: { children: 'You can dismiss this alert.', variant: 'success', onDismiss: () => {} },
29
+ };
30
+ export const AllVariants: Story = {
31
+ args: { children: '' },
32
+ render: () => (
33
+ <ThemeProvider>
34
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: 400 }}>
35
+ {(['info', 'success', 'warning', 'error'] as const).map(v => (
36
+ <Alert key={v} variant={v} title={v.charAt(0).toUpperCase() + v.slice(1)}>
37
+ This is a {v} alert.
38
+ </Alert>
39
+ ))}
40
+ </div>
41
+ </ThemeProvider>
42
+ ),
43
+ };
@@ -0,0 +1,94 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleSheet, View, Text, Pressable, StyleProp, ViewStyle } from 'react-native';
3
+ import { spacing, font, border, lineHeight } from '../../../native/tokens';
4
+ import { useTheme } from '../../../native/ThemeContext';
5
+
6
+ type AlertVariant = 'info' | 'success' | 'warning' | 'error';
7
+
8
+ interface AlertProps {
9
+ variant?: AlertVariant;
10
+ icon?: ReactNode;
11
+ title?: string;
12
+ children: ReactNode;
13
+ onDismiss?: () => void;
14
+ style?: StyleProp<ViewStyle>;
15
+ }
16
+
17
+ const styles = StyleSheet.create({
18
+ alert: {
19
+ flexDirection: 'row',
20
+ alignItems: 'flex-start',
21
+ padding: spacing.m,
22
+ borderRadius: border.radius.s,
23
+ gap: spacing.xs,
24
+ },
25
+ icon: {
26
+ flexShrink: 0,
27
+ marginTop: 2,
28
+ },
29
+ content: {
30
+ flex: 1,
31
+ gap: spacing.xxs,
32
+ },
33
+ title: {
34
+ fontSize: font.size.body,
35
+ fontFamily: font.family.body,
36
+ fontWeight: font.weight.semibold,
37
+ lineHeight: lineHeight.body,
38
+ },
39
+ body: {
40
+ fontSize: font.size.bodyS,
41
+ fontFamily: font.family.body,
42
+ lineHeight: lineHeight.bodyS,
43
+ },
44
+ dismiss: {
45
+ padding: spacing.xxs,
46
+ flexShrink: 0,
47
+ },
48
+ });
49
+
50
+ export function Alert({
51
+ variant = 'info',
52
+ icon,
53
+ title,
54
+ children,
55
+ onDismiss,
56
+ style,
57
+ }: Readonly<AlertProps>) {
58
+ const { colors } = useTheme();
59
+ const isUrgent = variant === 'error' || variant === 'warning';
60
+
61
+ const variantColors: Record<AlertVariant, { bg: string; text: string }> = {
62
+ info: { bg: colors.colorBlueSubtle, text: colors.colorOnBlueSubtle },
63
+ success: { bg: colors.colorSuccessSubtle, text: colors.colorOnSuccessSubtle },
64
+ warning: { bg: colors.colorOrangeSubtle, text: colors.colorOnOrangeSubtle },
65
+ error: { bg: colors.colorRedSubtle, text: colors.colorOnRedSubtle },
66
+ };
67
+
68
+ const { bg, text } = variantColors[variant];
69
+
70
+ return (
71
+ <View
72
+ accessibilityRole={isUrgent ? 'alert' : 'none'}
73
+ accessibilityLiveRegion={isUrgent ? 'assertive' : 'polite'}
74
+ style={[styles.alert, { backgroundColor: bg }, style]}
75
+ >
76
+ {icon && <View style={styles.icon}>{icon}</View>}
77
+ <View style={styles.content}>
78
+ {title && <Text style={[styles.title, { color: text }]}>{title}</Text>}
79
+ <Text style={[styles.body, { color: text }]}>{children}</Text>
80
+ </View>
81
+ {onDismiss && (
82
+ <Pressable
83
+ onPress={onDismiss}
84
+ style={styles.dismiss}
85
+ accessibilityLabel="Dismiss alert"
86
+ accessibilityRole="button"
87
+ hitSlop={8}
88
+ >
89
+ <Text style={{ color: text, fontSize: 16 }}>✕</Text>
90
+ </Pressable>
91
+ )}
92
+ </View>
93
+ );
94
+ }
@@ -3,7 +3,7 @@ import { useState } from 'react';
3
3
  import { Alert } from './Alert';
4
4
 
5
5
  const meta = {
6
- title: 'UI/Alert',
6
+ title: 'React/UI/Alert',
7
7
  component: Alert,
8
8
  argTypes: {
9
9
  variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
@@ -1,14 +1,14 @@
1
1
  import { ReactNode } from 'react';
2
2
  import css from './Alert.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- interface AlertProps {
6
+ interface AlertProps extends WithClassName {
6
7
  variant?: 'info' | 'success' | 'warning' | 'error';
7
8
  icon?: ReactNode;
8
9
  title?: string;
9
10
  children: ReactNode;
10
11
  onDismiss?: () => void;
11
- className?: string;
12
12
  }
13
13
 
14
14
  export function Alert({