@boostdev/design-system-components 2.4.1 → 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 +128 -62
  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 +128 -62
  8. package/dist/index.cjs +128 -62
  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 +128 -62
  13. package/dist/web-components/index.js +36 -4
  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 +206 -4
  17. package/src/components/layout/Grid/Grid.tsx +15 -1
  18. package/src/components/layout/Grid/masonry.ts +148 -10
  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,121 +0,0 @@
1
- import { Children, ReactNode } from 'react';
2
- import { StyleSheet, View, Pressable, Text, StyleProp, ViewStyle } from 'react-native';
3
- import { spacing, font, border } from '../../../../native/tokens';
4
- import { useTheme } from '../../../../native/ThemeContext';
5
-
6
- type SegmentedControlSize = 'small' | 'medium' | 'large';
7
- type SegmentedControlVariant = 'outline' | 'filled';
8
-
9
- interface SegmentedControlProps {
10
- selectedIndex?: number;
11
- disabled?: boolean;
12
- size?: SegmentedControlSize;
13
- variant?: SegmentedControlVariant;
14
- children: ReactNode;
15
- accessibilityLabel?: string;
16
- onValueChange?: (index: number) => void;
17
- style?: StyleProp<ViewStyle>;
18
- }
19
-
20
- const sizeTokens: Record<SegmentedControlSize, { height: number; fontSize: number; px: number }> = {
21
- small: { height: 32, fontSize: font.size.bodyS, px: spacing.s },
22
- medium: { height: 40, fontSize: font.size.body, px: spacing.m },
23
- large: { height: 48, fontSize: font.size.body, px: spacing.l },
24
- };
25
-
26
- const styles = StyleSheet.create({
27
- track: {
28
- flexDirection: 'row',
29
- borderRadius: border.radius.full,
30
- overflow: 'hidden',
31
- },
32
- item: {
33
- flex: 1,
34
- alignItems: 'center',
35
- justifyContent: 'center',
36
- minHeight: 44,
37
- },
38
- label: {
39
- fontFamily: font.family.body,
40
- fontWeight: font.weight.semibold,
41
- textAlign: 'center',
42
- },
43
- });
44
-
45
- export function SegmentedControl({
46
- selectedIndex = 0,
47
- disabled = false,
48
- size = 'medium',
49
- variant = 'outline',
50
- children,
51
- accessibilityLabel,
52
- onValueChange,
53
- style,
54
- }: Readonly<SegmentedControlProps>) {
55
- const { colors } = useTheme();
56
- const { height, fontSize, px } = sizeTokens[size];
57
- const items = Children.toArray(children);
58
-
59
- const trackStyle: ViewStyle = variant === 'filled'
60
- ? { backgroundColor: colors.colorBgSubtle }
61
- : { backgroundColor: 'transparent', borderWidth: 1, borderColor: colors.colorBgSubtle };
62
-
63
- return (
64
- <View
65
- style={[styles.track, trackStyle, { borderRadius: border.radius.full }, style]}
66
- accessibilityRole="tablist"
67
- accessibilityLabel={accessibilityLabel}
68
- >
69
- {items.map((child, index) => {
70
- const isActive = index === selectedIndex;
71
-
72
- const activeStyle: ViewStyle = variant === 'filled'
73
- ? { backgroundColor: colors.colorInteractive }
74
- : { borderWidth: 2, borderColor: colors.colorInteractive };
75
-
76
- const inactiveStyle: ViewStyle = variant === 'filled'
77
- ? {}
78
- : {};
79
-
80
- const textColor = variant === 'filled' && isActive
81
- ? colors.colorOnInteractive
82
- : isActive
83
- ? colors.colorInteractive
84
- : colors.colorOnBgSubtle;
85
-
86
- // Extract label from child: if it's a string, use it directly.
87
- // Otherwise render the child as-is.
88
- const label = typeof child === 'string' || typeof child === 'number'
89
- ? String(child)
90
- : null;
91
-
92
- return (
93
- <Pressable
94
- key={index}
95
- onPress={() => !disabled && onValueChange?.(index)}
96
- disabled={disabled}
97
- accessibilityRole="tab"
98
- accessibilityLabel={label ?? undefined}
99
- accessibilityState={{ selected: isActive, disabled }}
100
- style={({ pressed }) => [
101
- styles.item,
102
- { height, paddingHorizontal: px },
103
- isActive ? activeStyle : inactiveStyle,
104
- variant === 'filled' && isActive && { borderRadius: border.radius.full },
105
- pressed && !disabled && { opacity: 0.7 },
106
- disabled && { opacity: 0.4 },
107
- ]}
108
- >
109
- {label !== null ? (
110
- <Text style={[styles.label, { fontSize, color: textColor }]}>
111
- {label}
112
- </Text>
113
- ) : (
114
- child
115
- )}
116
- </Pressable>
117
- );
118
- })}
119
- </View>
120
- );
121
- }
@@ -1,85 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Select.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Select (React Native)
7
-
8
- Dropdown-style select component. Renders a trigger button that opens a bottom sheet modal with a scrollable list of options.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Select } from '@boostdev/design-system-components';
16
- import { useState } from 'react';
17
-
18
- const options = [
19
- { value: 'apple', label: 'Apple' },
20
- { value: 'banana', label: 'Banana' },
21
- { value: 'cherry', label: 'Cherry' },
22
- ];
23
-
24
- function Example() {
25
- const [value, setValue] = useState('');
26
- return (
27
- <Select
28
- label="Fruit"
29
- name="fruit"
30
- options={options}
31
- placeholder="Choose a fruit..."
32
- value={value}
33
- onChange={setValue}
34
- />
35
- );
36
- }
37
- ```
38
-
39
- ## Examples
40
-
41
- ### Default
42
- <Canvas of={Stories.Default} />
43
-
44
- ### With placeholder
45
- <Canvas of={Stories.WithPlaceholder} />
46
-
47
- ### With selected value
48
- <Canvas of={Stories.WithValue} />
49
-
50
- ### With error
51
- <Canvas of={Stories.WithError} />
52
-
53
- ### With hint
54
- <Canvas of={Stories.WithHint} />
55
-
56
- ### Disabled
57
- <Canvas of={Stories.Disabled} />
58
-
59
- ### Interactive
60
- <Canvas of={Stories.Interactive} />
61
-
62
- ## Props
63
-
64
- <ArgTypes of={Stories} />
65
-
66
- ## Props reference
67
-
68
- | Prop | Type | Default | Description |
69
- |------|------|---------|-------------|
70
- | `label` | ReactNode | — | Visible label text |
71
- | `name` | string | — | Field name (used for accessibility) |
72
- | `options` | `SelectOption[]` | — | Array of `{ value, label, disabled? }` |
73
- | `placeholder` | string | `'Select an option'` | Placeholder when no value is selected |
74
- | `value` | string | — | Currently selected value |
75
- | `error` | string | — | Error message displayed below the trigger |
76
- | `hint` | string | — | Hint text displayed below the trigger |
77
- | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
78
- | `onChange` | `(value: string) => void` | — | Called with the selected option value |
79
-
80
- ## Accessibility
81
-
82
- - Trigger announces the label and current value via `accessibilityLabel`
83
- - `accessibilityState.expanded` reflects whether the modal is open
84
- - Each option announces its label and selected state
85
- - Disabled options are communicated via `accessibilityState.disabled`
@@ -1,64 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Select } from './Select.native';
4
-
5
- const options = [
6
- { value: 'apple', label: 'Apple' },
7
- { value: 'banana', label: 'Banana' },
8
- { value: 'cherry', label: 'Cherry' },
9
- ];
10
-
11
- function renderWithTheme(ui: React.ReactElement) {
12
- return render(<ThemeProvider>{ui}</ThemeProvider>);
13
- }
14
-
15
- describe('Select (native)', () => {
16
- it('renders the label', () => {
17
- renderWithTheme(<Select label="Fruit" name="fruit" options={options} />);
18
- expect(screen.getByText('Fruit')).toBeInTheDocument();
19
- });
20
-
21
- it('shows placeholder when no value is selected', () => {
22
- renderWithTheme(
23
- <Select label="Fruit" name="fruit" options={options} placeholder="Choose..." />,
24
- );
25
- expect(screen.getByText('Choose...')).toBeInTheDocument();
26
- });
27
-
28
- it('shows the selected option label', () => {
29
- renderWithTheme(
30
- <Select label="Fruit" name="fruit" options={options} value="cherry" />,
31
- );
32
- expect(screen.getByText('Cherry')).toBeInTheDocument();
33
- });
34
-
35
- it('shows error message', () => {
36
- renderWithTheme(
37
- <Select label="Fruit" name="fruit" options={options} error="Required." />,
38
- );
39
- expect(screen.getByRole('alert')).toHaveTextContent('Required.');
40
- });
41
-
42
- it('shows hint message', () => {
43
- renderWithTheme(
44
- <Select label="Fruit" name="fruit" options={options} hint="Pick one." />,
45
- );
46
- expect(screen.getByText('Pick one.')).toBeInTheDocument();
47
- });
48
-
49
- it('renders the trigger button with correct accessibility', () => {
50
- renderWithTheme(
51
- <Select label="Fruit" name="fruit" options={options} />,
52
- );
53
- const trigger = screen.getByRole('button', { name: /Fruit/ });
54
- expect(trigger).toBeInTheDocument();
55
- });
56
-
57
- it('applies disabled opacity', () => {
58
- renderWithTheme(
59
- <Select label="Fruit" name="fruit" options={options} disabled value="banana" />,
60
- );
61
- const trigger = screen.getByRole('button', { name: /Fruit/ });
62
- expect(trigger).toBeDisabled();
63
- });
64
- });
@@ -1,75 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { Select } from './Select.native';
5
-
6
- const fruitOptions = [
7
- { value: 'apple', label: 'Apple' },
8
- { value: 'banana', label: 'Banana' },
9
- { value: 'cherry', label: 'Cherry' },
10
- { value: 'dragonfruit', label: 'Dragonfruit' },
11
- { value: 'elderberry', label: 'Elderberry' },
12
- ];
13
-
14
- const meta = {
15
- tags: ['!stable', 'alpha'],
16
- title: 'React Native/Form/Select',
17
- component: Select,
18
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
19
- parameters: { layout: 'padded' },
20
- } satisfies Meta<typeof Select>;
21
-
22
- export default meta;
23
- type Story = StoryObj<typeof meta>;
24
-
25
- export const Default: Story = {
26
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions },
27
- };
28
-
29
- export const WithPlaceholder: Story = {
30
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions, placeholder: 'Choose a fruit...' },
31
- };
32
-
33
- export const WithValue: Story = {
34
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions, value: 'cherry' },
35
- };
36
-
37
- export const WithError: Story = {
38
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions, error: 'Please select a fruit.' },
39
- };
40
-
41
- export const WithHint: Story = {
42
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions, hint: 'Pick your favourite.' },
43
- };
44
-
45
- export const Disabled: Story = {
46
- args: { label: 'Fruit', name: 'fruit', options: fruitOptions, disabled: true, value: 'banana' },
47
- };
48
-
49
- export const WithDisabledOption: Story = {
50
- args: {
51
- label: 'Fruit',
52
- name: 'fruit',
53
- options: [
54
- { value: 'apple', label: 'Apple' },
55
- { value: 'banana', label: 'Banana', disabled: true },
56
- { value: 'cherry', label: 'Cherry' },
57
- ],
58
- },
59
- };
60
-
61
- export const Interactive: Story = {
62
- render: () => {
63
- const [value, setValue] = useState('');
64
- return (
65
- <Select
66
- label="Favourite fruit"
67
- name="fruit"
68
- options={fruitOptions}
69
- placeholder="Choose a fruit..."
70
- value={value}
71
- onChange={setValue}
72
- />
73
- );
74
- },
75
- };
@@ -1,252 +0,0 @@
1
- import { ReactNode, useState } from 'react';
2
- import {
3
- StyleSheet,
4
- View,
5
- Text,
6
- Pressable,
7
- Modal,
8
- FlatList,
9
- SafeAreaView,
10
- StyleProp,
11
- ViewStyle,
12
- } from 'react-native';
13
- import { spacing, font, border, lineHeight } from '../../../../native/tokens';
14
- import { useTheme } from '../../../../native/ThemeContext';
15
- import { Label } from '../atoms/Label.native';
16
- import { Message } from '../atoms/Message.native';
17
-
18
- export interface SelectOption {
19
- value: string;
20
- label: string;
21
- disabled?: boolean;
22
- }
23
-
24
- interface SelectProps {
25
- label: ReactNode;
26
- name: string;
27
- options: SelectOption[];
28
- placeholder?: string;
29
- value?: string;
30
- error?: string;
31
- hint?: string;
32
- disabled?: boolean;
33
- onChange?: (value: string) => void;
34
- style?: StyleProp<ViewStyle>;
35
- }
36
-
37
- const styles = StyleSheet.create({
38
- container: {
39
- gap: spacing.xxs,
40
- },
41
- trigger: {
42
- flexDirection: 'row',
43
- alignItems: 'center',
44
- justifyContent: 'space-between',
45
- paddingHorizontal: spacing.s,
46
- paddingVertical: spacing.xs,
47
- borderWidth: 1,
48
- borderRadius: border.radius.full,
49
- minHeight: 44,
50
- },
51
- triggerText: {
52
- fontFamily: font.family.body,
53
- fontSize: font.size.body,
54
- lineHeight: lineHeight.body,
55
- flex: 1,
56
- },
57
- chevron: {
58
- fontFamily: font.family.body,
59
- fontSize: font.size.body,
60
- marginStart: spacing.xs,
61
- },
62
- overlay: {
63
- flex: 1,
64
- justifyContent: 'flex-end',
65
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
66
- },
67
- sheet: {
68
- maxHeight: '60%',
69
- borderTopStartRadius: border.radius.m,
70
- borderTopEndRadius: border.radius.m,
71
- paddingTop: spacing.m,
72
- paddingBottom: spacing.xxxl,
73
- },
74
- sheetHeader: {
75
- flexDirection: 'row',
76
- justifyContent: 'space-between',
77
- alignItems: 'center',
78
- paddingHorizontal: spacing.m,
79
- paddingBottom: spacing.s,
80
- },
81
- sheetTitle: {
82
- fontFamily: font.family.body,
83
- fontSize: font.size.heading3,
84
- fontWeight: font.weight.semibold,
85
- },
86
- closeText: {
87
- fontFamily: font.family.body,
88
- fontSize: font.size.body,
89
- fontWeight: font.weight.semibold,
90
- },
91
- optionItem: {
92
- paddingHorizontal: spacing.m,
93
- paddingVertical: spacing.s,
94
- minHeight: 44,
95
- justifyContent: 'center',
96
- },
97
- optionText: {
98
- fontFamily: font.family.body,
99
- fontSize: font.size.body,
100
- lineHeight: lineHeight.body,
101
- },
102
- });
103
-
104
- export function Select({
105
- label,
106
- name,
107
- options,
108
- placeholder = 'Select an option',
109
- value,
110
- error,
111
- hint,
112
- disabled = false,
113
- onChange,
114
- style,
115
- }: Readonly<SelectProps>) {
116
- const { colors } = useTheme();
117
- const [open, setOpen] = useState(false);
118
-
119
- const selectedOption = options.find((o) => o.value === value);
120
- const displayText = selectedOption?.label ?? placeholder;
121
- const isPlaceholder = !selectedOption;
122
-
123
- const borderColor = error
124
- ? colors.colorError
125
- : open
126
- ? colors.colorInteractive
127
- : colors.colorOnBg;
128
-
129
- const handleSelect = (optionValue: string) => {
130
- onChange?.(optionValue);
131
- setOpen(false);
132
- };
133
-
134
- return (
135
- <View style={[styles.container, style]}>
136
- <Label label={label} />
137
- <Pressable
138
- accessibilityRole="button"
139
- accessibilityLabel={typeof label === 'string' ? `${label}, ${displayText}` : name}
140
- accessibilityState={{ disabled, expanded: open }}
141
- accessibilityHint="Double tap to open options"
142
- disabled={disabled}
143
- onPress={() => setOpen(true)}
144
- style={[
145
- styles.trigger,
146
- {
147
- borderColor,
148
- backgroundColor: colors.colorBg,
149
- },
150
- disabled && { opacity: 0.4 },
151
- ]}
152
- >
153
- <Text
154
- style={[
155
- styles.triggerText,
156
- {
157
- color: isPlaceholder ? colors.colorOnBgSubtle : colors.colorOnBg,
158
- },
159
- ]}
160
- >
161
- {displayText}
162
- </Text>
163
- <Text style={[styles.chevron, { color: colors.colorOnBgSubtle }]} aria-hidden>
164
- {'\u25BE'}
165
- </Text>
166
- </Pressable>
167
-
168
- <Modal
169
- visible={open}
170
- transparent
171
- animationType="slide"
172
- onRequestClose={() => setOpen(false)}
173
- >
174
- <Pressable
175
- style={styles.overlay}
176
- onPress={() => setOpen(false)}
177
- accessibilityRole="button"
178
- accessibilityLabel="Close options"
179
- >
180
- <Pressable
181
- style={[styles.sheet, { backgroundColor: colors.colorBg }]}
182
- onPress={() => {
183
- /* prevent close when tapping sheet */
184
- }}
185
- >
186
- <SafeAreaView>
187
- <View style={styles.sheetHeader}>
188
- <Text style={[styles.sheetTitle, { color: colors.colorOnBg }]}>
189
- {typeof label === 'string' ? label : 'Select'}
190
- </Text>
191
- <Pressable
192
- accessibilityRole="button"
193
- accessibilityLabel="Close"
194
- onPress={() => setOpen(false)}
195
- hitSlop={8}
196
- >
197
- <Text style={[styles.closeText, { color: colors.colorInteractive }]}>
198
- Done
199
- </Text>
200
- </Pressable>
201
- </View>
202
- <FlatList
203
- data={options}
204
- keyExtractor={(item) => item.value}
205
- renderItem={({ item }) => {
206
- const isSelected = item.value === value;
207
- return (
208
- <Pressable
209
- accessibilityRole="button"
210
- accessibilityLabel={item.label}
211
- accessibilityState={{
212
- selected: isSelected,
213
- disabled: item.disabled,
214
- }}
215
- disabled={item.disabled}
216
- onPress={() => handleSelect(item.value)}
217
- style={({ pressed }) => [
218
- styles.optionItem,
219
- isSelected && { backgroundColor: colors.colorBgSubtle },
220
- pressed && { opacity: 0.7 },
221
- item.disabled && { opacity: 0.4 },
222
- ]}
223
- >
224
- <Text
225
- style={[
226
- styles.optionText,
227
- {
228
- color: isSelected
229
- ? colors.colorInteractive
230
- : colors.colorOnBg,
231
- fontWeight: isSelected
232
- ? font.weight.semibold
233
- : font.weight.body,
234
- },
235
- ]}
236
- >
237
- {item.label}
238
- </Text>
239
- </Pressable>
240
- );
241
- }}
242
- />
243
- </SafeAreaView>
244
- </Pressable>
245
- </Pressable>
246
- </Modal>
247
-
248
- <Message type="error" message={error} />
249
- <Message type="hint" message={hint} />
250
- </View>
251
- );
252
- }
@@ -1,83 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Slider.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Slider (React Native)
7
-
8
- Range slider with a draggable track, optional value display, and error/hint messages. Uses touch responders on the track for value selection.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Slider } from '@boostdev/design-system-components';
16
- import { useState } from 'react';
17
-
18
- function Example() {
19
- const [volume, setVolume] = useState(50);
20
- return (
21
- <Slider
22
- label="Volume"
23
- name="volume"
24
- value={volume}
25
- onChange={setVolume}
26
- showValue
27
- />
28
- );
29
- }
30
- ```
31
-
32
- ## Examples
33
-
34
- ### Default
35
- <Canvas of={Stories.Default} />
36
-
37
- ### With value display
38
- <Canvas of={Stories.WithValue} />
39
-
40
- ### Custom range
41
- <Canvas of={Stories.CustomRange} />
42
-
43
- ### With step
44
- <Canvas of={Stories.WithStep} />
45
-
46
- ### With error
47
- <Canvas of={Stories.WithError} />
48
-
49
- ### With hint
50
- <Canvas of={Stories.WithHint} />
51
-
52
- ### Disabled
53
- <Canvas of={Stories.Disabled} />
54
-
55
- ### Interactive
56
- <Canvas of={Stories.Interactive} />
57
-
58
- ## Props
59
-
60
- <ArgTypes of={Stories} />
61
-
62
- ## Props reference
63
-
64
- | Prop | Type | Default | Description |
65
- |------|------|---------|-------------|
66
- | `label` | string | — | Visible label text |
67
- | `name` | string | — | Field name |
68
- | `value` | number | — | Controlled value |
69
- | `defaultValue` | number | — | Uncontrolled default value |
70
- | `min` | number | `0` | Minimum value |
71
- | `max` | number | `100` | Maximum value |
72
- | `step` | number | `1` | Value increment |
73
- | `showValue` | boolean | `false` | Show current value next to the label |
74
- | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
75
- | `error` | string | — | Error message displayed below the track |
76
- | `hint` | string | — | Hint text displayed below the track |
77
- | `onChange` | `(value: number) => void` | — | Called with the new value |
78
-
79
- ## Accessibility
80
-
81
- - Uses `accessibilityRole="adjustable"` for VoiceOver/TalkBack
82
- - `accessibilityValue` provides `min`, `max`, `now`, and `text`
83
- - `disabled` communicates the disabled state via `accessibilityState`