@boostdev/design-system-components 2.4.2 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +194 -4
  17. package/src/components/layout/Grid/Grid.tsx +15 -1
  18. package/src/components/layout/Grid/masonry.ts +127 -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,50 +0,0 @@
1
- import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
2
- import { font } from '../../../native/tokens';
3
- import { useTheme } from '../../../native/ThemeContext';
4
-
5
- interface RatingProps {
6
- value: number;
7
- max?: number;
8
- style?: StyleProp<ViewStyle>;
9
- accessibilityLabel?: string;
10
- }
11
-
12
- const STAR_SIZE = 24;
13
-
14
- const styles = StyleSheet.create({
15
- container: {
16
- flexDirection: 'row',
17
- alignItems: 'center',
18
- },
19
- star: {
20
- fontSize: STAR_SIZE,
21
- fontFamily: font.family.body,
22
- },
23
- });
24
-
25
- export function Rating({ value, max = 5, style, accessibilityLabel }: Readonly<RatingProps>) {
26
- const { colors } = useTheme();
27
- const label = accessibilityLabel ?? `${value} out of ${max} stars`;
28
-
29
- return (
30
- <View
31
- style={[styles.container, style]}
32
- accessibilityRole="image"
33
- accessibilityLabel={label}
34
- >
35
- {Array.from({ length: max }).map((_, i) => (
36
- <Text
37
- key={i}
38
- style={[
39
- styles.star,
40
- { color: i < value ? colors.colorWarning : colors.colorBgSubtle },
41
- ]}
42
- importantForAccessibility="no"
43
- accessibilityElementsHidden={true}
44
- >
45
- {'\u2605'}
46
- </Text>
47
- ))}
48
- </View>
49
- );
50
- }
@@ -1,81 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Toast.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Toast (React Native)
7
-
8
- A toast notification system using React context. Provides `ToastProvider` and `useToast` hook. Toasts appear at the top of the screen with slide-in animation and auto-dismiss after 5 seconds.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- Wrap your app root in `ToastProvider`:
15
-
16
- ```tsx
17
- import { ToastProvider } from '@boostdev/design-system-components';
18
- import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
19
-
20
- export default function App() {
21
- return (
22
- <ThemeProvider>
23
- <ToastProvider>
24
- <YourApp />
25
- </ToastProvider>
26
- </ThemeProvider>
27
- );
28
- }
29
- ```
30
-
31
- Then use `useToast` in any descendant:
32
-
33
- ```tsx
34
- import { useToast } from '@boostdev/design-system-components';
35
-
36
- function SaveButton() {
37
- const { showToast } = useToast();
38
- return (
39
- <Button onPress={() => showToast('Saved successfully!', 'success')}>
40
- Save
41
- </Button>
42
- );
43
- }
44
- ```
45
-
46
- ## Variants
47
-
48
- - `'success'` — green background
49
- - `'error'` — red background
50
- - `'info'` — interactive/blue background
51
-
52
- ## Examples
53
-
54
- ### Default
55
- <Canvas of={Stories.Default} />
56
-
57
- ## API
58
-
59
- ### ToastProvider
60
-
61
- Wraps the application and renders toast notifications.
62
-
63
- | Prop | Type | Default | Description |
64
- |------|------|---------|-------------|
65
- | `children` | `ReactNode` | — | Application content |
66
- | `style` | `StyleProp<ViewStyle>` | — | Override wrapper styles |
67
-
68
- ### useToast
69
-
70
- Returns an object with:
71
-
72
- | Method | Signature | Description |
73
- |--------|-----------|-------------|
74
- | `showToast` | `(message: string, variant: 'success' \| 'error' \| 'info') => void` | Displays a toast notification |
75
-
76
- ## Accessibility
77
-
78
- - Toasts have `accessibilityRole="alert"` and `accessibilityLiveRegion="polite"`
79
- - Dismiss button has `accessibilityLabel="Dismiss"`
80
- - Toasts auto-dismiss after 5 seconds
81
- - Throws if `useToast` is called outside `ToastProvider`
@@ -1,80 +0,0 @@
1
- import { render, screen, act, fireEvent } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../native/ThemeContext';
3
- import { ToastProvider, useToast } from './Toast.native';
4
-
5
- function ToastTrigger({ message = 'Test toast', variant = 'info' as const }) {
6
- const { showToast } = useToast();
7
- return <button onClick={() => showToast(message, variant)}>Show Toast</button>;
8
- }
9
-
10
- function setup(props = {}) {
11
- return render(
12
- <ThemeProvider>
13
- <ToastProvider>
14
- <ToastTrigger {...props} />
15
- </ToastProvider>
16
- </ThemeProvider>,
17
- );
18
- }
19
-
20
- describe('Toast (native)', () => {
21
- it('shows a toast when showToast is called', () => {
22
- setup({ message: 'Hello toast' });
23
- fireEvent.click(screen.getByRole('button'));
24
- expect(screen.getByText('Hello toast')).toBeInTheDocument();
25
- });
26
-
27
- it('removes the toast after 5 seconds', () => {
28
- vi.useFakeTimers();
29
- setup({ message: 'Timed toast' });
30
- act(() => {
31
- fireEvent.click(screen.getByRole('button'));
32
- });
33
- expect(screen.getByText('Timed toast')).toBeInTheDocument();
34
- act(() => {
35
- vi.advanceTimersByTime(5100);
36
- });
37
- expect(screen.queryByText('Timed toast')).not.toBeInTheDocument();
38
- vi.useRealTimers();
39
- });
40
-
41
- it('renders dismiss button with accessible label', () => {
42
- setup({ message: 'Dismissable' });
43
- fireEvent.click(screen.getByRole('button', { name: 'Show Toast' }));
44
- expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
45
- });
46
-
47
- it('removes the toast when dismiss button is clicked', () => {
48
- setup({ message: 'Dismissable toast' });
49
- fireEvent.click(screen.getByRole('button', { name: 'Show Toast' }));
50
- expect(screen.getByText('Dismissable toast')).toBeInTheDocument();
51
- fireEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
52
- expect(screen.queryByText('Dismissable toast')).not.toBeInTheDocument();
53
- });
54
-
55
- it('throws when useToast is used outside ToastProvider', () => {
56
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
57
- const Bad = () => {
58
- useToast();
59
- return null;
60
- };
61
- expect(() =>
62
- render(
63
- <ThemeProvider>
64
- <Bad />
65
- </ThemeProvider>,
66
- ),
67
- ).toThrow('useToast must be used within a ToastProvider');
68
- errorSpy.mockRestore();
69
- });
70
-
71
- it('renders multiple toasts simultaneously', () => {
72
- setup({ message: 'Toast 1' });
73
- const trigger = screen.getByRole('button', { name: 'Show Toast' });
74
- fireEvent.click(trigger);
75
- fireEvent.click(trigger);
76
- // At least one toast should be visible (they share the same message text)
77
- const toasts = screen.getAllByText('Toast 1');
78
- expect(toasts.length).toBe(2);
79
- });
80
- });
@@ -1,47 +0,0 @@
1
- import React from 'react';
2
- import { View } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react';
4
- import { ThemeProvider } from '../../../native/ThemeContext';
5
- import { ToastProvider, useToast } from './Toast.native';
6
- import { Button } from '../Button/Button.native';
7
-
8
- const meta = {
9
- tags: ['!stable', 'alpha'],
10
- title: 'React Native/Interaction/Toast',
11
- component: ToastProvider,
12
- parameters: { layout: 'padded' },
13
- } satisfies Meta<typeof ToastProvider>;
14
-
15
- export default meta;
16
- type Story = StoryObj<typeof meta>;
17
-
18
- function ToastControls() {
19
- const { showToast } = useToast();
20
- return (
21
- <View style={{ gap: 12, paddingTop: 80 }}>
22
- <Button onPress={() => showToast('Item saved successfully.', 'success')}>
23
- Show Success Toast
24
- </Button>
25
- <Button onPress={() => showToast('Something went wrong. Please try again.', 'error')}>
26
- Show Error Toast
27
- </Button>
28
- <Button onPress={() => showToast('Your session will expire in 5 minutes.', 'info')}>
29
- Show Info Toast
30
- </Button>
31
- </View>
32
- );
33
- }
34
-
35
- function ToastDemo() {
36
- return (
37
- <ThemeProvider colorScheme="light">
38
- <ToastProvider>
39
- <ToastControls />
40
- </ToastProvider>
41
- </ThemeProvider>
42
- );
43
- }
44
-
45
- export const Default: Story = {
46
- render: () => <ToastDemo />,
47
- };
@@ -1,202 +0,0 @@
1
- import {
2
- useState,
3
- useEffect,
4
- useRef,
5
- useCallback,
6
- useMemo,
7
- createContext,
8
- useContext,
9
- ReactNode,
10
- } from 'react';
11
- import {
12
- View,
13
- Text,
14
- Pressable,
15
- Animated,
16
- StyleSheet,
17
- StyleProp,
18
- ViewStyle,
19
- } from 'react-native';
20
- import { spacing, font, border } from '../../../native/tokens';
21
- import { useTheme } from '../../../native/ThemeContext';
22
-
23
- type ToastVariant = 'success' | 'error' | 'info';
24
-
25
- interface Toast {
26
- id: string;
27
- message: string;
28
- variant: ToastVariant;
29
- }
30
-
31
- interface ToastContextType {
32
- showToast: (message: string, variant: ToastVariant) => void;
33
- }
34
-
35
- const ToastContext = createContext<ToastContextType | undefined>(undefined);
36
-
37
- const styles = StyleSheet.create({
38
- container: {
39
- position: 'absolute',
40
- top: 0,
41
- start: 0,
42
- end: 0,
43
- paddingTop: spacing.xxxl,
44
- paddingHorizontal: spacing.m,
45
- zIndex: 9999,
46
- pointerEvents: 'box-none',
47
- },
48
- toast: {
49
- flexDirection: 'row',
50
- alignItems: 'center',
51
- justifyContent: 'space-between',
52
- paddingVertical: spacing.s,
53
- paddingHorizontal: spacing.m,
54
- borderRadius: border.radius.s,
55
- marginBottom: spacing.xs,
56
- minHeight: 48,
57
- },
58
- message: {
59
- fontFamily: font.family.body,
60
- fontSize: font.size.body,
61
- fontWeight: font.weight.semibold,
62
- flex: 1,
63
- },
64
- closeButton: {
65
- width: 44,
66
- height: 44,
67
- alignItems: 'center',
68
- justifyContent: 'center',
69
- marginStart: spacing.xs,
70
- },
71
- closeText: {
72
- fontFamily: font.family.body,
73
- fontSize: 16,
74
- lineHeight: 20,
75
- },
76
- });
77
-
78
- function ToastItem({
79
- toast,
80
- onRemove,
81
- }: {
82
- toast: Toast;
83
- onRemove: () => void;
84
- }) {
85
- const { colors } = useTheme();
86
- const slideAnim = useRef(new Animated.Value(-100)).current;
87
- const opacityAnim = useRef(new Animated.Value(0)).current;
88
-
89
- const variantColors: Record<
90
- ToastVariant,
91
- { bg: string; text: string }
92
- > = useMemo(
93
- () => ({
94
- success: { bg: colors.colorSuccess, text: colors.colorOnSuccess },
95
- error: { bg: colors.colorError, text: colors.colorOnError },
96
- info: { bg: colors.colorBgSubtle, text: colors.colorOnBgSubtle },
97
- }),
98
- [colors],
99
- );
100
-
101
- useEffect(() => {
102
- Animated.parallel([
103
- Animated.timing(slideAnim, {
104
- toValue: 0,
105
- duration: 200,
106
- useNativeDriver: true,
107
- }),
108
- Animated.timing(opacityAnim, {
109
- toValue: 1,
110
- duration: 200,
111
- useNativeDriver: true,
112
- }),
113
- ]).start();
114
- }, [slideAnim, opacityAnim]);
115
-
116
- useEffect(() => {
117
- const timer = setTimeout(onRemove, 5000);
118
- return () => clearTimeout(timer);
119
- }, [onRemove]);
120
-
121
- const { bg, text } = variantColors[toast.variant];
122
-
123
- return (
124
- <Animated.View
125
- style={[
126
- styles.toast,
127
- {
128
- backgroundColor: bg,
129
- transform: [{ translateY: slideAnim }],
130
- opacity: opacityAnim,
131
- },
132
- ]}
133
- accessibilityRole="alert"
134
- accessibilityLiveRegion="polite"
135
- >
136
- <Text style={[styles.message, { color: text }]}>{toast.message}</Text>
137
- <Pressable
138
- style={styles.closeButton}
139
- onPress={onRemove}
140
- accessibilityRole="button"
141
- accessibilityLabel="Dismiss"
142
- hitSlop={8}
143
- >
144
- <Text style={[styles.closeText, { color: text }]} aria-hidden>
145
- {'\u2715'}
146
- </Text>
147
- </Pressable>
148
- </Animated.View>
149
- );
150
- }
151
-
152
- export function ToastProvider({
153
- children,
154
- style,
155
- }: {
156
- children: ReactNode;
157
- style?: StyleProp<ViewStyle>;
158
- }) {
159
- const [toasts, setToasts] = useState<Toast[]>([]);
160
-
161
- const showToast = useCallback((message: string, variant: ToastVariant) => {
162
- const id = Math.random().toString(36).substring(2, 9);
163
- setToasts((prev) => [...prev, { id, message, variant }]);
164
- }, []);
165
-
166
- const removeToast = useCallback((id: string) => {
167
- setToasts((prev) => prev.filter((t) => t.id !== id));
168
- }, []);
169
-
170
- const value = useMemo(() => ({ showToast }), [showToast]);
171
-
172
- return (
173
- <ToastContext.Provider value={value}>
174
- <View style={[{ flex: 1 }, style]}>
175
- {children}
176
- {toasts.length > 0 && (
177
- <View
178
- style={styles.container}
179
- accessibilityRole="summary"
180
- accessibilityLabel="Notifications"
181
- >
182
- {toasts.map((toast) => (
183
- <ToastItem
184
- key={toast.id}
185
- toast={toast}
186
- onRemove={() => removeToast(toast.id)}
187
- />
188
- ))}
189
- </View>
190
- )}
191
- </View>
192
- </ToastContext.Provider>
193
- );
194
- }
195
-
196
- export function useToast(): ToastContextType {
197
- const context = useContext(ToastContext);
198
- if (!context) {
199
- throw new Error('useToast must be used within a ToastProvider');
200
- }
201
- return context;
202
- }
@@ -1,74 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Checkbox.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Checkbox (React Native)
7
-
8
- React Native checkbox component with label, optional hint, and error state. Fully controlled — manage checked state via `checked` + `onChange`.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Checkbox } from '@boostdev/design-system-components';
16
- import { useState } from 'react';
17
-
18
- function Example() {
19
- const [checked, setChecked] = useState(false);
20
- return (
21
- <Checkbox
22
- name="terms"
23
- label="Accept terms and conditions"
24
- checked={checked}
25
- onChange={setChecked}
26
- />
27
- );
28
- }
29
- ```
30
-
31
- ## Examples
32
-
33
- ### Interactive
34
- <Canvas of={Stories.Interactive} />
35
-
36
- ### Unchecked
37
- <Canvas of={Stories.Unchecked} />
38
-
39
- ### Checked
40
- <Canvas of={Stories.Checked} />
41
-
42
- ### With hint
43
- <Canvas of={Stories.WithHint} />
44
-
45
- ### With error
46
- <Canvas of={Stories.WithError} />
47
-
48
- ### Disabled
49
- <Canvas of={Stories.Disabled} />
50
-
51
- ### Disabled + checked
52
- <Canvas of={Stories.DisabledChecked} />
53
-
54
- ## Props
55
-
56
- <ArgTypes of={Stories} />
57
-
58
- ## Props reference
59
-
60
- | Prop | Type | Default | Description |
61
- |------|------|---------|-------------|
62
- | `name` | string | — | Field name (used for accessibility grouping) |
63
- | `label` | string | — | Visible label text |
64
- | `checked` | boolean | `false` | Controlled checked state |
65
- | `onChange` | `(checked: boolean) => void` | — | Called with the new value on press |
66
- | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
67
- | `hint` | string | — | Helper text below the label |
68
- | `error` | string | — | Error message; shown instead of hint when set |
69
-
70
- ## Accessibility
71
-
72
- - The label, hint, and error are announced together by screen readers
73
- - `disabled` communicates the disabled state via `accessibilityState`
74
- - Group related checkboxes using a wrapping `View` with `accessibilityRole="radiogroup"` or a labelled `fieldset` equivalent
@@ -1,42 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Checkbox } from './Checkbox.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('Checkbox (native)', () => {
10
- it('has checkbox role with label', () => {
11
- renderWithTheme(<Checkbox label="Accept terms" name="terms" />);
12
- expect(screen.getByRole('checkbox', { name: 'Accept terms' })).toBeInTheDocument();
13
- });
14
-
15
- it('shows the checkmark when checked', () => {
16
- renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
17
- expect(screen.getByText('✓')).toBeInTheDocument();
18
- });
19
-
20
- it('hides the checkmark when unchecked', () => {
21
- renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={false} />);
22
- expect(screen.queryByText('✓')).not.toBeInTheDocument();
23
- });
24
-
25
- it('checkmark uses theme color (contrast regression: was hardcoded #fff)', () => {
26
- // Previously the checkmark had color: '#fff' hardcoded in the StyleSheet.
27
- // Now it uses colors.colorOnInteractive from the theme, which is #000 in dark
28
- // mode — fixing the 2.52:1 contrast failure on the dark interactive blue.
29
- renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
30
- const checkmark = screen.getByText('✓');
31
- // The color must be applied as an inline style (overrides StyleSheet), not absent.
32
- // In light mode colorOnInteractive=#fff; in dark mode it is #000.
33
- expect(checkmark.style.color).toBeTruthy();
34
- });
35
-
36
- it('renders with error message', () => {
37
- renderWithTheme(
38
- <Checkbox label="Accept terms" name="terms" error="You must accept." />,
39
- );
40
- expect(screen.getByRole('alert')).toHaveTextContent('You must accept.');
41
- });
42
- });
@@ -1,34 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { Checkbox } from './Checkbox.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/Checkbox',
9
- component: Checkbox,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof Checkbox>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Unchecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: false } };
18
- export const Checked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true } };
19
- export const Disabled: Story = { args: { label: 'Accept terms', name: 'terms', disabled: true } };
20
- export const DisabledChecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true, disabled: true } };
21
- export const WithError: Story = {
22
- args: { label: 'Accept terms', name: 'terms', error: 'You must accept the terms.' },
23
- };
24
- export const WithHint: Story = {
25
- args: { label: 'Subscribe to newsletter', name: 'newsletter', hint: 'Max one email per week.' },
26
- };
27
- export const Interactive: Story = {
28
- render: () => {
29
- const [checked, setChecked] = useState(false);
30
- return (
31
- <Checkbox label="Accept terms and conditions" name="terms" checked={checked} onChange={setChecked} />
32
- );
33
- },
34
- };
@@ -1,80 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
3
- import { spacing, border } from '../../../../native/tokens';
4
- import { useTheme } from '../../../../native/ThemeContext';
5
- import { Label } from '../atoms/Label.native';
6
- import { Message } from '../atoms/Message.native';
7
-
8
- interface CheckboxProps {
9
- label: ReactNode;
10
- name: string;
11
- checked?: boolean;
12
- onChange?: (checked: boolean) => void;
13
- disabled?: boolean;
14
- error?: string;
15
- hint?: string;
16
- style?: StyleProp<ViewStyle>;
17
- }
18
-
19
- const BOX = 22;
20
-
21
- const styles = StyleSheet.create({
22
- container: {
23
- gap: spacing.xxs,
24
- },
25
- row: {
26
- flexDirection: 'row',
27
- alignItems: 'center',
28
- gap: spacing.xs,
29
- minHeight: 44,
30
- },
31
- box: {
32
- width: BOX,
33
- height: BOX,
34
- borderRadius: border.radius.xs,
35
- borderWidth: 2,
36
- alignItems: 'center',
37
- justifyContent: 'center',
38
- },
39
- checkmark: {
40
- fontSize: 14,
41
- fontWeight: '700',
42
- lineHeight: 16,
43
- },
44
- });
45
-
46
- export function Checkbox({
47
- label,
48
- name,
49
- checked = false,
50
- onChange,
51
- disabled = false,
52
- error,
53
- hint,
54
- style,
55
- }: CheckboxProps) {
56
- const { colors } = useTheme();
57
-
58
- const borderColor = error ? colors.colorError : checked ? colors.colorInteractive : colors.colorOnBg;
59
- const bgColor = checked ? colors.colorInteractive : 'transparent';
60
-
61
- return (
62
- <View style={[styles.container, style]}>
63
- <Pressable
64
- onPress={() => !disabled && onChange?.(!checked)}
65
- accessibilityRole="checkbox"
66
- accessibilityLabel={typeof label === 'string' ? label : name}
67
- accessibilityState={{ checked, disabled }}
68
- disabled={disabled}
69
- style={({ pressed }) => [styles.row, pressed && { opacity: 0.7 }, disabled && { opacity: 0.4 }]}
70
- >
71
- <View style={[styles.box, { borderColor, backgroundColor: bgColor }]}>
72
- {checked && <Label label="✓" style={[styles.checkmark, { color: colors.colorOnInteractive }]} />}
73
- </View>
74
- <Label label={label} />
75
- </Pressable>
76
- <Message type="error" message={error} />
77
- <Message type="hint" message={hint} />
78
- </View>
79
- );
80
- }