@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,91 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes, Controls } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Button.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Button (React Native)
|
|
7
|
-
|
|
8
|
-
React Native button component using `Pressable`. Matches the web `Button` API as closely as possible, replacing `onClick` with `onPress` and `aria-label` with `accessibilityLabel`.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** — API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Button } from '@boostdev/design-system-components';
|
|
16
|
-
|
|
17
|
-
<Button variant="default" size="medium" onPress={() => console.log('pressed')}>
|
|
18
|
-
Save
|
|
19
|
-
</Button>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Wrap your tree in `ThemeProvider` (once, at the app root):
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
|
|
26
|
-
|
|
27
|
-
export default function App() {
|
|
28
|
-
return <ThemeProvider><YourApp /></ThemeProvider>;
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Playground
|
|
33
|
-
|
|
34
|
-
<Canvas of={Stories.Playground} />
|
|
35
|
-
<Controls of={Stories.Playground} />
|
|
36
|
-
|
|
37
|
-
## Variants
|
|
38
|
-
|
|
39
|
-
<Canvas of={Stories.Default} />
|
|
40
|
-
<Canvas of={Stories.Outline} />
|
|
41
|
-
|
|
42
|
-
## Sizes
|
|
43
|
-
|
|
44
|
-
<Canvas of={Stories.Small} />
|
|
45
|
-
<Canvas of={Stories.Medium} />
|
|
46
|
-
<Canvas of={Stories.Large} />
|
|
47
|
-
|
|
48
|
-
## Icons
|
|
49
|
-
|
|
50
|
-
<Canvas of={Stories.WithIconStart} />
|
|
51
|
-
<Canvas of={Stories.WithIconEnd} />
|
|
52
|
-
|
|
53
|
-
### Icon-only (1:1 aspect ratio)
|
|
54
|
-
|
|
55
|
-
Set `isIconOnly` to render a square button with equal `spacing.xs` padding on both axes. Composable with any `variant`. Always pair with `accessibilityLabel` for assistive tech.
|
|
56
|
-
|
|
57
|
-
<Canvas of={Stories.IconOnly} />
|
|
58
|
-
<Canvas of={Stories.IconOnlyOutline} />
|
|
59
|
-
<Canvas of={Stories.AllIconOnly} />
|
|
60
|
-
|
|
61
|
-
## All variants × sizes
|
|
62
|
-
|
|
63
|
-
<Canvas of={Stories.AllVariants} />
|
|
64
|
-
|
|
65
|
-
## Disabled
|
|
66
|
-
|
|
67
|
-
<Canvas of={Stories.Disabled} />
|
|
68
|
-
|
|
69
|
-
## Props
|
|
70
|
-
|
|
71
|
-
<ArgTypes of={Stories} />
|
|
72
|
-
|
|
73
|
-
## Props reference
|
|
74
|
-
|
|
75
|
-
| Prop | Type | Default | Description |
|
|
76
|
-
|------|------|---------|-------------|
|
|
77
|
-
| `variant` | `"default" \| "outline"` | `"default"` | Visual style (`"ghost"` is a deprecated alias for `"outline"`) |
|
|
78
|
-
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Height and horizontal padding |
|
|
79
|
-
| `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
|
|
80
|
-
| `onPress` | `() => void` | — | Called when the button is pressed |
|
|
81
|
-
| `iconStart` | `ReactNode` | — | Icon before the label |
|
|
82
|
-
| `iconEnd` | `ReactNode` | — | Icon after the label |
|
|
83
|
-
| `isIconOnly` | boolean | `false` | 1:1 aspect ratio with equal `spacing.xs` padding on both axes. Requires `accessibilityLabel`. |
|
|
84
|
-
| `accessibilityLabel` | string | — | Screen-reader label (required for icon-only buttons) |
|
|
85
|
-
| `style` | `StyleProp<ViewStyle>` | — | Override container styles |
|
|
86
|
-
|
|
87
|
-
## Accessibility
|
|
88
|
-
|
|
89
|
-
- Provide `accessibilityLabel` for icon-only buttons
|
|
90
|
-
- `disabled` communicates the disabled state to assistive technologies
|
|
91
|
-
- Pressed state reduces opacity to 0.75 for visual feedback
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
-
import { Button } from './Button.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('Button (native)', () => {
|
|
10
|
-
it('has button role', () => {
|
|
11
|
-
renderWithTheme(<Button>Save</Button>);
|
|
12
|
-
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('exposes disabled state', () => {
|
|
16
|
-
renderWithTheme(<Button disabled>Save</Button>);
|
|
17
|
-
expect(screen.getByRole('button', { name: 'Save' })).toHaveAttribute('aria-disabled', 'true');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('renders with icon-start without throwing', () => {
|
|
21
|
-
// Icon wrappers have importantForAccessibility="no-hide-descendants" (Android)
|
|
22
|
-
// and accessibilityElementsHidden={true} (iOS) so decorative icons are hidden on device.
|
|
23
|
-
const icon = <span data-testid="icon">★</span>;
|
|
24
|
-
renderWithTheme(<Button iconStart={icon}>Save</Button>);
|
|
25
|
-
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
26
|
-
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('renders with icon-end without throwing', () => {
|
|
30
|
-
const icon = <span data-testid="icon-end">→</span>;
|
|
31
|
-
renderWithTheme(<Button iconEnd={icon}>Next</Button>);
|
|
32
|
-
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
33
|
-
expect(screen.getByTestId('icon-end')).toBeInTheDocument();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('renders icon-only button with accessibilityLabel and icon as children (not wrapped in Text)', () => {
|
|
37
|
-
renderWithTheme(
|
|
38
|
-
<Button isIconOnly accessibilityLabel="Confirm">
|
|
39
|
-
<span data-testid="icon-only">✓</span>
|
|
40
|
-
</Button>
|
|
41
|
-
);
|
|
42
|
-
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
|
|
43
|
-
expect(screen.getByTestId('icon-only')).toBeInTheDocument();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('renders icon-only with outline variant', () => {
|
|
47
|
-
renderWithTheme(
|
|
48
|
-
<Button isIconOnly variant="outline" accessibilityLabel="Confirm">
|
|
49
|
-
<span data-testid="icon-only-outline">✓</span>
|
|
50
|
-
</Button>
|
|
51
|
-
);
|
|
52
|
-
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
|
|
53
|
-
expect(screen.getByTestId('icon-only-outline')).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
5
|
-
import { Button, type ButtonProps } from './Button.native';
|
|
6
|
-
|
|
7
|
-
const CheckIcon = (
|
|
8
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" aria-hidden>
|
|
9
|
-
<path d="M12 0a12 12 0 1 0 12 12A12 12 0 0 0 12 0Zm6.93 8.2-6.85 9.29a1 1 0 0 1-1.43.19l-4.89-3.91a1 1 0 0 1-.15-1.41A1 1 0 0 1 7 12.21l4.08 3.26L17.32 7a1 1 0 0 1 1.39-.21 1 1 0 0 1 .22 1.41Z" fill="currentColor"/>
|
|
10
|
-
</svg>
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
const ArrowRight = <span aria-hidden>→</span>;
|
|
14
|
-
const ArrowLeft = <span aria-hidden>←</span>;
|
|
15
|
-
|
|
16
|
-
const iconMap = {
|
|
17
|
-
none: undefined,
|
|
18
|
-
check: CheckIcon,
|
|
19
|
-
arrowRight: ArrowRight,
|
|
20
|
-
arrowLeft: ArrowLeft,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type IconKey = keyof typeof iconMap;
|
|
24
|
-
|
|
25
|
-
type ButtonStoryArgs = Omit<ButtonProps, 'iconStart' | 'iconEnd' | 'children'> & {
|
|
26
|
-
iconStart?: IconKey;
|
|
27
|
-
iconEnd?: IconKey;
|
|
28
|
-
children?: ReactNode;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const meta: Meta<ButtonStoryArgs> = {
|
|
32
|
-
tags: ['!stable', 'alpha'],
|
|
33
|
-
title: 'React Native/Interaction/Button',
|
|
34
|
-
component: Button,
|
|
35
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
36
|
-
parameters: { layout: 'centered' },
|
|
37
|
-
argTypes: {
|
|
38
|
-
variant: {
|
|
39
|
-
control: 'select',
|
|
40
|
-
options: ['default', 'outline'],
|
|
41
|
-
description: 'Visual style. `default` is filled; `outline` is transparent with a border.',
|
|
42
|
-
table: { defaultValue: { summary: 'default' } },
|
|
43
|
-
},
|
|
44
|
-
size: {
|
|
45
|
-
control: 'select',
|
|
46
|
-
options: ['small', 'medium', 'large'],
|
|
47
|
-
description: 'Height + horizontal padding.',
|
|
48
|
-
table: { defaultValue: { summary: 'medium' } },
|
|
49
|
-
},
|
|
50
|
-
iconStart: {
|
|
51
|
-
control: 'select',
|
|
52
|
-
options: Object.keys(iconMap) as IconKey[],
|
|
53
|
-
mapping: iconMap,
|
|
54
|
-
description: 'Icon rendered before the label.',
|
|
55
|
-
},
|
|
56
|
-
iconEnd: {
|
|
57
|
-
control: 'select',
|
|
58
|
-
options: Object.keys(iconMap) as IconKey[],
|
|
59
|
-
mapping: iconMap,
|
|
60
|
-
description: 'Icon rendered after the label.',
|
|
61
|
-
},
|
|
62
|
-
isIconOnly: {
|
|
63
|
-
control: 'boolean',
|
|
64
|
-
description: 'Forces 1:1 aspect ratio with minimal xs padding. Composable with any `variant`. Requires `accessibilityLabel`.',
|
|
65
|
-
table: { defaultValue: { summary: 'false' } },
|
|
66
|
-
},
|
|
67
|
-
disabled: { control: 'boolean' },
|
|
68
|
-
onPress: { action: 'pressed' },
|
|
69
|
-
},
|
|
70
|
-
args: { variant: 'default', size: 'medium' },
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default meta;
|
|
74
|
-
type Story = StoryObj<ButtonStoryArgs>;
|
|
75
|
-
|
|
76
|
-
export const Playground: Story = {
|
|
77
|
-
args: { children: 'Button', iconStart: 'none', iconEnd: 'none' },
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const Default: Story = { args: { children: 'Default' } };
|
|
81
|
-
export const Outline: Story = { args: { children: 'Outline', variant: 'outline' } };
|
|
82
|
-
export const Small: Story = { args: { children: 'Small', size: 'small' } };
|
|
83
|
-
export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
|
|
84
|
-
export const Large: Story = { args: { children: 'Large', size: 'large' } };
|
|
85
|
-
export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
|
|
86
|
-
|
|
87
|
-
export const WithIconStart: Story = { args: { children: 'Confirm', iconStart: 'check' } };
|
|
88
|
-
export const WithIconEnd: Story = { args: { children: 'Continue', iconEnd: 'arrowRight' } };
|
|
89
|
-
|
|
90
|
-
export const IconOnly: Story = {
|
|
91
|
-
args: {
|
|
92
|
-
isIconOnly: true,
|
|
93
|
-
accessibilityLabel: 'Confirm',
|
|
94
|
-
children: CheckIcon,
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const IconOnlyOutline: Story = {
|
|
99
|
-
args: {
|
|
100
|
-
variant: 'outline',
|
|
101
|
-
isIconOnly: true,
|
|
102
|
-
accessibilityLabel: 'Confirm',
|
|
103
|
-
children: CheckIcon,
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const AllVariants: Story = {
|
|
108
|
-
render: () => (
|
|
109
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
110
|
-
{(['default', 'outline'] as const).map(v => (
|
|
111
|
-
<div key={v} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
112
|
-
{(['small', 'medium', 'large'] as const).map(s => (
|
|
113
|
-
<Button key={s} variant={v} size={s}>{v} {s}</Button>
|
|
114
|
-
))}
|
|
115
|
-
</div>
|
|
116
|
-
))}
|
|
117
|
-
</div>
|
|
118
|
-
),
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const AllIconOnly: Story = {
|
|
122
|
-
render: () => (
|
|
123
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
124
|
-
{(['default', 'outline'] as const).map(v => (
|
|
125
|
-
<div key={v} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
126
|
-
{(['small', 'medium', 'large'] as const).map(s => (
|
|
127
|
-
<Button
|
|
128
|
-
key={s}
|
|
129
|
-
variant={v}
|
|
130
|
-
size={s}
|
|
131
|
-
isIconOnly
|
|
132
|
-
accessibilityLabel={`${v} ${s}`}
|
|
133
|
-
>
|
|
134
|
-
{CheckIcon}
|
|
135
|
-
</Button>
|
|
136
|
-
))}
|
|
137
|
-
</div>
|
|
138
|
-
))}
|
|
139
|
-
</div>
|
|
140
|
-
),
|
|
141
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { StyleSheet, Pressable, Text, View, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
-
import { spacing, font, border } from '../../../native/tokens';
|
|
4
|
-
import { useTheme } from '../../../native/ThemeContext';
|
|
5
|
-
|
|
6
|
-
type ButtonVariant = 'default' | 'outline' | 'ghost'; // 'ghost' is deprecated, use 'outline'
|
|
7
|
-
type ButtonSize = 'small' | 'medium' | 'large';
|
|
8
|
-
|
|
9
|
-
export interface ButtonProps {
|
|
10
|
-
children?: ReactNode;
|
|
11
|
-
variant?: ButtonVariant;
|
|
12
|
-
size?: ButtonSize;
|
|
13
|
-
iconStart?: ReactNode;
|
|
14
|
-
iconEnd?: ReactNode;
|
|
15
|
-
disabled?: boolean;
|
|
16
|
-
/** Forces a 1:1 aspect ratio with minimal padding for icon-only buttons. Requires `accessibilityLabel`. */
|
|
17
|
-
isIconOnly?: boolean;
|
|
18
|
-
onPress?: () => void;
|
|
19
|
-
accessibilityLabel?: string;
|
|
20
|
-
style?: StyleProp<ViewStyle>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const sizeTokens: Record<ButtonSize, { height: number; px: number; fontSize: number; radius: number }> = {
|
|
24
|
-
small: { height: 36, px: spacing.m, fontSize: font.size.bodyS, radius: border.radius.full },
|
|
25
|
-
medium: { height: 48, px: spacing.m, fontSize: font.size.body, radius: border.radius.full },
|
|
26
|
-
large: { height: 56, px: spacing.xxl, fontSize: font.size.heading3, radius: border.radius.full },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const styles = StyleSheet.create({
|
|
30
|
-
pressable: {
|
|
31
|
-
flexDirection: 'row',
|
|
32
|
-
alignItems: 'center',
|
|
33
|
-
justifyContent: 'center',
|
|
34
|
-
gap: spacing.xs,
|
|
35
|
-
alignSelf: 'flex-start',
|
|
36
|
-
},
|
|
37
|
-
label: {
|
|
38
|
-
fontFamily: font.family.body,
|
|
39
|
-
fontWeight: font.weight.semibold,
|
|
40
|
-
includeFontPadding: false,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
export function Button({
|
|
45
|
-
children,
|
|
46
|
-
variant = 'default',
|
|
47
|
-
size = 'medium',
|
|
48
|
-
iconStart,
|
|
49
|
-
iconEnd,
|
|
50
|
-
disabled = false,
|
|
51
|
-
isIconOnly = false,
|
|
52
|
-
onPress,
|
|
53
|
-
accessibilityLabel,
|
|
54
|
-
style,
|
|
55
|
-
}: Readonly<ButtonProps>) {
|
|
56
|
-
const { colors } = useTheme();
|
|
57
|
-
const { height, px, fontSize, radius } = sizeTokens[size];
|
|
58
|
-
|
|
59
|
-
// Color model: filled = interactive bg, outline = transparent bg + interactive_on-bg border/text
|
|
60
|
-
const primaryColor = colors.colorInteractive;
|
|
61
|
-
const onPrimaryColor = colors.colorOnInteractive;
|
|
62
|
-
// Outline uses colorInteractiveOnBg for text/border — colorInteractive may not meet 3:1 on white
|
|
63
|
-
const outlineTextColor = colors.colorInteractiveOnBg;
|
|
64
|
-
|
|
65
|
-
const isOutline = variant === 'outline' || variant === 'ghost';
|
|
66
|
-
const containerStyle: ViewStyle = {
|
|
67
|
-
height,
|
|
68
|
-
paddingHorizontal: isIconOnly ? spacing.xs : px,
|
|
69
|
-
borderRadius: radius,
|
|
70
|
-
opacity: disabled ? 0.4 : 1,
|
|
71
|
-
// Icon-only: square with equal xs padding on both axes.
|
|
72
|
-
...(isIconOnly && { aspectRatio: 1, paddingVertical: spacing.xs }),
|
|
73
|
-
...(variant === 'default' && { backgroundColor: primaryColor }),
|
|
74
|
-
...(isOutline && {
|
|
75
|
-
backgroundColor: 'transparent',
|
|
76
|
-
borderWidth: 1,
|
|
77
|
-
borderColor: outlineTextColor,
|
|
78
|
-
}),
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const textColor = variant === 'default' ? onPrimaryColor : outlineTextColor;
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<Pressable
|
|
85
|
-
onPress={onPress}
|
|
86
|
-
disabled={disabled}
|
|
87
|
-
accessibilityRole="button"
|
|
88
|
-
accessibilityLabel={accessibilityLabel}
|
|
89
|
-
accessibilityState={{ disabled }}
|
|
90
|
-
style={({ pressed }) => [styles.pressable, containerStyle, pressed && { opacity: 0.75 }, style]}
|
|
91
|
-
>
|
|
92
|
-
{iconStart && <View importantForAccessibility="no-hide-descendants" accessibilityElementsHidden={true}>{iconStart}</View>}
|
|
93
|
-
{children && (
|
|
94
|
-
isIconOnly
|
|
95
|
-
? children
|
|
96
|
-
: (
|
|
97
|
-
<Text style={[styles.label, { fontSize, color: textColor }]}>
|
|
98
|
-
{children}
|
|
99
|
-
</Text>
|
|
100
|
-
)
|
|
101
|
-
)}
|
|
102
|
-
{iconEnd && <View importantForAccessibility="no-hide-descendants">{iconEnd}</View>}
|
|
103
|
-
</Pressable>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Dialog.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Dialog (React Native)
|
|
7
|
-
|
|
8
|
-
A modal dialog overlay that centers content on screen with a semi-transparent backdrop. Built on React Native `Modal` with `transparent` background.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** — API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Dialog } from '@boostdev/design-system-components';
|
|
16
|
-
|
|
17
|
-
const [open, setOpen] = useState(false);
|
|
18
|
-
|
|
19
|
-
<Button onPress={() => setOpen(true)}>Open</Button>
|
|
20
|
-
<Dialog isOpen={open} onClose={() => setOpen(false)} title="Confirm">
|
|
21
|
-
<Text>Are you sure?</Text>
|
|
22
|
-
</Dialog>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Wrap your tree in `ThemeProvider` (once, at the app root):
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
|
|
29
|
-
|
|
30
|
-
export default function App() {
|
|
31
|
-
return <ThemeProvider><YourApp /></ThemeProvider>;
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Examples
|
|
36
|
-
|
|
37
|
-
### Default
|
|
38
|
-
<Canvas of={Stories.Default} />
|
|
39
|
-
|
|
40
|
-
### Without title
|
|
41
|
-
<Canvas of={Stories.WithoutTitle} />
|
|
42
|
-
|
|
43
|
-
## Props
|
|
44
|
-
|
|
45
|
-
<ArgTypes of={Stories} />
|
|
46
|
-
|
|
47
|
-
## Props reference
|
|
48
|
-
|
|
49
|
-
| Prop | Type | Default | Description |
|
|
50
|
-
|------|------|---------|-------------|
|
|
51
|
-
| `isOpen` | `boolean` | `false` | Controls visibility of the dialog |
|
|
52
|
-
| `onClose` | `() => void` | — | Called when backdrop or close button is pressed |
|
|
53
|
-
| `title` | `string` | — | Optional title displayed at the top |
|
|
54
|
-
| `children` | `ReactNode` | — | Dialog body content |
|
|
55
|
-
| `style` | `StyleProp<ViewStyle>` | — | Override panel styles |
|
|
56
|
-
|
|
57
|
-
## Accessibility
|
|
58
|
-
|
|
59
|
-
- Backdrop press and close button both call `onClose`
|
|
60
|
-
- Close button has `accessibilityLabel="Close dialog"`
|
|
61
|
-
- Android back button triggers `onRequestClose` which calls `onClose`
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
-
import { Dialog } from './Dialog.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('Dialog (native)', () => {
|
|
10
|
-
it('renders children when open', () => {
|
|
11
|
-
renderWithTheme(
|
|
12
|
-
<Dialog isOpen>
|
|
13
|
-
<span>Dialog body</span>
|
|
14
|
-
</Dialog>,
|
|
15
|
-
);
|
|
16
|
-
expect(screen.getByText('Dialog body')).toBeInTheDocument();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('does not render children when closed', () => {
|
|
20
|
-
renderWithTheme(
|
|
21
|
-
<Dialog isOpen={false}>
|
|
22
|
-
<span>Hidden content</span>
|
|
23
|
-
</Dialog>,
|
|
24
|
-
);
|
|
25
|
-
expect(screen.queryByText('Hidden content')).not.toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('renders the title when provided', () => {
|
|
29
|
-
renderWithTheme(
|
|
30
|
-
<Dialog isOpen title="Confirm action">
|
|
31
|
-
<span>Body</span>
|
|
32
|
-
</Dialog>,
|
|
33
|
-
);
|
|
34
|
-
expect(screen.getByText('Confirm action')).toBeInTheDocument();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('renders a close button with accessible label', () => {
|
|
38
|
-
renderWithTheme(
|
|
39
|
-
<Dialog isOpen onClose={() => {}}>
|
|
40
|
-
<span>Body</span>
|
|
41
|
-
</Dialog>,
|
|
42
|
-
);
|
|
43
|
-
const closeButtons = screen.getAllByRole('button', { name: 'Close dialog' });
|
|
44
|
-
expect(closeButtons.length).toBeGreaterThanOrEqual(1);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('calls onClose when close button is pressed', () => {
|
|
48
|
-
const onClose = vi.fn();
|
|
49
|
-
renderWithTheme(
|
|
50
|
-
<Dialog isOpen onClose={onClose}>
|
|
51
|
-
<span>Body</span>
|
|
52
|
-
</Dialog>,
|
|
53
|
-
);
|
|
54
|
-
const closeButtons = screen.getAllByRole('button', { name: 'Close dialog' });
|
|
55
|
-
// The inner close button (not the backdrop)
|
|
56
|
-
const innerClose = closeButtons[closeButtons.length - 1];
|
|
57
|
-
fireEvent.click(innerClose);
|
|
58
|
-
expect(onClose).toHaveBeenCalledTimes(1);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('calls onClose when backdrop is pressed', () => {
|
|
62
|
-
const onClose = vi.fn();
|
|
63
|
-
renderWithTheme(
|
|
64
|
-
<Dialog isOpen onClose={onClose}>
|
|
65
|
-
<span>Body</span>
|
|
66
|
-
</Dialog>,
|
|
67
|
-
);
|
|
68
|
-
const closeButtons = screen.getAllByRole('button', { name: 'Close dialog' });
|
|
69
|
-
// The first one is the backdrop
|
|
70
|
-
fireEvent.click(closeButtons[0]);
|
|
71
|
-
expect(onClose).toHaveBeenCalledTimes(1);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Text, View } from 'react-native';
|
|
3
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
5
|
-
import { Dialog } from './Dialog.native';
|
|
6
|
-
import { Button } from '../Button/Button.native';
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
tags: ['!stable', 'alpha'],
|
|
10
|
-
title: 'React Native/Interaction/Dialog',
|
|
11
|
-
component: Dialog,
|
|
12
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
13
|
-
parameters: { layout: 'centered' },
|
|
14
|
-
} satisfies Meta<typeof Dialog>;
|
|
15
|
-
|
|
16
|
-
export default meta;
|
|
17
|
-
type Story = StoryObj<typeof meta>;
|
|
18
|
-
|
|
19
|
-
function DialogDemo() {
|
|
20
|
-
const [open, setOpen] = useState(false);
|
|
21
|
-
return (
|
|
22
|
-
<>
|
|
23
|
-
<Button onPress={() => setOpen(true)}>Open Dialog</Button>
|
|
24
|
-
<Dialog isOpen={open} onClose={() => setOpen(false)} title="Confirmation">
|
|
25
|
-
<Text>Are you sure you want to proceed with this action?</Text>
|
|
26
|
-
<View style={{ flexDirection: 'row', gap: 8, marginTop: 16 }}>
|
|
27
|
-
<Button variant="outline" onPress={() => setOpen(false)}>Cancel</Button>
|
|
28
|
-
<Button onPress={() => setOpen(false)}>Confirm</Button>
|
|
29
|
-
</View>
|
|
30
|
-
</Dialog>
|
|
31
|
-
</>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function DialogWithoutTitleDemo() {
|
|
36
|
-
const [open, setOpen] = useState(false);
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
<Button onPress={() => setOpen(true)}>Open Dialog (no title)</Button>
|
|
40
|
-
<Dialog isOpen={open} onClose={() => setOpen(false)}>
|
|
41
|
-
<Text>This dialog has no title, just content.</Text>
|
|
42
|
-
</Dialog>
|
|
43
|
-
</>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export const Default: Story = {
|
|
48
|
-
render: () => <DialogDemo />,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const WithoutTitle: Story = {
|
|
52
|
-
render: () => <DialogWithoutTitleDemo />,
|
|
53
|
-
};
|