@boostdev/design-system-components 2.4.2 → 2.5.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 (201) hide show
  1. package/AGENTS.md +4 -20
  2. package/README.md +3 -23
  3. package/dist/client.cjs +115 -61
  4. package/dist/client.css +537 -537
  5. package/dist/client.d.cts +11 -1
  6. package/dist/client.d.ts +11 -1
  7. package/dist/client.js +115 -61
  8. package/dist/index.cjs +115 -61
  9. package/dist/index.css +537 -537
  10. package/dist/index.d.cts +11 -1
  11. package/dist/index.d.ts +11 -1
  12. package/dist/index.js +115 -61
  13. package/dist/web-components/index.js +23 -3
  14. package/package.json +2 -10
  15. package/src/components/layout/Grid/Grid.mdx +20 -2
  16. package/src/components/layout/Grid/Grid.spec.tsx +175 -4
  17. package/src/components/layout/Grid/Grid.tsx +15 -1
  18. package/src/components/layout/Grid/masonry.ts +120 -9
  19. package/dist/native/index.cjs +0 -4337
  20. package/dist/native/index.d.cts +0 -574
  21. package/dist/native/index.d.ts +0 -574
  22. package/dist/native/index.js +0 -4421
  23. package/src/components/interaction/Button/Button.native.mdx +0 -91
  24. package/src/components/interaction/Button/Button.native.spec.tsx +0 -55
  25. package/src/components/interaction/Button/Button.native.stories.tsx +0 -141
  26. package/src/components/interaction/Button/Button.native.tsx +0 -105
  27. package/src/components/interaction/Dialog/Dialog.native.mdx +0 -61
  28. package/src/components/interaction/Dialog/Dialog.native.spec.tsx +0 -73
  29. package/src/components/interaction/Dialog/Dialog.native.stories.tsx +0 -53
  30. package/src/components/interaction/Dialog/Dialog.native.tsx +0 -128
  31. package/src/components/interaction/Drawer/Drawer.native.mdx +0 -58
  32. package/src/components/interaction/Drawer/Drawer.native.spec.tsx +0 -81
  33. package/src/components/interaction/Drawer/Drawer.native.stories.tsx +0 -33
  34. package/src/components/interaction/Drawer/Drawer.native.tsx +0 -175
  35. package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +0 -74
  36. package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +0 -78
  37. package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +0 -51
  38. package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +0 -254
  39. package/src/components/interaction/Popover/Popover.native.mdx +0 -61
  40. package/src/components/interaction/Popover/Popover.native.spec.tsx +0 -73
  41. package/src/components/interaction/Popover/Popover.native.stories.tsx +0 -44
  42. package/src/components/interaction/Popover/Popover.native.tsx +0 -87
  43. package/src/components/interaction/Rating/Rating.native.mdx +0 -55
  44. package/src/components/interaction/Rating/Rating.native.spec.tsx +0 -38
  45. package/src/components/interaction/Rating/Rating.native.stories.tsx +0 -37
  46. package/src/components/interaction/Rating/Rating.native.tsx +0 -50
  47. package/src/components/interaction/Toast/Toast.native.mdx +0 -81
  48. package/src/components/interaction/Toast/Toast.native.spec.tsx +0 -80
  49. package/src/components/interaction/Toast/Toast.native.stories.tsx +0 -47
  50. package/src/components/interaction/Toast/Toast.native.tsx +0 -202
  51. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +0 -74
  52. package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +0 -42
  53. package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +0 -34
  54. package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +0 -80
  55. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +0 -78
  56. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +0 -57
  57. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +0 -96
  58. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +0 -61
  59. package/src/components/interaction/form/Combobox/Combobox.native.mdx +0 -86
  60. package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +0 -60
  61. package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +0 -99
  62. package/src/components/interaction/form/Combobox/Combobox.native.tsx +0 -211
  63. package/src/components/interaction/form/FileInput/FileInput.native.mdx +0 -86
  64. package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +0 -73
  65. package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +0 -69
  66. package/src/components/interaction/form/FileInput/FileInput.native.tsx +0 -129
  67. package/src/components/interaction/form/FormInput/FormInput.native.mdx +0 -81
  68. package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +0 -51
  69. package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +0 -54
  70. package/src/components/interaction/form/FormInput/FormInput.native.tsx +0 -122
  71. package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +0 -87
  72. package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +0 -63
  73. package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +0 -63
  74. package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +0 -198
  75. package/src/components/interaction/form/Radio/Radio.native.spec.tsx +0 -79
  76. package/src/components/interaction/form/Radio/Radio.native.stories.tsx +0 -44
  77. package/src/components/interaction/form/Radio/Radio.native.tsx +0 -79
  78. package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +0 -79
  79. package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +0 -66
  80. package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +0 -96
  81. package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +0 -61
  82. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +0 -78
  83. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +0 -82
  84. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +0 -99
  85. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +0 -121
  86. package/src/components/interaction/form/Select/Select.native.mdx +0 -85
  87. package/src/components/interaction/form/Select/Select.native.spec.tsx +0 -64
  88. package/src/components/interaction/form/Select/Select.native.stories.tsx +0 -75
  89. package/src/components/interaction/form/Select/Select.native.tsx +0 -252
  90. package/src/components/interaction/form/Slider/Slider.native.mdx +0 -83
  91. package/src/components/interaction/form/Slider/Slider.native.spec.tsx +0 -51
  92. package/src/components/interaction/form/Slider/Slider.native.stories.tsx +0 -58
  93. package/src/components/interaction/form/Slider/Slider.native.tsx +0 -188
  94. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +0 -60
  95. package/src/components/interaction/form/Switch/Switch.native.stories.tsx +0 -33
  96. package/src/components/interaction/form/Switch/Switch.native.tsx +0 -59
  97. package/src/components/interaction/form/Textarea/Textarea.native.mdx +0 -76
  98. package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +0 -50
  99. package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +0 -50
  100. package/src/components/interaction/form/Textarea/Textarea.native.tsx +0 -108
  101. package/src/components/interaction/form/atoms/Label.native.spec.tsx +0 -39
  102. package/src/components/interaction/form/atoms/Label.native.stories.tsx +0 -18
  103. package/src/components/interaction/form/atoms/Label.native.tsx +0 -29
  104. package/src/components/interaction/form/atoms/Message.native.spec.tsx +0 -30
  105. package/src/components/interaction/form/atoms/Message.native.stories.tsx +0 -22
  106. package/src/components/interaction/form/atoms/Message.native.tsx +0 -34
  107. package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +0 -83
  108. package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +0 -44
  109. package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +0 -44
  110. package/src/components/layout/Card/Card.native.spec.tsx +0 -102
  111. package/src/components/layout/Card/Card.native.stories.tsx +0 -51
  112. package/src/components/layout/Card/Card.native.tsx +0 -89
  113. package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +0 -39
  114. package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +0 -37
  115. package/src/components/layout/IconWrapper/IconWrapper.native.tsx +0 -20
  116. package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +0 -51
  117. package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +0 -36
  118. package/src/components/layout/SectionHeader/SectionHeader.native.tsx +0 -79
  119. package/src/components/ui/Accordion/Accordion.native.mdx +0 -65
  120. package/src/components/ui/Accordion/Accordion.native.spec.tsx +0 -69
  121. package/src/components/ui/Accordion/Accordion.native.stories.tsx +0 -52
  122. package/src/components/ui/Accordion/Accordion.native.tsx +0 -141
  123. package/src/components/ui/Alert/Alert.native.mdx +0 -62
  124. package/src/components/ui/Alert/Alert.native.spec.tsx +0 -76
  125. package/src/components/ui/Alert/Alert.native.stories.tsx +0 -41
  126. package/src/components/ui/Alert/Alert.native.tsx +0 -94
  127. package/src/components/ui/Avatar/Avatar.native.mdx +0 -50
  128. package/src/components/ui/Avatar/Avatar.native.spec.tsx +0 -47
  129. package/src/components/ui/Avatar/Avatar.native.stories.tsx +0 -32
  130. package/src/components/ui/Avatar/Avatar.native.tsx +0 -85
  131. package/src/components/ui/Badge/Badge.native.mdx +0 -54
  132. package/src/components/ui/Badge/Badge.native.spec.tsx +0 -45
  133. package/src/components/ui/Badge/Badge.native.stories.tsx +0 -34
  134. package/src/components/ui/Badge/Badge.native.tsx +0 -50
  135. package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +0 -52
  136. package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +0 -49
  137. package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +0 -46
  138. package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +0 -83
  139. package/src/components/ui/Calendar/Calendar.native.mdx +0 -58
  140. package/src/components/ui/Calendar/Calendar.native.spec.tsx +0 -92
  141. package/src/components/ui/Calendar/Calendar.native.stories.tsx +0 -45
  142. package/src/components/ui/Calendar/Calendar.native.tsx +0 -294
  143. package/src/components/ui/Carousel/Carousel.native.mdx +0 -55
  144. package/src/components/ui/Carousel/Carousel.native.spec.tsx +0 -55
  145. package/src/components/ui/Carousel/Carousel.native.stories.tsx +0 -59
  146. package/src/components/ui/Carousel/Carousel.native.tsx +0 -184
  147. package/src/components/ui/Collapsible/Collapsible.native.mdx +0 -53
  148. package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +0 -68
  149. package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +0 -48
  150. package/src/components/ui/Collapsible/Collapsible.native.tsx +0 -139
  151. package/src/components/ui/DescriptionList/DescriptionList.native.mdx +0 -50
  152. package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +0 -37
  153. package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +0 -48
  154. package/src/components/ui/DescriptionList/DescriptionList.native.tsx +0 -54
  155. package/src/components/ui/Link/Link.native.mdx +0 -53
  156. package/src/components/ui/Link/Link.native.spec.tsx +0 -35
  157. package/src/components/ui/Link/Link.native.stories.tsx +0 -46
  158. package/src/components/ui/Link/Link.native.tsx +0 -48
  159. package/src/components/ui/Loading/Loading.native.spec.tsx +0 -24
  160. package/src/components/ui/Loading/Loading.native.stories.tsx +0 -31
  161. package/src/components/ui/Loading/Loading.native.tsx +0 -29
  162. package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +0 -85
  163. package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +0 -37
  164. package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +0 -76
  165. package/src/components/ui/Pagination/Pagination.native.mdx +0 -62
  166. package/src/components/ui/Pagination/Pagination.native.spec.tsx +0 -86
  167. package/src/components/ui/Pagination/Pagination.native.stories.tsx +0 -47
  168. package/src/components/ui/Pagination/Pagination.native.tsx +0 -157
  169. package/src/components/ui/Progress/Progress.native.spec.tsx +0 -56
  170. package/src/components/ui/Progress/Progress.native.stories.tsx +0 -34
  171. package/src/components/ui/Progress/Progress.native.tsx +0 -84
  172. package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +0 -63
  173. package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +0 -39
  174. package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +0 -36
  175. package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +0 -176
  176. package/src/components/ui/Separator/Separator.native.spec.tsx +0 -39
  177. package/src/components/ui/Separator/Separator.native.stories.tsx +0 -38
  178. package/src/components/ui/Separator/Separator.native.tsx +0 -32
  179. package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +0 -39
  180. package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +0 -31
  181. package/src/components/ui/Skeleton/Skeleton.native.tsx +0 -42
  182. package/src/components/ui/Table/Table.native.mdx +0 -72
  183. package/src/components/ui/Table/Table.native.spec.tsx +0 -83
  184. package/src/components/ui/Table/Table.native.stories.tsx +0 -55
  185. package/src/components/ui/Table/Table.native.tsx +0 -196
  186. package/src/components/ui/Tabs/Tabs.native.mdx +0 -65
  187. package/src/components/ui/Tabs/Tabs.native.spec.tsx +0 -65
  188. package/src/components/ui/Tabs/Tabs.native.stories.tsx +0 -57
  189. package/src/components/ui/Tabs/Tabs.native.tsx +0 -133
  190. package/src/components/ui/Tooltip/Tooltip.native.mdx +0 -47
  191. package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +0 -37
  192. package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +0 -38
  193. package/src/components/ui/Tooltip/Tooltip.native.tsx +0 -57
  194. package/src/components/ui/Typography/Typography.native.mdx +0 -56
  195. package/src/components/ui/Typography/Typography.native.spec.tsx +0 -49
  196. package/src/components/ui/Typography/Typography.native.stories.tsx +0 -36
  197. package/src/components/ui/Typography/Typography.native.tsx +0 -65
  198. package/src/native/ThemeContext.tsx +0 -28
  199. package/src/native/tokens.ts +0 -13
  200. package/src/native.ts +0 -76
  201. package/src/stories/ReactNative.mdx +0 -156
@@ -1,128 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import {
3
- Modal,
4
- View,
5
- Pressable,
6
- Text,
7
- StyleSheet,
8
- StyleProp,
9
- ViewStyle,
10
- } from 'react-native';
11
- import { spacing, font, border } from '../../../native/tokens';
12
- import { useTheme } from '../../../native/ThemeContext';
13
-
14
- interface DialogProps {
15
- isOpen?: boolean;
16
- onClose?: () => void;
17
- title?: string;
18
- children?: ReactNode;
19
- style?: StyleProp<ViewStyle>;
20
- }
21
-
22
- const styles = StyleSheet.create({
23
- overlay: {
24
- flex: 1,
25
- justifyContent: 'center',
26
- alignItems: 'center',
27
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
28
- padding: spacing.m,
29
- },
30
- panel: {
31
- width: '100%',
32
- maxWidth: 480,
33
- borderRadius: border.radius.m,
34
- padding: spacing.l,
35
- paddingTop: spacing.xl,
36
- shadowColor: '#000',
37
- shadowOffset: { width: 0, height: 4 },
38
- shadowOpacity: 0.15,
39
- shadowRadius: 12,
40
- elevation: 8,
41
- },
42
- header: {
43
- flexDirection: 'row',
44
- justifyContent: 'space-between',
45
- alignItems: 'center',
46
- marginBottom: spacing.m,
47
- },
48
- title: {
49
- fontFamily: font.family.body,
50
- fontSize: font.size.heading3,
51
- fontWeight: font.weight.semibold,
52
- flex: 1,
53
- },
54
- closeButton: {
55
- position: 'absolute',
56
- top: spacing.s,
57
- end: spacing.s,
58
- width: 44,
59
- height: 44,
60
- alignItems: 'center',
61
- justifyContent: 'center',
62
- zIndex: 1,
63
- },
64
- closeText: {
65
- fontFamily: font.family.body,
66
- fontSize: 20,
67
- lineHeight: 24,
68
- },
69
- });
70
-
71
- export function Dialog({
72
- isOpen = false,
73
- onClose,
74
- title,
75
- children,
76
- style,
77
- }: Readonly<DialogProps>) {
78
- const { colors } = useTheme();
79
-
80
- return (
81
- <Modal
82
- visible={isOpen}
83
- transparent
84
- animationType="fade"
85
- onRequestClose={onClose}
86
- >
87
- <Pressable
88
- style={styles.overlay}
89
- onPress={onClose}
90
- accessibilityRole="button"
91
- accessibilityLabel="Close dialog"
92
- >
93
- <Pressable
94
- style={[styles.panel, { backgroundColor: colors.colorGreySubtle }, style]}
95
- onPress={() => {
96
- /* prevent close when pressing content */
97
- }}
98
- accessibilityRole="none"
99
- >
100
- <Pressable
101
- style={styles.closeButton}
102
- onPress={onClose}
103
- accessibilityRole="button"
104
- accessibilityLabel="Close dialog"
105
- hitSlop={8}
106
- >
107
- <Text
108
- style={[styles.closeText, { color: colors.colorOnBgSubtle }]}
109
- aria-hidden
110
- >
111
- {'\u2715'}
112
- </Text>
113
- </Pressable>
114
-
115
- {!!title && (
116
- <View style={styles.header}>
117
- <Text style={[styles.title, { color: colors.colorOnGreySubtle }]}>
118
- {title}
119
- </Text>
120
- </View>
121
- )}
122
-
123
- {children}
124
- </Pressable>
125
- </Pressable>
126
- </Modal>
127
- );
128
- }
@@ -1,58 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Drawer.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Drawer (React Native)
7
-
8
- A sliding panel that slides in from the bottom of the screen. Built on React Native `Modal` with `Animated` for smooth slide and backdrop fade transitions.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Drawer } from '@boostdev/design-system-components';
16
-
17
- const [open, setOpen] = useState(false);
18
-
19
- <Button onPress={() => setOpen(true)}>Open</Button>
20
- <Drawer isOpen={open} onClose={() => setOpen(false)} title="Settings">
21
- <Text>Drawer content here.</Text>
22
- </Drawer>
23
- ```
24
-
25
- Wrap your tree in `ThemeProvider` (once, at the app root):
26
-
27
- ```tsx
28
- import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
29
-
30
- export default function App() {
31
- return <ThemeProvider><YourApp /></ThemeProvider>;
32
- }
33
- ```
34
-
35
- ## Examples
36
-
37
- <Canvas of={Stories.Default} />
38
-
39
- ## Props
40
-
41
- <ArgTypes of={Stories} />
42
-
43
- ## Props reference
44
-
45
- | Prop | Type | Default | Description |
46
- |------|------|---------|-------------|
47
- | `isOpen` | `boolean` | — | Controls visibility of the drawer |
48
- | `onClose` | `() => void` | — | Called when backdrop or close button is pressed |
49
- | `title` | `ReactNode` | — | Title displayed in the drawer header |
50
- | `children` | `ReactNode` | — | Drawer body content |
51
- | `style` | `StyleProp<ViewStyle>` | — | Override panel styles |
52
-
53
- ## Accessibility
54
-
55
- - Backdrop press and close button both call `onClose`
56
- - Close button has `accessibilityLabel="Close drawer"`
57
- - Android back button triggers `onRequestClose` which calls `onClose`
58
- - Backdrop fades in, panel slides from bottom
@@ -1,81 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../native/ThemeContext';
3
- import { Drawer } from './Drawer.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('Drawer (native)', () => {
10
- it('renders children when open', () => {
11
- renderWithTheme(
12
- <Drawer isOpen onClose={() => {}}>
13
- <span>Drawer content</span>
14
- </Drawer>,
15
- );
16
- expect(screen.getByText('Drawer content')).toBeInTheDocument();
17
- });
18
-
19
- it('does not render children when closed', () => {
20
- renderWithTheme(
21
- <Drawer isOpen={false} onClose={() => {}}>
22
- <span>Hidden drawer</span>
23
- </Drawer>,
24
- );
25
- expect(screen.queryByText('Hidden drawer')).not.toBeInTheDocument();
26
- });
27
-
28
- it('renders the title when provided', () => {
29
- renderWithTheme(
30
- <Drawer isOpen onClose={() => {}} title="Settings">
31
- <span>Body</span>
32
- </Drawer>,
33
- );
34
- expect(screen.getByText('Settings')).toBeInTheDocument();
35
- });
36
-
37
- it('renders a close button with accessible label', () => {
38
- renderWithTheme(
39
- <Drawer isOpen onClose={() => {}}>
40
- <span>Body</span>
41
- </Drawer>,
42
- );
43
- const closeButtons = screen.getAllByRole('button', { name: 'Close drawer' });
44
- expect(closeButtons.length).toBeGreaterThanOrEqual(1);
45
- });
46
-
47
- it('calls onClose when close button is pressed', () => {
48
- const onClose = vi.fn();
49
- renderWithTheme(
50
- <Drawer isOpen onClose={onClose} title="Test">
51
- <span>Body</span>
52
- </Drawer>,
53
- );
54
- const closeButtons = screen.getAllByRole('button', { name: 'Close drawer' });
55
- // The inner close button (not the backdrop)
56
- const innerClose = closeButtons[closeButtons.length - 1];
57
- fireEvent.click(innerClose);
58
- expect(onClose).toHaveBeenCalledTimes(1);
59
- });
60
-
61
- it('calls onClose when backdrop is pressed', () => {
62
- const onClose = vi.fn();
63
- renderWithTheme(
64
- <Drawer isOpen onClose={onClose}>
65
- <span>Body</span>
66
- </Drawer>,
67
- );
68
- const closeButtons = screen.getAllByRole('button', { name: 'Close drawer' });
69
- fireEvent.click(closeButtons[0]);
70
- expect(onClose).toHaveBeenCalledTimes(1);
71
- });
72
-
73
- it('renders panel when open', () => {
74
- const { container } = renderWithTheme(
75
- <Drawer isOpen onClose={() => {}}>
76
- <span>Body</span>
77
- </Drawer>,
78
- );
79
- expect(container).toBeInTheDocument();
80
- });
81
- });
@@ -1,33 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Text } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react';
4
- import { ThemeProvider } from '../../../native/ThemeContext';
5
- import { Drawer } from './Drawer.native';
6
- import { Button } from '../Button/Button.native';
7
-
8
- const meta = {
9
- tags: ['!stable', 'alpha'],
10
- title: 'React Native/Interaction/Drawer',
11
- component: Drawer,
12
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
13
- parameters: { layout: 'centered' },
14
- } satisfies Meta<typeof Drawer>;
15
-
16
- export default meta;
17
- type Story = StoryObj<typeof meta>;
18
-
19
- function DrawerDemo() {
20
- const [open, setOpen] = useState(false);
21
- return (
22
- <>
23
- <Button onPress={() => setOpen(true)}>Open Drawer</Button>
24
- <Drawer isOpen={open} onClose={() => setOpen(false)} title="Settings">
25
- <Text>Drawer content slides in from the bottom.</Text>
26
- </Drawer>
27
- </>
28
- );
29
- }
30
-
31
- export const Default: Story = {
32
- render: () => <DrawerDemo />,
33
- };
@@ -1,175 +0,0 @@
1
- import { ReactNode, useEffect, useRef } from 'react';
2
- import {
3
- Modal,
4
- View,
5
- Pressable,
6
- Text,
7
- Animated,
8
- StyleSheet,
9
- Dimensions,
10
- StyleProp,
11
- ViewStyle,
12
- } from 'react-native';
13
- import { spacing, font, border } from '../../../native/tokens';
14
- import { useTheme } from '../../../native/ThemeContext';
15
-
16
- interface DrawerProps {
17
- isOpen: boolean;
18
- onClose: () => void;
19
- title?: ReactNode;
20
- children?: ReactNode;
21
- style?: StyleProp<ViewStyle>;
22
- }
23
-
24
- const styles = StyleSheet.create({
25
- overlay: {
26
- flex: 1,
27
- justifyContent: 'flex-end',
28
- },
29
- panel: {
30
- maxHeight: '90%',
31
- borderTopStartRadius: border.radius.m,
32
- borderTopEndRadius: border.radius.m,
33
- paddingTop: spacing.m,
34
- paddingHorizontal: spacing.m,
35
- paddingBottom: spacing.m,
36
- shadowColor: '#000',
37
- shadowOffset: { width: 0, height: -4 },
38
- shadowOpacity: 0.15,
39
- shadowRadius: 16,
40
- elevation: 16,
41
- },
42
- header: {
43
- flexDirection: 'row',
44
- justifyContent: 'space-between',
45
- alignItems: 'center',
46
- paddingBottom: spacing.m,
47
- borderBottomWidth: 1,
48
- marginBottom: spacing.m,
49
- },
50
- title: {
51
- fontFamily: font.family.body,
52
- fontSize: font.size.heading3,
53
- fontWeight: font.weight.semibold,
54
- flex: 1,
55
- },
56
- closeButton: {
57
- width: 44,
58
- height: 44,
59
- alignItems: 'center',
60
- justifyContent: 'center',
61
- },
62
- closeText: {
63
- fontFamily: font.family.body,
64
- fontSize: 20,
65
- lineHeight: 24,
66
- },
67
- body: {
68
- flex: 1,
69
- },
70
- });
71
-
72
- export function Drawer({
73
- isOpen,
74
- onClose,
75
- title,
76
- children,
77
- style,
78
- }: Readonly<DrawerProps>) {
79
- const { colors } = useTheme();
80
- const screenHeight = Dimensions.get('window').height;
81
-
82
- const slideAnim = useRef(new Animated.Value(screenHeight)).current;
83
- const backdropAnim = useRef(new Animated.Value(0)).current;
84
-
85
- useEffect(() => {
86
- if (isOpen) {
87
- Animated.parallel([
88
- Animated.timing(slideAnim, {
89
- toValue: 0,
90
- duration: 250,
91
- useNativeDriver: true,
92
- }),
93
- Animated.timing(backdropAnim, {
94
- toValue: 1,
95
- duration: 250,
96
- useNativeDriver: true,
97
- }),
98
- ]).start();
99
- } else {
100
- Animated.parallel([
101
- Animated.timing(slideAnim, {
102
- toValue: screenHeight,
103
- duration: 200,
104
- useNativeDriver: true,
105
- }),
106
- Animated.timing(backdropAnim, {
107
- toValue: 0,
108
- duration: 200,
109
- useNativeDriver: true,
110
- }),
111
- ]).start();
112
- }
113
- }, [isOpen, slideAnim, backdropAnim, screenHeight]);
114
-
115
- return (
116
- <Modal
117
- visible={isOpen}
118
- transparent
119
- animationType="none"
120
- onRequestClose={onClose}
121
- >
122
- <View style={styles.overlay}>
123
- <Animated.View
124
- style={[
125
- StyleSheet.absoluteFill,
126
- { backgroundColor: 'rgba(0, 0, 0, 0.5)', opacity: backdropAnim },
127
- ]}
128
- >
129
- <Pressable
130
- style={StyleSheet.absoluteFill}
131
- onPress={onClose}
132
- accessibilityRole="button"
133
- accessibilityLabel="Close drawer"
134
- />
135
- </Animated.View>
136
- <Animated.View
137
- style={[
138
- styles.panel,
139
- {
140
- backgroundColor: colors.colorBg,
141
- transform: [{ translateY: slideAnim }],
142
- },
143
- style,
144
- ]}
145
- accessibilityRole="none"
146
- >
147
- <View
148
- style={[styles.header, { borderBottomColor: colors.colorBgSubtle }]}
149
- >
150
- {!!title && (
151
- <Text style={[styles.title, { color: colors.colorOnBg }]}>
152
- {title}
153
- </Text>
154
- )}
155
- <Pressable
156
- style={styles.closeButton}
157
- onPress={onClose}
158
- accessibilityRole="button"
159
- accessibilityLabel="Close drawer"
160
- hitSlop={8}
161
- >
162
- <Text
163
- style={[styles.closeText, { color: colors.colorOnBgSubtle }]}
164
- aria-hidden
165
- >
166
- {'\u2715'}
167
- </Text>
168
- </Pressable>
169
- </View>
170
- <View style={styles.body}>{children}</View>
171
- </Animated.View>
172
- </View>
173
- </Modal>
174
- );
175
- }
@@ -1,74 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './DropdownMenu.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # DropdownMenu (React Native)
7
-
8
- A dropdown menu that opens as a bottom sheet modal on mobile. Accepts a trigger element and a list of menu items.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { DropdownMenu } from '@boostdev/design-system-components';
16
-
17
- <DropdownMenu
18
- trigger={<Button>Actions</Button>}
19
- title="Actions"
20
- items={[
21
- { id: 'edit', label: 'Edit', onPress: () => console.log('edit') },
22
- { id: 'delete', label: 'Delete', onPress: () => console.log('delete') },
23
- ]}
24
- />
25
- ```
26
-
27
- Wrap your tree in `ThemeProvider` (once, at the app root):
28
-
29
- ```tsx
30
- import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
31
-
32
- export default function App() {
33
- return <ThemeProvider><YourApp /></ThemeProvider>;
34
- }
35
- ```
36
-
37
- ## Examples
38
-
39
- ### Default
40
- <Canvas of={Stories.Default} />
41
-
42
- ### With disabled items
43
- <Canvas of={Stories.WithDisabledItems} />
44
-
45
- ## Props
46
-
47
- <ArgTypes of={Stories} />
48
-
49
- ## Props reference
50
-
51
- | Prop | Type | Default | Description |
52
- |------|------|---------|-------------|
53
- | `trigger` | `ReactElement` | — | Element that opens the menu on press |
54
- | `items` | `DropdownMenuItem[]` | — | Array of menu items to display |
55
- | `title` | `string` | `'Menu'` | Title displayed in the sheet header |
56
- | `style` | `StyleProp<ViewStyle>` | — | Override wrapper styles |
57
-
58
- ### DropdownMenuItem
59
-
60
- | Property | Type | Default | Description |
61
- |----------|------|---------|-------------|
62
- | `id` | `string` | — | Unique identifier |
63
- | `label` | `string` | — | Display text |
64
- | `onPress` | `() => void` | — | Called when item is pressed |
65
- | `disabled` | `boolean` | `false` | Prevents interaction |
66
- | `icon` | `ReactNode` | — | Optional icon before the label |
67
- | `separator` | `boolean` | `false` | Renders a separator line above the item |
68
-
69
- ## Accessibility
70
-
71
- - Trigger has `accessibilityRole="button"` with `expanded` state
72
- - Menu items have `accessibilityRole="menuitem"`
73
- - Disabled items communicate their state to assistive technologies
74
- - Android back button closes the menu
@@ -1,78 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { Text, Pressable } from 'react-native';
3
- import { ThemeProvider } from '../../../native/ThemeContext';
4
- import { DropdownMenu } from './DropdownMenu.native';
5
-
6
- function renderWithTheme(ui: React.ReactElement) {
7
- return render(<ThemeProvider>{ui}</ThemeProvider>);
8
- }
9
-
10
- function Trigger({ children, ...rest }: { children: string; onPress?: () => void }) {
11
- return <Pressable {...rest}><Text>{children}</Text></Pressable>;
12
- }
13
-
14
- const items = [
15
- { id: 'edit', label: 'Edit' },
16
- { id: 'delete', label: 'Delete' },
17
- ];
18
-
19
- const itemsWithDisabled = [
20
- { id: 'view', label: 'View' },
21
- { id: 'share', label: 'Share', disabled: true },
22
- ];
23
-
24
- describe('DropdownMenu (native)', () => {
25
- it('renders the trigger', () => {
26
- renderWithTheme(
27
- <DropdownMenu trigger={<Trigger>Open</Trigger>} items={items} />,
28
- );
29
- expect(screen.getByText('Open')).toBeInTheDocument();
30
- });
31
-
32
- it('opens the menu on trigger press', () => {
33
- renderWithTheme(
34
- <DropdownMenu trigger={<Trigger>Menu</Trigger>} items={items} title="Actions" />,
35
- );
36
- fireEvent.click(screen.getByRole('button', { name: 'Actions' }));
37
- expect(screen.getByText('Edit')).toBeInTheDocument();
38
- expect(screen.getByText('Delete')).toBeInTheDocument();
39
- });
40
-
41
- it('calls item onPress and closes menu when item is pressed', () => {
42
- const onEdit = vi.fn();
43
- const menuItems = [
44
- { id: 'edit', label: 'Edit', onPress: onEdit },
45
- { id: 'delete', label: 'Delete' },
46
- ];
47
- renderWithTheme(
48
- <DropdownMenu trigger={<Trigger>Actions</Trigger>} items={menuItems} title="Actions" />,
49
- );
50
- fireEvent.click(screen.getByRole('button', { name: 'Actions' }));
51
- fireEvent.click(screen.getByRole('menuitem', { name: 'Edit' }));
52
- expect(onEdit).toHaveBeenCalledTimes(1);
53
- });
54
-
55
- it('renders disabled items with disabled state', () => {
56
- renderWithTheme(
57
- <DropdownMenu trigger={<Trigger>Options</Trigger>} items={itemsWithDisabled} title="Options" />,
58
- );
59
- fireEvent.click(screen.getByRole('button', { name: 'Options' }));
60
- expect(screen.getByRole('menuitem', { name: 'Share' })).toHaveAttribute('aria-disabled', 'true');
61
- });
62
-
63
- it('renders a close button in the sheet header', () => {
64
- renderWithTheme(
65
- <DropdownMenu trigger={<Trigger>Menu</Trigger>} items={items} title="Menu" />,
66
- );
67
- fireEvent.click(screen.getByRole('button', { name: 'Menu' }));
68
- expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
69
- });
70
-
71
- it('renders the title in the sheet header', () => {
72
- renderWithTheme(
73
- <DropdownMenu trigger={<Trigger>Trigger</Trigger>} items={items} title="My Menu" />,
74
- );
75
- fireEvent.click(screen.getByRole('button', { name: 'My Menu' }));
76
- expect(screen.getByText('My Menu')).toBeInTheDocument();
77
- });
78
- });
@@ -1,51 +0,0 @@
1
- import React from 'react';
2
- import { Text } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react';
4
- import { ThemeProvider } from '../../../native/ThemeContext';
5
- import { DropdownMenu } from './DropdownMenu.native';
6
- import { Button } from '../Button/Button.native';
7
-
8
- const meta = {
9
- tags: ['!stable', 'alpha'],
10
- title: 'React Native/Interaction/DropdownMenu',
11
- component: DropdownMenu,
12
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
13
- parameters: { layout: 'centered' },
14
- } satisfies Meta<typeof DropdownMenu>;
15
-
16
- export default meta;
17
- type Story = StoryObj<typeof meta>;
18
-
19
- const defaultItems = [
20
- { id: 'edit', label: 'Edit' },
21
- { id: 'duplicate', label: 'Duplicate' },
22
- { id: 'archive', label: 'Archive', separator: true },
23
- { id: 'delete', label: 'Delete' },
24
- ];
25
-
26
- const withDisabledItems = [
27
- { id: 'view', label: 'View details' },
28
- { id: 'edit', label: 'Edit' },
29
- { id: 'share', label: 'Share', disabled: true },
30
- { id: 'delete', label: 'Delete', separator: true },
31
- ];
32
-
33
- export const Default: Story = {
34
- render: () => (
35
- <DropdownMenu
36
- trigger={<Button>Actions</Button>}
37
- items={defaultItems}
38
- title="Actions"
39
- />
40
- ),
41
- };
42
-
43
- export const WithDisabledItems: Story = {
44
- render: () => (
45
- <DropdownMenu
46
- trigger={<Text>{'More \u2026'}</Text>}
47
- items={withDisabledItems}
48
- title="Options"
49
- />
50
- ),
51
- };