@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,51 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Slider } from './Slider.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('Slider (native)', () => {
10
- it('renders the label', () => {
11
- renderWithTheme(<Slider label="Volume" name="volume" />);
12
- expect(screen.getByText('Volume')).toBeInTheDocument();
13
- });
14
-
15
- it('shows current value when showValue is true', () => {
16
- renderWithTheme(
17
- <Slider label="Volume" name="volume" value={75} showValue />,
18
- );
19
- expect(screen.getByText('75')).toBeInTheDocument();
20
- });
21
-
22
- it('hides value display when showValue is false', () => {
23
- renderWithTheme(
24
- <Slider label="Volume" name="volume" value={50} />,
25
- );
26
- // Only "Volume" text should be present, not the value
27
- expect(screen.queryByText('50')).not.toBeInTheDocument();
28
- });
29
-
30
- it('shows error message', () => {
31
- renderWithTheme(
32
- <Slider label="Volume" name="volume" error="Too low." />,
33
- );
34
- expect(screen.getByRole('alert')).toHaveTextContent('Too low.');
35
- });
36
-
37
- it('shows hint message', () => {
38
- renderWithTheme(
39
- <Slider label="Volume" name="volume" hint="Adjust volume." />,
40
- );
41
- expect(screen.getByText('Adjust volume.')).toBeInTheDocument();
42
- });
43
-
44
- it('uses default min value when no value is provided', () => {
45
- renderWithTheme(
46
- <Slider label="Volume" name="volume" showValue />,
47
- );
48
- // Default min is 0, which becomes the initial value
49
- expect(screen.getByText('0')).toBeInTheDocument();
50
- });
51
- });
@@ -1,58 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { Slider } from './Slider.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/Slider',
9
- component: Slider,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof Slider>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
18
- args: { label: 'Volume', name: 'volume', value: 50 },
19
- };
20
-
21
- export const WithValue: Story = {
22
- args: { label: 'Brightness', name: 'brightness', value: 75, showValue: true },
23
- };
24
-
25
- export const CustomRange: Story = {
26
- args: { label: 'Temperature', name: 'temp', min: 16, max: 30, value: 21, showValue: true },
27
- };
28
-
29
- export const WithStep: Story = {
30
- args: { label: 'Quantity', name: 'qty', min: 0, max: 50, step: 5, value: 25, showValue: true },
31
- };
32
-
33
- export const WithError: Story = {
34
- args: { label: 'Volume', name: 'volume', value: 0, error: 'Volume must be at least 10.' },
35
- };
36
-
37
- export const WithHint: Story = {
38
- args: { label: 'Volume', name: 'volume', value: 50, hint: 'Adjust playback volume.' },
39
- };
40
-
41
- export const Disabled: Story = {
42
- args: { label: 'Volume', name: 'volume', value: 30, disabled: true, showValue: true },
43
- };
44
-
45
- export const Interactive: Story = {
46
- render: () => {
47
- const [value, setValue] = useState(50);
48
- return (
49
- <Slider
50
- label="Volume"
51
- name="volume"
52
- value={value}
53
- onChange={setValue}
54
- showValue
55
- />
56
- );
57
- },
58
- };
@@ -1,188 +0,0 @@
1
- import { useState } from 'react';
2
- import {
3
- StyleSheet,
4
- View,
5
- Text,
6
- GestureResponderEvent,
7
- LayoutChangeEvent,
8
- StyleProp,
9
- ViewStyle,
10
- } from 'react-native';
11
- import { spacing, font, lineHeight } from '../../../../native/tokens';
12
- import { useTheme } from '../../../../native/ThemeContext';
13
- import { Label } from '../atoms/Label.native';
14
- import { Message } from '../atoms/Message.native';
15
-
16
- interface SliderProps {
17
- label: string;
18
- name: string;
19
- value?: number;
20
- defaultValue?: number;
21
- min?: number;
22
- max?: number;
23
- step?: number;
24
- showValue?: boolean;
25
- disabled?: boolean;
26
- error?: string;
27
- hint?: string;
28
- onChange?: (value: number) => void;
29
- style?: StyleProp<ViewStyle>;
30
- }
31
-
32
- const TRACK_HEIGHT = 6;
33
- const THUMB_SIZE = 24;
34
-
35
- const styles = StyleSheet.create({
36
- container: {
37
- gap: spacing.xxs,
38
- },
39
- labelRow: {
40
- flexDirection: 'row',
41
- justifyContent: 'space-between',
42
- alignItems: 'center',
43
- },
44
- valueText: {
45
- fontFamily: font.family.body,
46
- fontSize: font.size.bodyS,
47
- lineHeight: lineHeight.bodyS,
48
- fontWeight: font.weight.semibold,
49
- },
50
- trackContainer: {
51
- height: THUMB_SIZE + spacing.xs * 2,
52
- justifyContent: 'center',
53
- paddingHorizontal: THUMB_SIZE / 2,
54
- },
55
- track: {
56
- height: TRACK_HEIGHT,
57
- borderRadius: TRACK_HEIGHT / 2,
58
- overflow: 'hidden',
59
- },
60
- fill: {
61
- height: TRACK_HEIGHT,
62
- borderRadius: TRACK_HEIGHT / 2,
63
- },
64
- thumb: {
65
- position: 'absolute',
66
- width: THUMB_SIZE,
67
- height: THUMB_SIZE,
68
- borderRadius: THUMB_SIZE / 2,
69
- top: '50%',
70
- marginTop: -(THUMB_SIZE / 2),
71
- borderWidth: 2,
72
- },
73
- });
74
-
75
- function clampValue(v: number, min: number, max: number): number {
76
- return Math.min(max, Math.max(min, v));
77
- }
78
-
79
- function snapToStep(v: number, min: number, step: number): number {
80
- return Math.round((v - min) / step) * step + min;
81
- }
82
-
83
- export function Slider({
84
- label,
85
- name: _name, // eslint-disable-line @typescript-eslint/no-unused-vars
86
- value,
87
- defaultValue,
88
- min = 0,
89
- max = 100,
90
- step = 1,
91
- showValue = false,
92
- disabled = false,
93
- error,
94
- hint,
95
- onChange,
96
- style,
97
- }: Readonly<SliderProps>) {
98
- const { colors } = useTheme();
99
- const isControlled = value !== undefined;
100
- const [internalValue, setInternalValue] = useState(defaultValue ?? min);
101
- const [trackWidth, setTrackWidth] = useState(0);
102
-
103
- const currentValue = isControlled ? value : internalValue;
104
- const fillPct = max > min ? ((currentValue - min) / (max - min)) * 100 : 0;
105
-
106
- const updateValue = (v: number) => {
107
- const clamped = clampValue(v, min, max);
108
- const snapped = snapToStep(clamped, min, step);
109
- if (!isControlled) setInternalValue(snapped);
110
- onChange?.(snapped);
111
- };
112
-
113
- const handleTrackLayout = (e: LayoutChangeEvent) => {
114
- setTrackWidth(e.nativeEvent.layout.width);
115
- };
116
-
117
- const valueFromTouch = (pageX: number, layoutX: number): number => {
118
- const x = pageX - layoutX;
119
- const pct = clampValue(x / trackWidth, 0, 1);
120
- return pct * (max - min) + min;
121
- };
122
-
123
- const handleStartShouldSetResponder = () => !disabled;
124
- const handleResponderGrant = (e: GestureResponderEvent) => {
125
- const target = e.currentTarget as unknown as { measure: (cb: (x: number, y: number, w: number, h: number, px: number, py: number) => void) => void };
126
- target.measure((_x, _y, _w, _h, pageX) => {
127
- updateValue(valueFromTouch(e.nativeEvent.pageX, pageX));
128
- });
129
- };
130
- const handleResponderMove = (e: GestureResponderEvent) => {
131
- const target = e.currentTarget as unknown as { measure: (cb: (x: number, y: number, w: number, h: number, px: number, py: number) => void) => void };
132
- target.measure((_x, _y, _w, _h, pageX) => {
133
- updateValue(valueFromTouch(e.nativeEvent.pageX, pageX));
134
- });
135
- };
136
-
137
- return (
138
- <View
139
- style={[styles.container, style]}
140
- accessibilityRole="adjustable"
141
- accessibilityLabel={label}
142
- accessibilityValue={{ min, max, now: currentValue, text: String(currentValue) }}
143
- accessibilityState={{ disabled }}
144
- >
145
- <View style={styles.labelRow}>
146
- <Label label={label} />
147
- {showValue && (
148
- <Text style={[styles.valueText, { color: colors.colorOnBg }]}>
149
- {currentValue}
150
- </Text>
151
- )}
152
- </View>
153
- <View
154
- style={[styles.trackContainer, disabled && { opacity: 0.4 }]}
155
- onLayout={handleTrackLayout}
156
- onStartShouldSetResponder={handleStartShouldSetResponder}
157
- onMoveShouldSetResponder={handleStartShouldSetResponder}
158
- onResponderGrant={handleResponderGrant}
159
- onResponderMove={handleResponderMove}
160
- >
161
- <View style={[styles.track, { backgroundColor: colors.colorBgSubtle }]}>
162
- <View
163
- style={[
164
- styles.fill,
165
- {
166
- width: `${fillPct}%`,
167
- backgroundColor: error ? colors.colorError : colors.colorInteractive,
168
- },
169
- ]}
170
- />
171
- </View>
172
- <View
173
- style={[
174
- styles.thumb,
175
- {
176
- start: `${fillPct}%`,
177
- marginStart: -(THUMB_SIZE / 2),
178
- backgroundColor: colors.colorBg,
179
- borderColor: error ? colors.colorError : colors.colorInteractive,
180
- },
181
- ]}
182
- />
183
- </View>
184
- <Message type="error" message={error} />
185
- <Message type="hint" message={hint} />
186
- </View>
187
- );
188
- }
@@ -1,60 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Switch } from './Switch.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- // react-native-web renders RNSwitch as two role="switch" elements: an outer div
10
- // (for visual rendering) and an inner hidden <input type="checkbox"> for a11y.
11
- // The input carries the aria-label and aria-checked state.
12
- function getSwitchInput(container: HTMLElement): HTMLInputElement {
13
- const input = container.querySelector<HTMLInputElement>('input[role="switch"]');
14
- if (!input) throw new Error('Switch input not found');
15
- return input;
16
- }
17
-
18
- describe('Switch (native)', () => {
19
- it('renders a switch input with the correct label', () => {
20
- const { container } = renderWithTheme(
21
- <Switch label="Notifications" name="notifications" />,
22
- );
23
- const input = getSwitchInput(container);
24
- expect(input).toHaveAttribute('aria-label', 'Notifications');
25
- });
26
-
27
- it('reflects off state', () => {
28
- const { container } = renderWithTheme(
29
- <Switch label="Notifications" name="notifications" value={false} />,
30
- );
31
- expect(getSwitchInput(container)).not.toBeChecked();
32
- });
33
-
34
- it('reflects on state', () => {
35
- const { container } = renderWithTheme(
36
- <Switch label="Notifications" name="notifications" value={true} />,
37
- );
38
- expect(getSwitchInput(container)).toBeChecked();
39
- });
40
-
41
- it('renders thumb in both on and off states without error (thumb always uses colorInteractive)', () => {
42
- // thumbColor is always colorInteractive — interactive affordance is visible in both states.
43
- // Track color provides the on/off visual feedback instead.
44
- const { container: offContainer } = renderWithTheme(
45
- <Switch label="Toggle" name="toggle" value={false} />,
46
- );
47
- const { container: onContainer } = renderWithTheme(
48
- <Switch label="Toggle2" name="toggle2" value={true} />,
49
- );
50
- expect(offContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
51
- expect(onContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
52
- });
53
-
54
- it('renders hint message', () => {
55
- renderWithTheme(
56
- <Switch label="Marketing" name="marketing" hint="Unsubscribe anytime." />,
57
- );
58
- expect(screen.getByText('Unsubscribe anytime.')).toBeInTheDocument();
59
- });
60
- });
@@ -1,33 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { Switch } from './Switch.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/Switch',
9
- component: Switch,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof Switch>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Off: Story = { args: { label: 'Notifications', name: 'notifications', value: false } };
18
- export const On: Story = { args: { label: 'Notifications', name: 'notifications', value: true } };
19
- export const Disabled: Story = { args: { label: 'Notifications', name: 'notifications', disabled: true } };
20
- export const WithError: Story = {
21
- args: { label: 'Accept terms', name: 'terms', error: 'You must accept the terms.' },
22
- };
23
- export const WithHint: Story = {
24
- args: { label: 'Marketing emails', name: 'marketing', hint: 'You can unsubscribe at any time.' },
25
- };
26
- export const Interactive: Story = {
27
- render: () => {
28
- const [value, setValue] = useState(false);
29
- return (
30
- <Switch label="Enable feature" name="feature" value={value} onChange={setValue} />
31
- );
32
- },
33
- };
@@ -1,59 +0,0 @@
1
- import { Switch as RNSwitch, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
2
- import { spacing } from '../../../../native/tokens';
3
- import { useTheme } from '../../../../native/ThemeContext';
4
- import { Label } from '../atoms/Label.native';
5
- import { Message } from '../atoms/Message.native';
6
-
7
- interface SwitchProps {
8
- label: string;
9
- name: string;
10
- value?: boolean;
11
- onChange?: (value: boolean) => void;
12
- disabled?: boolean;
13
- error?: string;
14
- hint?: string;
15
- style?: StyleProp<ViewStyle>;
16
- }
17
-
18
- const styles = StyleSheet.create({
19
- container: {
20
- gap: spacing.xxs,
21
- },
22
- row: {
23
- flexDirection: 'row',
24
- alignItems: 'center',
25
- gap: spacing.xs,
26
- },
27
- });
28
-
29
- export function Switch({
30
- label,
31
- value = false,
32
- onChange,
33
- disabled = false,
34
- error,
35
- hint,
36
- style,
37
- }: Readonly<SwitchProps>) {
38
- const { colors } = useTheme();
39
-
40
- return (
41
- <View style={[styles.container, style]}>
42
- <View style={styles.row}>
43
- <RNSwitch
44
- value={value}
45
- onValueChange={onChange}
46
- disabled={disabled}
47
- accessibilityLabel={label}
48
- accessibilityRole="switch"
49
- accessibilityState={{ checked: value, disabled }}
50
- thumbColor={colors.colorInteractive}
51
- trackColor={{ false: colors.colorBgSubtle, true: colors.colorBlueSubtle }}
52
- />
53
- <Label label={label} />
54
- </View>
55
- <Message type="error" message={error} />
56
- <Message type="hint" message={hint} />
57
- </View>
58
- );
59
- }
@@ -1,76 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Textarea.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Textarea (React Native)
7
-
8
- Multiline text input with label, optional hint, and error state. Uses a `TextInput` with `multiline` enabled and `textAlignVertical: 'top'`.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Textarea } from '@boostdev/design-system-components';
16
- import { useState } from 'react';
17
-
18
- function Example() {
19
- const [bio, setBio] = useState('');
20
- return (
21
- <Textarea
22
- label="Bio"
23
- name="bio"
24
- placeholder="Tell us about yourself..."
25
- value={bio}
26
- onChange={setBio}
27
- />
28
- );
29
- }
30
- ```
31
-
32
- ## Examples
33
-
34
- ### Default
35
- <Canvas of={Stories.Default} />
36
-
37
- ### With placeholder
38
- <Canvas of={Stories.WithPlaceholder} />
39
-
40
- ### With error
41
- <Canvas of={Stories.WithError} />
42
-
43
- ### With hint
44
- <Canvas of={Stories.WithHint} />
45
-
46
- ### Disabled
47
- <Canvas of={Stories.Disabled} />
48
-
49
- ### Interactive
50
- <Canvas of={Stories.Interactive} />
51
-
52
- ## Props
53
-
54
- <ArgTypes of={Stories} />
55
-
56
- ## Props reference
57
-
58
- | Prop | Type | Default | Description |
59
- |------|------|---------|-------------|
60
- | `label` | ReactNode | — | Visible label text |
61
- | `name` | string | — | Field name (used for accessibility) |
62
- | `value` | string | — | Controlled value |
63
- | `defaultValue` | string | — | Uncontrolled default value |
64
- | `placeholder` | string | — | Placeholder text |
65
- | `numberOfLines` | number | `4` | Number of visible lines |
66
- | `error` | string | — | Error message displayed below the input |
67
- | `hint` | string | — | Hint text displayed below the input |
68
- | `required` | boolean | `false` | Marks the field as required |
69
- | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
70
- | `onChange` | `(value: string) => void` | — | Called with the new text value |
71
-
72
- ## Accessibility
73
-
74
- - The label is announced by screen readers via `accessibilityLabel`
75
- - Hint text is announced via `accessibilityHint`
76
- - `disabled` communicates the disabled state via `accessibilityState`
@@ -1,50 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Textarea } from './Textarea.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('Textarea (native)', () => {
10
- it('renders the label', () => {
11
- renderWithTheme(<Textarea label="Message" name="message" />);
12
- expect(screen.getByText('Message')).toBeInTheDocument();
13
- });
14
-
15
- it('renders with placeholder text', () => {
16
- renderWithTheme(
17
- <Textarea label="Bio" name="bio" placeholder="Tell us about yourself..." />,
18
- );
19
- expect(screen.getByPlaceholderText('Tell us about yourself...')).toBeInTheDocument();
20
- });
21
-
22
- it('renders with a value', () => {
23
- renderWithTheme(
24
- <Textarea label="Notes" name="notes" value="Some content" />,
25
- );
26
- expect(screen.getByDisplayValue('Some content')).toBeInTheDocument();
27
- });
28
-
29
- it('shows error message', () => {
30
- renderWithTheme(
31
- <Textarea label="Message" name="message" error="Message is required." />,
32
- );
33
- expect(screen.getByRole('alert')).toHaveTextContent('Message is required.');
34
- });
35
-
36
- it('shows hint message', () => {
37
- renderWithTheme(
38
- <Textarea label="Message" name="message" hint="Minimum 20 characters." />,
39
- );
40
- expect(screen.getByText('Minimum 20 characters.')).toBeInTheDocument();
41
- });
42
-
43
- it('applies disabled opacity', () => {
44
- const { container } = renderWithTheme(
45
- <Textarea label="Notes" name="notes" disabled />,
46
- );
47
- const inputs = container.querySelectorAll('textarea, [style*="opacity"]');
48
- expect(inputs.length).toBeGreaterThan(0);
49
- });
50
- });
@@ -1,50 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { Textarea } from './Textarea.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/Textarea',
9
- component: Textarea,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof Textarea>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
18
- args: { label: 'Message', name: 'message' },
19
- };
20
-
21
- export const WithPlaceholder: Story = {
22
- args: { label: 'Bio', name: 'bio', placeholder: 'Tell us about yourself...' },
23
- };
24
-
25
- export const WithError: Story = {
26
- args: { label: 'Message', name: 'message', error: 'Message is required.' },
27
- };
28
-
29
- export const WithHint: Story = {
30
- args: { label: 'Message', name: 'message', hint: 'Minimum 20 characters.' },
31
- };
32
-
33
- export const Disabled: Story = {
34
- args: { label: 'Notes', name: 'notes', disabled: true, value: 'Read-only content.' },
35
- };
36
-
37
- export const Interactive: Story = {
38
- render: () => {
39
- const [value, setValue] = useState('');
40
- return (
41
- <Textarea
42
- label="Feedback"
43
- name="feedback"
44
- placeholder="Share your thoughts..."
45
- value={value}
46
- onChange={setValue}
47
- />
48
- );
49
- },
50
- };