@boostdev/design-system-components 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/AGENTS.md +4 -20
  2. package/README.md +3 -23
  3. package/dist/client.cjs +115 -61
  4. package/dist/client.css +537 -537
  5. package/dist/client.d.cts +11 -1
  6. package/dist/client.d.ts +11 -1
  7. package/dist/client.js +115 -61
  8. package/dist/index.cjs +115 -61
  9. package/dist/index.css +537 -537
  10. package/dist/index.d.cts +11 -1
  11. package/dist/index.d.ts +11 -1
  12. package/dist/index.js +115 -61
  13. package/dist/web-components/index.js +23 -3
  14. package/package.json +2 -10
  15. package/src/components/layout/Grid/Grid.mdx +20 -2
  16. package/src/components/layout/Grid/Grid.spec.tsx +175 -4
  17. package/src/components/layout/Grid/Grid.tsx +15 -1
  18. package/src/components/layout/Grid/masonry.ts +120 -9
  19. package/dist/native/index.cjs +0 -4337
  20. package/dist/native/index.d.cts +0 -574
  21. package/dist/native/index.d.ts +0 -574
  22. package/dist/native/index.js +0 -4421
  23. package/src/components/interaction/Button/Button.native.mdx +0 -91
  24. package/src/components/interaction/Button/Button.native.spec.tsx +0 -55
  25. package/src/components/interaction/Button/Button.native.stories.tsx +0 -141
  26. package/src/components/interaction/Button/Button.native.tsx +0 -105
  27. package/src/components/interaction/Dialog/Dialog.native.mdx +0 -61
  28. package/src/components/interaction/Dialog/Dialog.native.spec.tsx +0 -73
  29. package/src/components/interaction/Dialog/Dialog.native.stories.tsx +0 -53
  30. package/src/components/interaction/Dialog/Dialog.native.tsx +0 -128
  31. package/src/components/interaction/Drawer/Drawer.native.mdx +0 -58
  32. package/src/components/interaction/Drawer/Drawer.native.spec.tsx +0 -81
  33. package/src/components/interaction/Drawer/Drawer.native.stories.tsx +0 -33
  34. package/src/components/interaction/Drawer/Drawer.native.tsx +0 -175
  35. package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +0 -74
  36. package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +0 -78
  37. package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +0 -51
  38. package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +0 -254
  39. package/src/components/interaction/Popover/Popover.native.mdx +0 -61
  40. package/src/components/interaction/Popover/Popover.native.spec.tsx +0 -73
  41. package/src/components/interaction/Popover/Popover.native.stories.tsx +0 -44
  42. package/src/components/interaction/Popover/Popover.native.tsx +0 -87
  43. package/src/components/interaction/Rating/Rating.native.mdx +0 -55
  44. package/src/components/interaction/Rating/Rating.native.spec.tsx +0 -38
  45. package/src/components/interaction/Rating/Rating.native.stories.tsx +0 -37
  46. package/src/components/interaction/Rating/Rating.native.tsx +0 -50
  47. package/src/components/interaction/Toast/Toast.native.mdx +0 -81
  48. package/src/components/interaction/Toast/Toast.native.spec.tsx +0 -80
  49. package/src/components/interaction/Toast/Toast.native.stories.tsx +0 -47
  50. package/src/components/interaction/Toast/Toast.native.tsx +0 -202
  51. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +0 -74
  52. package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +0 -42
  53. package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +0 -34
  54. package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +0 -80
  55. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +0 -78
  56. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +0 -57
  57. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +0 -96
  58. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +0 -61
  59. package/src/components/interaction/form/Combobox/Combobox.native.mdx +0 -86
  60. package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +0 -60
  61. package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +0 -99
  62. package/src/components/interaction/form/Combobox/Combobox.native.tsx +0 -211
  63. package/src/components/interaction/form/FileInput/FileInput.native.mdx +0 -86
  64. package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +0 -73
  65. package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +0 -69
  66. package/src/components/interaction/form/FileInput/FileInput.native.tsx +0 -129
  67. package/src/components/interaction/form/FormInput/FormInput.native.mdx +0 -81
  68. package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +0 -51
  69. package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +0 -54
  70. package/src/components/interaction/form/FormInput/FormInput.native.tsx +0 -122
  71. package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +0 -87
  72. package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +0 -63
  73. package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +0 -63
  74. package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +0 -198
  75. package/src/components/interaction/form/Radio/Radio.native.spec.tsx +0 -79
  76. package/src/components/interaction/form/Radio/Radio.native.stories.tsx +0 -44
  77. package/src/components/interaction/form/Radio/Radio.native.tsx +0 -79
  78. package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +0 -79
  79. package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +0 -66
  80. package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +0 -96
  81. package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +0 -61
  82. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +0 -78
  83. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +0 -82
  84. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +0 -99
  85. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +0 -121
  86. package/src/components/interaction/form/Select/Select.native.mdx +0 -85
  87. package/src/components/interaction/form/Select/Select.native.spec.tsx +0 -64
  88. package/src/components/interaction/form/Select/Select.native.stories.tsx +0 -75
  89. package/src/components/interaction/form/Select/Select.native.tsx +0 -252
  90. package/src/components/interaction/form/Slider/Slider.native.mdx +0 -83
  91. package/src/components/interaction/form/Slider/Slider.native.spec.tsx +0 -51
  92. package/src/components/interaction/form/Slider/Slider.native.stories.tsx +0 -58
  93. package/src/components/interaction/form/Slider/Slider.native.tsx +0 -188
  94. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +0 -60
  95. package/src/components/interaction/form/Switch/Switch.native.stories.tsx +0 -33
  96. package/src/components/interaction/form/Switch/Switch.native.tsx +0 -59
  97. package/src/components/interaction/form/Textarea/Textarea.native.mdx +0 -76
  98. package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +0 -50
  99. package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +0 -50
  100. package/src/components/interaction/form/Textarea/Textarea.native.tsx +0 -108
  101. package/src/components/interaction/form/atoms/Label.native.spec.tsx +0 -39
  102. package/src/components/interaction/form/atoms/Label.native.stories.tsx +0 -18
  103. package/src/components/interaction/form/atoms/Label.native.tsx +0 -29
  104. package/src/components/interaction/form/atoms/Message.native.spec.tsx +0 -30
  105. package/src/components/interaction/form/atoms/Message.native.stories.tsx +0 -22
  106. package/src/components/interaction/form/atoms/Message.native.tsx +0 -34
  107. package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +0 -83
  108. package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +0 -44
  109. package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +0 -44
  110. package/src/components/layout/Card/Card.native.spec.tsx +0 -102
  111. package/src/components/layout/Card/Card.native.stories.tsx +0 -51
  112. package/src/components/layout/Card/Card.native.tsx +0 -89
  113. package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +0 -39
  114. package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +0 -37
  115. package/src/components/layout/IconWrapper/IconWrapper.native.tsx +0 -20
  116. package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +0 -51
  117. package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +0 -36
  118. package/src/components/layout/SectionHeader/SectionHeader.native.tsx +0 -79
  119. package/src/components/ui/Accordion/Accordion.native.mdx +0 -65
  120. package/src/components/ui/Accordion/Accordion.native.spec.tsx +0 -69
  121. package/src/components/ui/Accordion/Accordion.native.stories.tsx +0 -52
  122. package/src/components/ui/Accordion/Accordion.native.tsx +0 -141
  123. package/src/components/ui/Alert/Alert.native.mdx +0 -62
  124. package/src/components/ui/Alert/Alert.native.spec.tsx +0 -76
  125. package/src/components/ui/Alert/Alert.native.stories.tsx +0 -41
  126. package/src/components/ui/Alert/Alert.native.tsx +0 -94
  127. package/src/components/ui/Avatar/Avatar.native.mdx +0 -50
  128. package/src/components/ui/Avatar/Avatar.native.spec.tsx +0 -47
  129. package/src/components/ui/Avatar/Avatar.native.stories.tsx +0 -32
  130. package/src/components/ui/Avatar/Avatar.native.tsx +0 -85
  131. package/src/components/ui/Badge/Badge.native.mdx +0 -54
  132. package/src/components/ui/Badge/Badge.native.spec.tsx +0 -45
  133. package/src/components/ui/Badge/Badge.native.stories.tsx +0 -34
  134. package/src/components/ui/Badge/Badge.native.tsx +0 -50
  135. package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +0 -52
  136. package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +0 -49
  137. package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +0 -46
  138. package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +0 -83
  139. package/src/components/ui/Calendar/Calendar.native.mdx +0 -58
  140. package/src/components/ui/Calendar/Calendar.native.spec.tsx +0 -92
  141. package/src/components/ui/Calendar/Calendar.native.stories.tsx +0 -45
  142. package/src/components/ui/Calendar/Calendar.native.tsx +0 -294
  143. package/src/components/ui/Carousel/Carousel.native.mdx +0 -55
  144. package/src/components/ui/Carousel/Carousel.native.spec.tsx +0 -55
  145. package/src/components/ui/Carousel/Carousel.native.stories.tsx +0 -59
  146. package/src/components/ui/Carousel/Carousel.native.tsx +0 -184
  147. package/src/components/ui/Collapsible/Collapsible.native.mdx +0 -53
  148. package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +0 -68
  149. package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +0 -48
  150. package/src/components/ui/Collapsible/Collapsible.native.tsx +0 -139
  151. package/src/components/ui/DescriptionList/DescriptionList.native.mdx +0 -50
  152. package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +0 -37
  153. package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +0 -48
  154. package/src/components/ui/DescriptionList/DescriptionList.native.tsx +0 -54
  155. package/src/components/ui/Link/Link.native.mdx +0 -53
  156. package/src/components/ui/Link/Link.native.spec.tsx +0 -35
  157. package/src/components/ui/Link/Link.native.stories.tsx +0 -46
  158. package/src/components/ui/Link/Link.native.tsx +0 -48
  159. package/src/components/ui/Loading/Loading.native.spec.tsx +0 -24
  160. package/src/components/ui/Loading/Loading.native.stories.tsx +0 -31
  161. package/src/components/ui/Loading/Loading.native.tsx +0 -29
  162. package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +0 -85
  163. package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +0 -37
  164. package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +0 -76
  165. package/src/components/ui/Pagination/Pagination.native.mdx +0 -62
  166. package/src/components/ui/Pagination/Pagination.native.spec.tsx +0 -86
  167. package/src/components/ui/Pagination/Pagination.native.stories.tsx +0 -47
  168. package/src/components/ui/Pagination/Pagination.native.tsx +0 -157
  169. package/src/components/ui/Progress/Progress.native.spec.tsx +0 -56
  170. package/src/components/ui/Progress/Progress.native.stories.tsx +0 -34
  171. package/src/components/ui/Progress/Progress.native.tsx +0 -84
  172. package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +0 -63
  173. package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +0 -39
  174. package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +0 -36
  175. package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +0 -176
  176. package/src/components/ui/Separator/Separator.native.spec.tsx +0 -39
  177. package/src/components/ui/Separator/Separator.native.stories.tsx +0 -38
  178. package/src/components/ui/Separator/Separator.native.tsx +0 -32
  179. package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +0 -39
  180. package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +0 -31
  181. package/src/components/ui/Skeleton/Skeleton.native.tsx +0 -42
  182. package/src/components/ui/Table/Table.native.mdx +0 -72
  183. package/src/components/ui/Table/Table.native.spec.tsx +0 -83
  184. package/src/components/ui/Table/Table.native.stories.tsx +0 -55
  185. package/src/components/ui/Table/Table.native.tsx +0 -196
  186. package/src/components/ui/Tabs/Tabs.native.mdx +0 -65
  187. package/src/components/ui/Tabs/Tabs.native.spec.tsx +0 -65
  188. package/src/components/ui/Tabs/Tabs.native.stories.tsx +0 -57
  189. package/src/components/ui/Tabs/Tabs.native.tsx +0 -133
  190. package/src/components/ui/Tooltip/Tooltip.native.mdx +0 -47
  191. package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +0 -37
  192. package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +0 -38
  193. package/src/components/ui/Tooltip/Tooltip.native.tsx +0 -57
  194. package/src/components/ui/Typography/Typography.native.mdx +0 -56
  195. package/src/components/ui/Typography/Typography.native.spec.tsx +0 -49
  196. package/src/components/ui/Typography/Typography.native.stories.tsx +0 -36
  197. package/src/components/ui/Typography/Typography.native.tsx +0 -65
  198. package/src/native/ThemeContext.tsx +0 -28
  199. package/src/native/tokens.ts +0 -13
  200. package/src/native.ts +0 -76
  201. package/src/stories/ReactNative.mdx +0 -156
@@ -1,54 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { FormInput } from './FormInput.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/FormInput',
9
- component: FormInput,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof FormInput>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
18
- args: { label: 'Full name', name: 'fullName' },
19
- };
20
-
21
- export const WithPlaceholder: Story = {
22
- args: { label: 'Email', name: 'email', type: 'email', placeholder: 'you@example.com' },
23
- };
24
-
25
- export const Password: Story = {
26
- args: { label: 'Password', name: 'password', type: 'password' },
27
- };
28
-
29
- export const WithError: Story = {
30
- args: { label: 'Email', name: 'email', type: 'email', error: 'Please enter a valid email address.' },
31
- };
32
-
33
- export const WithHint: Story = {
34
- args: { label: 'Username', name: 'username', hint: 'Must be at least 3 characters.' },
35
- };
36
-
37
- export const Disabled: Story = {
38
- args: { label: 'Full name', name: 'fullName', disabled: true, value: 'Jane Doe' },
39
- };
40
-
41
- export const Interactive: Story = {
42
- render: () => {
43
- const [value, setValue] = useState('');
44
- return (
45
- <FormInput
46
- label="Your name"
47
- name="name"
48
- placeholder="Enter your name"
49
- value={value}
50
- onChange={setValue}
51
- />
52
- );
53
- },
54
- };
@@ -1,122 +0,0 @@
1
- import { ReactNode, useState } from 'react';
2
- import {
3
- StyleSheet,
4
- View,
5
- TextInput,
6
- KeyboardTypeOptions,
7
- StyleProp,
8
- ViewStyle,
9
- } from 'react-native';
10
- import { spacing, font, border, lineHeight } from '../../../../native/tokens';
11
- import { useTheme } from '../../../../native/ThemeContext';
12
- import { Label } from '../atoms/Label.native';
13
- import { Message } from '../atoms/Message.native';
14
-
15
- type InputType = 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'search';
16
-
17
- interface FormInputProps {
18
- label: ReactNode;
19
- name: string;
20
- type?: InputType;
21
- value?: string;
22
- defaultValue?: string;
23
- placeholder?: string;
24
- error?: string;
25
- hint?: string;
26
- disabled?: boolean;
27
- onChange?: (value: string) => void;
28
- onBlur?: () => void;
29
- onFocus?: () => void;
30
- style?: StyleProp<ViewStyle>;
31
- }
32
-
33
- const keyboardTypeMap: Record<InputType, KeyboardTypeOptions> = {
34
- text: 'default',
35
- email: 'email-address',
36
- password: 'default',
37
- tel: 'phone-pad',
38
- url: 'url',
39
- number: 'numeric',
40
- search: 'default',
41
- };
42
-
43
- const styles = StyleSheet.create({
44
- container: {
45
- gap: spacing.xxs,
46
- },
47
- input: {
48
- fontFamily: font.family.body,
49
- fontSize: font.size.body,
50
- lineHeight: lineHeight.body,
51
- paddingHorizontal: spacing.s,
52
- paddingVertical: spacing.xs,
53
- borderWidth: 1,
54
- borderRadius: border.radius.full,
55
- minHeight: 44,
56
- },
57
- });
58
-
59
- export function FormInput({
60
- label,
61
- name,
62
- type = 'text',
63
- value,
64
- defaultValue,
65
- placeholder,
66
- error,
67
- hint,
68
- disabled = false,
69
- onChange,
70
- onBlur,
71
- onFocus,
72
- style,
73
- }: Readonly<FormInputProps>) {
74
- const { colors } = useTheme();
75
- const [focused, setFocused] = useState(false);
76
-
77
- const borderColor = error
78
- ? colors.colorError
79
- : focused
80
- ? colors.colorInteractive
81
- : colors.colorOnBg;
82
-
83
- return (
84
- <View style={[styles.container, style]}>
85
- <Label label={label} />
86
- <TextInput
87
- accessibilityLabel={typeof label === 'string' ? label : name}
88
- accessibilityState={{ disabled }}
89
- accessibilityHint={hint}
90
- value={value}
91
- defaultValue={defaultValue}
92
- placeholder={placeholder}
93
- placeholderTextColor={colors.colorOnBgSubtle}
94
- keyboardType={keyboardTypeMap[type]}
95
- secureTextEntry={type === 'password'}
96
- autoCapitalize={type === 'email' || type === 'url' ? 'none' : 'sentences'}
97
- autoComplete={type === 'email' ? 'email' : type === 'tel' ? 'tel' : undefined}
98
- editable={!disabled}
99
- onChangeText={onChange}
100
- onFocus={() => {
101
- setFocused(true);
102
- onFocus?.();
103
- }}
104
- onBlur={() => {
105
- setFocused(false);
106
- onBlur?.();
107
- }}
108
- style={[
109
- styles.input,
110
- {
111
- borderColor,
112
- color: colors.colorOnBg,
113
- backgroundColor: colors.colorBg,
114
- },
115
- disabled && { opacity: 0.4 },
116
- ]}
117
- />
118
- <Message type="error" message={error} />
119
- <Message type="hint" message={hint} />
120
- </View>
121
- );
122
- }
@@ -1,87 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './NumberInput.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # NumberInput (React Native)
7
-
8
- Numeric input with stepper buttons on each side. Supports min/max clamping, configurable step, and controlled or uncontrolled usage.
9
-
10
- > **Status: alpha** — API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { NumberInput } from '@boostdev/design-system-components';
16
- import { useState } from 'react';
17
-
18
- function Example() {
19
- const [qty, setQty] = useState(1);
20
- return (
21
- <NumberInput
22
- label="Quantity"
23
- name="qty"
24
- value={qty}
25
- min={1}
26
- max={20}
27
- onChange={setQty}
28
- />
29
- );
30
- }
31
- ```
32
-
33
- ## Examples
34
-
35
- ### Default
36
- <Canvas of={Stories.Default} />
37
-
38
- ### With min and max
39
- <Canvas of={Stories.WithMinMax} />
40
-
41
- ### With step
42
- <Canvas of={Stories.WithStep} />
43
-
44
- ### At minimum
45
- <Canvas of={Stories.AtMinimum} />
46
-
47
- ### At maximum
48
- <Canvas of={Stories.AtMaximum} />
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` | string | — | Visible label text |
71
- | `name` | string | — | Field name |
72
- | `value` | number | — | Controlled value |
73
- | `defaultValue` | number | — | Uncontrolled default value (defaults to `0`) |
74
- | `min` | number | — | Minimum allowed value |
75
- | `max` | number | — | Maximum allowed value |
76
- | `step` | number | `1` | Increment/decrement amount |
77
- | `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
78
- | `error` | string | — | Error message displayed below the input |
79
- | `hint` | string | — | Hint text displayed below the input |
80
- | `onChange` | `(value: number) => void` | — | Called with the clamped value |
81
-
82
- ## Accessibility
83
-
84
- - Decrease and Increase buttons have `accessibilityLabel` and `accessibilityRole="button"`
85
- - Stepper buttons are automatically disabled at min/max boundaries
86
- - `accessibilityValue` on the input provides `min`, `max`, `now`, and `text`
87
- - `disabled` communicates the disabled state via `accessibilityState`
@@ -1,63 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { NumberInput } from './NumberInput.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('NumberInput (native)', () => {
10
- it('renders the label', () => {
11
- renderWithTheme(<NumberInput label="Quantity" name="qty" />);
12
- expect(screen.getByText('Quantity')).toBeInTheDocument();
13
- });
14
-
15
- it('renders the current value in the input', () => {
16
- renderWithTheme(<NumberInput label="Quantity" name="qty" value={5} />);
17
- expect(screen.getByDisplayValue('5')).toBeInTheDocument();
18
- });
19
-
20
- it('renders decrease and increase buttons', () => {
21
- renderWithTheme(<NumberInput label="Quantity" name="qty" value={5} />);
22
- expect(screen.getByRole('button', { name: 'Decrease' })).toBeInTheDocument();
23
- expect(screen.getByRole('button', { name: 'Increase' })).toBeInTheDocument();
24
- });
25
-
26
- it('disables decrease button at min boundary', () => {
27
- renderWithTheme(
28
- <NumberInput label="Quantity" name="qty" value={0} min={0} max={10} />,
29
- );
30
- expect(screen.getByRole('button', { name: 'Decrease' })).toBeDisabled();
31
- expect(screen.getByRole('button', { name: 'Increase' })).not.toBeDisabled();
32
- });
33
-
34
- it('disables increase button at max boundary', () => {
35
- renderWithTheme(
36
- <NumberInput label="Quantity" name="qty" value={10} min={0} max={10} />,
37
- );
38
- expect(screen.getByRole('button', { name: 'Increase' })).toBeDisabled();
39
- expect(screen.getByRole('button', { name: 'Decrease' })).not.toBeDisabled();
40
- });
41
-
42
- it('shows error message', () => {
43
- renderWithTheme(
44
- <NumberInput label="Quantity" name="qty" error="Minimum is 1." />,
45
- );
46
- expect(screen.getByRole('alert')).toHaveTextContent('Minimum is 1.');
47
- });
48
-
49
- it('shows hint message', () => {
50
- renderWithTheme(
51
- <NumberInput label="Guests" name="guests" hint="Including yourself." />,
52
- );
53
- expect(screen.getByText('Including yourself.')).toBeInTheDocument();
54
- });
55
-
56
- it('disables all controls when disabled', () => {
57
- renderWithTheme(
58
- <NumberInput label="Quantity" name="qty" value={5} disabled />,
59
- );
60
- expect(screen.getByRole('button', { name: 'Decrease' })).toBeDisabled();
61
- expect(screen.getByRole('button', { name: 'Increase' })).toBeDisabled();
62
- });
63
- });
@@ -1,63 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../../native/ThemeContext';
4
- import { NumberInput } from './NumberInput.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Form/NumberInput',
9
- component: NumberInput,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- } satisfies Meta<typeof NumberInput>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
18
- args: { label: 'Quantity', name: 'qty', value: 1 },
19
- };
20
-
21
- export const WithMinMax: Story = {
22
- args: { label: 'Quantity', name: 'qty', value: 1, min: 0, max: 10 },
23
- };
24
-
25
- export const WithStep: Story = {
26
- args: { label: 'Amount', name: 'amount', value: 0, step: 5, min: 0, max: 100 },
27
- };
28
-
29
- export const AtMinimum: Story = {
30
- args: { label: 'Quantity', name: 'qty', value: 0, min: 0, max: 10 },
31
- };
32
-
33
- export const AtMaximum: Story = {
34
- args: { label: 'Quantity', name: 'qty', value: 10, min: 0, max: 10 },
35
- };
36
-
37
- export const WithError: Story = {
38
- args: { label: 'Quantity', name: 'qty', value: 0, error: 'Minimum quantity is 1.' },
39
- };
40
-
41
- export const WithHint: Story = {
42
- args: { label: 'Guests', name: 'guests', value: 2, hint: 'Including yourself.' },
43
- };
44
-
45
- export const Disabled: Story = {
46
- args: { label: 'Quantity', name: 'qty', value: 3, disabled: true },
47
- };
48
-
49
- export const Interactive: Story = {
50
- render: () => {
51
- const [value, setValue] = useState(1);
52
- return (
53
- <NumberInput
54
- label="Guests"
55
- name="guests"
56
- value={value}
57
- min={1}
58
- max={20}
59
- onChange={setValue}
60
- />
61
- );
62
- },
63
- };
@@ -1,198 +0,0 @@
1
- import { useState } from 'react';
2
- import {
3
- StyleSheet,
4
- View,
5
- TextInput,
6
- Text,
7
- Pressable,
8
- StyleProp,
9
- ViewStyle,
10
- } from 'react-native';
11
- import { spacing, font, border, 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 NumberInputProps {
17
- label: string;
18
- name: string;
19
- value?: number;
20
- defaultValue?: number;
21
- min?: number;
22
- max?: number;
23
- step?: number;
24
- disabled?: boolean;
25
- error?: string;
26
- hint?: string;
27
- onChange?: (value: number) => void;
28
- style?: StyleProp<ViewStyle>;
29
- }
30
-
31
- const STEPPER_SIZE = 44;
32
-
33
- const styles = StyleSheet.create({
34
- container: {
35
- gap: spacing.xxs,
36
- },
37
- inputRow: {
38
- flexDirection: 'row',
39
- alignItems: 'center',
40
- },
41
- stepper: {
42
- width: STEPPER_SIZE,
43
- height: STEPPER_SIZE,
44
- alignItems: 'center',
45
- justifyContent: 'center',
46
- borderWidth: 1,
47
- },
48
- stepperStart: {
49
- borderTopStartRadius: border.radius.full,
50
- borderBottomStartRadius: border.radius.full,
51
- borderEndWidth: 0,
52
- },
53
- stepperEnd: {
54
- borderTopEndRadius: border.radius.full,
55
- borderBottomEndRadius: border.radius.full,
56
- borderStartWidth: 0,
57
- },
58
- stepperText: {
59
- fontSize: 20,
60
- fontWeight: font.weight.semibold,
61
- lineHeight: 24,
62
- },
63
- input: {
64
- flex: 1,
65
- fontFamily: font.family.body,
66
- fontSize: font.size.body,
67
- lineHeight: lineHeight.body,
68
- textAlign: 'center',
69
- paddingHorizontal: spacing.xs,
70
- paddingVertical: spacing.xs,
71
- borderTopWidth: 1,
72
- borderBottomWidth: 1,
73
- minHeight: STEPPER_SIZE,
74
- },
75
- });
76
-
77
- export function NumberInput({
78
- label,
79
- name: _name, // eslint-disable-line @typescript-eslint/no-unused-vars
80
- value,
81
- defaultValue,
82
- min,
83
- max,
84
- step = 1,
85
- disabled = false,
86
- error,
87
- hint,
88
- onChange,
89
- style,
90
- }: Readonly<NumberInputProps>) {
91
- const { colors } = useTheme();
92
- const isControlled = value !== undefined;
93
- const [internalValue, setInternalValue] = useState(defaultValue ?? 0);
94
-
95
- const currentValue = isControlled ? value : internalValue;
96
-
97
- const clamp = (v: number): number => {
98
- const withMin = min !== undefined ? Math.max(min, v) : v;
99
- return max !== undefined ? Math.min(max, withMin) : withMin;
100
- };
101
-
102
- const update = (next: number) => {
103
- const clamped = clamp(next);
104
- if (!isControlled) setInternalValue(clamped);
105
- onChange?.(clamped);
106
- };
107
-
108
- const canDecrease = min === undefined || currentValue > min;
109
- const canIncrease = max === undefined || currentValue < max;
110
-
111
- const borderColor = error ? colors.colorError : colors.colorOnBg;
112
-
113
- return (
114
- <View style={[styles.container, style]}>
115
- <Label label={label} />
116
- <View style={[styles.inputRow, disabled && { opacity: 0.4 }]}>
117
- <Pressable
118
- accessibilityRole="button"
119
- accessibilityLabel="Decrease"
120
- accessibilityState={{ disabled: disabled || !canDecrease }}
121
- disabled={disabled || !canDecrease}
122
- onPress={() => update(currentValue - step)}
123
- style={({ pressed }) => [
124
- styles.stepper,
125
- styles.stepperStart,
126
- {
127
- borderColor,
128
- backgroundColor: colors.colorBgSubtle,
129
- },
130
- pressed && { opacity: 0.7 },
131
- (disabled || !canDecrease) && { opacity: 0.4 },
132
- ]}
133
- >
134
- <Text
135
- style={[styles.stepperText, { color: colors.colorOnBg }]}
136
- aria-hidden
137
- >
138
- {'\u2212'}
139
- </Text>
140
- </Pressable>
141
-
142
- <TextInput
143
- accessibilityLabel={label}
144
- accessibilityState={{ disabled }}
145
- accessibilityValue={{
146
- min,
147
- max,
148
- now: currentValue,
149
- text: String(currentValue),
150
- }}
151
- keyboardType="numeric"
152
- value={String(currentValue)}
153
- editable={!disabled}
154
- onChangeText={(text) => {
155
- const parsed = parseFloat(text);
156
- const safe = isNaN(parsed) ? 0 : parsed;
157
- update(safe);
158
- }}
159
- style={[
160
- styles.input,
161
- {
162
- borderColor,
163
- color: colors.colorOnBg,
164
- backgroundColor: colors.colorBg,
165
- },
166
- ]}
167
- />
168
-
169
- <Pressable
170
- accessibilityRole="button"
171
- accessibilityLabel="Increase"
172
- accessibilityState={{ disabled: disabled || !canIncrease }}
173
- disabled={disabled || !canIncrease}
174
- onPress={() => update(currentValue + step)}
175
- style={({ pressed }) => [
176
- styles.stepper,
177
- styles.stepperEnd,
178
- {
179
- borderColor,
180
- backgroundColor: colors.colorBgSubtle,
181
- },
182
- pressed && { opacity: 0.7 },
183
- (disabled || !canIncrease) && { opacity: 0.4 },
184
- ]}
185
- >
186
- <Text
187
- style={[styles.stepperText, { color: colors.colorOnBg }]}
188
- aria-hidden
189
- >
190
- +
191
- </Text>
192
- </Pressable>
193
- </View>
194
- <Message type="error" message={error} />
195
- <Message type="hint" message={hint} />
196
- </View>
197
- );
198
- }
@@ -1,79 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../../native/ThemeContext';
3
- import { Radio } from './Radio.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('Radio (native)', () => {
10
- it('has radio role with label', () => {
11
- renderWithTheme(<Radio label="Option A" name="choice" />);
12
- expect(screen.getByRole('radio', { name: 'Option A' })).toBeInTheDocument();
13
- });
14
-
15
- it('renders unchecked by default (no inner dot)', () => {
16
- renderWithTheme(<Radio label="Option A" name="choice" />);
17
- // The outer circle is always present; the inner dot only renders when checked
18
- const radio = screen.getByRole('radio');
19
- // When unchecked, the inner filled circle should not be rendered
20
- // The outer has 1 child (no inner dot) vs 2 when checked
21
- expect(radio).toBeInTheDocument();
22
- });
23
-
24
- it('renders inner dot when checked', () => {
25
- const { container: uncheckedContainer } = renderWithTheme(
26
- <Radio label="Unchecked" name="choice" />,
27
- );
28
- const { container: checkedContainer } = renderWithTheme(
29
- <Radio label="Checked" name="choice" checked />,
30
- );
31
- // The checked variant has more DOM nodes (the inner filled circle)
32
- expect(checkedContainer.innerHTML.length).toBeGreaterThan(
33
- uncheckedContainer.innerHTML.length,
34
- );
35
- });
36
-
37
- it('reflects disabled state', () => {
38
- renderWithTheme(<Radio label="Option A" name="choice" disabled />);
39
- const radio = screen.getByRole('radio');
40
- expect(radio).toHaveAttribute('aria-disabled', 'true');
41
- });
42
-
43
- it('calls onChange when clicked', () => {
44
- const handleChange = vi.fn();
45
- renderWithTheme(
46
- <Radio label="Option A" name="choice" onChange={handleChange} />,
47
- );
48
- fireEvent.click(screen.getByRole('radio'));
49
- expect(handleChange).toHaveBeenCalledTimes(1);
50
- });
51
-
52
- it('does not call onChange when disabled', () => {
53
- const handleChange = vi.fn();
54
- renderWithTheme(
55
- <Radio label="Option A" name="choice" disabled onChange={handleChange} />,
56
- );
57
- fireEvent.click(screen.getByRole('radio'));
58
- expect(handleChange).not.toHaveBeenCalled();
59
- });
60
-
61
- it('renders error message', () => {
62
- renderWithTheme(
63
- <Radio label="Option A" name="choice" error="Selection required." />,
64
- );
65
- expect(screen.getByRole('alert')).toHaveTextContent('Selection required.');
66
- });
67
-
68
- it('renders hint message', () => {
69
- renderWithTheme(
70
- <Radio label="Option A" name="choice" hint="Select your preference." />,
71
- );
72
- expect(screen.getByText('Select your preference.')).toBeInTheDocument();
73
- });
74
-
75
- it('uses name as accessibility label when label is not a string', () => {
76
- renderWithTheme(<Radio label={<span>Custom</span>} name="fallback" />);
77
- expect(screen.getByRole('radio', { name: 'fallback' })).toBeInTheDocument();
78
- });
79
- });