@boostdev/design-system-components 1.0.3 → 1.1.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 +5 -5
- package/dist/client.cjs +236 -153
- package/dist/client.css +547 -483
- package/dist/client.d.cts +53 -94
- package/dist/client.d.ts +53 -94
- package/dist/client.js +237 -163
- package/dist/index.cjs +236 -153
- package/dist/index.css +547 -483
- package/dist/index.d.cts +53 -94
- package/dist/index.d.ts +53 -94
- package/dist/index.js +237 -163
- package/dist/native/index.cjs +991 -0
- package/dist/native/index.d.cts +208 -0
- package/dist/native/index.d.ts +208 -0
- package/dist/native/index.js +968 -0
- package/dist/web-components.d.ts +303 -0
- package/dist/web-components.js +1968 -0
- package/package.json +27 -5
- package/src/components/interaction/Button/Button.module.css +10 -7
- package/src/components/interaction/Button/Button.native.spec.tsx +35 -0
- package/src/components/interaction/Button/Button.native.stories.tsx +42 -0
- package/src/components/interaction/Button/Button.native.tsx +95 -0
- package/src/components/interaction/Button/Button.stories.tsx +1 -1
- package/src/components/interaction/Button/Button.tsx +2 -2
- package/src/components/interaction/Command/Command.mdx +16 -0
- package/src/components/interaction/Command/Command.stories.tsx +1 -1
- package/src/components/interaction/Command/Command.tsx +2 -2
- package/src/components/interaction/Dialog/Dialog.stories.tsx +1 -1
- package/src/components/interaction/Dialog/Dialog.tsx +2 -2
- package/src/components/interaction/Drawer/Drawer.mdx +14 -0
- package/src/components/interaction/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/interaction/Drawer/Drawer.tsx +2 -2
- package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +1 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/components/interaction/Popover/Popover.mdx +17 -0
- package/src/components/interaction/Popover/Popover.module.css +70 -13
- package/src/components/interaction/Popover/Popover.spec.tsx +68 -4
- package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
- package/src/components/interaction/Popover/Popover.tsx +76 -20
- package/src/components/interaction/Rating/Rating.stories.tsx +1 -1
- package/src/components/interaction/Rating/Rating.tsx +2 -2
- package/src/components/interaction/Toast/Toast.module.css +6 -1
- package/src/components/interaction/Toast/Toast.spec.tsx +21 -0
- package/src/components/interaction/Toast/Toast.stories.tsx +1 -1
- package/src/components/interaction/Toast/Toast.tsx +22 -2
- package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +3 -3
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +42 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +36 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +80 -0
- package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +44 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -2
- package/src/components/interaction/form/Combobox/Combobox.mdx +21 -0
- package/src/components/interaction/form/Combobox/Combobox.module.css +1 -1
- package/src/components/interaction/form/Combobox/Combobox.stories.tsx +1 -1
- package/src/components/interaction/form/Combobox/Combobox.tsx +2 -2
- package/src/components/interaction/form/FileInput/FileInput.mdx +14 -0
- package/src/components/interaction/form/FileInput/FileInput.module.css +1 -1
- package/src/components/interaction/form/FileInput/FileInput.stories.tsx +1 -1
- package/src/components/interaction/form/FileInput/FileInput.tsx +2 -2
- package/src/components/interaction/form/FormInput/FormInput.stories.tsx +1 -1
- package/src/components/interaction/form/FormInput/FormInput.tsx +2 -2
- package/src/components/interaction/form/NumberInput/NumberInput.mdx +16 -0
- package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -2
- package/src/components/interaction/form/Radio/Radio.mdx +3 -3
- package/src/components/interaction/form/Radio/Radio.module.css +3 -3
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +46 -0
- package/src/components/interaction/form/Radio/Radio.native.tsx +79 -0
- package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
- package/src/components/interaction/form/Radio/Radio.tsx +2 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +45 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -2
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +1 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +2 -2
- package/src/components/interaction/form/Select/Select.stories.tsx +1 -1
- package/src/components/interaction/form/Select/Select.tsx +2 -2
- package/src/components/interaction/form/Slider/Slider.mdx +1 -1
- package/src/components/interaction/form/Slider/Slider.module.css +4 -4
- package/src/components/interaction/form/Slider/Slider.stories.tsx +1 -1
- package/src/components/interaction/form/Slider/Slider.tsx +2 -2
- package/src/components/interaction/form/Switch/Switch.mdx +4 -4
- package/src/components/interaction/form/Switch/Switch.module.css +3 -3
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +60 -0
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +35 -0
- package/src/components/interaction/form/Switch/Switch.native.tsx +59 -0
- package/src/components/interaction/form/Switch/Switch.stories.tsx +1 -1
- package/src/components/interaction/form/Switch/Switch.tsx +2 -2
- package/src/components/interaction/form/Textarea/Textarea.mdx +17 -0
- package/src/components/interaction/form/Textarea/Textarea.stories.tsx +1 -1
- package/src/components/interaction/form/Textarea/Textarea.tsx +2 -2
- package/src/components/interaction/form/atoms/InputContainer.tsx +2 -1
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +18 -0
- package/src/components/interaction/form/atoms/Label.native.tsx +29 -0
- package/src/components/interaction/form/atoms/Label.tsx +5 -3
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +30 -0
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +22 -0
- package/src/components/interaction/form/atoms/Message.native.tsx +34 -0
- package/src/components/interaction/form/atoms/Message.tsx +5 -3
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +50 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +34 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +2 -2
- package/src/components/layout/Card/Card.native.stories.tsx +53 -0
- package/src/components/layout/Card/Card.native.tsx +89 -0
- package/src/components/layout/Card/Card.stories.tsx +1 -1
- package/src/components/layout/Card/Card.tsx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +39 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +41 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +20 -0
- package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.tsx +2 -2
- package/src/components/layout/SectionHeader/SectionHeader.mdx +13 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +38 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +79 -0
- package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +1 -1
- package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
- package/src/components/ui/Accordion/Accordion.mdx +14 -0
- package/src/components/ui/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/ui/Accordion/Accordion.tsx +2 -2
- package/src/components/ui/Alert/Alert.native.stories.tsx +43 -0
- package/src/components/ui/Alert/Alert.native.tsx +94 -0
- package/src/components/ui/Alert/Alert.stories.tsx +1 -1
- package/src/components/ui/Alert/Alert.tsx +2 -2
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +47 -0
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +34 -0
- package/src/components/ui/Avatar/Avatar.native.tsx +85 -0
- package/src/components/ui/Avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/Avatar/Avatar.tsx +2 -2
- package/src/components/ui/Badge/Badge.mdx +2 -2
- package/src/components/ui/Badge/Badge.native.stories.tsx +36 -0
- package/src/components/ui/Badge/Badge.native.tsx +50 -0
- package/src/components/ui/Badge/Badge.stories.tsx +1 -1
- package/src/components/ui/Badge/Badge.tsx +2 -2
- package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +1 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/ui/Calendar/Calendar.mdx +16 -0
- package/src/components/ui/Calendar/Calendar.stories.tsx +1 -1
- package/src/components/ui/Calendar/Calendar.tsx +2 -2
- package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
- package/src/components/ui/Carousel/Carousel.tsx +2 -2
- package/src/components/ui/Collapsible/Collapsible.stories.tsx +1 -1
- package/src/components/ui/Collapsible/Collapsible.tsx +2 -2
- package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +1 -1
- package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -2
- package/src/components/ui/Link/Link.mdx +14 -0
- package/src/components/ui/Link/Link.stories.tsx +1 -1
- package/src/components/ui/Link/Link.tsx +2 -2
- package/src/components/ui/Loading/Loading.native.spec.tsx +24 -0
- package/src/components/ui/Loading/Loading.native.stories.tsx +33 -0
- package/src/components/ui/Loading/Loading.native.tsx +29 -0
- package/src/components/ui/Loading/Loading.stories.tsx +1 -1
- package/src/components/ui/Loading/Loading.tsx +2 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +39 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +76 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +1 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -2
- package/src/components/ui/Pagination/Pagination.module.css +1 -1
- package/src/components/ui/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/ui/Pagination/Pagination.tsx +2 -2
- package/src/components/ui/Progress/Progress.mdx +1 -1
- package/src/components/ui/Progress/Progress.module.css +1 -1
- package/src/components/ui/Progress/Progress.native.stories.tsx +34 -0
- package/src/components/ui/Progress/Progress.native.tsx +84 -0
- package/src/components/ui/Progress/Progress.stories.tsx +1 -1
- package/src/components/ui/Progress/Progress.tsx +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -2
- package/src/components/ui/Separator/Separator.mdx +14 -0
- package/src/components/ui/Separator/Separator.native.stories.tsx +42 -0
- package/src/components/ui/Separator/Separator.native.tsx +32 -0
- package/src/components/ui/Separator/Separator.stories.tsx +1 -1
- package/src/components/ui/Separator/Separator.tsx +2 -2
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +33 -0
- package/src/components/ui/Skeleton/Skeleton.native.tsx +41 -0
- package/src/components/ui/Skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/Skeleton/Skeleton.tsx +2 -3
- package/src/components/ui/SkipLink/SkipLink.stories.tsx +1 -1
- package/src/components/ui/SkipLink/SkipLink.tsx +5 -3
- package/src/components/ui/Table/Table.mdx +14 -0
- package/src/components/ui/Table/Table.stories.tsx +1 -1
- package/src/components/ui/Table/Table.tsx +2 -2
- package/src/components/ui/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/Tabs/Tabs.tsx +2 -2
- package/src/components/ui/Tooltip/Tooltip.mdx +14 -0
- package/src/components/ui/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/ui/Tooltip/Tooltip.tsx +2 -2
- package/src/components/ui/Typography/Typography.mdx +13 -0
- package/src/components/ui/Typography/Typography.native.stories.tsx +38 -0
- package/src/components/ui/Typography/Typography.native.tsx +65 -0
- package/src/components/ui/Typography/Typography.stories.tsx +1 -1
- package/src/components/ui/Typography/Typography.tsx +2 -2
- package/src/css/bdc.css +8 -0
- package/src/index.ts +1 -0
- package/src/native/ThemeContext.tsx +28 -0
- package/src/native/tokens.ts +13 -0
- package/src/native.ts +39 -0
- package/src/react-augment.d.ts +13 -0
- package/src/stories/DesignSystem/DarkMode.mdx +130 -0
- package/src/types.ts +2 -0
- package/src/typings.d.ts +3 -0
- package/src/web-components/globals.ts +61 -0
- package/src/web-components/index.ts +12 -0
- package/src/web-components/interaction/BdsButton.stories.tsx +60 -0
- package/src/web-components/interaction/BdsPopover.stories.tsx +70 -0
- package/src/web-components/interaction/BdsToastProvider.stories.tsx +73 -0
- package/src/web-components/interaction/bds-button.spec.ts +95 -0
- package/src/web-components/interaction/bds-button.ts +293 -0
- package/src/web-components/interaction/bds-popover.spec.ts +126 -0
- package/src/web-components/interaction/bds-popover.ts +217 -0
- package/src/web-components/interaction/bds-toast-provider.spec.ts +122 -0
- package/src/web-components/interaction/bds-toast-provider.ts +211 -0
- package/src/web-components/test/helpers.ts +14 -0
- package/src/web-components/ui/BdsAlert.stories.tsx +60 -0
- package/src/web-components/ui/BdsBadge.stories.tsx +37 -0
- package/src/web-components/ui/bds-alert.spec.ts +109 -0
- package/src/web-components/ui/bds-alert.ts +209 -0
- package/src/web-components/ui/bds-badge.spec.ts +51 -0
- package/src/web-components/ui/bds-badge.ts +88 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Avatar } from './Avatar.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Avatar (native)', () => {
|
|
10
|
+
describe('initials fallback', () => {
|
|
11
|
+
it('has image role and accessible label on the container', () => {
|
|
12
|
+
renderWithTheme(<Avatar name="John Doe" />);
|
|
13
|
+
expect(screen.getByRole('img', { name: 'John Doe' })).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('derives initials from a two-word name', () => {
|
|
17
|
+
renderWithTheme(<Avatar name="John Doe" />);
|
|
18
|
+
expect(screen.getByText('JD')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('uses a single-word name as one initial', () => {
|
|
22
|
+
renderWithTheme(<Avatar name="Alice" />);
|
|
23
|
+
expect(screen.getByText('A')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('uses the alt prop as accessible label when provided', () => {
|
|
27
|
+
renderWithTheme(<Avatar name="John Doe" alt="Profile picture" />);
|
|
28
|
+
expect(screen.getByRole('img', { name: 'Profile picture' })).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('image variant', () => {
|
|
33
|
+
it('has image role and accessible label on the container', () => {
|
|
34
|
+
renderWithTheme(<Avatar src="https://example.com/avatar.jpg" alt="Jane Smith" />);
|
|
35
|
+
expect(screen.getByRole('img', { name: 'Jane Smith' })).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does not duplicate the accessible label (regression: duplicate label bug)', () => {
|
|
39
|
+
// Previously, both the outer View and the inner Image had accessibilityLabel={label},
|
|
40
|
+
// causing screen readers to announce the name twice.
|
|
41
|
+
renderWithTheme(<Avatar src="https://example.com/avatar.jpg" alt="Jane Smith" />);
|
|
42
|
+
// Only one element should have aria-label="Jane Smith".
|
|
43
|
+
const labeled = screen.getAllByLabelText('Jane Smith');
|
|
44
|
+
expect(labeled).toHaveLength(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
+
import { Avatar } from './Avatar.native';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
tags: ['!stable', 'alpha'],
|
|
8
|
+
title: 'React Native/UI/Avatar',
|
|
9
|
+
component: Avatar,
|
|
10
|
+
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof Avatar>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const WithInitials: Story = { args: { name: 'John Doe', size: 'medium' } };
|
|
21
|
+
export const Small: Story = { args: { name: 'Jane Smith', size: 'small' } };
|
|
22
|
+
export const Large: Story = { args: { name: 'Alex Brown', size: 'large' } };
|
|
23
|
+
export const SingleName: Story = { args: { name: 'Alice', size: 'medium' } };
|
|
24
|
+
export const AllSizes: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<ThemeProvider>
|
|
27
|
+
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
|
28
|
+
{(['small', 'medium', 'large'] as const).map(s => (
|
|
29
|
+
<Avatar key={s} name="John Doe" size={s} />
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
</ThemeProvider>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { StyleSheet, View, Text, Image, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { font } from '../../../native/tokens';
|
|
3
|
+
import { useTheme } from '../../../native/ThemeContext';
|
|
4
|
+
|
|
5
|
+
type AvatarSize = 'small' | 'medium' | 'large';
|
|
6
|
+
|
|
7
|
+
interface AvatarProps {
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
size?: AvatarSize;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sizeMap: Record<AvatarSize, { container: number; fontSize: number }> = {
|
|
16
|
+
small: { container: 32, fontSize: font.size.bodyS },
|
|
17
|
+
medium: { container: 48, fontSize: font.size.body },
|
|
18
|
+
large: { container: 72, fontSize: font.size.heading3 },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getInitials(name: string): string {
|
|
22
|
+
return name
|
|
23
|
+
.split(' ')
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.slice(0, 2)
|
|
26
|
+
.map(word => word[0].toUpperCase())
|
|
27
|
+
.join('');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const styles = StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
borderRadius: 999,
|
|
33
|
+
overflow: 'hidden',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
justifyContent: 'center',
|
|
36
|
+
},
|
|
37
|
+
image: {
|
|
38
|
+
width: '100%',
|
|
39
|
+
height: '100%',
|
|
40
|
+
},
|
|
41
|
+
initials: {
|
|
42
|
+
fontFamily: font.family.body,
|
|
43
|
+
fontWeight: font.weight.semibold,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export function Avatar({ src, alt, name, size = 'medium', style }: Readonly<AvatarProps>) {
|
|
48
|
+
const { colors } = useTheme();
|
|
49
|
+
const { container, fontSize } = sizeMap[size];
|
|
50
|
+
const label = alt ?? name ?? 'User avatar';
|
|
51
|
+
|
|
52
|
+
if (src) {
|
|
53
|
+
return (
|
|
54
|
+
<View
|
|
55
|
+
style={[styles.container, { width: container, height: container }, style]}
|
|
56
|
+
accessibilityLabel={label}
|
|
57
|
+
accessibilityRole="image"
|
|
58
|
+
>
|
|
59
|
+
<Image source={{ uri: src }} style={styles.image} />
|
|
60
|
+
</View>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const initials = name ? getInitials(name) : '';
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<View
|
|
68
|
+
style={[
|
|
69
|
+
styles.container,
|
|
70
|
+
{ width: container, height: container, backgroundColor: colors.colorBlue },
|
|
71
|
+
style,
|
|
72
|
+
]}
|
|
73
|
+
accessibilityLabel={label}
|
|
74
|
+
accessibilityRole="image"
|
|
75
|
+
>
|
|
76
|
+
<Text
|
|
77
|
+
style={[styles.initials, { fontSize, color: colors.colorOnBlue }]}
|
|
78
|
+
importantForAccessibility="no"
|
|
79
|
+
accessibilityElementsHidden={true}
|
|
80
|
+
>
|
|
81
|
+
{initials}
|
|
82
|
+
</Text>
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import css from './Avatar.module.css';
|
|
2
2
|
import { cn } from '@boostdev/design-system-foundation';
|
|
3
|
+
import type { WithClassName } from '../../../types';
|
|
3
4
|
|
|
4
|
-
interface AvatarProps {
|
|
5
|
+
interface AvatarProps extends WithClassName {
|
|
5
6
|
src?: string;
|
|
6
7
|
alt?: string;
|
|
7
8
|
name?: string;
|
|
8
9
|
size?: 'small' | 'medium' | 'large';
|
|
9
|
-
className?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function getInitials(name: string): string {
|
|
@@ -50,8 +50,8 @@ Override these on a parent element to customise the badge without touching globa
|
|
|
50
50
|
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
51
51
|
</thead>
|
|
52
52
|
<tbody>
|
|
53
|
-
<tr><td>`--
|
|
54
|
-
<tr><td>`--
|
|
53
|
+
<tr><td>`--badge_bg`</td><td>`var(--bds-color_blue)`</td><td>Background colour</td></tr>
|
|
54
|
+
<tr><td>`--badge_text`</td><td>`var(--bds-color_on-blue)`</td><td>Text colour</td></tr>
|
|
55
55
|
</tbody>
|
|
56
56
|
</table>
|
|
57
57
|
|
|
@@ -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 { Badge } from './Badge.native';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
tags: ['!stable', 'alpha'],
|
|
8
|
+
title: 'React Native/UI/Badge',
|
|
9
|
+
component: Badge,
|
|
10
|
+
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
variant: { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'] },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof Badge>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Primary: Story = { args: { children: 'Primary', variant: 'primary' } };
|
|
21
|
+
export const Secondary: Story = { args: { children: 'Secondary', variant: 'secondary' } };
|
|
22
|
+
export const Success: Story = { args: { children: 'Success', variant: 'success' } };
|
|
23
|
+
export const Error: Story = { args: { children: 'Error', variant: 'error' } };
|
|
24
|
+
export const Warning: Story = { args: { children: 'Warning', variant: 'warning' } };
|
|
25
|
+
export const AllVariants: Story = {
|
|
26
|
+
args: { children: '' },
|
|
27
|
+
render: () => (
|
|
28
|
+
<ThemeProvider>
|
|
29
|
+
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
30
|
+
{(['primary', 'secondary', 'success', 'error', 'warning'] as const).map(v => (
|
|
31
|
+
<Badge key={v} variant={v}>{v}</Badge>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</ThemeProvider>
|
|
35
|
+
),
|
|
36
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { spacing, font } from '../../../native/tokens';
|
|
4
|
+
import { useTheme } from '../../../native/ThemeContext';
|
|
5
|
+
|
|
6
|
+
type BadgeVariant = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
|
|
7
|
+
|
|
8
|
+
interface BadgeProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
variant?: BadgeVariant;
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
badge: {
|
|
16
|
+
alignSelf: 'flex-start',
|
|
17
|
+
flexDirection: 'row',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
justifyContent: 'center',
|
|
20
|
+
paddingVertical: spacing.xxs,
|
|
21
|
+
paddingHorizontal: spacing.xs,
|
|
22
|
+
borderRadius: 999,
|
|
23
|
+
},
|
|
24
|
+
text: {
|
|
25
|
+
fontSize: font.size.bodyS,
|
|
26
|
+
fontFamily: font.family.body,
|
|
27
|
+
fontWeight: font.weight.semibold,
|
|
28
|
+
lineHeight: font.size.bodyS,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export function Badge({ children, variant = 'primary', style }: BadgeProps) {
|
|
33
|
+
const { colors } = useTheme();
|
|
34
|
+
|
|
35
|
+
const variantColors: Record<BadgeVariant, { bg: string; text: string }> = {
|
|
36
|
+
primary: { bg: colors.colorInteractive, text: colors.colorOnInteractive },
|
|
37
|
+
secondary: { bg: colors.colorBgSubtle, text: colors.colorOnBgSubtle },
|
|
38
|
+
success: { bg: colors.colorSuccess, text: colors.colorOnSuccess },
|
|
39
|
+
error: { bg: colors.colorError, text: colors.colorOnError },
|
|
40
|
+
warning: { bg: colors.colorWarning, text: colors.colorOnWarning },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const { bg, text } = variantColors[variant];
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={[styles.badge, { backgroundColor: bg }, style]}>
|
|
47
|
+
<Text style={[styles.text, { color: text }]}>{children}</Text>
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { Badge } from './Badge';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'UI/Badge',
|
|
5
|
+
title: 'React/UI/Badge',
|
|
6
6
|
component: Badge,
|
|
7
7
|
argTypes: {
|
|
8
8
|
variant: { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'] },
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import css from './Badge.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
interface BadgeProps {
|
|
6
|
+
interface BadgeProps extends WithClassName {
|
|
6
7
|
children: ReactNode;
|
|
7
8
|
variant?: 'primary' | 'secondary' | 'success' | 'error' | 'warning';
|
|
8
|
-
className?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function Badge({ children, variant = 'primary', className }: BadgeProps) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import css from './Breadcrumb.module.css';
|
|
2
2
|
import { cn } from '@boostdev/design-system-foundation';
|
|
3
|
+
import type { WithClassName } from '../../../types';
|
|
3
4
|
|
|
4
5
|
export interface BreadcrumbItem {
|
|
5
6
|
label: string;
|
|
6
7
|
href?: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
interface BreadcrumbProps {
|
|
10
|
+
interface BreadcrumbProps extends WithClassName {
|
|
10
11
|
items: BreadcrumbItem[];
|
|
11
|
-
className?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function Breadcrumb({ items, className }: Readonly<BreadcrumbProps>) {
|
|
@@ -26,6 +26,22 @@ Date picker calendar grid. Use standalone or as the popover content inside a dat
|
|
|
26
26
|
|
|
27
27
|
<ArgTypes of={Stories} />
|
|
28
28
|
|
|
29
|
+
## CSS variables
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<table>
|
|
33
|
+
<thead>
|
|
34
|
+
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody>
|
|
37
|
+
<tr><td>`--calendar_border`</td><td>`none`</td><td>Container border</td></tr>
|
|
38
|
+
<tr><td>`--calendar_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Container corner radius</td></tr>
|
|
39
|
+
<tr><td>`--calendar_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Container box shadow</td></tr>
|
|
40
|
+
<tr><td>`--calendar_today-border`</td><td>`none`</td><td>Border on today's date cell</td></tr>
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
|
|
44
|
+
|
|
29
45
|
## Accessibility
|
|
30
46
|
|
|
31
47
|
- Grid uses `role="grid"` with `role="gridcell"` for each day
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useId, KeyboardEvent } from 'react';
|
|
2
2
|
import css from './Calendar.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
6
|
const DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
6
7
|
const DAY_LABELS: Record<string, string> = {
|
|
@@ -12,13 +13,12 @@ const MONTHS = [
|
|
|
12
13
|
'July', 'August', 'September', 'October', 'November', 'December',
|
|
13
14
|
];
|
|
14
15
|
|
|
15
|
-
interface CalendarProps {
|
|
16
|
+
interface CalendarProps extends WithClassName {
|
|
16
17
|
value?: Date;
|
|
17
18
|
defaultValue?: Date;
|
|
18
19
|
min?: Date;
|
|
19
20
|
max?: Date;
|
|
20
21
|
onChange?: (date: Date) => void;
|
|
21
|
-
className?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function isSameDay(a: Date, b: Date): boolean {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ReactNode, useRef, useId } from 'react';
|
|
2
2
|
import css from './Carousel.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
interface CarouselProps {
|
|
6
|
+
interface CarouselProps extends WithClassName {
|
|
6
7
|
items: ReactNode[];
|
|
7
8
|
label: string;
|
|
8
|
-
className?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function Carousel({ items, label, className }: Readonly<CarouselProps>) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode, SyntheticEvent } from 'react';
|
|
2
2
|
import css from './Collapsible.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDetailsElement>, 'onToggle'> {
|
|
6
|
+
export interface CollapsibleProps extends WithClassName, Omit<HTMLAttributes<HTMLDetailsElement>, 'onToggle'> {
|
|
6
7
|
/** The trigger label — always visible */
|
|
7
8
|
summary: ReactNode;
|
|
8
9
|
/** The content revealed when expanded */
|
|
@@ -18,7 +19,6 @@ export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDetailsElement
|
|
|
18
19
|
* (native <details name="…"> behaviour).
|
|
19
20
|
*/
|
|
20
21
|
name?: string;
|
|
21
|
-
className?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function Collapsible({
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { DescriptionList } from './DescriptionList';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'UI/DescriptionList',
|
|
5
|
+
title: 'React/UI/DescriptionList',
|
|
6
6
|
component: DescriptionList,
|
|
7
7
|
} satisfies Meta<typeof DescriptionList>;
|
|
8
8
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import css from './DescriptionList.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
6
|
export interface DescriptionItem {
|
|
6
7
|
term: ReactNode;
|
|
7
8
|
details: ReactNode | ReactNode[];
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
interface DescriptionListProps {
|
|
11
|
+
interface DescriptionListProps extends WithClassName {
|
|
11
12
|
items: DescriptionItem[];
|
|
12
13
|
layout?: 'stacked' | 'inline';
|
|
13
|
-
className?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function DescriptionList({ items, layout = 'stacked', className }: Readonly<DescriptionListProps>) {
|
|
@@ -38,6 +38,20 @@ Styled anchor element with three visual variants. Renders as `<a>` by default, b
|
|
|
38
38
|
|
|
39
39
|
<ArgTypes of={Stories} />
|
|
40
40
|
|
|
41
|
+
## CSS variables
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<table>
|
|
45
|
+
<thead>
|
|
46
|
+
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
47
|
+
</thead>
|
|
48
|
+
<tbody>
|
|
49
|
+
<tr><td>`--link_color`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Link text colour</td></tr>
|
|
50
|
+
<tr><td>`--link_color-hover`</td><td>`var(--bds-color_blue--strong)`</td><td>Link text colour on hover</td></tr>
|
|
51
|
+
</tbody>
|
|
52
|
+
</table>
|
|
53
|
+
|
|
54
|
+
|
|
41
55
|
## Accessibility
|
|
42
56
|
|
|
43
57
|
- External links add `target="_blank"` with `rel="noopener noreferrer"` and a visually-hidden "(opens in new tab)" label
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
2
2
|
import css from './Link.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
type LinkOwnProps<T extends ElementType> = {
|
|
6
|
+
type LinkOwnProps<T extends ElementType> = WithClassName & {
|
|
6
7
|
as?: T;
|
|
7
8
|
children: ReactNode;
|
|
8
9
|
variant?: 'default' | 'subtle' | 'standalone';
|
|
9
10
|
external?: boolean;
|
|
10
11
|
externalLabel?: string;
|
|
11
|
-
className?: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
type LinkProps<T extends ElementType = 'a'> = LinkOwnProps<T> &
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Loading } from './Loading.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Loading (native)', () => {
|
|
10
|
+
it('has progressbar role', () => {
|
|
11
|
+
renderWithTheme(<Loading />);
|
|
12
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('has default accessible label "Loading"', () => {
|
|
16
|
+
renderWithTheme(<Loading />);
|
|
17
|
+
expect(screen.getByRole('progressbar', { name: 'Loading' })).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('accepts a custom accessibilityLabel', () => {
|
|
21
|
+
renderWithTheme(<Loading accessibilityLabel="Laden…" />);
|
|
22
|
+
expect(screen.getByRole('progressbar', { name: 'Laden…' })).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
+
import { Loading } from './Loading.native';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
tags: ['!stable', 'alpha'],
|
|
8
|
+
title: 'React Native/UI/Loading',
|
|
9
|
+
component: Loading,
|
|
10
|
+
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof Loading>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Small: Story = { args: { size: 'small' } };
|
|
21
|
+
export const Medium: Story = { args: { size: 'medium' } };
|
|
22
|
+
export const Large: Story = { args: { size: 'large' } };
|
|
23
|
+
export const AllSizes: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<ThemeProvider>
|
|
26
|
+
<div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
|
|
27
|
+
<Loading size="small" />
|
|
28
|
+
<Loading size="medium" />
|
|
29
|
+
<Loading size="large" />
|
|
30
|
+
</div>
|
|
31
|
+
</ThemeProvider>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ActivityIndicator, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { useTheme } from '../../../native/ThemeContext';
|
|
3
|
+
|
|
4
|
+
type LoadingSize = 'small' | 'medium' | 'large';
|
|
5
|
+
|
|
6
|
+
interface LoadingProps {
|
|
7
|
+
size?: LoadingSize;
|
|
8
|
+
accessibilityLabel?: string;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const rnSize: Record<LoadingSize, 'small' | 'large'> = {
|
|
13
|
+
small: 'small',
|
|
14
|
+
medium: 'large',
|
|
15
|
+
large: 'large',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function Loading({ size = 'medium', accessibilityLabel = 'Loading', style }: LoadingProps) {
|
|
19
|
+
const { colors } = useTheme();
|
|
20
|
+
return (
|
|
21
|
+
<ActivityIndicator
|
|
22
|
+
size={rnSize[size]}
|
|
23
|
+
color={colors.colorInteractive}
|
|
24
|
+
style={style}
|
|
25
|
+
accessibilityRole="progressbar"
|
|
26
|
+
accessibilityLabel={accessibilityLabel}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { Loading } from './Loading';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'UI/Loading',
|
|
5
|
+
title: 'React/UI/Loading',
|
|
6
6
|
component: Loading,
|
|
7
7
|
argTypes: {
|
|
8
8
|
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import css from './Loading.module.css';
|
|
2
2
|
import { cn } from '@boostdev/design-system-foundation';
|
|
3
|
+
import type { WithClassName } from '../../../types';
|
|
3
4
|
|
|
4
|
-
interface LoadingProps {
|
|
5
|
+
interface LoadingProps extends WithClassName {
|
|
5
6
|
size?: 'small' | 'medium' | 'large';
|
|
6
|
-
className?: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function Loading({ size = 'medium', className }: LoadingProps) {
|