@boostdev/design-system-components 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +175 -4
- package/src/components/layout/Grid/Grid.tsx +15 -1
- package/src/components/layout/Grid/masonry.ts +120 -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,51 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
3
|
-
import { Slider } from './Slider.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('Slider (native)', () => {
|
|
10
|
-
it('renders the label', () => {
|
|
11
|
-
renderWithTheme(<Slider label="Volume" name="volume" />);
|
|
12
|
-
expect(screen.getByText('Volume')).toBeInTheDocument();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('shows current value when showValue is true', () => {
|
|
16
|
-
renderWithTheme(
|
|
17
|
-
<Slider label="Volume" name="volume" value={75} showValue />,
|
|
18
|
-
);
|
|
19
|
-
expect(screen.getByText('75')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('hides value display when showValue is false', () => {
|
|
23
|
-
renderWithTheme(
|
|
24
|
-
<Slider label="Volume" name="volume" value={50} />,
|
|
25
|
-
);
|
|
26
|
-
// Only "Volume" text should be present, not the value
|
|
27
|
-
expect(screen.queryByText('50')).not.toBeInTheDocument();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('shows error message', () => {
|
|
31
|
-
renderWithTheme(
|
|
32
|
-
<Slider label="Volume" name="volume" error="Too low." />,
|
|
33
|
-
);
|
|
34
|
-
expect(screen.getByRole('alert')).toHaveTextContent('Too low.');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('shows hint message', () => {
|
|
38
|
-
renderWithTheme(
|
|
39
|
-
<Slider label="Volume" name="volume" hint="Adjust volume." />,
|
|
40
|
-
);
|
|
41
|
-
expect(screen.getByText('Adjust volume.')).toBeInTheDocument();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('uses default min value when no value is provided', () => {
|
|
45
|
-
renderWithTheme(
|
|
46
|
-
<Slider label="Volume" name="volume" showValue />,
|
|
47
|
-
);
|
|
48
|
-
// Default min is 0, which becomes the initial value
|
|
49
|
-
expect(screen.getByText('0')).toBeInTheDocument();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
4
|
-
import { Slider } from './Slider.native';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
tags: ['!stable', 'alpha'],
|
|
8
|
-
title: 'React Native/Form/Slider',
|
|
9
|
-
component: Slider,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
|
-
parameters: { layout: 'padded' },
|
|
12
|
-
} satisfies Meta<typeof Slider>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Default: Story = {
|
|
18
|
-
args: { label: 'Volume', name: 'volume', value: 50 },
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const WithValue: Story = {
|
|
22
|
-
args: { label: 'Brightness', name: 'brightness', value: 75, showValue: true },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const CustomRange: Story = {
|
|
26
|
-
args: { label: 'Temperature', name: 'temp', min: 16, max: 30, value: 21, showValue: true },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const WithStep: Story = {
|
|
30
|
-
args: { label: 'Quantity', name: 'qty', min: 0, max: 50, step: 5, value: 25, showValue: true },
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const WithError: Story = {
|
|
34
|
-
args: { label: 'Volume', name: 'volume', value: 0, error: 'Volume must be at least 10.' },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const WithHint: Story = {
|
|
38
|
-
args: { label: 'Volume', name: 'volume', value: 50, hint: 'Adjust playback volume.' },
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const Disabled: Story = {
|
|
42
|
-
args: { label: 'Volume', name: 'volume', value: 30, disabled: true, showValue: true },
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const Interactive: Story = {
|
|
46
|
-
render: () => {
|
|
47
|
-
const [value, setValue] = useState(50);
|
|
48
|
-
return (
|
|
49
|
-
<Slider
|
|
50
|
-
label="Volume"
|
|
51
|
-
name="volume"
|
|
52
|
-
value={value}
|
|
53
|
-
onChange={setValue}
|
|
54
|
-
showValue
|
|
55
|
-
/>
|
|
56
|
-
);
|
|
57
|
-
},
|
|
58
|
-
};
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
StyleSheet,
|
|
4
|
-
View,
|
|
5
|
-
Text,
|
|
6
|
-
GestureResponderEvent,
|
|
7
|
-
LayoutChangeEvent,
|
|
8
|
-
StyleProp,
|
|
9
|
-
ViewStyle,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
import { spacing, font, lineHeight } from '../../../../native/tokens';
|
|
12
|
-
import { useTheme } from '../../../../native/ThemeContext';
|
|
13
|
-
import { Label } from '../atoms/Label.native';
|
|
14
|
-
import { Message } from '../atoms/Message.native';
|
|
15
|
-
|
|
16
|
-
interface SliderProps {
|
|
17
|
-
label: string;
|
|
18
|
-
name: string;
|
|
19
|
-
value?: number;
|
|
20
|
-
defaultValue?: number;
|
|
21
|
-
min?: number;
|
|
22
|
-
max?: number;
|
|
23
|
-
step?: number;
|
|
24
|
-
showValue?: boolean;
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
error?: string;
|
|
27
|
-
hint?: string;
|
|
28
|
-
onChange?: (value: number) => void;
|
|
29
|
-
style?: StyleProp<ViewStyle>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const TRACK_HEIGHT = 6;
|
|
33
|
-
const THUMB_SIZE = 24;
|
|
34
|
-
|
|
35
|
-
const styles = StyleSheet.create({
|
|
36
|
-
container: {
|
|
37
|
-
gap: spacing.xxs,
|
|
38
|
-
},
|
|
39
|
-
labelRow: {
|
|
40
|
-
flexDirection: 'row',
|
|
41
|
-
justifyContent: 'space-between',
|
|
42
|
-
alignItems: 'center',
|
|
43
|
-
},
|
|
44
|
-
valueText: {
|
|
45
|
-
fontFamily: font.family.body,
|
|
46
|
-
fontSize: font.size.bodyS,
|
|
47
|
-
lineHeight: lineHeight.bodyS,
|
|
48
|
-
fontWeight: font.weight.semibold,
|
|
49
|
-
},
|
|
50
|
-
trackContainer: {
|
|
51
|
-
height: THUMB_SIZE + spacing.xs * 2,
|
|
52
|
-
justifyContent: 'center',
|
|
53
|
-
paddingHorizontal: THUMB_SIZE / 2,
|
|
54
|
-
},
|
|
55
|
-
track: {
|
|
56
|
-
height: TRACK_HEIGHT,
|
|
57
|
-
borderRadius: TRACK_HEIGHT / 2,
|
|
58
|
-
overflow: 'hidden',
|
|
59
|
-
},
|
|
60
|
-
fill: {
|
|
61
|
-
height: TRACK_HEIGHT,
|
|
62
|
-
borderRadius: TRACK_HEIGHT / 2,
|
|
63
|
-
},
|
|
64
|
-
thumb: {
|
|
65
|
-
position: 'absolute',
|
|
66
|
-
width: THUMB_SIZE,
|
|
67
|
-
height: THUMB_SIZE,
|
|
68
|
-
borderRadius: THUMB_SIZE / 2,
|
|
69
|
-
top: '50%',
|
|
70
|
-
marginTop: -(THUMB_SIZE / 2),
|
|
71
|
-
borderWidth: 2,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
function clampValue(v: number, min: number, max: number): number {
|
|
76
|
-
return Math.min(max, Math.max(min, v));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function snapToStep(v: number, min: number, step: number): number {
|
|
80
|
-
return Math.round((v - min) / step) * step + min;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function Slider({
|
|
84
|
-
label,
|
|
85
|
-
name: _name, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
86
|
-
value,
|
|
87
|
-
defaultValue,
|
|
88
|
-
min = 0,
|
|
89
|
-
max = 100,
|
|
90
|
-
step = 1,
|
|
91
|
-
showValue = false,
|
|
92
|
-
disabled = false,
|
|
93
|
-
error,
|
|
94
|
-
hint,
|
|
95
|
-
onChange,
|
|
96
|
-
style,
|
|
97
|
-
}: Readonly<SliderProps>) {
|
|
98
|
-
const { colors } = useTheme();
|
|
99
|
-
const isControlled = value !== undefined;
|
|
100
|
-
const [internalValue, setInternalValue] = useState(defaultValue ?? min);
|
|
101
|
-
const [trackWidth, setTrackWidth] = useState(0);
|
|
102
|
-
|
|
103
|
-
const currentValue = isControlled ? value : internalValue;
|
|
104
|
-
const fillPct = max > min ? ((currentValue - min) / (max - min)) * 100 : 0;
|
|
105
|
-
|
|
106
|
-
const updateValue = (v: number) => {
|
|
107
|
-
const clamped = clampValue(v, min, max);
|
|
108
|
-
const snapped = snapToStep(clamped, min, step);
|
|
109
|
-
if (!isControlled) setInternalValue(snapped);
|
|
110
|
-
onChange?.(snapped);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const handleTrackLayout = (e: LayoutChangeEvent) => {
|
|
114
|
-
setTrackWidth(e.nativeEvent.layout.width);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const valueFromTouch = (pageX: number, layoutX: number): number => {
|
|
118
|
-
const x = pageX - layoutX;
|
|
119
|
-
const pct = clampValue(x / trackWidth, 0, 1);
|
|
120
|
-
return pct * (max - min) + min;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const handleStartShouldSetResponder = () => !disabled;
|
|
124
|
-
const handleResponderGrant = (e: GestureResponderEvent) => {
|
|
125
|
-
const target = e.currentTarget as unknown as { measure: (cb: (x: number, y: number, w: number, h: number, px: number, py: number) => void) => void };
|
|
126
|
-
target.measure((_x, _y, _w, _h, pageX) => {
|
|
127
|
-
updateValue(valueFromTouch(e.nativeEvent.pageX, pageX));
|
|
128
|
-
});
|
|
129
|
-
};
|
|
130
|
-
const handleResponderMove = (e: GestureResponderEvent) => {
|
|
131
|
-
const target = e.currentTarget as unknown as { measure: (cb: (x: number, y: number, w: number, h: number, px: number, py: number) => void) => void };
|
|
132
|
-
target.measure((_x, _y, _w, _h, pageX) => {
|
|
133
|
-
updateValue(valueFromTouch(e.nativeEvent.pageX, pageX));
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<View
|
|
139
|
-
style={[styles.container, style]}
|
|
140
|
-
accessibilityRole="adjustable"
|
|
141
|
-
accessibilityLabel={label}
|
|
142
|
-
accessibilityValue={{ min, max, now: currentValue, text: String(currentValue) }}
|
|
143
|
-
accessibilityState={{ disabled }}
|
|
144
|
-
>
|
|
145
|
-
<View style={styles.labelRow}>
|
|
146
|
-
<Label label={label} />
|
|
147
|
-
{showValue && (
|
|
148
|
-
<Text style={[styles.valueText, { color: colors.colorOnBg }]}>
|
|
149
|
-
{currentValue}
|
|
150
|
-
</Text>
|
|
151
|
-
)}
|
|
152
|
-
</View>
|
|
153
|
-
<View
|
|
154
|
-
style={[styles.trackContainer, disabled && { opacity: 0.4 }]}
|
|
155
|
-
onLayout={handleTrackLayout}
|
|
156
|
-
onStartShouldSetResponder={handleStartShouldSetResponder}
|
|
157
|
-
onMoveShouldSetResponder={handleStartShouldSetResponder}
|
|
158
|
-
onResponderGrant={handleResponderGrant}
|
|
159
|
-
onResponderMove={handleResponderMove}
|
|
160
|
-
>
|
|
161
|
-
<View style={[styles.track, { backgroundColor: colors.colorBgSubtle }]}>
|
|
162
|
-
<View
|
|
163
|
-
style={[
|
|
164
|
-
styles.fill,
|
|
165
|
-
{
|
|
166
|
-
width: `${fillPct}%`,
|
|
167
|
-
backgroundColor: error ? colors.colorError : colors.colorInteractive,
|
|
168
|
-
},
|
|
169
|
-
]}
|
|
170
|
-
/>
|
|
171
|
-
</View>
|
|
172
|
-
<View
|
|
173
|
-
style={[
|
|
174
|
-
styles.thumb,
|
|
175
|
-
{
|
|
176
|
-
start: `${fillPct}%`,
|
|
177
|
-
marginStart: -(THUMB_SIZE / 2),
|
|
178
|
-
backgroundColor: colors.colorBg,
|
|
179
|
-
borderColor: error ? colors.colorError : colors.colorInteractive,
|
|
180
|
-
},
|
|
181
|
-
]}
|
|
182
|
-
/>
|
|
183
|
-
</View>
|
|
184
|
-
<Message type="error" message={error} />
|
|
185
|
-
<Message type="hint" message={hint} />
|
|
186
|
-
</View>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
3
|
-
import { Switch } from './Switch.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// react-native-web renders RNSwitch as two role="switch" elements: an outer div
|
|
10
|
-
// (for visual rendering) and an inner hidden <input type="checkbox"> for a11y.
|
|
11
|
-
// The input carries the aria-label and aria-checked state.
|
|
12
|
-
function getSwitchInput(container: HTMLElement): HTMLInputElement {
|
|
13
|
-
const input = container.querySelector<HTMLInputElement>('input[role="switch"]');
|
|
14
|
-
if (!input) throw new Error('Switch input not found');
|
|
15
|
-
return input;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('Switch (native)', () => {
|
|
19
|
-
it('renders a switch input with the correct label', () => {
|
|
20
|
-
const { container } = renderWithTheme(
|
|
21
|
-
<Switch label="Notifications" name="notifications" />,
|
|
22
|
-
);
|
|
23
|
-
const input = getSwitchInput(container);
|
|
24
|
-
expect(input).toHaveAttribute('aria-label', 'Notifications');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('reflects off state', () => {
|
|
28
|
-
const { container } = renderWithTheme(
|
|
29
|
-
<Switch label="Notifications" name="notifications" value={false} />,
|
|
30
|
-
);
|
|
31
|
-
expect(getSwitchInput(container)).not.toBeChecked();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('reflects on state', () => {
|
|
35
|
-
const { container } = renderWithTheme(
|
|
36
|
-
<Switch label="Notifications" name="notifications" value={true} />,
|
|
37
|
-
);
|
|
38
|
-
expect(getSwitchInput(container)).toBeChecked();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('renders thumb in both on and off states without error (thumb always uses colorInteractive)', () => {
|
|
42
|
-
// thumbColor is always colorInteractive — interactive affordance is visible in both states.
|
|
43
|
-
// Track color provides the on/off visual feedback instead.
|
|
44
|
-
const { container: offContainer } = renderWithTheme(
|
|
45
|
-
<Switch label="Toggle" name="toggle" value={false} />,
|
|
46
|
-
);
|
|
47
|
-
const { container: onContainer } = renderWithTheme(
|
|
48
|
-
<Switch label="Toggle2" name="toggle2" value={true} />,
|
|
49
|
-
);
|
|
50
|
-
expect(offContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
|
|
51
|
-
expect(onContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('renders hint message', () => {
|
|
55
|
-
renderWithTheme(
|
|
56
|
-
<Switch label="Marketing" name="marketing" hint="Unsubscribe anytime." />,
|
|
57
|
-
);
|
|
58
|
-
expect(screen.getByText('Unsubscribe anytime.')).toBeInTheDocument();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
4
|
-
import { Switch } from './Switch.native';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
tags: ['!stable', 'alpha'],
|
|
8
|
-
title: 'React Native/Form/Switch',
|
|
9
|
-
component: Switch,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
|
-
parameters: { layout: 'padded' },
|
|
12
|
-
} satisfies Meta<typeof Switch>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Off: Story = { args: { label: 'Notifications', name: 'notifications', value: false } };
|
|
18
|
-
export const On: Story = { args: { label: 'Notifications', name: 'notifications', value: true } };
|
|
19
|
-
export const Disabled: Story = { args: { label: 'Notifications', name: 'notifications', disabled: true } };
|
|
20
|
-
export const WithError: Story = {
|
|
21
|
-
args: { label: 'Accept terms', name: 'terms', error: 'You must accept the terms.' },
|
|
22
|
-
};
|
|
23
|
-
export const WithHint: Story = {
|
|
24
|
-
args: { label: 'Marketing emails', name: 'marketing', hint: 'You can unsubscribe at any time.' },
|
|
25
|
-
};
|
|
26
|
-
export const Interactive: Story = {
|
|
27
|
-
render: () => {
|
|
28
|
-
const [value, setValue] = useState(false);
|
|
29
|
-
return (
|
|
30
|
-
<Switch label="Enable feature" name="feature" value={value} onChange={setValue} />
|
|
31
|
-
);
|
|
32
|
-
},
|
|
33
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { Switch as RNSwitch, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
-
import { spacing } from '../../../../native/tokens';
|
|
3
|
-
import { useTheme } from '../../../../native/ThemeContext';
|
|
4
|
-
import { Label } from '../atoms/Label.native';
|
|
5
|
-
import { Message } from '../atoms/Message.native';
|
|
6
|
-
|
|
7
|
-
interface SwitchProps {
|
|
8
|
-
label: string;
|
|
9
|
-
name: string;
|
|
10
|
-
value?: boolean;
|
|
11
|
-
onChange?: (value: boolean) => void;
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
error?: string;
|
|
14
|
-
hint?: string;
|
|
15
|
-
style?: StyleProp<ViewStyle>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const styles = StyleSheet.create({
|
|
19
|
-
container: {
|
|
20
|
-
gap: spacing.xxs,
|
|
21
|
-
},
|
|
22
|
-
row: {
|
|
23
|
-
flexDirection: 'row',
|
|
24
|
-
alignItems: 'center',
|
|
25
|
-
gap: spacing.xs,
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export function Switch({
|
|
30
|
-
label,
|
|
31
|
-
value = false,
|
|
32
|
-
onChange,
|
|
33
|
-
disabled = false,
|
|
34
|
-
error,
|
|
35
|
-
hint,
|
|
36
|
-
style,
|
|
37
|
-
}: Readonly<SwitchProps>) {
|
|
38
|
-
const { colors } = useTheme();
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<View style={[styles.container, style]}>
|
|
42
|
-
<View style={styles.row}>
|
|
43
|
-
<RNSwitch
|
|
44
|
-
value={value}
|
|
45
|
-
onValueChange={onChange}
|
|
46
|
-
disabled={disabled}
|
|
47
|
-
accessibilityLabel={label}
|
|
48
|
-
accessibilityRole="switch"
|
|
49
|
-
accessibilityState={{ checked: value, disabled }}
|
|
50
|
-
thumbColor={colors.colorInteractive}
|
|
51
|
-
trackColor={{ false: colors.colorBgSubtle, true: colors.colorBlueSubtle }}
|
|
52
|
-
/>
|
|
53
|
-
<Label label={label} />
|
|
54
|
-
</View>
|
|
55
|
-
<Message type="error" message={error} />
|
|
56
|
-
<Message type="hint" message={hint} />
|
|
57
|
-
</View>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Textarea.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Textarea (React Native)
|
|
7
|
-
|
|
8
|
-
Multiline text input with label, optional hint, and error state. Uses a `TextInput` with `multiline` enabled and `textAlignVertical: 'top'`.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** — API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Textarea } from '@boostdev/design-system-components';
|
|
16
|
-
import { useState } from 'react';
|
|
17
|
-
|
|
18
|
-
function Example() {
|
|
19
|
-
const [bio, setBio] = useState('');
|
|
20
|
-
return (
|
|
21
|
-
<Textarea
|
|
22
|
-
label="Bio"
|
|
23
|
-
name="bio"
|
|
24
|
-
placeholder="Tell us about yourself..."
|
|
25
|
-
value={bio}
|
|
26
|
-
onChange={setBio}
|
|
27
|
-
/>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Examples
|
|
33
|
-
|
|
34
|
-
### Default
|
|
35
|
-
<Canvas of={Stories.Default} />
|
|
36
|
-
|
|
37
|
-
### With placeholder
|
|
38
|
-
<Canvas of={Stories.WithPlaceholder} />
|
|
39
|
-
|
|
40
|
-
### With error
|
|
41
|
-
<Canvas of={Stories.WithError} />
|
|
42
|
-
|
|
43
|
-
### With hint
|
|
44
|
-
<Canvas of={Stories.WithHint} />
|
|
45
|
-
|
|
46
|
-
### Disabled
|
|
47
|
-
<Canvas of={Stories.Disabled} />
|
|
48
|
-
|
|
49
|
-
### Interactive
|
|
50
|
-
<Canvas of={Stories.Interactive} />
|
|
51
|
-
|
|
52
|
-
## Props
|
|
53
|
-
|
|
54
|
-
<ArgTypes of={Stories} />
|
|
55
|
-
|
|
56
|
-
## Props reference
|
|
57
|
-
|
|
58
|
-
| Prop | Type | Default | Description |
|
|
59
|
-
|------|------|---------|-------------|
|
|
60
|
-
| `label` | ReactNode | — | Visible label text |
|
|
61
|
-
| `name` | string | — | Field name (used for accessibility) |
|
|
62
|
-
| `value` | string | — | Controlled value |
|
|
63
|
-
| `defaultValue` | string | — | Uncontrolled default value |
|
|
64
|
-
| `placeholder` | string | — | Placeholder text |
|
|
65
|
-
| `numberOfLines` | number | `4` | Number of visible lines |
|
|
66
|
-
| `error` | string | — | Error message displayed below the input |
|
|
67
|
-
| `hint` | string | — | Hint text displayed below the input |
|
|
68
|
-
| `required` | boolean | `false` | Marks the field as required |
|
|
69
|
-
| `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
|
|
70
|
-
| `onChange` | `(value: string) => void` | — | Called with the new text value |
|
|
71
|
-
|
|
72
|
-
## Accessibility
|
|
73
|
-
|
|
74
|
-
- The label is announced by screen readers via `accessibilityLabel`
|
|
75
|
-
- Hint text is announced via `accessibilityHint`
|
|
76
|
-
- `disabled` communicates the disabled state via `accessibilityState`
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
3
|
-
import { Textarea } from './Textarea.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('Textarea (native)', () => {
|
|
10
|
-
it('renders the label', () => {
|
|
11
|
-
renderWithTheme(<Textarea label="Message" name="message" />);
|
|
12
|
-
expect(screen.getByText('Message')).toBeInTheDocument();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('renders with placeholder text', () => {
|
|
16
|
-
renderWithTheme(
|
|
17
|
-
<Textarea label="Bio" name="bio" placeholder="Tell us about yourself..." />,
|
|
18
|
-
);
|
|
19
|
-
expect(screen.getByPlaceholderText('Tell us about yourself...')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('renders with a value', () => {
|
|
23
|
-
renderWithTheme(
|
|
24
|
-
<Textarea label="Notes" name="notes" value="Some content" />,
|
|
25
|
-
);
|
|
26
|
-
expect(screen.getByDisplayValue('Some content')).toBeInTheDocument();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('shows error message', () => {
|
|
30
|
-
renderWithTheme(
|
|
31
|
-
<Textarea label="Message" name="message" error="Message is required." />,
|
|
32
|
-
);
|
|
33
|
-
expect(screen.getByRole('alert')).toHaveTextContent('Message is required.');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('shows hint message', () => {
|
|
37
|
-
renderWithTheme(
|
|
38
|
-
<Textarea label="Message" name="message" hint="Minimum 20 characters." />,
|
|
39
|
-
);
|
|
40
|
-
expect(screen.getByText('Minimum 20 characters.')).toBeInTheDocument();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('applies disabled opacity', () => {
|
|
44
|
-
const { container } = renderWithTheme(
|
|
45
|
-
<Textarea label="Notes" name="notes" disabled />,
|
|
46
|
-
);
|
|
47
|
-
const inputs = container.querySelectorAll('textarea, [style*="opacity"]');
|
|
48
|
-
expect(inputs.length).toBeGreaterThan(0);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { ThemeProvider } from '../../../../native/ThemeContext';
|
|
4
|
-
import { Textarea } from './Textarea.native';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
tags: ['!stable', 'alpha'],
|
|
8
|
-
title: 'React Native/Form/Textarea',
|
|
9
|
-
component: Textarea,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
|
-
parameters: { layout: 'padded' },
|
|
12
|
-
} satisfies Meta<typeof Textarea>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Default: Story = {
|
|
18
|
-
args: { label: 'Message', name: 'message' },
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const WithPlaceholder: Story = {
|
|
22
|
-
args: { label: 'Bio', name: 'bio', placeholder: 'Tell us about yourself...' },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const WithError: Story = {
|
|
26
|
-
args: { label: 'Message', name: 'message', error: 'Message is required.' },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const WithHint: Story = {
|
|
30
|
-
args: { label: 'Message', name: 'message', hint: 'Minimum 20 characters.' },
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const Disabled: Story = {
|
|
34
|
-
args: { label: 'Notes', name: 'notes', disabled: true, value: 'Read-only content.' },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const Interactive: Story = {
|
|
38
|
-
render: () => {
|
|
39
|
-
const [value, setValue] = useState('');
|
|
40
|
-
return (
|
|
41
|
-
<Textarea
|
|
42
|
-
label="Feedback"
|
|
43
|
-
name="feedback"
|
|
44
|
-
placeholder="Share your thoughts..."
|
|
45
|
-
value={value}
|
|
46
|
-
onChange={setValue}
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
49
|
-
},
|
|
50
|
-
};
|