@boostdev/design-system-components 2.4.2 → 2.5.1

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 +194 -4
  17. package/src/components/layout/Grid/Grid.tsx +15 -1
  18. package/src/components/layout/Grid/masonry.ts +127 -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,89 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
3
- import { spacing, border } from '../../../native/tokens';
4
- import { useTheme } from '../../../native/ThemeContext';
5
-
6
- type CardVariant = 'default' | 'elevated' | 'outlined';
7
- type CardPadding = 'none' | 'small' | 'medium' | 'large';
8
- type CardAlign = 'start' | 'center' | 'end';
9
-
10
- interface CardProps {
11
- children: ReactNode;
12
- variant?: CardVariant;
13
- padding?: CardPadding;
14
- textAlign?: CardAlign;
15
- style?: StyleProp<ViewStyle>;
16
- onPress?: () => void;
17
- accessibilityLabel?: string;
18
- }
19
-
20
- const paddingMap: Record<CardPadding, number> = {
21
- none: 0,
22
- small: spacing.s,
23
- medium: spacing.m,
24
- large: spacing.xxl,
25
- };
26
-
27
- const alignMap: Record<CardAlign, 'flex-start' | 'center' | 'flex-end'> = {
28
- start: 'flex-start',
29
- center: 'center',
30
- end: 'flex-end',
31
- };
32
-
33
- const styles = StyleSheet.create({
34
- card: {
35
- borderRadius: border.radius.m,
36
- overflow: 'hidden',
37
- },
38
- });
39
-
40
- export function Card({
41
- children,
42
- variant = 'default',
43
- padding = 'medium',
44
- textAlign = 'start',
45
- style,
46
- onPress,
47
- accessibilityLabel,
48
- }: CardProps) {
49
- const { colors } = useTheme();
50
-
51
- const variantStyle: ViewStyle = {
52
- backgroundColor: colors.colorBg,
53
- ...(variant === 'elevated' && {
54
- shadowColor: '#000',
55
- shadowOffset: { width: 0, height: 2 },
56
- shadowOpacity: 0.1,
57
- shadowRadius: 8,
58
- elevation: 4,
59
- }),
60
- ...(variant === 'outlined' && {
61
- borderWidth: 1,
62
- borderColor: colors.colorBgSubtle,
63
- }),
64
- };
65
-
66
- const inner: ViewStyle = {
67
- padding: paddingMap[padding],
68
- alignItems: alignMap[textAlign],
69
- };
70
-
71
- if (onPress) {
72
- return (
73
- <Pressable
74
- onPress={onPress}
75
- accessibilityRole="button"
76
- accessibilityLabel={accessibilityLabel}
77
- style={({ pressed }) => [styles.card, variantStyle, inner, pressed && { opacity: 0.8 }, style]}
78
- >
79
- {children}
80
- </Pressable>
81
- );
82
- }
83
-
84
- return (
85
- <View style={[styles.card, variantStyle, inner, style]}>
86
- {children}
87
- </View>
88
- );
89
- }
@@ -1,39 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../native/ThemeContext';
3
- import { IconWrapper } from './IconWrapper.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('IconWrapper (native)', () => {
10
- it('renders children', () => {
11
- renderWithTheme(
12
- <IconWrapper>
13
- <span data-testid="icon">★</span>
14
- </IconWrapper>,
15
- );
16
- expect(screen.getByTestId('icon')).toBeInTheDocument();
17
- });
18
-
19
- it('renders without error when accessibilityElementsHidden is true', () => {
20
- // accessibilityElementsHidden={true} hides the element from iOS VoiceOver.
21
- // importantForAccessibility="no-hide-descendants" hides it from Android TalkBack.
22
- // Both are applied when this prop is set.
23
- renderWithTheme(
24
- <IconWrapper accessibilityElementsHidden>
25
- <span data-testid="icon">★</span>
26
- </IconWrapper>,
27
- );
28
- expect(screen.getByTestId('icon')).toBeInTheDocument();
29
- });
30
-
31
- it('renders without error when accessibilityElementsHidden is false', () => {
32
- renderWithTheme(
33
- <IconWrapper accessibilityElementsHidden={false}>
34
- <span data-testid="icon">★</span>
35
- </IconWrapper>,
36
- );
37
- expect(screen.getByTestId('icon')).toBeInTheDocument();
38
- });
39
- });
@@ -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 { IconWrapper } from './IconWrapper.native';
5
-
6
- // Simple SVG icon for demonstration
7
- const StarIcon = () => (
8
- <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
9
- <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
10
- </svg>
11
- );
12
-
13
- const meta = {
14
- tags: ['!stable', 'alpha'],
15
- title: 'React Native/Layout/IconWrapper',
16
- component: IconWrapper,
17
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
18
- parameters: { layout: 'centered' },
19
- } satisfies Meta<typeof IconWrapper>;
20
-
21
- export default meta;
22
- type Story = StoryObj<typeof meta>;
23
-
24
- export const Default: Story = {
25
- render: (args) => (
26
- <IconWrapper {...args}>
27
- <StarIcon />
28
- </IconWrapper>
29
- ),
30
- };
31
- export const HiddenFromAccessibility: Story = {
32
- render: (args) => (
33
- <IconWrapper {...args} accessibilityElementsHidden>
34
- <StarIcon />
35
- </IconWrapper>
36
- ),
37
- };
@@ -1,20 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { View, StyleProp, ViewStyle } from 'react-native';
3
-
4
- export interface IconWrapperProps {
5
- children: ReactNode;
6
- style?: StyleProp<ViewStyle>;
7
- accessibilityElementsHidden?: boolean;
8
- }
9
-
10
- export function IconWrapper({ children, style, accessibilityElementsHidden }: IconWrapperProps) {
11
- return (
12
- <View
13
- style={[{ alignItems: 'center', justifyContent: 'center' }, style]}
14
- importantForAccessibility={accessibilityElementsHidden ? 'no-hide-descendants' : 'auto'}
15
- accessibilityElementsHidden={accessibilityElementsHidden}
16
- >
17
- {children}
18
- </View>
19
- );
20
- }
@@ -1,51 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../native/ThemeContext';
3
- import { SectionHeader } from './SectionHeader.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- describe('SectionHeader (native)', () => {
10
- it('renders the title with header role', () => {
11
- renderWithTheme(<SectionHeader title="Overview" />);
12
- expect(screen.getByRole('heading', { name: 'Overview' })).toBeInTheDocument();
13
- });
14
-
15
- it('renders the subtitle when provided', () => {
16
- renderWithTheme(<SectionHeader title="Overview" subtitle="A brief summary" />);
17
- expect(screen.getByText('A brief summary')).toBeInTheDocument();
18
- });
19
-
20
- it('does not render subtitle when not provided', () => {
21
- renderWithTheme(<SectionHeader title="Overview" />);
22
- expect(screen.queryByText('A brief summary')).not.toBeInTheDocument();
23
- });
24
-
25
- it('aligns content to flex-start by default', () => {
26
- const { container } = renderWithTheme(<SectionHeader title="Overview" />);
27
- expect(container.firstChild).toHaveStyle({ alignItems: 'flex-start' });
28
- });
29
-
30
- it('aligns content to center when alignment is center', () => {
31
- const { container } = renderWithTheme(
32
- <SectionHeader title="Overview" alignment="center" />,
33
- );
34
- expect(container.firstChild).toHaveStyle({ alignItems: 'center' });
35
- });
36
-
37
- it('aligns content to flex-end when alignment is end', () => {
38
- const { container } = renderWithTheme(
39
- <SectionHeader title="Overview" alignment="end" />,
40
- );
41
- expect(container.firstChild).toHaveStyle({ alignItems: 'flex-end' });
42
- });
43
-
44
- it('renders title and subtitle together', () => {
45
- renderWithTheme(
46
- <SectionHeader title="Settings" subtitle="Manage your preferences" />,
47
- );
48
- expect(screen.getByRole('heading', { name: 'Settings' })).toBeInTheDocument();
49
- expect(screen.getByText('Manage your preferences')).toBeInTheDocument();
50
- });
51
- });
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { ThemeProvider } from '../../../native/ThemeContext';
4
- import { SectionHeader } from './SectionHeader.native';
5
-
6
- const meta = {
7
- tags: ['!stable', 'alpha'],
8
- title: 'React Native/Layout/SectionHeader',
9
- component: SectionHeader,
10
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
11
- parameters: { layout: 'padded' },
12
- argTypes: {
13
- size: { control: 'select', options: ['small', 'medium', 'large'] },
14
- alignment: { control: 'select', options: ['start', 'center', 'end'] },
15
- },
16
- } satisfies Meta<typeof SectionHeader>;
17
-
18
- export default meta;
19
- type Story = StoryObj<typeof meta>;
20
-
21
- export const Default: Story = { args: { title: 'Section Title' } };
22
- export const WithSubtitle: Story = { args: { title: 'Section Title', subtitle: 'Optional subtitle text.' } };
23
- export const Small: Story = { args: { title: 'Small Section', size: 'small' } };
24
- export const Large: Story = { args: { title: 'Large Section', size: 'large' } };
25
- export const Centered: Story = {
26
- args: { title: 'Centered Header', subtitle: 'Centered subtitle text.', alignment: 'center' },
27
- };
28
- export const AllSizes: Story = {
29
- render: () => (
30
- <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
31
- <SectionHeader size="large" title="Large Heading" subtitle="Large section subtitle." />
32
- <SectionHeader size="medium" title="Medium Heading" subtitle="Medium section subtitle." />
33
- <SectionHeader size="small" title="Small Heading" subtitle="Small section subtitle." />
34
- </div>
35
- ),
36
- };
@@ -1,79 +0,0 @@
1
- import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
2
- import { spacing, font, lineHeight } from '../../../native/tokens';
3
- import { useTheme } from '../../../native/ThemeContext';
4
-
5
- type SectionHeaderAlignment = 'start' | 'center' | 'end';
6
- type SectionHeaderSize = 'small' | 'medium' | 'large';
7
-
8
- interface SectionHeaderProps {
9
- title: string;
10
- subtitle?: string;
11
- alignment?: SectionHeaderAlignment;
12
- size?: SectionHeaderSize;
13
- style?: StyleProp<ViewStyle>;
14
- }
15
-
16
- const alignMap: Record<SectionHeaderAlignment, 'flex-start' | 'center' | 'flex-end'> = {
17
- start: 'flex-start',
18
- center: 'center',
19
- end: 'flex-end',
20
- };
21
-
22
- const titleStyles = StyleSheet.create({
23
- small: {
24
- fontSize: font.size.heading3,
25
- fontFamily: font.family.heading,
26
- fontWeight: font.weight.heading,
27
- lineHeight: lineHeight.heading3,
28
- },
29
- medium: {
30
- fontSize: font.size.heading2,
31
- fontFamily: font.family.heading,
32
- fontWeight: font.weight.heading,
33
- lineHeight: lineHeight.heading2,
34
- },
35
- large: {
36
- fontSize: font.size.heading1,
37
- fontFamily: font.family.heading,
38
- fontWeight: font.weight.heading,
39
- lineHeight: lineHeight.heading1,
40
- },
41
- });
42
-
43
- const styles = StyleSheet.create({
44
- container: {
45
- gap: spacing.xxs,
46
- },
47
- subtitle: {
48
- fontSize: font.size.bodyS,
49
- fontFamily: font.family.body,
50
- lineHeight: lineHeight.bodyS,
51
- },
52
- });
53
-
54
- export function SectionHeader({
55
- title,
56
- subtitle,
57
- alignment = 'start',
58
- size = 'medium',
59
- style,
60
- }: Readonly<SectionHeaderProps>) {
61
- const { colors } = useTheme();
62
- const align = alignMap[alignment];
63
-
64
- return (
65
- <View style={[styles.container, { alignItems: align }, style]}>
66
- <Text
67
- accessibilityRole="header"
68
- style={[titleStyles[size], { color: colors.colorOnBg }]}
69
- >
70
- {title}
71
- </Text>
72
- {subtitle && (
73
- <Text style={[styles.subtitle, { color: colors.colorOnBgSubtle }]}>
74
- {subtitle}
75
- </Text>
76
- )}
77
- </View>
78
- );
79
- }
@@ -1,65 +0,0 @@
1
- import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
- import * as Stories from './Accordion.native.stories';
3
-
4
- <Meta of={Stories} />
5
-
6
- # Accordion (React Native)
7
-
8
- React Native accordion component for stacked collapsible sections. By default only one section can be open at a time (exclusive mode). Set `allowMultiple` to permit multiple open sections.
9
-
10
- > **Status: alpha** -- API may change before stable release.
11
-
12
- ## Usage
13
-
14
- ```tsx
15
- import { Accordion } from '@boostdev/design-system-components';
16
-
17
- <Accordion
18
- items={[
19
- { id: '1', title: 'Section one', content: <Text>First section content.</Text> },
20
- { id: '2', title: 'Section two', content: <Text>Second section content.</Text> },
21
- ]}
22
- />
23
- ```
24
-
25
- ## Examples
26
-
27
- ### Default
28
- <Canvas of={Stories.Default} />
29
-
30
- ### With default open section
31
- <Canvas of={Stories.WithDefaultOpen} />
32
-
33
- ### Allow multiple
34
- <Canvas of={Stories.AllowMultiple} />
35
-
36
- ### With disabled item
37
- <Canvas of={Stories.WithDisabledItem} />
38
-
39
- ## Props
40
-
41
- <ArgTypes of={Stories} />
42
-
43
- ## Props reference
44
-
45
- | Prop | Type | Default | Description |
46
- |------|------|---------|-------------|
47
- | `items` | `AccordionItem[]` | -- | Array of accordion sections |
48
- | `allowMultiple` | `boolean` | `false` | Allow multiple sections open simultaneously |
49
- | `defaultOpen` | `string[]` | `[]` | IDs of sections open initially |
50
- | `style` | `StyleProp<ViewStyle>` | -- | Custom style for the container |
51
-
52
- ### AccordionItem
53
-
54
- | Prop | Type | Description |
55
- |------|------|-------------|
56
- | `id` | `string` | Unique identifier |
57
- | `title` | `ReactNode` | Header label |
58
- | `content` | `ReactNode` | Panel content |
59
- | `disabled` | `boolean` | Prevents toggling |
60
-
61
- ## Accessibility
62
-
63
- - Each header has `button` role with `expanded` state
64
- - Disabled items have `disabled` accessibility state and reduced opacity
65
- - Chevron indicators are hidden from the accessibility tree
@@ -1,69 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { ThemeProvider } from '../../../native/ThemeContext';
3
- import { Accordion } from './Accordion.native';
4
-
5
- function renderWithTheme(ui: React.ReactElement) {
6
- return render(<ThemeProvider>{ui}</ThemeProvider>);
7
- }
8
-
9
- const items = [
10
- { id: '1', title: 'Section 1', content: <span>Content 1</span> },
11
- { id: '2', title: 'Section 2', content: <span>Content 2</span> },
12
- { id: '3', title: 'Section 3', content: <span>Content 3</span> },
13
- ];
14
-
15
- describe('Accordion (native)', () => {
16
- it('renders all section headers', () => {
17
- renderWithTheme(<Accordion items={items} />);
18
- expect(screen.getByText('Section 1')).toBeInTheDocument();
19
- expect(screen.getByText('Section 2')).toBeInTheDocument();
20
- expect(screen.getByText('Section 3')).toBeInTheDocument();
21
- });
22
-
23
- it('hides all content by default', () => {
24
- renderWithTheme(<Accordion items={items} />);
25
- expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
26
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
27
- expect(screen.queryByText('Content 3')).not.toBeInTheDocument();
28
- });
29
-
30
- it('opens a section with defaultOpen', () => {
31
- renderWithTheme(<Accordion items={items} defaultOpen={['2']} />);
32
- expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
33
- expect(screen.getByText('Content 2')).toBeInTheDocument();
34
- });
35
-
36
- it('toggles a section on press (exclusive mode)', () => {
37
- renderWithTheme(<Accordion items={items} />);
38
- fireEvent.click(screen.getByText('Section 1'));
39
- expect(screen.getByText('Content 1')).toBeInTheDocument();
40
-
41
- fireEvent.click(screen.getByText('Section 2'));
42
- expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
43
- expect(screen.getByText('Content 2')).toBeInTheDocument();
44
- });
45
-
46
- it('allows multiple open sections when allowMultiple is true', () => {
47
- renderWithTheme(<Accordion items={items} allowMultiple />);
48
- fireEvent.click(screen.getByText('Section 1'));
49
- fireEvent.click(screen.getByText('Section 2'));
50
- expect(screen.getByText('Content 1')).toBeInTheDocument();
51
- expect(screen.getByText('Content 2')).toBeInTheDocument();
52
- });
53
-
54
- it('does not toggle disabled items', () => {
55
- const withDisabled = [
56
- ...items,
57
- { id: '4', title: 'Disabled', content: <span>Disabled content</span>, disabled: true },
58
- ];
59
- renderWithTheme(<Accordion items={withDisabled} />);
60
- fireEvent.click(screen.getByText('Disabled'));
61
- expect(screen.queryByText('Disabled content')).not.toBeInTheDocument();
62
- });
63
-
64
- it('shows content of open sections and has button role on headers', () => {
65
- renderWithTheme(<Accordion items={items} defaultOpen={['1']} />);
66
- expect(screen.getByText('Content 1')).toBeInTheDocument();
67
- expect(screen.getByText('Section 1')).toBeInTheDocument();
68
- });
69
- });
@@ -1,52 +0,0 @@
1
- import React from 'react';
2
- import { Text } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react';
4
- import { ThemeProvider } from '../../../native/ThemeContext';
5
- import { Accordion } from './Accordion.native';
6
-
7
- const meta = {
8
- tags: ['!stable', 'alpha'],
9
- title: 'React Native/UI/Accordion',
10
- component: Accordion,
11
- decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
12
- parameters: { layout: 'padded' },
13
- } satisfies Meta<typeof Accordion>;
14
-
15
- export default meta;
16
- type Story = StoryObj<typeof meta>;
17
-
18
- const sampleItems = [
19
- { id: '1', title: 'What is this library?', content: <Text>A design system component library built for React, React Native, and Web Components.</Text> },
20
- { id: '2', title: 'How do I install it?', content: <Text>Run pnpm add @boostdev/design-system-components to add it to your project.</Text> },
21
- { id: '3', title: 'Is it production ready?', content: <Text>The React components are stable. React Native components are in alpha.</Text> },
22
- ];
23
-
24
- export const Default: Story = {
25
- args: {
26
- items: sampleItems,
27
- },
28
- };
29
-
30
- export const WithDefaultOpen: Story = {
31
- args: {
32
- items: sampleItems,
33
- defaultOpen: ['1'],
34
- },
35
- };
36
-
37
- export const AllowMultiple: Story = {
38
- args: {
39
- items: sampleItems,
40
- allowMultiple: true,
41
- defaultOpen: ['1', '2'],
42
- },
43
- };
44
-
45
- export const WithDisabledItem: Story = {
46
- args: {
47
- items: [
48
- ...sampleItems,
49
- { id: '4', title: 'Disabled section', content: <Text>You cannot see this.</Text>, disabled: true },
50
- ],
51
- },
52
- };
@@ -1,141 +0,0 @@
1
- import { ReactNode, useState } from 'react';
2
- import {
3
- StyleSheet,
4
- View,
5
- Text,
6
- Pressable,
7
- LayoutAnimation,
8
- Platform,
9
- UIManager,
10
- StyleProp,
11
- ViewStyle,
12
- } from 'react-native';
13
- import { spacing, font, border, lineHeight } from '../../../native/tokens';
14
- import { useTheme } from '../../../native/ThemeContext';
15
-
16
- if (
17
- Platform.OS === 'android' &&
18
- UIManager.setLayoutAnimationEnabledExperimental
19
- ) {
20
- UIManager.setLayoutAnimationEnabledExperimental(true);
21
- }
22
-
23
- export interface AccordionItem {
24
- id: string;
25
- title: ReactNode;
26
- content: ReactNode;
27
- disabled?: boolean;
28
- }
29
-
30
- interface AccordionProps {
31
- items: AccordionItem[];
32
- allowMultiple?: boolean;
33
- defaultOpen?: string[];
34
- style?: StyleProp<ViewStyle>;
35
- }
36
-
37
- const styles = StyleSheet.create({
38
- container: {
39
- overflow: 'hidden',
40
- borderRadius: border.radius.s,
41
- },
42
- item: {
43
- borderBottomWidth: 1,
44
- },
45
- header: {
46
- flexDirection: 'row',
47
- alignItems: 'center',
48
- justifyContent: 'space-between',
49
- minHeight: 44,
50
- paddingVertical: spacing.s,
51
- paddingHorizontal: spacing.m,
52
- },
53
- titleText: {
54
- flex: 1,
55
- fontSize: font.size.body,
56
- fontFamily: font.family.body,
57
- fontWeight: font.weight.semibold,
58
- lineHeight: lineHeight.body,
59
- },
60
- chevron: {
61
- fontSize: font.size.bodyS,
62
- marginStart: spacing.xs,
63
- },
64
- panel: {
65
- paddingHorizontal: spacing.m,
66
- paddingBottom: spacing.m,
67
- },
68
- pressed: {
69
- opacity: 0.75,
70
- },
71
- disabled: {
72
- opacity: 0.4,
73
- },
74
- });
75
-
76
- export function Accordion({
77
- items,
78
- allowMultiple = false,
79
- defaultOpen = [],
80
- style,
81
- }: Readonly<AccordionProps>) {
82
- const { colors } = useTheme();
83
- const [openIds, setOpenIds] = useState<string[]>(defaultOpen);
84
-
85
- const toggle = (id: string) => {
86
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
87
- setOpenIds(prev => {
88
- const isOpen = prev.includes(id);
89
- if (isOpen) return prev.filter(i => i !== id);
90
- return allowMultiple ? [...prev, id] : [id];
91
- });
92
- };
93
-
94
- return (
95
- <View style={[styles.container, style]}>
96
- {items.map((item, index) => {
97
- const isOpen = openIds.includes(item.id);
98
- const isLast = index === items.length - 1;
99
-
100
- return (
101
- <View
102
- key={item.id}
103
- style={[
104
- styles.item,
105
- { borderBottomColor: colors.colorBgSubtle },
106
- isLast && { borderBottomWidth: 0 },
107
- ]}
108
- >
109
- <Pressable
110
- onPress={() => toggle(item.id)}
111
- disabled={item.disabled}
112
- accessibilityRole="button"
113
- accessibilityState={{ expanded: isOpen, disabled: item.disabled }}
114
- style={({ pressed }) => [
115
- styles.header,
116
- item.disabled && styles.disabled,
117
- pressed && !item.disabled && styles.pressed,
118
- ]}
119
- >
120
- <Text style={[styles.titleText, { color: colors.colorOnBg }]}>
121
- {typeof item.title === 'string' ? item.title : item.title}
122
- </Text>
123
- <Text
124
- style={[styles.chevron, { color: colors.colorOnBgSubtle }]}
125
- importantForAccessibility="no"
126
- accessibilityElementsHidden={true}
127
- >
128
- {isOpen ? '▾' : '▸'}
129
- </Text>
130
- </Pressable>
131
- {isOpen && (
132
- <View style={styles.panel} accessibilityRole="summary">
133
- {item.content}
134
- </View>
135
- )}
136
- </View>
137
- );
138
- })}
139
- </View>
140
- );
141
- }