@boostdev/design-system-components 1.2.2 → 1.2.4

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 (215) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +50 -5
  3. package/dist/client.cjs +50 -50
  4. package/dist/client.css +506 -503
  5. package/dist/client.js +50 -50
  6. package/dist/index.cjs +50 -50
  7. package/dist/index.css +506 -503
  8. package/dist/index.js +50 -50
  9. package/dist/native/index.cjs +3692 -352
  10. package/dist/native/index.d.cts +359 -3
  11. package/dist/native/index.d.ts +359 -3
  12. package/dist/native/index.js +3811 -364
  13. package/package.json +3 -3
  14. package/src/components/interaction/Button/Button.module.css +1 -4
  15. package/src/components/interaction/Button/Button.native.stories.tsx +10 -12
  16. package/src/components/interaction/Button/Button.native.tsx +3 -3
  17. package/src/components/interaction/Dialog/Dialog.module.css +1 -1
  18. package/src/components/interaction/Dialog/Dialog.native.mdx +61 -0
  19. package/src/components/interaction/Dialog/Dialog.native.spec.tsx +73 -0
  20. package/src/components/interaction/Dialog/Dialog.native.stories.tsx +53 -0
  21. package/src/components/interaction/Dialog/Dialog.native.tsx +128 -0
  22. package/src/components/interaction/Drawer/Drawer.module.css +1 -1
  23. package/src/components/interaction/Drawer/Drawer.native.mdx +58 -0
  24. package/src/components/interaction/Drawer/Drawer.native.spec.tsx +81 -0
  25. package/src/components/interaction/Drawer/Drawer.native.stories.tsx +33 -0
  26. package/src/components/interaction/Drawer/Drawer.native.tsx +175 -0
  27. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +4 -0
  28. package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +74 -0
  29. package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +78 -0
  30. package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +51 -0
  31. package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +254 -0
  32. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +4 -4
  33. package/src/components/interaction/Popover/Popover.native.mdx +61 -0
  34. package/src/components/interaction/Popover/Popover.native.spec.tsx +73 -0
  35. package/src/components/interaction/Popover/Popover.native.stories.tsx +44 -0
  36. package/src/components/interaction/Popover/Popover.native.tsx +87 -0
  37. package/src/components/interaction/Rating/Rating.native.mdx +55 -0
  38. package/src/components/interaction/Rating/Rating.native.spec.tsx +38 -0
  39. package/src/components/interaction/Rating/Rating.native.stories.tsx +37 -0
  40. package/src/components/interaction/Rating/Rating.native.tsx +50 -0
  41. package/src/components/interaction/Toast/Toast.native.mdx +81 -0
  42. package/src/components/interaction/Toast/Toast.native.spec.tsx +80 -0
  43. package/src/components/interaction/Toast/Toast.native.stories.tsx +47 -0
  44. package/src/components/interaction/Toast/Toast.native.tsx +202 -0
  45. package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +2 -4
  46. package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +1 -1
  47. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +78 -0
  48. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +57 -0
  49. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +96 -0
  50. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +61 -0
  51. package/src/components/interaction/form/Combobox/Combobox.module.css +1 -1
  52. package/src/components/interaction/form/Combobox/Combobox.native.mdx +86 -0
  53. package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +60 -0
  54. package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +99 -0
  55. package/src/components/interaction/form/Combobox/Combobox.native.tsx +211 -0
  56. package/src/components/interaction/form/FileInput/FileInput.native.mdx +86 -0
  57. package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +73 -0
  58. package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +69 -0
  59. package/src/components/interaction/form/FileInput/FileInput.native.tsx +129 -0
  60. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -1
  61. package/src/components/interaction/form/FormInput/FormInput.native.mdx +81 -0
  62. package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +51 -0
  63. package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +54 -0
  64. package/src/components/interaction/form/FormInput/FormInput.native.tsx +122 -0
  65. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  66. package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +87 -0
  67. package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +63 -0
  68. package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +63 -0
  69. package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +198 -0
  70. package/src/components/interaction/form/Radio/Radio.native.spec.tsx +79 -0
  71. package/src/components/interaction/form/Radio/Radio.native.stories.tsx +12 -14
  72. package/src/components/interaction/form/Radio/Radio.native.tsx +1 -1
  73. package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +79 -0
  74. package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +66 -0
  75. package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +96 -0
  76. package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +61 -0
  77. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -5
  78. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +78 -0
  79. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +82 -0
  80. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +99 -0
  81. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +121 -0
  82. package/src/components/interaction/form/Select/Select.module.css +1 -1
  83. package/src/components/interaction/form/Select/Select.native.mdx +85 -0
  84. package/src/components/interaction/form/Select/Select.native.spec.tsx +64 -0
  85. package/src/components/interaction/form/Select/Select.native.stories.tsx +75 -0
  86. package/src/components/interaction/form/Select/Select.native.tsx +252 -0
  87. package/src/components/interaction/form/Slider/Slider.module.css +4 -4
  88. package/src/components/interaction/form/Slider/Slider.native.mdx +83 -0
  89. package/src/components/interaction/form/Slider/Slider.native.spec.tsx +51 -0
  90. package/src/components/interaction/form/Slider/Slider.native.stories.tsx +58 -0
  91. package/src/components/interaction/form/Slider/Slider.native.tsx +188 -0
  92. package/src/components/interaction/form/Switch/Switch.module.css +3 -3
  93. package/src/components/interaction/form/Switch/Switch.native.stories.tsx +2 -4
  94. package/src/components/interaction/form/Textarea/Textarea.native.mdx +76 -0
  95. package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +50 -0
  96. package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +50 -0
  97. package/src/components/interaction/form/Textarea/Textarea.native.tsx +108 -0
  98. package/src/components/interaction/form/atoms/Label.native.spec.tsx +39 -0
  99. package/src/components/interaction/form/atoms/Label.native.stories.tsx +1 -1
  100. package/src/components/interaction/form/atoms/Message.native.stories.tsx +1 -1
  101. package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +83 -0
  102. package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +13 -19
  103. package/src/components/layout/Card/Card.native.spec.tsx +102 -0
  104. package/src/components/layout/Card/Card.native.stories.tsx +9 -11
  105. package/src/components/layout/Card/Card.native.tsx +1 -1
  106. package/src/components/layout/IconWrapper/IconWrapper.module.css +1 -1
  107. package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +7 -11
  108. package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +51 -0
  109. package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +6 -8
  110. package/src/components/ui/Accordion/Accordion.native.mdx +65 -0
  111. package/src/components/ui/Accordion/Accordion.native.spec.tsx +69 -0
  112. package/src/components/ui/Accordion/Accordion.native.stories.tsx +52 -0
  113. package/src/components/ui/Accordion/Accordion.native.tsx +141 -0
  114. package/src/components/ui/Alert/Alert.module.css +1 -1
  115. package/src/components/ui/Alert/Alert.native.spec.tsx +76 -0
  116. package/src/components/ui/Alert/Alert.native.stories.tsx +8 -10
  117. package/src/components/ui/Alert/Alert.native.tsx +7 -7
  118. package/src/components/ui/Avatar/Avatar.module.css +1 -1
  119. package/src/components/ui/Avatar/Avatar.native.stories.tsx +6 -8
  120. package/src/components/ui/Avatar/Avatar.native.tsx +2 -2
  121. package/src/components/ui/Badge/Badge.module.css +1 -1
  122. package/src/components/ui/Badge/Badge.native.spec.tsx +45 -0
  123. package/src/components/ui/Badge/Badge.native.stories.tsx +6 -8
  124. package/src/components/ui/Badge/Badge.native.tsx +2 -2
  125. package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +52 -0
  126. package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +49 -0
  127. package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +46 -0
  128. package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +83 -0
  129. package/src/components/ui/Calendar/Calendar.module.css +2 -2
  130. package/src/components/ui/Calendar/Calendar.native.mdx +58 -0
  131. package/src/components/ui/Calendar/Calendar.native.spec.tsx +92 -0
  132. package/src/components/ui/Calendar/Calendar.native.stories.tsx +45 -0
  133. package/src/components/ui/Calendar/Calendar.native.tsx +294 -0
  134. package/src/components/ui/Carousel/Carousel.module.css +1 -1
  135. package/src/components/ui/Carousel/Carousel.native.mdx +55 -0
  136. package/src/components/ui/Carousel/Carousel.native.spec.tsx +55 -0
  137. package/src/components/ui/Carousel/Carousel.native.stories.tsx +59 -0
  138. package/src/components/ui/Carousel/Carousel.native.tsx +184 -0
  139. package/src/components/ui/Collapsible/Collapsible.native.mdx +53 -0
  140. package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +68 -0
  141. package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +48 -0
  142. package/src/components/ui/Collapsible/Collapsible.native.tsx +139 -0
  143. package/src/components/ui/DescriptionList/DescriptionList.native.mdx +50 -0
  144. package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +37 -0
  145. package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +48 -0
  146. package/src/components/ui/DescriptionList/DescriptionList.native.tsx +54 -0
  147. package/src/components/ui/Link/Link.native.mdx +53 -0
  148. package/src/components/ui/Link/Link.native.spec.tsx +35 -0
  149. package/src/components/ui/Link/Link.native.stories.tsx +46 -0
  150. package/src/components/ui/Link/Link.native.tsx +48 -0
  151. package/src/components/ui/Loading/Loading.module.css +1 -1
  152. package/src/components/ui/Loading/Loading.native.stories.tsx +6 -8
  153. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -1
  154. package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +85 -0
  155. package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +8 -10
  156. package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +7 -7
  157. package/src/components/ui/Pagination/Pagination.module.css +5 -1
  158. package/src/components/ui/Pagination/Pagination.native.mdx +62 -0
  159. package/src/components/ui/Pagination/Pagination.native.spec.tsx +86 -0
  160. package/src/components/ui/Pagination/Pagination.native.stories.tsx +47 -0
  161. package/src/components/ui/Pagination/Pagination.native.tsx +157 -0
  162. package/src/components/ui/Progress/Progress.module.css +2 -2
  163. package/src/components/ui/Progress/Progress.native.spec.tsx +56 -0
  164. package/src/components/ui/Progress/Progress.native.stories.tsx +1 -1
  165. package/src/components/ui/Progress/Progress.native.tsx +2 -2
  166. package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +63 -0
  167. package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +39 -0
  168. package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +36 -0
  169. package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +176 -0
  170. package/src/components/ui/Separator/Separator.native.spec.tsx +39 -0
  171. package/src/components/ui/Separator/Separator.native.stories.tsx +10 -14
  172. package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +39 -0
  173. package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +6 -8
  174. package/src/components/ui/Skeleton/Skeleton.native.tsx +2 -1
  175. package/src/components/ui/Table/Table.native.mdx +72 -0
  176. package/src/components/ui/Table/Table.native.spec.tsx +83 -0
  177. package/src/components/ui/Table/Table.native.stories.tsx +55 -0
  178. package/src/components/ui/Table/Table.native.tsx +196 -0
  179. package/src/components/ui/Tabs/Tabs.native.mdx +65 -0
  180. package/src/components/ui/Tabs/Tabs.native.spec.tsx +65 -0
  181. package/src/components/ui/Tabs/Tabs.native.stories.tsx +57 -0
  182. package/src/components/ui/Tabs/Tabs.native.tsx +133 -0
  183. package/src/components/ui/Tooltip/Tooltip.module.css +1 -1
  184. package/src/components/ui/Tooltip/Tooltip.native.mdx +47 -0
  185. package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +37 -0
  186. package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +38 -0
  187. package/src/components/ui/Tooltip/Tooltip.native.tsx +57 -0
  188. package/src/components/ui/Typography/Typography.native.spec.tsx +49 -0
  189. package/src/components/ui/Typography/Typography.native.stories.tsx +8 -10
  190. package/src/native/ThemeContext.tsx +3 -3
  191. package/src/native.ts +37 -0
  192. package/src/stories/ReactNative.mdx +48 -13
  193. package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
  194. package/src/web-components/interaction/BdsCollapsible.stories.tsx +1 -1
  195. package/src/web-components/interaction/BdsSkipLink.stories.tsx +1 -1
  196. package/src/web-components/interaction/BdsTabs.stories.tsx +1 -1
  197. package/src/web-components/interaction/BdsTooltip.stories.tsx +1 -1
  198. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +1 -1
  199. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +1 -1
  200. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +1 -1
  201. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +1 -1
  202. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +1 -1
  203. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +1 -1
  204. package/src/web-components/interaction/form/BdsRadio.stories.tsx +1 -1
  205. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +1 -1
  206. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +1 -1
  207. package/src/web-components/interaction/form/BdsSelect.stories.tsx +1 -1
  208. package/src/web-components/interaction/form/BdsSlider.stories.tsx +1 -1
  209. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +1 -1
  210. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +1 -1
  211. package/src/web-components/ui/BdsButtonGroup.stories.tsx +1 -1
  212. package/src/web-components/ui/BdsCard.stories.tsx +1 -1
  213. package/src/web-components/ui/BdsIconWrapper.stories.tsx +1 -1
  214. package/src/web-components/ui/BdsRating.stories.tsx +1 -1
  215. package/src/web-components/ui/BdsSectionHeader.stories.tsx +1 -1
@@ -7,7 +7,7 @@ const meta = {
7
7
  tags: ['!stable', 'alpha'],
8
8
  title: 'React Native/Form/Switch',
9
9
  component: Switch,
10
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
11
  parameters: { layout: 'padded' },
12
12
  } satisfies Meta<typeof Switch>;
13
13
 
@@ -27,9 +27,7 @@ export const Interactive: Story = {
27
27
  render: () => {
28
28
  const [value, setValue] = useState(false);
29
29
  return (
30
- <ThemeProvider>
31
- <Switch label="Enable feature" name="feature" value={value} onChange={setValue} />
32
- </ThemeProvider>
30
+ <Switch label="Enable feature" name="feature" value={value} onChange={setValue} />
33
31
  );
34
32
  },
35
33
  };
@@ -0,0 +1,76 @@
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/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`
@@ -0,0 +1,50 @@
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
+ });
@@ -0,0 +1,50 @@
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
+ };
@@ -0,0 +1,108 @@
1
+ import { ReactNode, useState } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ TextInput,
6
+ StyleProp,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+ import { spacing, font, border, lineHeight } from '../../../../native/tokens';
10
+ import { useTheme } from '../../../../native/ThemeContext';
11
+ import { Label } from '../atoms/Label.native';
12
+ import { Message } from '../atoms/Message.native';
13
+
14
+ interface TextareaProps {
15
+ label: ReactNode;
16
+ name: string;
17
+ value?: string;
18
+ defaultValue?: string;
19
+ placeholder?: string;
20
+ error?: string;
21
+ hint?: string;
22
+ disabled?: boolean;
23
+ numberOfLines?: number;
24
+ onChange?: (value: string) => void;
25
+ onBlur?: () => void;
26
+ onFocus?: () => void;
27
+ style?: StyleProp<ViewStyle>;
28
+ }
29
+
30
+ const styles = StyleSheet.create({
31
+ container: {
32
+ gap: spacing.xxs,
33
+ },
34
+ textarea: {
35
+ fontFamily: font.family.body,
36
+ fontSize: font.size.body,
37
+ lineHeight: lineHeight.body,
38
+ paddingHorizontal: spacing.s,
39
+ paddingVertical: spacing.xs,
40
+ borderWidth: 1,
41
+ borderRadius: border.radius.xs,
42
+ minHeight: 100,
43
+ textAlignVertical: 'top',
44
+ },
45
+ });
46
+
47
+ export function Textarea({
48
+ label,
49
+ name,
50
+ value,
51
+ defaultValue,
52
+ placeholder,
53
+ error,
54
+ hint,
55
+ disabled = false,
56
+ numberOfLines = 4,
57
+ onChange,
58
+ onBlur,
59
+ onFocus,
60
+ style,
61
+ }: Readonly<TextareaProps>) {
62
+ const { colors } = useTheme();
63
+ const [focused, setFocused] = useState(false);
64
+
65
+ const borderColor = error
66
+ ? colors.colorError
67
+ : focused
68
+ ? colors.colorInteractive
69
+ : colors.colorOnBg;
70
+
71
+ return (
72
+ <View style={[styles.container, style]}>
73
+ <Label label={label} />
74
+ <TextInput
75
+ accessibilityLabel={typeof label === 'string' ? label : name}
76
+ accessibilityState={{ disabled }}
77
+ accessibilityHint={hint}
78
+ multiline
79
+ numberOfLines={numberOfLines}
80
+ value={value}
81
+ defaultValue={defaultValue}
82
+ placeholder={placeholder}
83
+ placeholderTextColor={colors.colorOnBgSubtle}
84
+ editable={!disabled}
85
+ onChangeText={onChange}
86
+ onFocus={() => {
87
+ setFocused(true);
88
+ onFocus?.();
89
+ }}
90
+ onBlur={() => {
91
+ setFocused(false);
92
+ onBlur?.();
93
+ }}
94
+ style={[
95
+ styles.textarea,
96
+ {
97
+ borderColor,
98
+ color: colors.colorOnBg,
99
+ backgroundColor: colors.colorBg,
100
+ },
101
+ disabled && { opacity: 0.4 },
102
+ ]}
103
+ />
104
+ <Message type="error" message={error} />
105
+ <Message type="hint" message={hint} />
106
+ </View>
107
+ );
108
+ }
@@ -0,0 +1,39 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../../native/ThemeContext';
3
+ import { Label } from './Label.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Label (native)', () => {
10
+ it('renders the label text', () => {
11
+ renderWithTheme(<Label label="Email address" />);
12
+ expect(screen.getByText('Email address')).toBeInTheDocument();
13
+ });
14
+
15
+ it('renders ReactNode content as label', () => {
16
+ renderWithTheme(
17
+ <Label
18
+ label={
19
+ <span data-testid="custom-label">
20
+ Custom <strong>label</strong>
21
+ </span>
22
+ }
23
+ />,
24
+ );
25
+ expect(screen.getByTestId('custom-label')).toBeInTheDocument();
26
+ });
27
+
28
+ it('applies semibold font weight', () => {
29
+ renderWithTheme(<Label label="Name" />);
30
+ const label = screen.getByText('Name');
31
+ expect(label).toHaveStyle({ fontWeight: expect.stringMatching(/600|bold|semibold/) });
32
+ });
33
+
34
+ it('applies custom style when provided', () => {
35
+ renderWithTheme(<Label label="Name" style={{ marginBottom: 8 }} />);
36
+ const label = screen.getByText('Name');
37
+ expect(label).toHaveStyle({ marginBottom: 8 });
38
+ });
39
+ });
@@ -7,7 +7,7 @@ const meta = {
7
7
  tags: ['!stable', 'alpha'],
8
8
  title: 'React Native/Form/Label',
9
9
  component: Label,
10
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
11
  parameters: { layout: 'centered' },
12
12
  } satisfies Meta<typeof Label>;
13
13
 
@@ -7,7 +7,7 @@ const meta = {
7
7
  tags: ['!stable', 'alpha'],
8
8
  title: 'React Native/Form/Message',
9
9
  component: Message,
10
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
11
  parameters: { layout: 'padded' },
12
12
  argTypes: {
13
13
  type: { control: 'select', options: ['hint', 'error'] },
@@ -0,0 +1,83 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { ButtonGroup } from './ButtonGroup.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('ButtonGroup (native)', () => {
10
+ it('renders children', () => {
11
+ renderWithTheme(
12
+ <ButtonGroup>
13
+ <span data-testid="child-a">A</span>
14
+ <span data-testid="child-b">B</span>
15
+ </ButtonGroup>,
16
+ );
17
+ expect(screen.getByTestId('child-a')).toBeInTheDocument();
18
+ expect(screen.getByTestId('child-b')).toBeInTheDocument();
19
+ });
20
+
21
+ it('applies accessibilityLabel to the container', () => {
22
+ const { container } = renderWithTheme(
23
+ <ButtonGroup accessibilityLabel="Actions">
24
+ <span>OK</span>
25
+ </ButtonGroup>,
26
+ );
27
+ expect(container.firstChild).toHaveAttribute('aria-label', 'Actions');
28
+ });
29
+
30
+ it('renders without accessibilityLabel when not provided', () => {
31
+ const { container } = renderWithTheme(
32
+ <ButtonGroup>
33
+ <span>OK</span>
34
+ </ButtonGroup>,
35
+ );
36
+ expect(container.firstChild).not.toHaveAttribute('aria-label');
37
+ });
38
+
39
+ it('renders in row direction by default', () => {
40
+ const { container } = renderWithTheme(
41
+ <ButtonGroup>
42
+ <span>A</span>
43
+ </ButtonGroup>,
44
+ );
45
+ expect(container.firstChild).toHaveStyle({ flexDirection: 'row' });
46
+ });
47
+
48
+ it('renders in row direction for flow variant', () => {
49
+ const { container } = renderWithTheme(
50
+ <ButtonGroup variant="flow">
51
+ <span>A</span>
52
+ </ButtonGroup>,
53
+ );
54
+ expect(container.firstChild).toHaveStyle({ flexDirection: 'row' });
55
+ });
56
+
57
+ it('renders in row direction for content variant', () => {
58
+ const { container } = renderWithTheme(
59
+ <ButtonGroup variant="content">
60
+ <span>A</span>
61
+ </ButtonGroup>,
62
+ );
63
+ expect(container.firstChild).toHaveStyle({ flexDirection: 'row' });
64
+ });
65
+
66
+ it('renders in column direction for card variant', () => {
67
+ const { container } = renderWithTheme(
68
+ <ButtonGroup variant="card">
69
+ <span>A</span>
70
+ </ButtonGroup>,
71
+ );
72
+ expect(container.firstChild).toHaveStyle({ flexDirection: 'column' });
73
+ });
74
+
75
+ it('renders in column direction for modal variant', () => {
76
+ const { container } = renderWithTheme(
77
+ <ButtonGroup variant="modal">
78
+ <span>A</span>
79
+ </ButtonGroup>,
80
+ );
81
+ expect(container.firstChild).toHaveStyle({ flexDirection: 'column' });
82
+ });
83
+ });
@@ -8,7 +8,7 @@ const meta = {
8
8
  tags: ['!stable', 'alpha'],
9
9
  title: 'React Native/Layout/ButtonGroup',
10
10
  component: ButtonGroup,
11
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
12
12
  parameters: { layout: 'padded' },
13
13
  argTypes: {
14
14
  variant: { control: 'select', options: ['flow', 'card', 'modal', 'content'] },
@@ -20,31 +20,25 @@ type Story = StoryObj<typeof meta>;
20
20
 
21
21
  export const Flow: Story = {
22
22
  render: (args) => (
23
- <ThemeProvider>
24
- <ButtonGroup {...args} variant="flow">
25
- <Button>Primary</Button>
26
- <Button variant="ghost">Secondary</Button>
27
- </ButtonGroup>
28
- </ThemeProvider>
23
+ <ButtonGroup {...args} variant="flow">
24
+ <Button>Primary</Button>
25
+ <Button variant="ghost">Secondary</Button>
26
+ </ButtonGroup>
29
27
  ),
30
28
  };
31
29
  export const Card: Story = {
32
30
  render: (args) => (
33
- <ThemeProvider>
34
- <ButtonGroup {...args} variant="card">
35
- <Button>Confirm</Button>
36
- <Button variant="ghost">Cancel</Button>
37
- </ButtonGroup>
38
- </ThemeProvider>
31
+ <ButtonGroup {...args} variant="card">
32
+ <Button>Confirm</Button>
33
+ <Button variant="ghost">Cancel</Button>
34
+ </ButtonGroup>
39
35
  ),
40
36
  };
41
37
  export const Modal: Story = {
42
38
  render: (args) => (
43
- <ThemeProvider>
44
- <ButtonGroup {...args} variant="modal">
45
- <Button>Save</Button>
46
- <Button variant="ghost">Discard</Button>
47
- </ButtonGroup>
48
- </ThemeProvider>
39
+ <ButtonGroup {...args} variant="modal">
40
+ <Button>Save</Button>
41
+ <Button variant="ghost">Discard</Button>
42
+ </ButtonGroup>
49
43
  ),
50
44
  };
@@ -0,0 +1,102 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Card } from './Card.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Card (native)', () => {
10
+ it('renders children', () => {
11
+ renderWithTheme(
12
+ <Card>
13
+ <span data-testid="content">Hello</span>
14
+ </Card>,
15
+ );
16
+ expect(screen.getByTestId('content')).toBeInTheDocument();
17
+ });
18
+
19
+ it('renders as a View when onPress is not provided', () => {
20
+ const { container } = renderWithTheme(
21
+ <Card>
22
+ <span>Content</span>
23
+ </Card>,
24
+ );
25
+ expect(container.firstChild).not.toHaveAttribute('role');
26
+ });
27
+
28
+ it('renders as a Pressable with button role when onPress is provided', () => {
29
+ const handlePress = vi.fn();
30
+ renderWithTheme(
31
+ <Card onPress={handlePress} accessibilityLabel="Tap me">
32
+ <span>Content</span>
33
+ </Card>,
34
+ );
35
+ expect(screen.getByRole('button', { name: 'Tap me' })).toBeInTheDocument();
36
+ });
37
+
38
+ it('calls onPress when pressed', () => {
39
+ const handlePress = vi.fn();
40
+ renderWithTheme(
41
+ <Card onPress={handlePress} accessibilityLabel="Tap me">
42
+ <span>Content</span>
43
+ </Card>,
44
+ );
45
+ fireEvent.click(screen.getByRole('button'));
46
+ expect(handlePress).toHaveBeenCalledOnce();
47
+ });
48
+
49
+ it('applies accessibilityLabel to the pressable card', () => {
50
+ renderWithTheme(
51
+ <Card onPress={() => {}} accessibilityLabel="Product card">
52
+ <span>Content</span>
53
+ </Card>,
54
+ );
55
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Product card');
56
+ });
57
+
58
+ it('applies border for outlined variant', () => {
59
+ const { container } = renderWithTheme(
60
+ <Card variant="outlined">
61
+ <span>Content</span>
62
+ </Card>,
63
+ );
64
+ expect(container.firstChild).toHaveStyle({ borderWidth: 1 });
65
+ });
66
+
67
+ it('renders elevated variant without throwing', () => {
68
+ renderWithTheme(
69
+ <Card variant="elevated">
70
+ <span data-testid="elevated-content">Content</span>
71
+ </Card>,
72
+ );
73
+ expect(screen.getByTestId('elevated-content')).toBeInTheDocument();
74
+ });
75
+
76
+ it('renders with no padding when padding is none', () => {
77
+ const { container } = renderWithTheme(
78
+ <Card padding="none">
79
+ <span>Content</span>
80
+ </Card>,
81
+ );
82
+ expect(container.firstChild).toHaveStyle({ padding: 0 });
83
+ });
84
+
85
+ it('centers content when textAlign is center', () => {
86
+ const { container } = renderWithTheme(
87
+ <Card textAlign="center">
88
+ <span>Content</span>
89
+ </Card>,
90
+ );
91
+ expect(container.firstChild).toHaveStyle({ alignItems: 'center' });
92
+ });
93
+
94
+ it('aligns content to flex-end when textAlign is end', () => {
95
+ const { container } = renderWithTheme(
96
+ <Card textAlign="end">
97
+ <span>Content</span>
98
+ </Card>,
99
+ );
100
+ expect(container.firstChild).toHaveStyle({ alignItems: 'flex-end' });
101
+ });
102
+ });
@@ -8,7 +8,7 @@ const meta = {
8
8
  tags: ['!stable', 'alpha'],
9
9
  title: 'React Native/Layout/Card',
10
10
  component: Card,
11
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
11
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
12
12
  parameters: { layout: 'padded' },
13
13
  argTypes: {
14
14
  variant: { control: 'select', options: ['default', 'elevated', 'outlined'] },
@@ -39,15 +39,13 @@ export const Pressable: Story = {
39
39
  };
40
40
  export const AllVariants: Story = {
41
41
  render: () => (
42
- <ThemeProvider>
43
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: 320 }}>
44
- {(['default', 'elevated', 'outlined'] as const).map(v => (
45
- <Card key={v} variant={v} padding="medium">
46
- <Typography variant="h3">{v.charAt(0).toUpperCase() + v.slice(1)}</Typography>
47
- <Typography>Card with {v} variant.</Typography>
48
- </Card>
49
- ))}
50
- </div>
51
- </ThemeProvider>
42
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: 320 }}>
43
+ {(['default', 'elevated', 'outlined'] as const).map(v => (
44
+ <Card key={v} variant={v} padding="medium">
45
+ <Typography variant="h3">{v.charAt(0).toUpperCase() + v.slice(1)}</Typography>
46
+ <Typography>Card with {v} variant.</Typography>
47
+ </Card>
48
+ ))}
49
+ </div>
52
50
  ),
53
51
  };
@@ -32,7 +32,7 @@ const alignMap: Record<CardAlign, 'flex-start' | 'center' | 'flex-end'> = {
32
32
 
33
33
  const styles = StyleSheet.create({
34
34
  card: {
35
- borderRadius: border.radius.s,
35
+ borderRadius: border.radius.m,
36
36
  overflow: 'hidden',
37
37
  },
38
38
  });
@@ -3,7 +3,7 @@
3
3
  inline-size: 2em;
4
4
  block-size: 2em;
5
5
  font-size: 3em;
6
- border-radius: 50%;
6
+ border-radius: var(--icon-wrapper-radius, var(--bds-border_radius--circle));
7
7
  display: flex;
8
8
  align-items: center;
9
9
  justify-content: center;