@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.
- package/AGENTS.md +4 -20
- package/README.md +3 -23
- package/dist/client.cjs +115 -61
- package/dist/client.css +537 -537
- package/dist/client.d.cts +11 -1
- package/dist/client.d.ts +11 -1
- package/dist/client.js +115 -61
- package/dist/index.cjs +115 -61
- package/dist/index.css +537 -537
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +115 -61
- package/dist/web-components/index.js +23 -3
- package/package.json +2 -10
- package/src/components/layout/Grid/Grid.mdx +20 -2
- package/src/components/layout/Grid/Grid.spec.tsx +194 -4
- package/src/components/layout/Grid/Grid.tsx +15 -1
- package/src/components/layout/Grid/masonry.ts +127 -9
- package/dist/native/index.cjs +0 -4337
- package/dist/native/index.d.cts +0 -574
- package/dist/native/index.d.ts +0 -574
- package/dist/native/index.js +0 -4421
- package/src/components/interaction/Button/Button.native.mdx +0 -91
- package/src/components/interaction/Button/Button.native.spec.tsx +0 -55
- package/src/components/interaction/Button/Button.native.stories.tsx +0 -141
- package/src/components/interaction/Button/Button.native.tsx +0 -105
- package/src/components/interaction/Dialog/Dialog.native.mdx +0 -61
- package/src/components/interaction/Dialog/Dialog.native.spec.tsx +0 -73
- package/src/components/interaction/Dialog/Dialog.native.stories.tsx +0 -53
- package/src/components/interaction/Dialog/Dialog.native.tsx +0 -128
- package/src/components/interaction/Drawer/Drawer.native.mdx +0 -58
- package/src/components/interaction/Drawer/Drawer.native.spec.tsx +0 -81
- package/src/components/interaction/Drawer/Drawer.native.stories.tsx +0 -33
- package/src/components/interaction/Drawer/Drawer.native.tsx +0 -175
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +0 -74
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +0 -78
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +0 -51
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +0 -254
- package/src/components/interaction/Popover/Popover.native.mdx +0 -61
- package/src/components/interaction/Popover/Popover.native.spec.tsx +0 -73
- package/src/components/interaction/Popover/Popover.native.stories.tsx +0 -44
- package/src/components/interaction/Popover/Popover.native.tsx +0 -87
- package/src/components/interaction/Rating/Rating.native.mdx +0 -55
- package/src/components/interaction/Rating/Rating.native.spec.tsx +0 -38
- package/src/components/interaction/Rating/Rating.native.stories.tsx +0 -37
- package/src/components/interaction/Rating/Rating.native.tsx +0 -50
- package/src/components/interaction/Toast/Toast.native.mdx +0 -81
- package/src/components/interaction/Toast/Toast.native.spec.tsx +0 -80
- package/src/components/interaction/Toast/Toast.native.stories.tsx +0 -47
- package/src/components/interaction/Toast/Toast.native.tsx +0 -202
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +0 -74
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +0 -42
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +0 -34
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +0 -80
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +0 -78
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +0 -57
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +0 -61
- package/src/components/interaction/form/Combobox/Combobox.native.mdx +0 -86
- package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +0 -60
- package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +0 -99
- package/src/components/interaction/form/Combobox/Combobox.native.tsx +0 -211
- package/src/components/interaction/form/FileInput/FileInput.native.mdx +0 -86
- package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +0 -73
- package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +0 -69
- package/src/components/interaction/form/FileInput/FileInput.native.tsx +0 -129
- package/src/components/interaction/form/FormInput/FormInput.native.mdx +0 -81
- package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +0 -51
- package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +0 -54
- package/src/components/interaction/form/FormInput/FormInput.native.tsx +0 -122
- package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +0 -87
- package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +0 -198
- package/src/components/interaction/form/Radio/Radio.native.spec.tsx +0 -79
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +0 -44
- package/src/components/interaction/form/Radio/Radio.native.tsx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +0 -66
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +0 -61
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +0 -78
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +0 -82
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +0 -99
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +0 -121
- package/src/components/interaction/form/Select/Select.native.mdx +0 -85
- package/src/components/interaction/form/Select/Select.native.spec.tsx +0 -64
- package/src/components/interaction/form/Select/Select.native.stories.tsx +0 -75
- package/src/components/interaction/form/Select/Select.native.tsx +0 -252
- package/src/components/interaction/form/Slider/Slider.native.mdx +0 -83
- package/src/components/interaction/form/Slider/Slider.native.spec.tsx +0 -51
- package/src/components/interaction/form/Slider/Slider.native.stories.tsx +0 -58
- package/src/components/interaction/form/Slider/Slider.native.tsx +0 -188
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +0 -60
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +0 -33
- package/src/components/interaction/form/Switch/Switch.native.tsx +0 -59
- package/src/components/interaction/form/Textarea/Textarea.native.mdx +0 -76
- package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.tsx +0 -108
- package/src/components/interaction/form/atoms/Label.native.spec.tsx +0 -39
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +0 -18
- package/src/components/interaction/form/atoms/Label.native.tsx +0 -29
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +0 -30
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +0 -22
- package/src/components/interaction/form/atoms/Message.native.tsx +0 -34
- package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +0 -83
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +0 -44
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +0 -44
- package/src/components/layout/Card/Card.native.spec.tsx +0 -102
- package/src/components/layout/Card/Card.native.stories.tsx +0 -51
- package/src/components/layout/Card/Card.native.tsx +0 -89
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +0 -39
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +0 -37
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +0 -20
- package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +0 -51
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +0 -36
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +0 -79
- package/src/components/ui/Accordion/Accordion.native.mdx +0 -65
- package/src/components/ui/Accordion/Accordion.native.spec.tsx +0 -69
- package/src/components/ui/Accordion/Accordion.native.stories.tsx +0 -52
- package/src/components/ui/Accordion/Accordion.native.tsx +0 -141
- package/src/components/ui/Alert/Alert.native.mdx +0 -62
- package/src/components/ui/Alert/Alert.native.spec.tsx +0 -76
- package/src/components/ui/Alert/Alert.native.stories.tsx +0 -41
- package/src/components/ui/Alert/Alert.native.tsx +0 -94
- package/src/components/ui/Avatar/Avatar.native.mdx +0 -50
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +0 -47
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +0 -32
- package/src/components/ui/Avatar/Avatar.native.tsx +0 -85
- package/src/components/ui/Badge/Badge.native.mdx +0 -54
- package/src/components/ui/Badge/Badge.native.spec.tsx +0 -45
- package/src/components/ui/Badge/Badge.native.stories.tsx +0 -34
- package/src/components/ui/Badge/Badge.native.tsx +0 -50
- package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +0 -52
- package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +0 -49
- package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +0 -46
- package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +0 -83
- package/src/components/ui/Calendar/Calendar.native.mdx +0 -58
- package/src/components/ui/Calendar/Calendar.native.spec.tsx +0 -92
- package/src/components/ui/Calendar/Calendar.native.stories.tsx +0 -45
- package/src/components/ui/Calendar/Calendar.native.tsx +0 -294
- package/src/components/ui/Carousel/Carousel.native.mdx +0 -55
- package/src/components/ui/Carousel/Carousel.native.spec.tsx +0 -55
- package/src/components/ui/Carousel/Carousel.native.stories.tsx +0 -59
- package/src/components/ui/Carousel/Carousel.native.tsx +0 -184
- package/src/components/ui/Collapsible/Collapsible.native.mdx +0 -53
- package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +0 -68
- package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +0 -48
- package/src/components/ui/Collapsible/Collapsible.native.tsx +0 -139
- package/src/components/ui/DescriptionList/DescriptionList.native.mdx +0 -50
- package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +0 -37
- package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +0 -48
- package/src/components/ui/DescriptionList/DescriptionList.native.tsx +0 -54
- package/src/components/ui/Link/Link.native.mdx +0 -53
- package/src/components/ui/Link/Link.native.spec.tsx +0 -35
- package/src/components/ui/Link/Link.native.stories.tsx +0 -46
- package/src/components/ui/Link/Link.native.tsx +0 -48
- package/src/components/ui/Loading/Loading.native.spec.tsx +0 -24
- package/src/components/ui/Loading/Loading.native.stories.tsx +0 -31
- package/src/components/ui/Loading/Loading.native.tsx +0 -29
- package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +0 -85
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +0 -37
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +0 -76
- package/src/components/ui/Pagination/Pagination.native.mdx +0 -62
- package/src/components/ui/Pagination/Pagination.native.spec.tsx +0 -86
- package/src/components/ui/Pagination/Pagination.native.stories.tsx +0 -47
- package/src/components/ui/Pagination/Pagination.native.tsx +0 -157
- package/src/components/ui/Progress/Progress.native.spec.tsx +0 -56
- package/src/components/ui/Progress/Progress.native.stories.tsx +0 -34
- package/src/components/ui/Progress/Progress.native.tsx +0 -84
- package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +0 -63
- package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +0 -39
- package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +0 -36
- package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +0 -176
- package/src/components/ui/Separator/Separator.native.spec.tsx +0 -39
- package/src/components/ui/Separator/Separator.native.stories.tsx +0 -38
- package/src/components/ui/Separator/Separator.native.tsx +0 -32
- package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +0 -39
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +0 -31
- package/src/components/ui/Skeleton/Skeleton.native.tsx +0 -42
- package/src/components/ui/Table/Table.native.mdx +0 -72
- package/src/components/ui/Table/Table.native.spec.tsx +0 -83
- package/src/components/ui/Table/Table.native.stories.tsx +0 -55
- package/src/components/ui/Table/Table.native.tsx +0 -196
- package/src/components/ui/Tabs/Tabs.native.mdx +0 -65
- package/src/components/ui/Tabs/Tabs.native.spec.tsx +0 -65
- package/src/components/ui/Tabs/Tabs.native.stories.tsx +0 -57
- package/src/components/ui/Tabs/Tabs.native.tsx +0 -133
- package/src/components/ui/Tooltip/Tooltip.native.mdx +0 -47
- package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +0 -37
- package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +0 -38
- package/src/components/ui/Tooltip/Tooltip.native.tsx +0 -57
- package/src/components/ui/Typography/Typography.native.mdx +0 -56
- package/src/components/ui/Typography/Typography.native.spec.tsx +0 -49
- package/src/components/ui/Typography/Typography.native.stories.tsx +0 -36
- package/src/components/ui/Typography/Typography.native.tsx +0 -65
- package/src/native/ThemeContext.tsx +0 -28
- package/src/native/tokens.ts +0 -13
- package/src/native.ts +0 -76
- 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
|
-
}
|