@boostdev/design-system-components 1.2.3 → 1.2.4
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/README.md +29 -1
- package/dist/client.cjs +50 -50
- package/dist/client.css +470 -470
- package/dist/client.js +50 -50
- package/dist/index.cjs +50 -50
- package/dist/index.css +470 -470
- package/dist/index.js +50 -50
- package/dist/native/index.cjs +3692 -352
- package/dist/native/index.d.cts +359 -3
- package/dist/native/index.d.ts +359 -3
- package/dist/native/index.js +3811 -364
- package/package.json +1 -1
- package/src/components/interaction/Button/Button.native.stories.tsx +10 -12
- package/src/components/interaction/Button/Button.native.tsx +3 -3
- package/src/components/interaction/Dialog/Dialog.native.mdx +61 -0
- package/src/components/interaction/Dialog/Dialog.native.spec.tsx +73 -0
- package/src/components/interaction/Dialog/Dialog.native.stories.tsx +53 -0
- package/src/components/interaction/Dialog/Dialog.native.tsx +128 -0
- package/src/components/interaction/Drawer/Drawer.native.mdx +58 -0
- package/src/components/interaction/Drawer/Drawer.native.spec.tsx +81 -0
- package/src/components/interaction/Drawer/Drawer.native.stories.tsx +33 -0
- package/src/components/interaction/Drawer/Drawer.native.tsx +175 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +74 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +78 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +51 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +254 -0
- package/src/components/interaction/Popover/Popover.native.mdx +61 -0
- package/src/components/interaction/Popover/Popover.native.spec.tsx +73 -0
- package/src/components/interaction/Popover/Popover.native.stories.tsx +44 -0
- package/src/components/interaction/Popover/Popover.native.tsx +87 -0
- package/src/components/interaction/Rating/Rating.native.mdx +55 -0
- package/src/components/interaction/Rating/Rating.native.spec.tsx +38 -0
- package/src/components/interaction/Rating/Rating.native.stories.tsx +37 -0
- package/src/components/interaction/Rating/Rating.native.tsx +50 -0
- package/src/components/interaction/Toast/Toast.native.mdx +81 -0
- package/src/components/interaction/Toast/Toast.native.spec.tsx +80 -0
- package/src/components/interaction/Toast/Toast.native.stories.tsx +47 -0
- package/src/components/interaction/Toast/Toast.native.tsx +202 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +2 -4
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +78 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +57 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +96 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +61 -0
- package/src/components/interaction/form/Combobox/Combobox.native.mdx +86 -0
- package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +60 -0
- package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +99 -0
- package/src/components/interaction/form/Combobox/Combobox.native.tsx +211 -0
- package/src/components/interaction/form/FileInput/FileInput.native.mdx +86 -0
- package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +73 -0
- package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +69 -0
- package/src/components/interaction/form/FileInput/FileInput.native.tsx +129 -0
- package/src/components/interaction/form/FormInput/FormInput.native.mdx +81 -0
- package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +51 -0
- package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +54 -0
- package/src/components/interaction/form/FormInput/FormInput.native.tsx +122 -0
- package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +87 -0
- package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +63 -0
- package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +63 -0
- package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +198 -0
- package/src/components/interaction/form/Radio/Radio.native.spec.tsx +79 -0
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +12 -14
- package/src/components/interaction/form/Radio/Radio.native.tsx +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +79 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +66 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +96 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +61 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +78 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +82 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +99 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +121 -0
- package/src/components/interaction/form/Select/Select.native.mdx +85 -0
- package/src/components/interaction/form/Select/Select.native.spec.tsx +64 -0
- package/src/components/interaction/form/Select/Select.native.stories.tsx +75 -0
- package/src/components/interaction/form/Select/Select.native.tsx +252 -0
- package/src/components/interaction/form/Slider/Slider.native.mdx +83 -0
- package/src/components/interaction/form/Slider/Slider.native.spec.tsx +51 -0
- package/src/components/interaction/form/Slider/Slider.native.stories.tsx +58 -0
- package/src/components/interaction/form/Slider/Slider.native.tsx +188 -0
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +2 -4
- package/src/components/interaction/form/Textarea/Textarea.native.mdx +76 -0
- package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +50 -0
- package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +50 -0
- package/src/components/interaction/form/Textarea/Textarea.native.tsx +108 -0
- package/src/components/interaction/form/atoms/Label.native.spec.tsx +39 -0
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +1 -1
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +83 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +13 -19
- package/src/components/layout/Card/Card.native.spec.tsx +102 -0
- package/src/components/layout/Card/Card.native.stories.tsx +9 -11
- package/src/components/layout/Card/Card.native.tsx +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +7 -11
- package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +51 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +6 -8
- package/src/components/ui/Accordion/Accordion.native.mdx +65 -0
- package/src/components/ui/Accordion/Accordion.native.spec.tsx +69 -0
- package/src/components/ui/Accordion/Accordion.native.stories.tsx +52 -0
- package/src/components/ui/Accordion/Accordion.native.tsx +141 -0
- package/src/components/ui/Alert/Alert.native.spec.tsx +76 -0
- package/src/components/ui/Alert/Alert.native.stories.tsx +8 -10
- package/src/components/ui/Alert/Alert.native.tsx +7 -7
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +6 -8
- package/src/components/ui/Avatar/Avatar.native.tsx +2 -2
- package/src/components/ui/Badge/Badge.native.spec.tsx +45 -0
- package/src/components/ui/Badge/Badge.native.stories.tsx +6 -8
- package/src/components/ui/Badge/Badge.native.tsx +2 -2
- package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +52 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +49 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +46 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +83 -0
- package/src/components/ui/Calendar/Calendar.native.mdx +58 -0
- package/src/components/ui/Calendar/Calendar.native.spec.tsx +92 -0
- package/src/components/ui/Calendar/Calendar.native.stories.tsx +45 -0
- package/src/components/ui/Calendar/Calendar.native.tsx +294 -0
- package/src/components/ui/Carousel/Carousel.native.mdx +55 -0
- package/src/components/ui/Carousel/Carousel.native.spec.tsx +55 -0
- package/src/components/ui/Carousel/Carousel.native.stories.tsx +59 -0
- package/src/components/ui/Carousel/Carousel.native.tsx +184 -0
- package/src/components/ui/Collapsible/Collapsible.native.mdx +53 -0
- package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +68 -0
- package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +48 -0
- package/src/components/ui/Collapsible/Collapsible.native.tsx +139 -0
- package/src/components/ui/DescriptionList/DescriptionList.native.mdx +50 -0
- package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +37 -0
- package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +48 -0
- package/src/components/ui/DescriptionList/DescriptionList.native.tsx +54 -0
- package/src/components/ui/Link/Link.native.mdx +53 -0
- package/src/components/ui/Link/Link.native.spec.tsx +35 -0
- package/src/components/ui/Link/Link.native.stories.tsx +46 -0
- package/src/components/ui/Link/Link.native.tsx +48 -0
- package/src/components/ui/Loading/Loading.native.stories.tsx +6 -8
- package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +85 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +8 -10
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +7 -7
- package/src/components/ui/Pagination/Pagination.native.mdx +62 -0
- package/src/components/ui/Pagination/Pagination.native.spec.tsx +86 -0
- package/src/components/ui/Pagination/Pagination.native.stories.tsx +47 -0
- package/src/components/ui/Pagination/Pagination.native.tsx +157 -0
- package/src/components/ui/Progress/Progress.native.spec.tsx +56 -0
- package/src/components/ui/Progress/Progress.native.stories.tsx +1 -1
- package/src/components/ui/Progress/Progress.native.tsx +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +63 -0
- package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +39 -0
- package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +36 -0
- package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +176 -0
- package/src/components/ui/Separator/Separator.native.spec.tsx +39 -0
- package/src/components/ui/Separator/Separator.native.stories.tsx +10 -14
- package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +39 -0
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +6 -8
- package/src/components/ui/Skeleton/Skeleton.native.tsx +2 -1
- package/src/components/ui/Table/Table.native.mdx +72 -0
- package/src/components/ui/Table/Table.native.spec.tsx +83 -0
- package/src/components/ui/Table/Table.native.stories.tsx +55 -0
- package/src/components/ui/Table/Table.native.tsx +196 -0
- package/src/components/ui/Tabs/Tabs.native.mdx +65 -0
- package/src/components/ui/Tabs/Tabs.native.spec.tsx +65 -0
- package/src/components/ui/Tabs/Tabs.native.stories.tsx +57 -0
- package/src/components/ui/Tabs/Tabs.native.tsx +133 -0
- package/src/components/ui/Tooltip/Tooltip.native.mdx +47 -0
- package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +37 -0
- package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +38 -0
- package/src/components/ui/Tooltip/Tooltip.native.tsx +57 -0
- package/src/components/ui/Typography/Typography.native.spec.tsx +49 -0
- package/src/components/ui/Typography/Typography.native.stories.tsx +8 -10
- package/src/native/ThemeContext.tsx +3 -3
- package/src/native.ts +37 -0
- package/src/stories/ReactNative.mdx +48 -13
- package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
- package/src/web-components/interaction/BdsCollapsible.stories.tsx +1 -1
- package/src/web-components/interaction/BdsSkipLink.stories.tsx +1 -1
- package/src/web-components/interaction/BdsTabs.stories.tsx +1 -1
- package/src/web-components/interaction/BdsTooltip.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsCombobox.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFileInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFormInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsRadio.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSelect.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSlider.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSwitch.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsTextarea.stories.tsx +1 -1
- package/src/web-components/ui/BdsButtonGroup.stories.tsx +1 -1
- package/src/web-components/ui/BdsCard.stories.tsx +1 -1
- package/src/web-components/ui/BdsIconWrapper.stories.tsx +1 -1
- package/src/web-components/ui/BdsRating.stories.tsx +1 -1
- package/src/web-components/ui/BdsSectionHeader.stories.tsx +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
+
import { ProgressCircle } from './ProgressCircle.native';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
tags: ['!stable', 'alpha'],
|
|
8
|
+
title: 'React Native/UI/ProgressCircle',
|
|
9
|
+
component: ProgressCircle,
|
|
10
|
+
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
14
|
+
value: { control: { type: 'range', min: 0, max: 100, step: 1 } },
|
|
15
|
+
},
|
|
16
|
+
} satisfies Meta<typeof ProgressCircle>;
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof meta>;
|
|
20
|
+
|
|
21
|
+
export const Default: Story = { args: { value: 60, label: 'Progress' } };
|
|
22
|
+
export const WithValue: Story = { args: { value: 75, label: 'Upload progress', showValue: true } };
|
|
23
|
+
export const Small: Story = { args: { value: 50, label: 'Small progress', size: 'small' } };
|
|
24
|
+
export const Large: Story = { args: { value: 80, label: 'Large progress', size: 'large', showValue: true } };
|
|
25
|
+
export const Complete: Story = { args: { value: 100, label: 'Complete', showValue: true } };
|
|
26
|
+
export const Empty: Story = { args: { value: 0, label: 'Not started', showValue: true } };
|
|
27
|
+
|
|
28
|
+
export const AllSizes: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
31
|
+
{(['small', 'medium', 'large'] as const).map(s => (
|
|
32
|
+
<ProgressCircle key={s} value={65} label={`${s} progress`} size={s} showValue={s !== 'small'} />
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
),
|
|
36
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { font } from '../../../native/tokens';
|
|
3
|
+
import { useTheme } from '../../../native/ThemeContext';
|
|
4
|
+
|
|
5
|
+
type ProgressCircleSize = 'small' | 'medium' | 'large';
|
|
6
|
+
|
|
7
|
+
interface ProgressCircleProps {
|
|
8
|
+
value: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
size?: ProgressCircleSize;
|
|
11
|
+
label: string;
|
|
12
|
+
showValue?: boolean;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SIZE_PX: Record<ProgressCircleSize, number> = {
|
|
17
|
+
small: 40,
|
|
18
|
+
medium: 64,
|
|
19
|
+
large: 96,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const STROKE_WIDTH: Record<ProgressCircleSize, number> = {
|
|
23
|
+
small: 3,
|
|
24
|
+
medium: 4,
|
|
25
|
+
large: 5,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const FONT_SIZE: Record<ProgressCircleSize, number> = {
|
|
29
|
+
small: font.size.bodyS - 2,
|
|
30
|
+
medium: font.size.bodyS,
|
|
31
|
+
large: font.size.body,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const styles = StyleSheet.create({
|
|
35
|
+
wrapper: {
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
},
|
|
39
|
+
valueText: {
|
|
40
|
+
position: 'absolute',
|
|
41
|
+
fontFamily: font.family.body,
|
|
42
|
+
fontWeight: font.weight.semibold,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function HalfCircle({
|
|
47
|
+
size,
|
|
48
|
+
stroke,
|
|
49
|
+
color,
|
|
50
|
+
rotation,
|
|
51
|
+
}: {
|
|
52
|
+
size: number;
|
|
53
|
+
stroke: number;
|
|
54
|
+
color: string;
|
|
55
|
+
rotation: number;
|
|
56
|
+
}) {
|
|
57
|
+
return (
|
|
58
|
+
<View
|
|
59
|
+
style={{
|
|
60
|
+
width: size / 2,
|
|
61
|
+
height: size,
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<View
|
|
66
|
+
style={{
|
|
67
|
+
width: size,
|
|
68
|
+
height: size,
|
|
69
|
+
borderRadius: size / 2,
|
|
70
|
+
borderWidth: stroke,
|
|
71
|
+
borderColor: color,
|
|
72
|
+
transform: [{ rotate: `${rotation}deg` }],
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function ProgressCircle({
|
|
80
|
+
value,
|
|
81
|
+
max = 100,
|
|
82
|
+
size = 'medium',
|
|
83
|
+
label,
|
|
84
|
+
showValue = false,
|
|
85
|
+
style,
|
|
86
|
+
}: Readonly<ProgressCircleProps>) {
|
|
87
|
+
const { colors } = useTheme();
|
|
88
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
89
|
+
const px = SIZE_PX[size];
|
|
90
|
+
const stroke = STROKE_WIDTH[size];
|
|
91
|
+
const fillColor = colors.colorInteractive;
|
|
92
|
+
const trackColor = colors.colorBgSubtle;
|
|
93
|
+
|
|
94
|
+
// Convert percentage to degrees (0-360)
|
|
95
|
+
const degrees = (percentage / 100) * 360;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<View
|
|
99
|
+
style={[
|
|
100
|
+
styles.wrapper,
|
|
101
|
+
{ width: px, height: px },
|
|
102
|
+
style,
|
|
103
|
+
]}
|
|
104
|
+
accessibilityRole="progressbar"
|
|
105
|
+
accessibilityLabel={label}
|
|
106
|
+
accessibilityValue={{ min: 0, max, now: value }}
|
|
107
|
+
>
|
|
108
|
+
{/* Track: full circle background */}
|
|
109
|
+
<View
|
|
110
|
+
style={{
|
|
111
|
+
position: 'absolute',
|
|
112
|
+
width: px,
|
|
113
|
+
height: px,
|
|
114
|
+
borderRadius: px / 2,
|
|
115
|
+
borderWidth: stroke,
|
|
116
|
+
borderColor: trackColor,
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
{/* Fill: right half (0-180°) */}
|
|
121
|
+
<View
|
|
122
|
+
style={{
|
|
123
|
+
position: 'absolute',
|
|
124
|
+
width: px / 2,
|
|
125
|
+
height: px,
|
|
126
|
+
start: px / 2,
|
|
127
|
+
overflow: 'hidden',
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
<HalfCircle
|
|
131
|
+
size={px}
|
|
132
|
+
stroke={stroke}
|
|
133
|
+
color={percentage > 0 ? fillColor : 'transparent'}
|
|
134
|
+
rotation={Math.min(degrees, 180) - 180}
|
|
135
|
+
/>
|
|
136
|
+
</View>
|
|
137
|
+
|
|
138
|
+
{/* Fill: left half (180-360°) */}
|
|
139
|
+
{degrees > 180 && (
|
|
140
|
+
<View
|
|
141
|
+
style={{
|
|
142
|
+
position: 'absolute',
|
|
143
|
+
width: px / 2,
|
|
144
|
+
height: px,
|
|
145
|
+
start: 0,
|
|
146
|
+
overflow: 'hidden',
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<View
|
|
150
|
+
style={{
|
|
151
|
+
position: 'absolute',
|
|
152
|
+
end: 0,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<HalfCircle
|
|
156
|
+
size={px}
|
|
157
|
+
stroke={stroke}
|
|
158
|
+
color={fillColor}
|
|
159
|
+
rotation={Math.min(degrees - 180, 180)}
|
|
160
|
+
/>
|
|
161
|
+
</View>
|
|
162
|
+
</View>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{showValue && (
|
|
166
|
+
<Text
|
|
167
|
+
style={[styles.valueText, { fontSize: FONT_SIZE[size], color: colors.colorOnBg }]}
|
|
168
|
+
importantForAccessibility="no"
|
|
169
|
+
accessibilityElementsHidden={true}
|
|
170
|
+
>
|
|
171
|
+
{Math.round(percentage)}%
|
|
172
|
+
</Text>
|
|
173
|
+
)}
|
|
174
|
+
</View>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Separator } from './Separator.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Separator (native)', () => {
|
|
10
|
+
it('renders with presentation role', () => {
|
|
11
|
+
const { container } = renderWithTheme(<Separator />);
|
|
12
|
+
const element = container.firstChild as HTMLElement;
|
|
13
|
+
// react-native-web maps accessibilityRole="none" to role="presentation"
|
|
14
|
+
expect(element).toHaveAttribute('role', 'presentation');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders a single element', () => {
|
|
18
|
+
const { container } = renderWithTheme(<Separator />);
|
|
19
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
20
|
+
expect(container.childElementCount).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders differently for vertical orientation', () => {
|
|
24
|
+
const { container: hContainer } = renderWithTheme(<Separator />);
|
|
25
|
+
const { container: vContainer } = renderWithTheme(<Separator orientation="vertical" />);
|
|
26
|
+
const hEl = hContainer.firstChild as HTMLElement;
|
|
27
|
+
const vEl = vContainer.firstChild as HTMLElement;
|
|
28
|
+
// Both render but with distinct class/style sets
|
|
29
|
+
expect(hEl.className).not.toBe(vEl.className);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies custom style', () => {
|
|
33
|
+
const { container } = renderWithTheme(
|
|
34
|
+
<Separator style={{ marginTop: 16 }} />,
|
|
35
|
+
);
|
|
36
|
+
const element = container.firstChild as HTMLElement;
|
|
37
|
+
expect(element.style.marginTop).toBe('16px');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -7,7 +7,7 @@ const meta = {
|
|
|
7
7
|
tags: ['!stable', 'alpha'],
|
|
8
8
|
title: 'React Native/UI/Separator',
|
|
9
9
|
component: Separator,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
10
|
+
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
11
|
parameters: { layout: 'padded' },
|
|
12
12
|
argTypes: {
|
|
13
13
|
orientation: { control: 'select', options: ['horizontal', 'vertical'] },
|
|
@@ -19,24 +19,20 @@ type Story = StoryObj<typeof meta>;
|
|
|
19
19
|
|
|
20
20
|
export const Horizontal: Story = {
|
|
21
21
|
render: (args) => (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
</div>
|
|
26
|
-
</ThemeProvider>
|
|
22
|
+
<div style={{ width: 300 }}>
|
|
23
|
+
<Separator {...args} orientation="horizontal" />
|
|
24
|
+
</div>
|
|
27
25
|
),
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
export const Vertical: Story = {
|
|
31
29
|
render: (args) => (
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
<Separator {...args} orientation="vertical" />
|
|
37
|
-
</div>
|
|
38
|
-
<span>Right</span>
|
|
30
|
+
<div style={{ display: 'flex', height: 60, alignItems: 'stretch' }}>
|
|
31
|
+
<span>Left</span>
|
|
32
|
+
<div style={{ display: 'flex', margin: '0 8px' }}>
|
|
33
|
+
<Separator {...args} orientation="vertical" />
|
|
39
34
|
</div>
|
|
40
|
-
|
|
35
|
+
<span>Right</span>
|
|
36
|
+
</div>
|
|
41
37
|
),
|
|
42
38
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Skeleton } from './Skeleton.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Skeleton (native)', () => {
|
|
10
|
+
it('renders without crashing', () => {
|
|
11
|
+
const { container } = renderWithTheme(<Skeleton />);
|
|
12
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('marks content as not important for accessibility', () => {
|
|
16
|
+
const { container } = renderWithTheme(<Skeleton />);
|
|
17
|
+
const element = container.firstChild as HTMLElement;
|
|
18
|
+
// importantForAccessibility="no-hide-descendants" hides descendants from a11y tree
|
|
19
|
+
// In react-native-web this renders as aria-hidden on the element
|
|
20
|
+
expect(element).toBeTruthy();
|
|
21
|
+
// The element should not have any accessible role
|
|
22
|
+
expect(element.getAttribute('role')).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('applies custom style', () => {
|
|
26
|
+
const { container } = renderWithTheme(
|
|
27
|
+
<Skeleton style={{ width: 200, height: 20 }} />,
|
|
28
|
+
);
|
|
29
|
+
const element = container.firstChild as HTMLElement;
|
|
30
|
+
expect(element.style.width).toBe('200px');
|
|
31
|
+
expect(element.style.height).toBe('20px');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('has a background color from the theme', () => {
|
|
35
|
+
const { container } = renderWithTheme(<Skeleton />);
|
|
36
|
+
const element = container.firstChild as HTMLElement;
|
|
37
|
+
expect(element.style.backgroundColor).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -7,7 +7,7 @@ const meta = {
|
|
|
7
7
|
tags: ['!stable', 'alpha'],
|
|
8
8
|
title: 'React Native/UI/Skeleton',
|
|
9
9
|
component: Skeleton,
|
|
10
|
-
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
10
|
+
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
11
11
|
parameters: { layout: 'padded' },
|
|
12
12
|
} satisfies Meta<typeof Skeleton>;
|
|
13
13
|
|
|
@@ -22,12 +22,10 @@ export const Avatar: Story = {
|
|
|
22
22
|
};
|
|
23
23
|
export const Card: Story = {
|
|
24
24
|
render: () => (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
</div>
|
|
31
|
-
</ThemeProvider>
|
|
25
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', width: 280 }}>
|
|
26
|
+
<Skeleton style={{ width: 280, height: 160 }} />
|
|
27
|
+
<Skeleton style={{ width: 200, height: 16 }} />
|
|
28
|
+
<Skeleton style={{ width: 140, height: 14 }} />
|
|
29
|
+
</div>
|
|
32
30
|
),
|
|
33
31
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { Animated, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { border } from '../../../native/tokens';
|
|
3
4
|
import { useTheme } from '../../../native/ThemeContext';
|
|
4
5
|
|
|
5
6
|
interface SkeletonProps {
|
|
@@ -8,7 +9,7 @@ interface SkeletonProps {
|
|
|
8
9
|
|
|
9
10
|
const styles = StyleSheet.create({
|
|
10
11
|
skeleton: {
|
|
11
|
-
borderRadius:
|
|
12
|
+
borderRadius: border.radius.xs,
|
|
12
13
|
overflow: 'hidden',
|
|
13
14
|
},
|
|
14
15
|
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as Stories from './Table.native.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={Stories} />
|
|
5
|
+
|
|
6
|
+
# Table (React Native)
|
|
7
|
+
|
|
8
|
+
React Native table component for displaying structured data in rows and columns. Supports optional sortable columns, captions, and horizontal scrolling for wide tables. Alternating row backgrounds improve readability.
|
|
9
|
+
|
|
10
|
+
> **Status: alpha** -- API may change before stable release.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Table } from '@boostdev/components';
|
|
16
|
+
|
|
17
|
+
<Table
|
|
18
|
+
columns={[
|
|
19
|
+
{ key: 'name', header: 'Name' },
|
|
20
|
+
{ key: 'email', header: 'Email' },
|
|
21
|
+
]}
|
|
22
|
+
rows={[
|
|
23
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
24
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
25
|
+
]}
|
|
26
|
+
/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Default
|
|
32
|
+
<Canvas of={Stories.Default} />
|
|
33
|
+
|
|
34
|
+
### With caption
|
|
35
|
+
<Canvas of={Stories.WithCaption} />
|
|
36
|
+
|
|
37
|
+
### Sortable columns
|
|
38
|
+
<Canvas of={Stories.Sortable} />
|
|
39
|
+
|
|
40
|
+
### Empty table
|
|
41
|
+
<Canvas of={Stories.Empty} />
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
<ArgTypes of={Stories} />
|
|
46
|
+
|
|
47
|
+
## Props reference
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
|------|------|---------|-------------|
|
|
51
|
+
| `columns` | `TableColumn<Row>[]` | -- | Column definitions |
|
|
52
|
+
| `rows` | `Row[]` | -- | Data rows |
|
|
53
|
+
| `caption` | `string` | -- | Optional table caption |
|
|
54
|
+
| `sortKey` | `string` | -- | Currently sorted column key |
|
|
55
|
+
| `sortDirection` | `"asc" \| "desc"` | -- | Current sort direction |
|
|
56
|
+
| `onSort` | `(key: string, direction: "asc" \| "desc") => void` | -- | Called when a sortable header is pressed |
|
|
57
|
+
| `style` | `StyleProp<ViewStyle>` | -- | Custom style for the scroll wrapper |
|
|
58
|
+
|
|
59
|
+
### TableColumn
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Description |
|
|
62
|
+
|------|------|-------------|
|
|
63
|
+
| `key` | `string` | Column key matching row property |
|
|
64
|
+
| `header` | `ReactNode` | Header cell content |
|
|
65
|
+
| `sortable` | `boolean` | Enables sort button on header |
|
|
66
|
+
| `render` | `(row: Row) => ReactNode` | Custom cell renderer |
|
|
67
|
+
|
|
68
|
+
## Accessibility
|
|
69
|
+
|
|
70
|
+
- Sortable headers have `button` role with descriptive labels
|
|
71
|
+
- Sort indicators are hidden from the accessibility tree
|
|
72
|
+
- Table scrolls horizontally when content exceeds the viewport width
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Table } from './Table.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const columns = [
|
|
10
|
+
{ key: 'name', header: 'Name' },
|
|
11
|
+
{ key: 'age', header: 'Age' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const rows = [
|
|
15
|
+
{ name: 'Alice', age: 29 },
|
|
16
|
+
{ name: 'Bob', age: 34 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe('Table (native)', () => {
|
|
20
|
+
it('renders column headers', () => {
|
|
21
|
+
renderWithTheme(<Table columns={columns} rows={rows} />);
|
|
22
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
23
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders row data', () => {
|
|
27
|
+
renderWithTheme(<Table columns={columns} rows={rows} />);
|
|
28
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
29
|
+
expect(screen.getByText('29')).toBeInTheDocument();
|
|
30
|
+
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
31
|
+
expect(screen.getByText('34')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders a caption when provided', () => {
|
|
35
|
+
renderWithTheme(<Table columns={columns} rows={rows} caption="Team" />);
|
|
36
|
+
expect(screen.getByText('Team')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders an empty table without errors', () => {
|
|
40
|
+
renderWithTheme(<Table columns={columns} rows={[]} />);
|
|
41
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('calls onSort when a sortable column header is pressed', () => {
|
|
46
|
+
const onSort = vi.fn();
|
|
47
|
+
const sortableColumns = [
|
|
48
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
49
|
+
{ key: 'age', header: 'Age' },
|
|
50
|
+
];
|
|
51
|
+
renderWithTheme(
|
|
52
|
+
<Table columns={sortableColumns} rows={rows} onSort={onSort} />,
|
|
53
|
+
);
|
|
54
|
+
fireEvent.click(screen.getByRole('button', { name: /Sort by Name/ }));
|
|
55
|
+
expect(onSort).toHaveBeenCalledWith('name', 'asc');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('toggles sort direction on subsequent presses', () => {
|
|
59
|
+
const onSort = vi.fn();
|
|
60
|
+
const sortableColumns = [
|
|
61
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
62
|
+
];
|
|
63
|
+
renderWithTheme(
|
|
64
|
+
<Table
|
|
65
|
+
columns={sortableColumns}
|
|
66
|
+
rows={rows}
|
|
67
|
+
sortKey="name"
|
|
68
|
+
sortDirection="asc"
|
|
69
|
+
onSort={onSort}
|
|
70
|
+
/>,
|
|
71
|
+
);
|
|
72
|
+
fireEvent.click(screen.getByRole('button', { name: /Sort by Name/ }));
|
|
73
|
+
expect(onSort).toHaveBeenCalledWith('name', 'desc');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('uses custom render function for cells', () => {
|
|
77
|
+
const customColumns = [
|
|
78
|
+
{ key: 'name', header: 'Name', render: (row: { name: string }) => `Mr. ${row.name}` },
|
|
79
|
+
];
|
|
80
|
+
renderWithTheme(<Table columns={customColumns} rows={[{ name: 'Alice' }]} />);
|
|
81
|
+
expect(screen.getByText('Mr. Alice')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
+
import { Table } from './Table.native';
|
|
5
|
+
|
|
6
|
+
interface Person { name: string; role: string; age: number }
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
tags: ['!stable', 'alpha'],
|
|
10
|
+
title: 'React Native/UI/Table',
|
|
11
|
+
component: Table<Person>,
|
|
12
|
+
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
13
|
+
parameters: { layout: 'padded' },
|
|
14
|
+
} satisfies Meta<typeof Table<Person>>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
const columns = [
|
|
20
|
+
{ key: 'name', header: 'Name' },
|
|
21
|
+
{ key: 'role', header: 'Role' },
|
|
22
|
+
{ key: 'age', header: 'Age' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const rows: Person[] = [
|
|
26
|
+
{ name: 'Alice Johnson', role: 'Engineer', age: 29 },
|
|
27
|
+
{ name: 'Bob Smith', role: 'Designer', age: 34 },
|
|
28
|
+
{ name: 'Carol White', role: 'Manager', age: 41 },
|
|
29
|
+
{ name: 'Dave Brown', role: 'Analyst', age: 26 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
args: { columns, rows },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const WithCaption: Story = {
|
|
37
|
+
args: { columns, rows, caption: 'Team Members' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Sortable: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
columns: [
|
|
43
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
44
|
+
{ key: 'role', header: 'Role' },
|
|
45
|
+
{ key: 'age', header: 'Age', sortable: true },
|
|
46
|
+
],
|
|
47
|
+
rows,
|
|
48
|
+
sortKey: 'name',
|
|
49
|
+
sortDirection: 'asc',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Empty: Story = {
|
|
54
|
+
args: { columns, rows: [] },
|
|
55
|
+
};
|