@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,47 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Avatar } from './Avatar.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Avatar (native)', () => {
10
+ describe('initials fallback', () => {
11
+ it('has image role and accessible label on the container', () => {
12
+ renderWithTheme(<Avatar name="John Doe" />);
13
+ expect(screen.getByRole('img', { name: 'John Doe' })).toBeInTheDocument();
14
+ });
15
+
16
+ it('derives initials from a two-word name', () => {
17
+ renderWithTheme(<Avatar name="John Doe" />);
18
+ expect(screen.getByText('JD')).toBeInTheDocument();
19
+ });
20
+
21
+ it('uses a single-word name as one initial', () => {
22
+ renderWithTheme(<Avatar name="Alice" />);
23
+ expect(screen.getByText('A')).toBeInTheDocument();
24
+ });
25
+
26
+ it('uses the alt prop as accessible label when provided', () => {
27
+ renderWithTheme(<Avatar name="John Doe" alt="Profile picture" />);
28
+ expect(screen.getByRole('img', { name: 'Profile picture' })).toBeInTheDocument();
29
+ });
30
+ });
31
+
32
+ describe('image variant', () => {
33
+ it('has image role and accessible label on the container', () => {
34
+ renderWithTheme(<Avatar src="https://example.com/avatar.jpg" alt="Jane Smith" />);
35
+ expect(screen.getByRole('img', { name: 'Jane Smith' })).toBeInTheDocument();
36
+ });
37
+
38
+ it('does not duplicate the accessible label (regression: duplicate label bug)', () => {
39
+ // Previously, both the outer View and the inner Image had accessibilityLabel={label},
40
+ // causing screen readers to announce the name twice.
41
+ renderWithTheme(<Avatar src="https://example.com/avatar.jpg" alt="Jane Smith" />);
42
+ // Only one element should have aria-label="Jane Smith".
43
+ const labeled = screen.getAllByLabelText('Jane Smith');
44
+ expect(labeled).toHaveLength(1);
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Avatar } from './Avatar.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/UI/Avatar',
9
+ component: Avatar,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'centered' },
12
+ argTypes: {
13
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
14
+ },
15
+ } satisfies Meta<typeof Avatar>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const WithInitials: Story = { args: { name: 'John Doe', size: 'medium' } };
21
+ export const Small: Story = { args: { name: 'Jane Smith', size: 'small' } };
22
+ export const Large: Story = { args: { name: 'Alex Brown', size: 'large' } };
23
+ export const SingleName: Story = { args: { name: 'Alice', size: 'medium' } };
24
+ export const AllSizes: Story = {
25
+ render: () => (
26
+ <ThemeProvider>
27
+ <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
28
+ {(['small', 'medium', 'large'] as const).map(s => (
29
+ <Avatar key={s} name="John Doe" size={s} />
30
+ ))}
31
+ </div>
32
+ </ThemeProvider>
33
+ ),
34
+ };
@@ -0,0 +1,85 @@
1
+ import { StyleSheet, View, Text, Image, StyleProp, ViewStyle } from 'react-native';
2
+ import { font } from '../../../native/tokens';
3
+ import { useTheme } from '../../../native/ThemeContext';
4
+
5
+ type AvatarSize = 'small' | 'medium' | 'large';
6
+
7
+ interface AvatarProps {
8
+ src?: string;
9
+ alt?: string;
10
+ name?: string;
11
+ size?: AvatarSize;
12
+ style?: StyleProp<ViewStyle>;
13
+ }
14
+
15
+ const sizeMap: Record<AvatarSize, { container: number; fontSize: number }> = {
16
+ small: { container: 32, fontSize: font.size.bodyS },
17
+ medium: { container: 48, fontSize: font.size.body },
18
+ large: { container: 72, fontSize: font.size.heading3 },
19
+ };
20
+
21
+ function getInitials(name: string): string {
22
+ return name
23
+ .split(' ')
24
+ .filter(Boolean)
25
+ .slice(0, 2)
26
+ .map(word => word[0].toUpperCase())
27
+ .join('');
28
+ }
29
+
30
+ const styles = StyleSheet.create({
31
+ container: {
32
+ borderRadius: 999,
33
+ overflow: 'hidden',
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ },
37
+ image: {
38
+ width: '100%',
39
+ height: '100%',
40
+ },
41
+ initials: {
42
+ fontFamily: font.family.body,
43
+ fontWeight: font.weight.semibold,
44
+ },
45
+ });
46
+
47
+ export function Avatar({ src, alt, name, size = 'medium', style }: Readonly<AvatarProps>) {
48
+ const { colors } = useTheme();
49
+ const { container, fontSize } = sizeMap[size];
50
+ const label = alt ?? name ?? 'User avatar';
51
+
52
+ if (src) {
53
+ return (
54
+ <View
55
+ style={[styles.container, { width: container, height: container }, style]}
56
+ accessibilityLabel={label}
57
+ accessibilityRole="image"
58
+ >
59
+ <Image source={{ uri: src }} style={styles.image} />
60
+ </View>
61
+ );
62
+ }
63
+
64
+ const initials = name ? getInitials(name) : '';
65
+
66
+ return (
67
+ <View
68
+ style={[
69
+ styles.container,
70
+ { width: container, height: container, backgroundColor: colors.colorBlue },
71
+ style,
72
+ ]}
73
+ accessibilityLabel={label}
74
+ accessibilityRole="image"
75
+ >
76
+ <Text
77
+ style={[styles.initials, { fontSize, color: colors.colorOnBlue }]}
78
+ importantForAccessibility="no"
79
+ accessibilityElementsHidden={true}
80
+ >
81
+ {initials}
82
+ </Text>
83
+ </View>
84
+ );
85
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Avatar } from './Avatar';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Avatar',
5
+ title: 'React/UI/Avatar',
6
6
  component: Avatar,
7
7
  argTypes: {
8
8
  size: { control: 'radio', options: ['small', 'medium', 'large'] },
@@ -1,12 +1,12 @@
1
1
  import css from './Avatar.module.css';
2
2
  import { cn } from '@boostdev/design-system-foundation';
3
+ import type { WithClassName } from '../../../types';
3
4
 
4
- interface AvatarProps {
5
+ interface AvatarProps extends WithClassName {
5
6
  src?: string;
6
7
  alt?: string;
7
8
  name?: string;
8
9
  size?: 'small' | 'medium' | 'large';
9
- className?: string;
10
10
  }
11
11
 
12
12
  function getInitials(name: string): string {
@@ -50,8 +50,8 @@ Override these on a parent element to customise the badge without touching globa
50
50
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
51
51
  </thead>
52
52
  <tbody>
53
- <tr><td>`--badge_color`</td><td>`var(--bds-cta--subtle)`</td><td>Background colour</td></tr>
54
- <tr><td>`--badge_on-color`</td><td>`var(--bds-on-cta--subtle)`</td><td>Text colour</td></tr>
53
+ <tr><td>`--badge_bg`</td><td>`var(--bds-color_blue)`</td><td>Background colour</td></tr>
54
+ <tr><td>`--badge_text`</td><td>`var(--bds-color_on-blue)`</td><td>Text colour</td></tr>
55
55
  </tbody>
56
56
  </table>
57
57
 
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Badge } from './Badge.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/UI/Badge',
9
+ component: Badge,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'centered' },
12
+ argTypes: {
13
+ variant: { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'] },
14
+ },
15
+ } satisfies Meta<typeof Badge>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Primary: Story = { args: { children: 'Primary', variant: 'primary' } };
21
+ export const Secondary: Story = { args: { children: 'Secondary', variant: 'secondary' } };
22
+ export const Success: Story = { args: { children: 'Success', variant: 'success' } };
23
+ export const Error: Story = { args: { children: 'Error', variant: 'error' } };
24
+ export const Warning: Story = { args: { children: 'Warning', variant: 'warning' } };
25
+ export const AllVariants: Story = {
26
+ args: { children: '' },
27
+ render: () => (
28
+ <ThemeProvider>
29
+ <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
30
+ {(['primary', 'secondary', 'success', 'error', 'warning'] as const).map(v => (
31
+ <Badge key={v} variant={v}>{v}</Badge>
32
+ ))}
33
+ </div>
34
+ </ThemeProvider>
35
+ ),
36
+ };
@@ -0,0 +1,50 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
3
+ import { spacing, font } from '../../../native/tokens';
4
+ import { useTheme } from '../../../native/ThemeContext';
5
+
6
+ type BadgeVariant = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
7
+
8
+ interface BadgeProps {
9
+ children: ReactNode;
10
+ variant?: BadgeVariant;
11
+ style?: StyleProp<ViewStyle>;
12
+ }
13
+
14
+ const styles = StyleSheet.create({
15
+ badge: {
16
+ alignSelf: 'flex-start',
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ justifyContent: 'center',
20
+ paddingVertical: spacing.xxs,
21
+ paddingHorizontal: spacing.xs,
22
+ borderRadius: 999,
23
+ },
24
+ text: {
25
+ fontSize: font.size.bodyS,
26
+ fontFamily: font.family.body,
27
+ fontWeight: font.weight.semibold,
28
+ lineHeight: font.size.bodyS,
29
+ },
30
+ });
31
+
32
+ export function Badge({ children, variant = 'primary', style }: BadgeProps) {
33
+ const { colors } = useTheme();
34
+
35
+ const variantColors: Record<BadgeVariant, { bg: string; text: string }> = {
36
+ primary: { bg: colors.colorInteractive, text: colors.colorOnInteractive },
37
+ secondary: { bg: colors.colorBgSubtle, text: colors.colorOnBgSubtle },
38
+ success: { bg: colors.colorSuccess, text: colors.colorOnSuccess },
39
+ error: { bg: colors.colorError, text: colors.colorOnError },
40
+ warning: { bg: colors.colorWarning, text: colors.colorOnWarning },
41
+ };
42
+
43
+ const { bg, text } = variantColors[variant];
44
+
45
+ return (
46
+ <View style={[styles.badge, { backgroundColor: bg }, style]}>
47
+ <Text style={[styles.text, { color: text }]}>{children}</Text>
48
+ </View>
49
+ );
50
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Badge } from './Badge';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Badge',
5
+ title: 'React/UI/Badge',
6
6
  component: Badge,
7
7
  argTypes: {
8
8
  variant: { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'] },
@@ -1,11 +1,11 @@
1
1
  import { ReactNode } from 'react';
2
2
  import css from './Badge.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- interface BadgeProps {
6
+ interface BadgeProps extends WithClassName {
6
7
  children: ReactNode;
7
8
  variant?: 'primary' | 'secondary' | 'success' | 'error' | 'warning';
8
- className?: string;
9
9
  }
10
10
 
11
11
  export function Badge({ children, variant = 'primary', className }: BadgeProps) {
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Breadcrumb } from './Breadcrumb';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Breadcrumb',
5
+ title: 'React/UI/Breadcrumb',
6
6
  component: Breadcrumb,
7
7
  } satisfies Meta<typeof Breadcrumb>;
8
8
 
@@ -1,14 +1,14 @@
1
1
  import css from './Breadcrumb.module.css';
2
2
  import { cn } from '@boostdev/design-system-foundation';
3
+ import type { WithClassName } from '../../../types';
3
4
 
4
5
  export interface BreadcrumbItem {
5
6
  label: string;
6
7
  href?: string;
7
8
  }
8
9
 
9
- interface BreadcrumbProps {
10
+ interface BreadcrumbProps extends WithClassName {
10
11
  items: BreadcrumbItem[];
11
- className?: string;
12
12
  }
13
13
 
14
14
  export function Breadcrumb({ items, className }: Readonly<BreadcrumbProps>) {
@@ -26,6 +26,22 @@ Date picker calendar grid. Use standalone or as the popover content inside a dat
26
26
 
27
27
  <ArgTypes of={Stories} />
28
28
 
29
+ ## CSS variables
30
+
31
+
32
+ <table>
33
+ <thead>
34
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
35
+ </thead>
36
+ <tbody>
37
+ <tr><td>`--calendar_border`</td><td>`none`</td><td>Container border</td></tr>
38
+ <tr><td>`--calendar_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Container corner radius</td></tr>
39
+ <tr><td>`--calendar_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Container box shadow</td></tr>
40
+ <tr><td>`--calendar_today-border`</td><td>`none`</td><td>Border on today's date cell</td></tr>
41
+ </tbody>
42
+ </table>
43
+
44
+
29
45
  ## Accessibility
30
46
 
31
47
  - Grid uses `role="grid"` with `role="gridcell"` for each day
@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
3
3
  import { Calendar } from './Calendar';
4
4
 
5
5
  const meta = {
6
- title: 'UI/Calendar',
6
+ title: 'React/UI/Calendar',
7
7
  component: Calendar,
8
8
  decorators: [
9
9
  Story => (
@@ -1,6 +1,7 @@
1
1
  import { useState, useId, KeyboardEvent } from 'react';
2
2
  import css from './Calendar.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
6
  const DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
6
7
  const DAY_LABELS: Record<string, string> = {
@@ -12,13 +13,12 @@ const MONTHS = [
12
13
  'July', 'August', 'September', 'October', 'November', 'December',
13
14
  ];
14
15
 
15
- interface CalendarProps {
16
+ interface CalendarProps extends WithClassName {
16
17
  value?: Date;
17
18
  defaultValue?: Date;
18
19
  min?: Date;
19
20
  max?: Date;
20
21
  onChange?: (date: Date) => void;
21
- className?: string;
22
22
  }
23
23
 
24
24
  function isSameDay(a: Date, b: Date): boolean {
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Carousel } from './Carousel';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Carousel',
5
+ title: 'React/UI/Carousel',
6
6
  component: Carousel,
7
7
  } satisfies Meta<typeof Carousel>;
8
8
 
@@ -1,11 +1,11 @@
1
1
  import { ReactNode, useRef, useId } from 'react';
2
2
  import css from './Carousel.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- interface CarouselProps {
6
+ interface CarouselProps extends WithClassName {
6
7
  items: ReactNode[];
7
8
  label: string;
8
- className?: string;
9
9
  }
10
10
 
11
11
  export function Carousel({ items, label, className }: Readonly<CarouselProps>) {
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Collapsible } from './Collapsible';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Collapsible',
5
+ title: 'React/UI/Collapsible',
6
6
  component: Collapsible,
7
7
  } satisfies Meta<typeof Collapsible>;
8
8
 
@@ -1,8 +1,9 @@
1
1
  import { HTMLAttributes, ReactNode, SyntheticEvent } from 'react';
2
2
  import css from './Collapsible.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDetailsElement>, 'onToggle'> {
6
+ export interface CollapsibleProps extends WithClassName, Omit<HTMLAttributes<HTMLDetailsElement>, 'onToggle'> {
6
7
  /** The trigger label — always visible */
7
8
  summary: ReactNode;
8
9
  /** The content revealed when expanded */
@@ -18,7 +19,6 @@ export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDetailsElement
18
19
  * (native <details name="…"> behaviour).
19
20
  */
20
21
  name?: string;
21
- className?: string;
22
22
  }
23
23
 
24
24
  export function Collapsible({
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { DescriptionList } from './DescriptionList';
3
3
 
4
4
  const meta = {
5
- title: 'UI/DescriptionList',
5
+ title: 'React/UI/DescriptionList',
6
6
  component: DescriptionList,
7
7
  } satisfies Meta<typeof DescriptionList>;
8
8
 
@@ -1,16 +1,16 @@
1
1
  import { ReactNode } from 'react';
2
2
  import css from './DescriptionList.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
6
  export interface DescriptionItem {
6
7
  term: ReactNode;
7
8
  details: ReactNode | ReactNode[];
8
9
  }
9
10
 
10
- interface DescriptionListProps {
11
+ interface DescriptionListProps extends WithClassName {
11
12
  items: DescriptionItem[];
12
13
  layout?: 'stacked' | 'inline';
13
- className?: string;
14
14
  }
15
15
 
16
16
  export function DescriptionList({ items, layout = 'stacked', className }: Readonly<DescriptionListProps>) {
@@ -38,6 +38,20 @@ Styled anchor element with three visual variants. Renders as `<a>` by default, b
38
38
 
39
39
  <ArgTypes of={Stories} />
40
40
 
41
+ ## CSS variables
42
+
43
+
44
+ <table>
45
+ <thead>
46
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
47
+ </thead>
48
+ <tbody>
49
+ <tr><td>`--link_color`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Link text colour</td></tr>
50
+ <tr><td>`--link_color-hover`</td><td>`var(--bds-color_blue--strong)`</td><td>Link text colour on hover</td></tr>
51
+ </tbody>
52
+ </table>
53
+
54
+
41
55
  ## Accessibility
42
56
 
43
57
  - External links add `target="_blank"` with `rel="noopener noreferrer"` and a visually-hidden "(opens in new tab)" label
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Link } from './Link';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Link',
5
+ title: 'React/UI/Link',
6
6
  component: Link,
7
7
  argTypes: {
8
8
  variant: { control: 'radio', options: ['default', 'subtle', 'standalone'] },
@@ -1,14 +1,14 @@
1
1
  import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
2
2
  import css from './Link.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
+ import type { WithClassName } from '../../../types';
4
5
 
5
- type LinkOwnProps<T extends ElementType> = {
6
+ type LinkOwnProps<T extends ElementType> = WithClassName & {
6
7
  as?: T;
7
8
  children: ReactNode;
8
9
  variant?: 'default' | 'subtle' | 'standalone';
9
10
  external?: boolean;
10
11
  externalLabel?: string;
11
- className?: string;
12
12
  };
13
13
 
14
14
  type LinkProps<T extends ElementType = 'a'> = LinkOwnProps<T> &
@@ -0,0 +1,24 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Loading } from './Loading.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Loading (native)', () => {
10
+ it('has progressbar role', () => {
11
+ renderWithTheme(<Loading />);
12
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
13
+ });
14
+
15
+ it('has default accessible label "Loading"', () => {
16
+ renderWithTheme(<Loading />);
17
+ expect(screen.getByRole('progressbar', { name: 'Loading' })).toBeInTheDocument();
18
+ });
19
+
20
+ it('accepts a custom accessibilityLabel', () => {
21
+ renderWithTheme(<Loading accessibilityLabel="Laden…" />);
22
+ expect(screen.getByRole('progressbar', { name: 'Laden…' })).toBeInTheDocument();
23
+ });
24
+ });
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Loading } from './Loading.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/UI/Loading',
9
+ component: Loading,
10
+ decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ parameters: { layout: 'centered' },
12
+ argTypes: {
13
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
14
+ },
15
+ } satisfies Meta<typeof Loading>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Small: Story = { args: { size: 'small' } };
21
+ export const Medium: Story = { args: { size: 'medium' } };
22
+ export const Large: Story = { args: { size: 'large' } };
23
+ export const AllSizes: Story = {
24
+ render: () => (
25
+ <ThemeProvider>
26
+ <div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
27
+ <Loading size="small" />
28
+ <Loading size="medium" />
29
+ <Loading size="large" />
30
+ </div>
31
+ </ThemeProvider>
32
+ ),
33
+ };
@@ -0,0 +1,29 @@
1
+ import { ActivityIndicator, StyleProp, ViewStyle } from 'react-native';
2
+ import { useTheme } from '../../../native/ThemeContext';
3
+
4
+ type LoadingSize = 'small' | 'medium' | 'large';
5
+
6
+ interface LoadingProps {
7
+ size?: LoadingSize;
8
+ accessibilityLabel?: string;
9
+ style?: StyleProp<ViewStyle>;
10
+ }
11
+
12
+ const rnSize: Record<LoadingSize, 'small' | 'large'> = {
13
+ small: 'small',
14
+ medium: 'large',
15
+ large: 'large',
16
+ };
17
+
18
+ export function Loading({ size = 'medium', accessibilityLabel = 'Loading', style }: LoadingProps) {
19
+ const { colors } = useTheme();
20
+ return (
21
+ <ActivityIndicator
22
+ size={rnSize[size]}
23
+ color={colors.colorInteractive}
24
+ style={style}
25
+ accessibilityRole="progressbar"
26
+ accessibilityLabel={accessibilityLabel}
27
+ />
28
+ );
29
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { Loading } from './Loading';
3
3
 
4
4
  const meta = {
5
- title: 'UI/Loading',
5
+ title: 'React/UI/Loading',
6
6
  component: Loading,
7
7
  argTypes: {
8
8
  size: { control: 'select', options: ['small', 'medium', 'large'] },
@@ -1,9 +1,9 @@
1
1
  import css from './Loading.module.css';
2
2
  import { cn } from '@boostdev/design-system-foundation';
3
+ import type { WithClassName } from '../../../types';
3
4
 
4
- interface LoadingProps {
5
+ interface LoadingProps extends WithClassName {
5
6
  size?: 'small' | 'medium' | 'large';
6
- className?: string;
7
7
  }
8
8
 
9
9
  export function Loading({ size = 'medium', className }: LoadingProps) {