@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,50 +0,0 @@
|
|
|
1
|
-
import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
-
import { font } from '../../../native/tokens';
|
|
3
|
-
import { useTheme } from '../../../native/ThemeContext';
|
|
4
|
-
|
|
5
|
-
interface RatingProps {
|
|
6
|
-
value: number;
|
|
7
|
-
max?: number;
|
|
8
|
-
style?: StyleProp<ViewStyle>;
|
|
9
|
-
accessibilityLabel?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const STAR_SIZE = 24;
|
|
13
|
-
|
|
14
|
-
const styles = StyleSheet.create({
|
|
15
|
-
container: {
|
|
16
|
-
flexDirection: 'row',
|
|
17
|
-
alignItems: 'center',
|
|
18
|
-
},
|
|
19
|
-
star: {
|
|
20
|
-
fontSize: STAR_SIZE,
|
|
21
|
-
fontFamily: font.family.body,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export function Rating({ value, max = 5, style, accessibilityLabel }: Readonly<RatingProps>) {
|
|
26
|
-
const { colors } = useTheme();
|
|
27
|
-
const label = accessibilityLabel ?? `${value} out of ${max} stars`;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<View
|
|
31
|
-
style={[styles.container, style]}
|
|
32
|
-
accessibilityRole="image"
|
|
33
|
-
accessibilityLabel={label}
|
|
34
|
-
>
|
|
35
|
-
{Array.from({ length: max }).map((_, i) => (
|
|
36
|
-
<Text
|
|
37
|
-
key={i}
|
|
38
|
-
style={[
|
|
39
|
-
styles.star,
|
|
40
|
-
{ color: i < value ? colors.colorWarning : colors.colorBgSubtle },
|
|
41
|
-
]}
|
|
42
|
-
importantForAccessibility="no"
|
|
43
|
-
accessibilityElementsHidden={true}
|
|
44
|
-
>
|
|
45
|
-
{'\u2605'}
|
|
46
|
-
</Text>
|
|
47
|
-
))}
|
|
48
|
-
</View>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Toast.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Toast (React Native)
|
|
7
|
-
|
|
8
|
-
A toast notification system using React context. Provides `ToastProvider` and `useToast` hook. Toasts appear at the top of the screen with slide-in animation and auto-dismiss after 5 seconds.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** — API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
Wrap your app root in `ToastProvider`:
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
import { ToastProvider } from '@boostdev/design-system-components';
|
|
18
|
-
import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
|
|
19
|
-
|
|
20
|
-
export default function App() {
|
|
21
|
-
return (
|
|
22
|
-
<ThemeProvider>
|
|
23
|
-
<ToastProvider>
|
|
24
|
-
<YourApp />
|
|
25
|
-
</ToastProvider>
|
|
26
|
-
</ThemeProvider>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then use `useToast` in any descendant:
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import { useToast } from '@boostdev/design-system-components';
|
|
35
|
-
|
|
36
|
-
function SaveButton() {
|
|
37
|
-
const { showToast } = useToast();
|
|
38
|
-
return (
|
|
39
|
-
<Button onPress={() => showToast('Saved successfully!', 'success')}>
|
|
40
|
-
Save
|
|
41
|
-
</Button>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Variants
|
|
47
|
-
|
|
48
|
-
- `'success'` — green background
|
|
49
|
-
- `'error'` — red background
|
|
50
|
-
- `'info'` — interactive/blue background
|
|
51
|
-
|
|
52
|
-
## Examples
|
|
53
|
-
|
|
54
|
-
### Default
|
|
55
|
-
<Canvas of={Stories.Default} />
|
|
56
|
-
|
|
57
|
-
## API
|
|
58
|
-
|
|
59
|
-
### ToastProvider
|
|
60
|
-
|
|
61
|
-
Wraps the application and renders toast notifications.
|
|
62
|
-
|
|
63
|
-
| Prop | Type | Default | Description |
|
|
64
|
-
|------|------|---------|-------------|
|
|
65
|
-
| `children` | `ReactNode` | — | Application content |
|
|
66
|
-
| `style` | `StyleProp<ViewStyle>` | — | Override wrapper styles |
|
|
67
|
-
|
|
68
|
-
### useToast
|
|
69
|
-
|
|
70
|
-
Returns an object with:
|
|
71
|
-
|
|
72
|
-
| Method | Signature | Description |
|
|
73
|
-
|--------|-----------|-------------|
|
|
74
|
-
| `showToast` | `(message: string, variant: 'success' \| 'error' \| 'info') => void` | Displays a toast notification |
|
|
75
|
-
|
|
76
|
-
## Accessibility
|
|
77
|
-
|
|
78
|
-
- Toasts have `accessibilityRole="alert"` and `accessibilityLiveRegion="polite"`
|
|
79
|
-
- Dismiss button has `accessibilityLabel="Dismiss"`
|
|
80
|
-
- Toasts auto-dismiss after 5 seconds
|
|
81
|
-
- Throws if `useToast` is called outside `ToastProvider`
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { render, screen, act, fireEvent } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
-
import { ToastProvider, useToast } from './Toast.native';
|
|
4
|
-
|
|
5
|
-
function ToastTrigger({ message = 'Test toast', variant = 'info' as const }) {
|
|
6
|
-
const { showToast } = useToast();
|
|
7
|
-
return <button onClick={() => showToast(message, variant)}>Show Toast</button>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function setup(props = {}) {
|
|
11
|
-
return render(
|
|
12
|
-
<ThemeProvider>
|
|
13
|
-
<ToastProvider>
|
|
14
|
-
<ToastTrigger {...props} />
|
|
15
|
-
</ToastProvider>
|
|
16
|
-
</ThemeProvider>,
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('Toast (native)', () => {
|
|
21
|
-
it('shows a toast when showToast is called', () => {
|
|
22
|
-
setup({ message: 'Hello toast' });
|
|
23
|
-
fireEvent.click(screen.getByRole('button'));
|
|
24
|
-
expect(screen.getByText('Hello toast')).toBeInTheDocument();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('removes the toast after 5 seconds', () => {
|
|
28
|
-
vi.useFakeTimers();
|
|
29
|
-
setup({ message: 'Timed toast' });
|
|
30
|
-
act(() => {
|
|
31
|
-
fireEvent.click(screen.getByRole('button'));
|
|
32
|
-
});
|
|
33
|
-
expect(screen.getByText('Timed toast')).toBeInTheDocument();
|
|
34
|
-
act(() => {
|
|
35
|
-
vi.advanceTimersByTime(5100);
|
|
36
|
-
});
|
|
37
|
-
expect(screen.queryByText('Timed toast')).not.toBeInTheDocument();
|
|
38
|
-
vi.useRealTimers();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('renders dismiss button with accessible label', () => {
|
|
42
|
-
setup({ message: 'Dismissable' });
|
|
43
|
-
fireEvent.click(screen.getByRole('button', { name: 'Show Toast' }));
|
|
44
|
-
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('removes the toast when dismiss button is clicked', () => {
|
|
48
|
-
setup({ message: 'Dismissable toast' });
|
|
49
|
-
fireEvent.click(screen.getByRole('button', { name: 'Show Toast' }));
|
|
50
|
-
expect(screen.getByText('Dismissable toast')).toBeInTheDocument();
|
|
51
|
-
fireEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
|
|
52
|
-
expect(screen.queryByText('Dismissable toast')).not.toBeInTheDocument();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('throws when useToast is used outside ToastProvider', () => {
|
|
56
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
57
|
-
const Bad = () => {
|
|
58
|
-
useToast();
|
|
59
|
-
return null;
|
|
60
|
-
};
|
|
61
|
-
expect(() =>
|
|
62
|
-
render(
|
|
63
|
-
<ThemeProvider>
|
|
64
|
-
<Bad />
|
|
65
|
-
</ThemeProvider>,
|
|
66
|
-
),
|
|
67
|
-
).toThrow('useToast must be used within a ToastProvider');
|
|
68
|
-
errorSpy.mockRestore();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('renders multiple toasts simultaneously', () => {
|
|
72
|
-
setup({ message: 'Toast 1' });
|
|
73
|
-
const trigger = screen.getByRole('button', { name: 'Show Toast' });
|
|
74
|
-
fireEvent.click(trigger);
|
|
75
|
-
fireEvent.click(trigger);
|
|
76
|
-
// At least one toast should be visible (they share the same message text)
|
|
77
|
-
const toasts = screen.getAllByText('Toast 1');
|
|
78
|
-
expect(toasts.length).toBe(2);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
5
|
-
import { ToastProvider, useToast } from './Toast.native';
|
|
6
|
-
import { Button } from '../Button/Button.native';
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
tags: ['!stable', 'alpha'],
|
|
10
|
-
title: 'React Native/Interaction/Toast',
|
|
11
|
-
component: ToastProvider,
|
|
12
|
-
parameters: { layout: 'padded' },
|
|
13
|
-
} satisfies Meta<typeof ToastProvider>;
|
|
14
|
-
|
|
15
|
-
export default meta;
|
|
16
|
-
type Story = StoryObj<typeof meta>;
|
|
17
|
-
|
|
18
|
-
function ToastControls() {
|
|
19
|
-
const { showToast } = useToast();
|
|
20
|
-
return (
|
|
21
|
-
<View style={{ gap: 12, paddingTop: 80 }}>
|
|
22
|
-
<Button onPress={() => showToast('Item saved successfully.', 'success')}>
|
|
23
|
-
Show Success Toast
|
|
24
|
-
</Button>
|
|
25
|
-
<Button onPress={() => showToast('Something went wrong. Please try again.', 'error')}>
|
|
26
|
-
Show Error Toast
|
|
27
|
-
</Button>
|
|
28
|
-
<Button onPress={() => showToast('Your session will expire in 5 minutes.', 'info')}>
|
|
29
|
-
Show Info Toast
|
|
30
|
-
</Button>
|
|
31
|
-
</View>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ToastDemo() {
|
|
36
|
-
return (
|
|
37
|
-
<ThemeProvider colorScheme="light">
|
|
38
|
-
<ToastProvider>
|
|
39
|
-
<ToastControls />
|
|
40
|
-
</ToastProvider>
|
|
41
|
-
</ThemeProvider>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const Default: Story = {
|
|
46
|
-
render: () => <ToastDemo />,
|
|
47
|
-
};
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useState,
|
|
3
|
-
useEffect,
|
|
4
|
-
useRef,
|
|
5
|
-
useCallback,
|
|
6
|
-
useMemo,
|
|
7
|
-
createContext,
|
|
8
|
-
useContext,
|
|
9
|
-
ReactNode,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import {
|
|
12
|
-
View,
|
|
13
|
-
Text,
|
|
14
|
-
Pressable,
|
|
15
|
-
Animated,
|
|
16
|
-
StyleSheet,
|
|
17
|
-
StyleProp,
|
|
18
|
-
ViewStyle,
|
|
19
|
-
} from 'react-native';
|
|
20
|
-
import { spacing, font, border } from '../../../native/tokens';
|
|
21
|
-
import { useTheme } from '../../../native/ThemeContext';
|
|
22
|
-
|
|
23
|
-
type ToastVariant = 'success' | 'error' | 'info';
|
|
24
|
-
|
|
25
|
-
interface Toast {
|
|
26
|
-
id: string;
|
|
27
|
-
message: string;
|
|
28
|
-
variant: ToastVariant;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ToastContextType {
|
|
32
|
-
showToast: (message: string, variant: ToastVariant) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
|
36
|
-
|
|
37
|
-
const styles = StyleSheet.create({
|
|
38
|
-
container: {
|
|
39
|
-
position: 'absolute',
|
|
40
|
-
top: 0,
|
|
41
|
-
start: 0,
|
|
42
|
-
end: 0,
|
|
43
|
-
paddingTop: spacing.xxxl,
|
|
44
|
-
paddingHorizontal: spacing.m,
|
|
45
|
-
zIndex: 9999,
|
|
46
|
-
pointerEvents: 'box-none',
|
|
47
|
-
},
|
|
48
|
-
toast: {
|
|
49
|
-
flexDirection: 'row',
|
|
50
|
-
alignItems: 'center',
|
|
51
|
-
justifyContent: 'space-between',
|
|
52
|
-
paddingVertical: spacing.s,
|
|
53
|
-
paddingHorizontal: spacing.m,
|
|
54
|
-
borderRadius: border.radius.s,
|
|
55
|
-
marginBottom: spacing.xs,
|
|
56
|
-
minHeight: 48,
|
|
57
|
-
},
|
|
58
|
-
message: {
|
|
59
|
-
fontFamily: font.family.body,
|
|
60
|
-
fontSize: font.size.body,
|
|
61
|
-
fontWeight: font.weight.semibold,
|
|
62
|
-
flex: 1,
|
|
63
|
-
},
|
|
64
|
-
closeButton: {
|
|
65
|
-
width: 44,
|
|
66
|
-
height: 44,
|
|
67
|
-
alignItems: 'center',
|
|
68
|
-
justifyContent: 'center',
|
|
69
|
-
marginStart: spacing.xs,
|
|
70
|
-
},
|
|
71
|
-
closeText: {
|
|
72
|
-
fontFamily: font.family.body,
|
|
73
|
-
fontSize: 16,
|
|
74
|
-
lineHeight: 20,
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
function ToastItem({
|
|
79
|
-
toast,
|
|
80
|
-
onRemove,
|
|
81
|
-
}: {
|
|
82
|
-
toast: Toast;
|
|
83
|
-
onRemove: () => void;
|
|
84
|
-
}) {
|
|
85
|
-
const { colors } = useTheme();
|
|
86
|
-
const slideAnim = useRef(new Animated.Value(-100)).current;
|
|
87
|
-
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
88
|
-
|
|
89
|
-
const variantColors: Record<
|
|
90
|
-
ToastVariant,
|
|
91
|
-
{ bg: string; text: string }
|
|
92
|
-
> = useMemo(
|
|
93
|
-
() => ({
|
|
94
|
-
success: { bg: colors.colorSuccess, text: colors.colorOnSuccess },
|
|
95
|
-
error: { bg: colors.colorError, text: colors.colorOnError },
|
|
96
|
-
info: { bg: colors.colorBgSubtle, text: colors.colorOnBgSubtle },
|
|
97
|
-
}),
|
|
98
|
-
[colors],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
Animated.parallel([
|
|
103
|
-
Animated.timing(slideAnim, {
|
|
104
|
-
toValue: 0,
|
|
105
|
-
duration: 200,
|
|
106
|
-
useNativeDriver: true,
|
|
107
|
-
}),
|
|
108
|
-
Animated.timing(opacityAnim, {
|
|
109
|
-
toValue: 1,
|
|
110
|
-
duration: 200,
|
|
111
|
-
useNativeDriver: true,
|
|
112
|
-
}),
|
|
113
|
-
]).start();
|
|
114
|
-
}, [slideAnim, opacityAnim]);
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
const timer = setTimeout(onRemove, 5000);
|
|
118
|
-
return () => clearTimeout(timer);
|
|
119
|
-
}, [onRemove]);
|
|
120
|
-
|
|
121
|
-
const { bg, text } = variantColors[toast.variant];
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<Animated.View
|
|
125
|
-
style={[
|
|
126
|
-
styles.toast,
|
|
127
|
-
{
|
|
128
|
-
backgroundColor: bg,
|
|
129
|
-
transform: [{ translateY: slideAnim }],
|
|
130
|
-
opacity: opacityAnim,
|
|
131
|
-
},
|
|
132
|
-
]}
|
|
133
|
-
accessibilityRole="alert"
|
|
134
|
-
accessibilityLiveRegion="polite"
|
|
135
|
-
>
|
|
136
|
-
<Text style={[styles.message, { color: text }]}>{toast.message}</Text>
|
|
137
|
-
<Pressable
|
|
138
|
-
style={styles.closeButton}
|
|
139
|
-
onPress={onRemove}
|
|
140
|
-
accessibilityRole="button"
|
|
141
|
-
accessibilityLabel="Dismiss"
|
|
142
|
-
hitSlop={8}
|
|
143
|
-
>
|
|
144
|
-
<Text style={[styles.closeText, { color: text }]} aria-hidden>
|
|
145
|
-
{'\u2715'}
|
|
146
|
-
</Text>
|
|
147
|
-
</Pressable>
|
|
148
|
-
</Animated.View>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function ToastProvider({
|
|
153
|
-
children,
|
|
154
|
-
style,
|
|
155
|
-
}: {
|
|
156
|
-
children: ReactNode;
|
|
157
|
-
style?: StyleProp<ViewStyle>;
|
|
158
|
-
}) {
|
|
159
|
-
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
160
|
-
|
|
161
|
-
const showToast = useCallback((message: string, variant: ToastVariant) => {
|
|
162
|
-
const id = Math.random().toString(36).substring(2, 9);
|
|
163
|
-
setToasts((prev) => [...prev, { id, message, variant }]);
|
|
164
|
-
}, []);
|
|
165
|
-
|
|
166
|
-
const removeToast = useCallback((id: string) => {
|
|
167
|
-
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
168
|
-
}, []);
|
|
169
|
-
|
|
170
|
-
const value = useMemo(() => ({ showToast }), [showToast]);
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
<ToastContext.Provider value={value}>
|
|
174
|
-
<View style={[{ flex: 1 }, style]}>
|
|
175
|
-
{children}
|
|
176
|
-
{toasts.length > 0 && (
|
|
177
|
-
<View
|
|
178
|
-
style={styles.container}
|
|
179
|
-
accessibilityRole="summary"
|
|
180
|
-
accessibilityLabel="Notifications"
|
|
181
|
-
>
|
|
182
|
-
{toasts.map((toast) => (
|
|
183
|
-
<ToastItem
|
|
184
|
-
key={toast.id}
|
|
185
|
-
toast={toast}
|
|
186
|
-
onRemove={() => removeToast(toast.id)}
|
|
187
|
-
/>
|
|
188
|
-
))}
|
|
189
|
-
</View>
|
|
190
|
-
)}
|
|
191
|
-
</View>
|
|
192
|
-
</ToastContext.Provider>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function useToast(): ToastContextType {
|
|
197
|
-
const context = useContext(ToastContext);
|
|
198
|
-
if (!context) {
|
|
199
|
-
throw new Error('useToast must be used within a ToastProvider');
|
|
200
|
-
}
|
|
201
|
-
return context;
|
|
202
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Checkbox.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Checkbox (React Native)
|
|
7
|
-
|
|
8
|
-
React Native checkbox component with label, optional hint, and error state. Fully controlled — manage checked state via `checked` + `onChange`.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** — API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Checkbox } from '@boostdev/design-system-components';
|
|
16
|
-
import { useState } from 'react';
|
|
17
|
-
|
|
18
|
-
function Example() {
|
|
19
|
-
const [checked, setChecked] = useState(false);
|
|
20
|
-
return (
|
|
21
|
-
<Checkbox
|
|
22
|
-
name="terms"
|
|
23
|
-
label="Accept terms and conditions"
|
|
24
|
-
checked={checked}
|
|
25
|
-
onChange={setChecked}
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Examples
|
|
32
|
-
|
|
33
|
-
### Interactive
|
|
34
|
-
<Canvas of={Stories.Interactive} />
|
|
35
|
-
|
|
36
|
-
### Unchecked
|
|
37
|
-
<Canvas of={Stories.Unchecked} />
|
|
38
|
-
|
|
39
|
-
### Checked
|
|
40
|
-
<Canvas of={Stories.Checked} />
|
|
41
|
-
|
|
42
|
-
### With hint
|
|
43
|
-
<Canvas of={Stories.WithHint} />
|
|
44
|
-
|
|
45
|
-
### With error
|
|
46
|
-
<Canvas of={Stories.WithError} />
|
|
47
|
-
|
|
48
|
-
### Disabled
|
|
49
|
-
<Canvas of={Stories.Disabled} />
|
|
50
|
-
|
|
51
|
-
### Disabled + checked
|
|
52
|
-
<Canvas of={Stories.DisabledChecked} />
|
|
53
|
-
|
|
54
|
-
## Props
|
|
55
|
-
|
|
56
|
-
<ArgTypes of={Stories} />
|
|
57
|
-
|
|
58
|
-
## Props reference
|
|
59
|
-
|
|
60
|
-
| Prop | Type | Default | Description |
|
|
61
|
-
|------|------|---------|-------------|
|
|
62
|
-
| `name` | string | — | Field name (used for accessibility grouping) |
|
|
63
|
-
| `label` | string | — | Visible label text |
|
|
64
|
-
| `checked` | boolean | `false` | Controlled checked state |
|
|
65
|
-
| `onChange` | `(checked: boolean) => void` | — | Called with the new value on press |
|
|
66
|
-
| `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
|
|
67
|
-
| `hint` | string | — | Helper text below the label |
|
|
68
|
-
| `error` | string | — | Error message; shown instead of hint when set |
|
|
69
|
-
|
|
70
|
-
## Accessibility
|
|
71
|
-
|
|
72
|
-
- The label, hint, and error are announced together by screen readers
|
|
73
|
-
- `disabled` communicates the disabled state via `accessibilityState`
|
|
74
|
-
- Group related checkboxes using a wrapping `View` with `accessibilityRole="radiogroup"` or a labelled `fieldset` equivalent
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
3
|
-
import { Checkbox } from './Checkbox.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('Checkbox (native)', () => {
|
|
10
|
-
it('has checkbox role with label', () => {
|
|
11
|
-
renderWithTheme(<Checkbox label="Accept terms" name="terms" />);
|
|
12
|
-
expect(screen.getByRole('checkbox', { name: 'Accept terms' })).toBeInTheDocument();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('shows the checkmark when checked', () => {
|
|
16
|
-
renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
|
|
17
|
-
expect(screen.getByText('✓')).toBeInTheDocument();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('hides the checkmark when unchecked', () => {
|
|
21
|
-
renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={false} />);
|
|
22
|
-
expect(screen.queryByText('✓')).not.toBeInTheDocument();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('checkmark uses theme color (contrast regression: was hardcoded #fff)', () => {
|
|
26
|
-
// Previously the checkmark had color: '#fff' hardcoded in the StyleSheet.
|
|
27
|
-
// Now it uses colors.colorOnInteractive from the theme, which is #000 in dark
|
|
28
|
-
// mode — fixing the 2.52:1 contrast failure on the dark interactive blue.
|
|
29
|
-
renderWithTheme(<Checkbox label="Accept terms" name="terms" checked={true} />);
|
|
30
|
-
const checkmark = screen.getByText('✓');
|
|
31
|
-
// The color must be applied as an inline style (overrides StyleSheet), not absent.
|
|
32
|
-
// In light mode colorOnInteractive=#fff; in dark mode it is #000.
|
|
33
|
-
expect(checkmark.style.color).toBeTruthy();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('renders with error message', () => {
|
|
37
|
-
renderWithTheme(
|
|
38
|
-
<Checkbox label="Accept terms" name="terms" error="You must accept." />,
|
|
39
|
-
);
|
|
40
|
-
expect(screen.getByRole('alert')).toHaveTextContent('You must accept.');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
4
|
-
import { Checkbox } from './Checkbox.native';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
tags: ['!stable', 'alpha'],
|
|
8
|
-
title: 'React Native/Form/Checkbox',
|
|
9
|
-
component: Checkbox,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
|
-
parameters: { layout: 'padded' },
|
|
12
|
-
} satisfies Meta<typeof Checkbox>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Unchecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: false } };
|
|
18
|
-
export const Checked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true } };
|
|
19
|
-
export const Disabled: Story = { args: { label: 'Accept terms', name: 'terms', disabled: true } };
|
|
20
|
-
export const DisabledChecked: Story = { args: { label: 'Accept terms', name: 'terms', checked: true, disabled: true } };
|
|
21
|
-
export const WithError: Story = {
|
|
22
|
-
args: { label: 'Accept terms', name: 'terms', error: 'You must accept the terms.' },
|
|
23
|
-
};
|
|
24
|
-
export const WithHint: Story = {
|
|
25
|
-
args: { label: 'Subscribe to newsletter', name: 'newsletter', hint: 'Max one email per week.' },
|
|
26
|
-
};
|
|
27
|
-
export const Interactive: Story = {
|
|
28
|
-
render: () => {
|
|
29
|
-
const [checked, setChecked] = useState(false);
|
|
30
|
-
return (
|
|
31
|
-
<Checkbox label="Accept terms and conditions" name="terms" checked={checked} onChange={setChecked} />
|
|
32
|
-
);
|
|
33
|
-
},
|
|
34
|
-
};
|
|
@@ -1,80 +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
|
-
import { Label } from '../atoms/Label.native';
|
|
6
|
-
import { Message } from '../atoms/Message.native';
|
|
7
|
-
|
|
8
|
-
interface CheckboxProps {
|
|
9
|
-
label: ReactNode;
|
|
10
|
-
name: string;
|
|
11
|
-
checked?: boolean;
|
|
12
|
-
onChange?: (checked: boolean) => void;
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
error?: string;
|
|
15
|
-
hint?: string;
|
|
16
|
-
style?: StyleProp<ViewStyle>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const BOX = 22;
|
|
20
|
-
|
|
21
|
-
const styles = StyleSheet.create({
|
|
22
|
-
container: {
|
|
23
|
-
gap: spacing.xxs,
|
|
24
|
-
},
|
|
25
|
-
row: {
|
|
26
|
-
flexDirection: 'row',
|
|
27
|
-
alignItems: 'center',
|
|
28
|
-
gap: spacing.xs,
|
|
29
|
-
minHeight: 44,
|
|
30
|
-
},
|
|
31
|
-
box: {
|
|
32
|
-
width: BOX,
|
|
33
|
-
height: BOX,
|
|
34
|
-
borderRadius: border.radius.xs,
|
|
35
|
-
borderWidth: 2,
|
|
36
|
-
alignItems: 'center',
|
|
37
|
-
justifyContent: 'center',
|
|
38
|
-
},
|
|
39
|
-
checkmark: {
|
|
40
|
-
fontSize: 14,
|
|
41
|
-
fontWeight: '700',
|
|
42
|
-
lineHeight: 16,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
export function Checkbox({
|
|
47
|
-
label,
|
|
48
|
-
name,
|
|
49
|
-
checked = false,
|
|
50
|
-
onChange,
|
|
51
|
-
disabled = false,
|
|
52
|
-
error,
|
|
53
|
-
hint,
|
|
54
|
-
style,
|
|
55
|
-
}: CheckboxProps) {
|
|
56
|
-
const { colors } = useTheme();
|
|
57
|
-
|
|
58
|
-
const borderColor = error ? colors.colorError : checked ? colors.colorInteractive : colors.colorOnBg;
|
|
59
|
-
const bgColor = checked ? colors.colorInteractive : 'transparent';
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<View style={[styles.container, style]}>
|
|
63
|
-
<Pressable
|
|
64
|
-
onPress={() => !disabled && onChange?.(!checked)}
|
|
65
|
-
accessibilityRole="checkbox"
|
|
66
|
-
accessibilityLabel={typeof label === 'string' ? label : name}
|
|
67
|
-
accessibilityState={{ checked, disabled }}
|
|
68
|
-
disabled={disabled}
|
|
69
|
-
style={({ pressed }) => [styles.row, pressed && { opacity: 0.7 }, disabled && { opacity: 0.4 }]}
|
|
70
|
-
>
|
|
71
|
-
<View style={[styles.box, { borderColor, backgroundColor: bgColor }]}>
|
|
72
|
-
{checked && <Label label="✓" style={[styles.checkmark, { color: colors.colorOnInteractive }]} />}
|
|
73
|
-
</View>
|
|
74
|
-
<Label label={label} />
|
|
75
|
-
</Pressable>
|
|
76
|
-
<Message type="error" message={error} />
|
|
77
|
-
<Message type="hint" message={hint} />
|
|
78
|
-
</View>
|
|
79
|
-
);
|
|
80
|
-
}
|