@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
@@ -10,7 +10,7 @@
10
10
  block-size: 2.5em;
11
11
  border: 4px solid var(--bds-color_bg);
12
12
  border-block-start: 4px solid var(--bds-color_interactive);
13
- border-radius: 50%;
13
+ border-radius: var(--loading_radius, var(--bds-border_radius--circle));
14
14
  animation: spin 1s linear infinite;
15
15
  }
16
16
 
@@ -7,7 +7,7 @@ const meta = {
7
7
  tags: ['!stable', 'alpha'],
8
8
  title: 'React Native/UI/Loading',
9
9
  component: Loading,
10
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
11
  parameters: { layout: 'centered' },
12
12
  argTypes: {
13
13
  size: { control: 'select', options: ['small', 'medium', 'large'] },
@@ -22,12 +22,10 @@ export const Medium: Story = { args: { size: 'medium' } };
22
22
  export const Large: Story = { args: { size: 'large' } };
23
23
  export const AllSizes: Story = {
24
24
  render: () => (
25
- <ThemeProvider>
26
- <div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
27
- <Loading size="small" />
28
- <Loading size="medium" />
29
- <Loading size="large" />
30
- </div>
31
- </ThemeProvider>
25
+ <div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
26
+ <Loading size="small" />
27
+ <Loading size="medium" />
28
+ <Loading size="large" />
29
+ </div>
32
30
  ),
33
31
  };
@@ -52,7 +52,7 @@
52
52
  flex-shrink: 0;
53
53
  cursor: pointer;
54
54
  color: currentcolor;
55
- border-radius: 50%;
55
+ border-radius: var(--banner_dismiss-radius, var(--bds-border_radius--circle));
56
56
  border: var(--banner_dismiss-border, none);
57
57
  transition: var(--bds-animation_transition);
58
58
  --bdc_color: currentcolor;
@@ -0,0 +1,85 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { Text } from 'react-native';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { NotificationBanner } from './NotificationBanner.native';
5
+
6
+ function renderWithTheme(ui: React.ReactElement) {
7
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
8
+ }
9
+
10
+ describe('NotificationBanner (native)', () => {
11
+ it('renders children text', () => {
12
+ renderWithTheme(<NotificationBanner>Update available</NotificationBanner>);
13
+ expect(screen.getByText('Update available')).toBeInTheDocument();
14
+ });
15
+
16
+ it('has alert role for error variant', () => {
17
+ renderWithTheme(
18
+ <NotificationBanner variant="error">Something went wrong</NotificationBanner>,
19
+ );
20
+ expect(screen.getByRole('alert')).toBeInTheDocument();
21
+ });
22
+
23
+ it('has alert role for warning variant', () => {
24
+ renderWithTheme(
25
+ <NotificationBanner variant="warning">Attention needed</NotificationBanner>,
26
+ );
27
+ expect(screen.getByRole('alert')).toBeInTheDocument();
28
+ });
29
+
30
+ it('does not have alert role for info variant', () => {
31
+ renderWithTheme(
32
+ <NotificationBanner variant="info">FYI</NotificationBanner>,
33
+ );
34
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
35
+ });
36
+
37
+ it('does not have alert role for success variant', () => {
38
+ renderWithTheme(
39
+ <NotificationBanner variant="success">All good</NotificationBanner>,
40
+ );
41
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
42
+ });
43
+
44
+ it('renders an action element when provided', () => {
45
+ renderWithTheme(
46
+ <NotificationBanner action={<Text>Retry</Text>}>
47
+ Failed to save
48
+ </NotificationBanner>,
49
+ );
50
+ expect(screen.getByText('Retry')).toBeInTheDocument();
51
+ expect(screen.getByText('Failed to save')).toBeInTheDocument();
52
+ });
53
+
54
+ it('renders a dismiss button when onDismiss is provided', () => {
55
+ renderWithTheme(
56
+ <NotificationBanner onDismiss={() => {}}>Dismissable</NotificationBanner>,
57
+ );
58
+ expect(
59
+ screen.getByRole('button', { name: 'Dismiss notification' }),
60
+ ).toBeInTheDocument();
61
+ });
62
+
63
+ it('calls onDismiss when dismiss button is pressed', () => {
64
+ const onDismiss = vi.fn();
65
+ renderWithTheme(
66
+ <NotificationBanner onDismiss={onDismiss}>Dismissable</NotificationBanner>,
67
+ );
68
+ fireEvent.click(screen.getByRole('button', { name: 'Dismiss notification' }));
69
+ expect(onDismiss).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it('does not render a dismiss button when onDismiss is omitted', () => {
73
+ renderWithTheme(<NotificationBanner>No dismiss</NotificationBanner>);
74
+ expect(
75
+ screen.queryByRole('button', { name: 'Dismiss notification' }),
76
+ ).not.toBeInTheDocument();
77
+ });
78
+
79
+ it('accepts custom style', () => {
80
+ renderWithTheme(
81
+ <NotificationBanner style={{ marginTop: 10 }}>Styled</NotificationBanner>,
82
+ );
83
+ expect(screen.getByText('Styled')).toBeInTheDocument();
84
+ });
85
+ });
@@ -7,7 +7,7 @@ const meta = {
7
7
  tags: ['!stable', 'alpha'],
8
8
  title: 'React Native/UI/NotificationBanner',
9
9
  component: NotificationBanner,
10
- decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
11
  parameters: { layout: 'padded' },
12
12
  argTypes: {
13
13
  variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
@@ -26,14 +26,12 @@ export const Dismissible: Story = {
26
26
  };
27
27
  export const AllVariants: Story = {
28
28
  render: () => (
29
- <ThemeProvider>
30
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
31
- {(['info', 'success', 'warning', 'error'] as const).map(v => (
32
- <NotificationBanner key={v} variant={v}>
33
- {v.charAt(0).toUpperCase() + v.slice(1)} notification
34
- </NotificationBanner>
35
- ))}
36
- </div>
37
- </ThemeProvider>
29
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
30
+ {(['info', 'success', 'warning', 'error'] as const).map(v => (
31
+ <NotificationBanner key={v} variant={v}>
32
+ {v.charAt(0).toUpperCase() + v.slice(1)} notification
33
+ </NotificationBanner>
34
+ ))}
35
+ </div>
38
36
  ),
39
37
  };
@@ -43,20 +43,20 @@ export function NotificationBanner({
43
43
  const { colors } = useTheme();
44
44
  const isUrgent = variant === 'error' || variant === 'warning';
45
45
 
46
- const variantColors: Record<BannerVariant, { bg: string; text: string }> = {
47
- info: { bg: colors.colorBlueSubtle, text: colors.colorOnBlueSubtle },
48
- success: { bg: colors.colorSuccessSubtle, text: colors.colorOnSuccessSubtle },
49
- warning: { bg: colors.colorOrangeSubtle, text: colors.colorOnOrangeSubtle },
50
- error: { bg: colors.colorRedSubtle, text: colors.colorOnRedSubtle },
46
+ const variantColors: Record<BannerVariant, { bg: string; text: string; border: string }> = {
47
+ info: { bg: colors.colorBlueSubtle, text: colors.colorOnBlueSubtle, border: colors.colorBlue },
48
+ success: { bg: colors.colorSuccessSubtle, text: colors.colorOnSuccessSubtle, border: colors.colorSuccess },
49
+ warning: { bg: colors.colorOrangeSubtle, text: colors.colorOnOrangeSubtle, border: colors.colorWarning },
50
+ error: { bg: colors.colorRedSubtle, text: colors.colorOnBg, border: colors.colorError },
51
51
  };
52
52
 
53
- const { bg, text } = variantColors[variant];
53
+ const { bg, text, border: borderColor } = variantColors[variant];
54
54
 
55
55
  return (
56
56
  <View
57
57
  accessibilityRole={isUrgent ? 'alert' : 'none'}
58
58
  accessibilityLiveRegion={isUrgent ? 'assertive' : 'polite'}
59
- style={[styles.banner, { backgroundColor: bg }, style]}
59
+ style={[styles.banner, { backgroundColor: bg, borderBottomWidth: 3, borderBottomColor: borderColor }, style]}
60
60
  >
61
61
  <Text style={[styles.content, { color: text }]}>{children}</Text>
62
62
  {action}
@@ -13,6 +13,10 @@
13
13
  padding: 0;
14
14
  }
15
15
 
16
+ .list li {
17
+ list-style: none;
18
+ }
19
+
16
20
  .button {
17
21
  all: unset;
18
22
  display: inline-flex;
@@ -21,7 +25,7 @@
21
25
  min-inline-size: 2.25rem;
22
26
  block-size: 2.25rem;
23
27
  padding-inline: var(--bds-space_xs);
24
- border-radius: var(--bds-border_radius--xs);
28
+ border-radius: var(--pagination_radius, var(--bds-border_radius--full));
25
29
  background-color: var(--btn_bg, transparent);
26
30
 
27
31
  --bdc_color: var(--btn_border, var(--bds-color_bg--subtle));
@@ -0,0 +1,62 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Pagination.native.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Pagination (React Native)
7
+
8
+ React Native pagination component for navigating between pages of content. Shows page numbers with ellipsis for large ranges, with Previous/Next navigation buttons.
9
+
10
+ > **Status: alpha** -- API may change before stable release.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Pagination } from '@boostdev/components';
16
+
17
+ <Pagination
18
+ currentPage={3}
19
+ totalPages={10}
20
+ onPageChange={(page) => setPage(page)}
21
+ />
22
+ ```
23
+
24
+ ## Examples
25
+
26
+ ### Default (first page)
27
+ <Canvas of={Stories.Default} />
28
+
29
+ ### Middle page
30
+ <Canvas of={Stories.MiddlePage} />
31
+
32
+ ### Last page
33
+ <Canvas of={Stories.LastPage} />
34
+
35
+ ### Many pages (with ellipsis)
36
+ <Canvas of={Stories.ManyPages} />
37
+
38
+ ### Single page
39
+ <Canvas of={Stories.SinglePage} />
40
+
41
+ ### Interactive
42
+ <Canvas of={Stories.Interactive} />
43
+
44
+ ## Props
45
+
46
+ <ArgTypes of={Stories} />
47
+
48
+ ## Props reference
49
+
50
+ | Prop | Type | Default | Description |
51
+ |------|------|---------|-------------|
52
+ | `currentPage` | `number` | -- | The currently active page (1-based) |
53
+ | `totalPages` | `number` | -- | Total number of pages |
54
+ | `onPageChange` | `(page: number) => void` | -- | Called when a page button is pressed |
55
+ | `style` | `StyleProp<ViewStyle>` | -- | Custom style for the container |
56
+
57
+ ## Accessibility
58
+
59
+ - Container has `navigation` role with "Pagination" label
60
+ - Previous/Next buttons are disabled at bounds
61
+ - Active page is indicated with `selected` accessibility state
62
+ - Each page button has a descriptive label (e.g. "Page 3")
@@ -0,0 +1,86 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Pagination } from './Pagination.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Pagination (native)', () => {
10
+ it('renders the navigation container', () => {
11
+ renderWithTheme(
12
+ <Pagination currentPage={1} totalPages={5} onPageChange={() => {}} />,
13
+ );
14
+ expect(screen.getByRole('navigation', { name: 'Pagination' })).toBeInTheDocument();
15
+ });
16
+
17
+ it('renders page buttons', () => {
18
+ renderWithTheme(
19
+ <Pagination currentPage={1} totalPages={3} onPageChange={() => {}} />,
20
+ );
21
+ expect(screen.getByRole('button', { name: 'Page 1' })).toBeInTheDocument();
22
+ expect(screen.getByRole('button', { name: 'Page 2' })).toBeInTheDocument();
23
+ expect(screen.getByRole('button', { name: 'Page 3' })).toBeInTheDocument();
24
+ });
25
+
26
+ it('renders the current page button', () => {
27
+ renderWithTheme(
28
+ <Pagination currentPage={2} totalPages={3} onPageChange={() => {}} />,
29
+ );
30
+ expect(screen.getByRole('button', { name: 'Page 2' })).toBeInTheDocument();
31
+ });
32
+
33
+ it('disables Previous on first page', () => {
34
+ renderWithTheme(
35
+ <Pagination currentPage={1} totalPages={5} onPageChange={() => {}} />,
36
+ );
37
+ expect(screen.getByRole('button', { name: 'Previous page' })).toHaveAttribute(
38
+ 'aria-disabled',
39
+ 'true',
40
+ );
41
+ });
42
+
43
+ it('disables Next on last page', () => {
44
+ renderWithTheme(
45
+ <Pagination currentPage={5} totalPages={5} onPageChange={() => {}} />,
46
+ );
47
+ expect(screen.getByRole('button', { name: 'Next page' })).toHaveAttribute(
48
+ 'aria-disabled',
49
+ 'true',
50
+ );
51
+ });
52
+
53
+ it('calls onPageChange with the next page when Next is pressed', () => {
54
+ const onPageChange = vi.fn();
55
+ renderWithTheme(
56
+ <Pagination currentPage={2} totalPages={5} onPageChange={onPageChange} />,
57
+ );
58
+ fireEvent.click(screen.getByRole('button', { name: 'Next page' }));
59
+ expect(onPageChange).toHaveBeenCalledWith(3);
60
+ });
61
+
62
+ it('calls onPageChange with the previous page when Previous is pressed', () => {
63
+ const onPageChange = vi.fn();
64
+ renderWithTheme(
65
+ <Pagination currentPage={3} totalPages={5} onPageChange={onPageChange} />,
66
+ );
67
+ fireEvent.click(screen.getByRole('button', { name: 'Previous page' }));
68
+ expect(onPageChange).toHaveBeenCalledWith(2);
69
+ });
70
+
71
+ it('calls onPageChange when a page number is pressed', () => {
72
+ const onPageChange = vi.fn();
73
+ renderWithTheme(
74
+ <Pagination currentPage={1} totalPages={5} onPageChange={onPageChange} />,
75
+ );
76
+ fireEvent.click(screen.getByRole('button', { name: 'Page 2' }));
77
+ expect(onPageChange).toHaveBeenCalledWith(2);
78
+ });
79
+
80
+ it('shows ellipsis for large page ranges', () => {
81
+ renderWithTheme(
82
+ <Pagination currentPage={5} totalPages={20} onPageChange={() => {}} />,
83
+ );
84
+ expect(screen.getAllByText('...').length).toBeGreaterThanOrEqual(1);
85
+ });
86
+ });
@@ -0,0 +1,47 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ThemeProvider } from '../../../native/ThemeContext';
4
+ import { Pagination } from './Pagination.native';
5
+
6
+ const meta = {
7
+ tags: ['!stable', 'alpha'],
8
+ title: 'React Native/UI/Pagination',
9
+ component: Pagination,
10
+ decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
+ parameters: { layout: 'padded' },
12
+ } satisfies Meta<typeof Pagination>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: { currentPage: 1, totalPages: 5 },
19
+ };
20
+
21
+ export const MiddlePage: Story = {
22
+ args: { currentPage: 3, totalPages: 5 },
23
+ };
24
+
25
+ export const LastPage: Story = {
26
+ args: { currentPage: 5, totalPages: 5 },
27
+ };
28
+
29
+ export const ManyPages: Story = {
30
+ args: { currentPage: 5, totalPages: 20 },
31
+ };
32
+
33
+ export const SinglePage: Story = {
34
+ args: { currentPage: 1, totalPages: 1 },
35
+ };
36
+
37
+ function InteractiveDemo() {
38
+ const [page, setPage] = useState(1);
39
+ return (
40
+ <Pagination currentPage={page} totalPages={10} onPageChange={setPage} />
41
+ );
42
+ }
43
+
44
+ export const Interactive: Story = {
45
+ args: { currentPage: 1, totalPages: 10 },
46
+ render: () => <InteractiveDemo />,
47
+ };
@@ -0,0 +1,157 @@
1
+ import { AccessibilityRole, StyleSheet, View, Text, Pressable, StyleProp, ViewStyle } from 'react-native';
2
+ import { spacing, font, border, lineHeight } from '../../../native/tokens';
3
+ import { useTheme } from '../../../native/ThemeContext';
4
+
5
+ interface PaginationProps {
6
+ currentPage: number;
7
+ totalPages: number;
8
+ onPageChange: (page: number) => void;
9
+ style?: StyleProp<ViewStyle>;
10
+ }
11
+
12
+ function getPageRange(current: number, total: number): (number | '...')[] {
13
+ const delta = 1;
14
+ const range: (number | '...')[] = [];
15
+
16
+ for (let i = 1; i <= total; i++) {
17
+ if (i === 1 || i === total || (i >= current - delta && i <= current + delta)) {
18
+ range.push(i);
19
+ } else if (range[range.length - 1] !== '...') {
20
+ range.push('...');
21
+ }
22
+ }
23
+
24
+ return range;
25
+ }
26
+
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ flexDirection: 'row',
30
+ alignItems: 'center',
31
+ gap: spacing.xxs,
32
+ },
33
+ button: {
34
+ minWidth: 44,
35
+ minHeight: 44,
36
+ alignItems: 'center',
37
+ justifyContent: 'center',
38
+ borderRadius: border.radius.full,
39
+ paddingHorizontal: spacing.xs,
40
+ paddingVertical: spacing.xxs,
41
+ },
42
+ buttonText: {
43
+ fontSize: font.size.body,
44
+ fontFamily: font.family.body,
45
+ lineHeight: lineHeight.body,
46
+ fontWeight: font.weight.body,
47
+ },
48
+ navText: {
49
+ fontSize: font.size.body,
50
+ fontFamily: font.family.body,
51
+ lineHeight: lineHeight.body,
52
+ fontWeight: font.weight.semibold,
53
+ },
54
+ ellipsis: {
55
+ fontSize: font.size.body,
56
+ fontFamily: font.family.body,
57
+ lineHeight: lineHeight.body,
58
+ minWidth: 44,
59
+ minHeight: 44,
60
+ textAlign: 'center',
61
+ textAlignVertical: 'center',
62
+ },
63
+ pressed: {
64
+ opacity: 0.75,
65
+ },
66
+ disabled: {
67
+ opacity: 0.4,
68
+ },
69
+ });
70
+
71
+ export function Pagination({
72
+ currentPage,
73
+ totalPages,
74
+ onPageChange,
75
+ style,
76
+ }: Readonly<PaginationProps>) {
77
+ const { colors } = useTheme();
78
+ const pages = getPageRange(currentPage, totalPages);
79
+
80
+ return (
81
+ <View
82
+ style={[styles.container, style]}
83
+ accessibilityRole={'navigation' as AccessibilityRole}
84
+ accessibilityLabel="Pagination"
85
+ >
86
+ <Pressable
87
+ onPress={() => onPageChange(currentPage - 1)}
88
+ disabled={currentPage <= 1}
89
+ accessibilityRole="button"
90
+ accessibilityLabel="Previous page"
91
+ accessibilityState={{ disabled: currentPage <= 1 }}
92
+ style={({ pressed }) => [
93
+ styles.button,
94
+ currentPage <= 1 && styles.disabled,
95
+ pressed && !currentPage && styles.pressed,
96
+ ]}
97
+ >
98
+ <Text style={[styles.navText, { color: colors.colorOnBg }]}>‹</Text>
99
+ </Pressable>
100
+
101
+ {pages.map((page, index) =>
102
+ page === '...' ? (
103
+ <Text
104
+ key={`ellipsis-${index}`}
105
+ style={[styles.ellipsis, { color: colors.colorOnBgSubtle }]}
106
+ importantForAccessibility="no"
107
+ accessibilityElementsHidden={true}
108
+ >
109
+ ...
110
+ </Text>
111
+ ) : (
112
+ <Pressable
113
+ key={page}
114
+ onPress={() => onPageChange(page)}
115
+ accessibilityRole="button"
116
+ accessibilityLabel={`Page ${page}`}
117
+ accessibilityState={{ selected: currentPage === page }}
118
+ style={({ pressed }) => [
119
+ styles.button,
120
+ currentPage === page && { backgroundColor: colors.colorInteractive },
121
+ pressed && styles.pressed,
122
+ ]}
123
+ >
124
+ <Text
125
+ style={[
126
+ styles.buttonText,
127
+ {
128
+ color: currentPage === page
129
+ ? colors.colorOnInteractive
130
+ : colors.colorOnBg,
131
+ },
132
+ currentPage === page && { fontWeight: font.weight.semibold },
133
+ ]}
134
+ >
135
+ {page}
136
+ </Text>
137
+ </Pressable>
138
+ ),
139
+ )}
140
+
141
+ <Pressable
142
+ onPress={() => onPageChange(currentPage + 1)}
143
+ disabled={currentPage >= totalPages}
144
+ accessibilityRole="button"
145
+ accessibilityLabel="Next page"
146
+ accessibilityState={{ disabled: currentPage >= totalPages }}
147
+ style={({ pressed }) => [
148
+ styles.button,
149
+ currentPage >= totalPages && styles.disabled,
150
+ pressed && styles.pressed,
151
+ ]}
152
+ >
153
+ <Text style={[styles.navText, { color: colors.colorOnBg }]}>›</Text>
154
+ </Pressable>
155
+ </View>
156
+ );
157
+ }
@@ -22,7 +22,7 @@
22
22
  inline-size: 100%;
23
23
  block-size: var(--progress_height, var(--bds-space_xs));
24
24
  background-color: var(--bds-color_bg--subtle);
25
- border-radius: 999px;
25
+ border-radius: var(--progress_radius, var(--bds-border_radius--full));
26
26
  overflow: hidden;
27
27
  }
28
28
 
@@ -33,7 +33,7 @@
33
33
  .fill {
34
34
  block-size: 100%;
35
35
  background-color: var(--progress_color-active, var(--bdc-color_active));
36
- border-radius: 999px;
36
+ border-radius: var(--progress_radius, var(--bds-border_radius--full));
37
37
  transition: inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
38
38
  }
39
39
 
@@ -0,0 +1,56 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ThemeProvider } from '../../../native/ThemeContext';
3
+ import { Progress } from './Progress.native';
4
+
5
+ function renderWithTheme(ui: React.ReactElement) {
6
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
7
+ }
8
+
9
+ describe('Progress (native)', () => {
10
+ it('has progressbar role with accessible label', () => {
11
+ renderWithTheme(<Progress value={50} label="Upload progress" />);
12
+ expect(screen.getByRole('progressbar', { name: 'Upload progress' })).toBeInTheDocument();
13
+ });
14
+
15
+ it('renders progressbar for various values', () => {
16
+ renderWithTheme(<Progress value={30} max={100} label="Loading" />);
17
+ expect(screen.getByRole('progressbar', { name: 'Loading' })).toBeInTheDocument();
18
+ });
19
+
20
+ it('respects a custom max value in percentage display', () => {
21
+ renderWithTheme(<Progress value={5} max={10} label="Steps" showLabel />);
22
+ expect(screen.getByText('50%')).toBeInTheDocument();
23
+ });
24
+
25
+ it('does not show label by default', () => {
26
+ renderWithTheme(<Progress value={60} label="Progress" />);
27
+ expect(screen.queryByText('Progress')).not.toBeInTheDocument();
28
+ expect(screen.queryByText('60%')).not.toBeInTheDocument();
29
+ });
30
+
31
+ it('shows label text and percentage when showLabel is true', () => {
32
+ renderWithTheme(<Progress value={60} label="Progress" showLabel />);
33
+ expect(screen.getByText('Progress')).toBeInTheDocument();
34
+ expect(screen.getByText('60%')).toBeInTheDocument();
35
+ });
36
+
37
+ it('clamps percentage display at 100%', () => {
38
+ renderWithTheme(<Progress value={150} max={100} label="Overflow" showLabel />);
39
+ expect(screen.getByText('100%')).toBeInTheDocument();
40
+ });
41
+
42
+ it('clamps percentage display at 0%', () => {
43
+ renderWithTheme(<Progress value={-10} max={100} label="Underflow" showLabel />);
44
+ expect(screen.getByText('0%')).toBeInTheDocument();
45
+ });
46
+
47
+ it('rounds the percentage to the nearest integer', () => {
48
+ renderWithTheme(<Progress value={33} max={100} label="Loading" showLabel />);
49
+ expect(screen.getByText('33%')).toBeInTheDocument();
50
+ });
51
+
52
+ it('defaults to medium size', () => {
53
+ renderWithTheme(<Progress value={50} label="Loading" />);
54
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
55
+ });
56
+ });
@@ -9,7 +9,7 @@ const meta = {
9
9
  component: Progress,
10
10
  decorators: [
11
11
  (Story) => (
12
- <ThemeProvider>
12
+ <ThemeProvider colorScheme="light">
13
13
  <div style={{ width: 300 }}>
14
14
  <Story />
15
15
  </div>
@@ -35,12 +35,12 @@ const styles = StyleSheet.create({
35
35
  },
36
36
  track: {
37
37
  width: '100%',
38
- borderRadius: border.radius.xs,
38
+ borderRadius: border.radius.full,
39
39
  overflow: 'hidden',
40
40
  },
41
41
  fill: {
42
42
  height: '100%',
43
- borderRadius: border.radius.xs,
43
+ borderRadius: border.radius.full,
44
44
  },
45
45
  });
46
46