@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,29 +0,0 @@
1
- import { ActivityIndicator, StyleProp, ViewStyle } from 'react-native';
2
- import { useTheme } from '../../../native/ThemeContext';
3
-
4
- type LoadingSize = 'small' | 'medium' | 'large';
5
-
6
- interface LoadingProps {
7
- size?: LoadingSize;
8
- accessibilityLabel?: string;
9
- style?: StyleProp<ViewStyle>;
10
- }
11
-
12
- const rnSize: Record<LoadingSize, 'small' | 'large'> = {
13
- small: 'small',
14
- medium: 'large',
15
- large: 'large',
16
- };
17
-
18
- export function Loading({ size = 'medium', accessibilityLabel = 'Loading', style }: LoadingProps) {
19
- const { colors } = useTheme();
20
- return (
21
- <ActivityIndicator
22
- size={rnSize[size]}
23
- color={colors.colorInteractive}
24
- style={style}
25
- accessibilityRole="progressbar"
26
- accessibilityLabel={accessibilityLabel}
27
- />
28
- );
29
- }
@@ -1,85 +0,0 @@
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
- });
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../native/ThemeContext';
4
- import { NotificationBanner } from './NotificationBanner.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/UI/NotificationBanner',
9
- component: NotificationBanner,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- argTypes: {
13
- variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
14
- },
15
- } satisfies Meta<typeof NotificationBanner>;
16
-
17
- export default meta;
18
- type Story = StoryObj<typeof meta>;
19
-
20
- export const Info: Story = { args: { children: 'New update available.', variant: 'info' } };
21
- export const Success: Story = { args: { children: 'Changes saved successfully.', variant: 'success' } };
22
- export const Warning: Story = { args: { children: 'Your session will expire soon.', variant: 'warning' } };
23
- export const Error: Story = { args: { children: 'Failed to load data. Please retry.', variant: 'error' } };
24
- export const Dismissible: Story = {
25
- args: { children: 'You can dismiss this notification.', variant: 'info', onDismiss: () => {} },
26
- };
27
- export const AllVariants: Story = {
28
- render: () => (
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>
36
- ),
37
- };
@@ -1,76 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { StyleSheet, View, Text, Pressable, StyleProp, ViewStyle } from 'react-native';
3
- import { spacing, font, lineHeight } from '../../../native/tokens';
4
- import { useTheme } from '../../../native/ThemeContext';
5
-
6
- type BannerVariant = 'info' | 'success' | 'warning' | 'error';
7
-
8
- interface NotificationBannerProps {
9
- variant?: BannerVariant;
10
- children: ReactNode;
11
- action?: ReactNode;
12
- onDismiss?: () => void;
13
- style?: StyleProp<ViewStyle>;
14
- }
15
-
16
- const styles = StyleSheet.create({
17
- banner: {
18
- flexDirection: 'row',
19
- alignItems: 'center',
20
- paddingVertical: spacing.s,
21
- paddingHorizontal: spacing.m,
22
- gap: spacing.xs,
23
- },
24
- content: {
25
- flex: 1,
26
- fontSize: font.size.bodyS,
27
- fontFamily: font.family.body,
28
- lineHeight: lineHeight.bodyS,
29
- },
30
- dismiss: {
31
- padding: spacing.xxs,
32
- flexShrink: 0,
33
- },
34
- });
35
-
36
- export function NotificationBanner({
37
- variant = 'info',
38
- children,
39
- action,
40
- onDismiss,
41
- style,
42
- }: Readonly<NotificationBannerProps>) {
43
- const { colors } = useTheme();
44
- const isUrgent = variant === 'error' || variant === 'warning';
45
-
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
- };
52
-
53
- const { bg, text, border: borderColor } = variantColors[variant];
54
-
55
- return (
56
- <View
57
- accessibilityRole={isUrgent ? 'alert' : 'none'}
58
- accessibilityLiveRegion={isUrgent ? 'assertive' : 'polite'}
59
- style={[styles.banner, { backgroundColor: bg, borderBottomWidth: 3, borderBottomColor: borderColor }, style]}
60
- >
61
- <Text style={[styles.content, { color: text }]}>{children}</Text>
62
- {action}
63
- {onDismiss && (
64
- <Pressable
65
- onPress={onDismiss}
66
- style={styles.dismiss}
67
- accessibilityLabel="Dismiss notification"
68
- accessibilityRole="button"
69
- hitSlop={8}
70
- >
71
- <Text style={{ color: text, fontSize: 16 }}>✕</Text>
72
- </Pressable>
73
- )}
74
- </View>
75
- );
76
- }
@@ -1,62 +0,0 @@
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/design-system-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")
@@ -1,86 +0,0 @@
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
- });
@@ -1,47 +0,0 @@
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
- };
@@ -1,157 +0,0 @@
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
- }
@@ -1,56 +0,0 @@
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
- });
@@ -1,34 +0,0 @@
1
- import React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../native/ThemeContext';
4
- import { Progress } from './Progress.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/UI/Progress',
9
- component: Progress,
10
- decorators: [
11
- (Story) => (
12
- <ThemeProvider colorScheme="light">
13
- <div style={{ width: 300 }}>
14
- <Story />
15
- </div>
16
- </ThemeProvider>
17
- ),
18
- ],
19
- parameters: { layout: 'padded' },
20
- argTypes: {
21
- size: { control: 'select', options: ['small', 'medium', 'large'] },
22
- value: { control: { type: 'range', min: 0, max: 100, step: 1 } },
23
- },
24
- } satisfies Meta<typeof Progress>;
25
-
26
- export default meta;
27
- type Story = StoryObj<typeof meta>;
28
-
29
- export const Default: Story = { args: { value: 60, label: 'Progress' } };
30
- export const WithLabel: Story = { args: { value: 40, label: 'Loading', showLabel: true } };
31
- export const Small: Story = { args: { value: 70, label: 'Small progress', size: 'small' } };
32
- export const Large: Story = { args: { value: 50, label: 'Large progress', size: 'large', showLabel: true } };
33
- export const Complete: Story = { args: { value: 100, label: 'Complete', showLabel: true } };
34
- export const Empty: Story = { args: { value: 0, label: 'Not started', showLabel: true } };