@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boostdev/design-system-components",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"React",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
+
"react-native": "./dist/native/index.js",
|
|
29
30
|
"types": "./dist/index.d.ts",
|
|
30
31
|
"import": "./dist/index.js",
|
|
31
32
|
"require": "./dist/index.cjs"
|
|
@@ -35,6 +36,10 @@
|
|
|
35
36
|
"import": "./dist/client.js",
|
|
36
37
|
"require": "./dist/client.cjs"
|
|
37
38
|
},
|
|
39
|
+
"./web-components": {
|
|
40
|
+
"types": "./dist/web-components.d.ts",
|
|
41
|
+
"import": "./dist/web-components.js"
|
|
42
|
+
},
|
|
38
43
|
"./css": "./dist/index.css",
|
|
39
44
|
"./css/bdc": "./src/css/bdc.css",
|
|
40
45
|
"./css/accordion": "./src/components/ui/Accordion/Accordion.module.css",
|
|
@@ -89,6 +94,7 @@
|
|
|
89
94
|
"./css/section-header": "./src/components/layout/SectionHeader/SectionHeader.module.css"
|
|
90
95
|
},
|
|
91
96
|
"main": "./dist/index.js",
|
|
97
|
+
"react-native": "./dist/native/index.js",
|
|
92
98
|
"types": "./dist/index.d.ts",
|
|
93
99
|
"files": [
|
|
94
100
|
"dist",
|
|
@@ -121,15 +127,19 @@
|
|
|
121
127
|
"@testing-library/react": "^16.3.2",
|
|
122
128
|
"@testing-library/user-event": "^14.6.1",
|
|
123
129
|
"@types/node": "^25.5.0",
|
|
124
|
-
"@types/react": "^
|
|
125
|
-
"@types/react-dom": "^
|
|
130
|
+
"@types/react": "^19",
|
|
131
|
+
"@types/react-dom": "^19",
|
|
132
|
+
"@types/react-native": "^0.73.0",
|
|
126
133
|
"@vitejs/plugin-react": "^5.1.4",
|
|
127
134
|
"eslint": "^9",
|
|
128
135
|
"git-cliff": "^2.12.0",
|
|
129
136
|
"identity-obj-proxy": "^3.0.0",
|
|
130
137
|
"jsdom": "^28.1.0",
|
|
138
|
+
"lit": "^3.3.2",
|
|
139
|
+
"react-native-web": "^0.21.2",
|
|
131
140
|
"remark-gfm": "^4.0.1",
|
|
132
141
|
"storybook": "^8.6.18",
|
|
142
|
+
"storybook-addon-tag-badges": "^1.4.0",
|
|
133
143
|
"stylelint": "^16",
|
|
134
144
|
"stylelint-config-standard": "^37",
|
|
135
145
|
"tsup": "^8",
|
|
@@ -142,7 +152,16 @@
|
|
|
142
152
|
"peerDependencies": {
|
|
143
153
|
"@boostdev/design-system-foundation": ">=1.0.4",
|
|
144
154
|
"react": ">=18",
|
|
145
|
-
"react-dom": ">=18"
|
|
155
|
+
"react-dom": ">=18",
|
|
156
|
+
"react-native": ">=0.70"
|
|
157
|
+
},
|
|
158
|
+
"peerDependenciesMeta": {
|
|
159
|
+
"react-dom": {
|
|
160
|
+
"optional": true
|
|
161
|
+
},
|
|
162
|
+
"react-native": {
|
|
163
|
+
"optional": true
|
|
164
|
+
}
|
|
146
165
|
},
|
|
147
166
|
"publishConfig": {
|
|
148
167
|
"access": "public",
|
|
@@ -150,5 +169,8 @@
|
|
|
150
169
|
},
|
|
151
170
|
"sideEffects": [
|
|
152
171
|
"src/css/**/*.css"
|
|
153
|
-
]
|
|
172
|
+
],
|
|
173
|
+
"dependencies": {
|
|
174
|
+
"@oddbird/css-anchor-positioning": "^0.9.0"
|
|
175
|
+
}
|
|
154
176
|
}
|
|
@@ -47,8 +47,11 @@
|
|
|
47
47
|
line-height: 1;
|
|
48
48
|
|
|
49
49
|
/* Colour model: override --button_color + --button_on-color to re-theme */
|
|
50
|
-
--button_color:
|
|
51
|
-
--button_on-color:
|
|
50
|
+
--button_color: var(--bds-color_green);
|
|
51
|
+
--button_on-color: var(--bds-color_on-green);
|
|
52
|
+
|
|
53
|
+
/* Text-safe variant of --button_color for use on page background (ghost text/border) */
|
|
54
|
+
--button_color--on-bg: var(--bds-color_green--strong);
|
|
52
55
|
|
|
53
56
|
background-color: var(--button_bg, var(--button_color));
|
|
54
57
|
color: var(--button_text, var(--button_on-color));
|
|
@@ -63,11 +66,11 @@
|
|
|
63
66
|
/* Default: filled with --button_color, no visible border */
|
|
64
67
|
.button.--default { /* inherits base — no overrides needed */ }
|
|
65
68
|
|
|
66
|
-
/* Ghost: transparent background, border and text use --button_color */
|
|
69
|
+
/* Ghost: transparent background, border and text use text-safe --button_color--on-bg */
|
|
67
70
|
.button.--ghost {
|
|
68
71
|
--button_bg: transparent;
|
|
69
|
-
--button_text: var(--button_color);
|
|
70
|
-
--bdc_color: var(--button_color);
|
|
72
|
+
--button_text: var(--button_color--on-bg);
|
|
73
|
+
--bdc_color: var(--button_color--on-bg);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
.button.--size_small {
|
|
@@ -141,8 +144,8 @@
|
|
|
141
144
|
@media (hover: hover) and (pointer: fine) {
|
|
142
145
|
.button.--default:hover {
|
|
143
146
|
--button_bg: var(--bds-color_bg);
|
|
144
|
-
--button_text: var(--bds-
|
|
145
|
-
--button_pulse-color: var(--bds-
|
|
147
|
+
--button_text: var(--bds-color_interactive_on-bg);
|
|
148
|
+
--button_pulse-color: var(--bds-color_interactive_on-bg);
|
|
146
149
|
--bdc_color: currentcolor;
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
+
import { Button } from './Button.native';
|
|
4
|
+
|
|
5
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Button (native)', () => {
|
|
10
|
+
it('has button role', () => {
|
|
11
|
+
renderWithTheme(<Button>Save</Button>);
|
|
12
|
+
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('exposes disabled state', () => {
|
|
16
|
+
renderWithTheme(<Button disabled>Save</Button>);
|
|
17
|
+
expect(screen.getByRole('button', { name: 'Save' })).toHaveAttribute('aria-disabled', 'true');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders with icon-start without throwing', () => {
|
|
21
|
+
// Icon wrappers have importantForAccessibility="no-hide-descendants" (Android)
|
|
22
|
+
// and accessibilityElementsHidden={true} (iOS) so decorative icons are hidden on device.
|
|
23
|
+
const icon = <span data-testid="icon">★</span>;
|
|
24
|
+
renderWithTheme(<Button iconStart={icon}>Save</Button>);
|
|
25
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders with icon-end without throwing', () => {
|
|
30
|
+
const icon = <span data-testid="icon-end">→</span>;
|
|
31
|
+
renderWithTheme(<Button iconEnd={icon}>Next</Button>);
|
|
32
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByTestId('icon-end')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
+
import { Button } from './Button.native';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
tags: ['!stable', 'alpha'],
|
|
8
|
+
title: 'React Native/Interaction/Button',
|
|
9
|
+
component: Button,
|
|
10
|
+
decorators: [(Story) => <ThemeProvider><Story /></ThemeProvider>],
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
variant: { control: 'select', options: ['default', 'ghost'] },
|
|
14
|
+
size: { control: 'select', options: ['small', 'medium', 'large'] },
|
|
15
|
+
onPress: { action: 'pressed' },
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof Button>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = { args: { children: 'Default', variant: 'default' } };
|
|
23
|
+
export const Ghost: Story = { args: { children: 'Ghost', variant: 'ghost' } };
|
|
24
|
+
export const Small: Story = { args: { children: 'Small', size: 'small' } };
|
|
25
|
+
export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
|
|
26
|
+
export const Large: Story = { args: { children: 'Large', size: 'large' } };
|
|
27
|
+
export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
|
|
28
|
+
export const AllVariants: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<ThemeProvider>
|
|
31
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
32
|
+
{(['default', 'ghost'] as const).map(v => (
|
|
33
|
+
<div key={v} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
34
|
+
{(['small', 'medium', 'large'] as const).map(s => (
|
|
35
|
+
<Button key={s} variant={v} size={s}>{v} {s}</Button>
|
|
36
|
+
))}
|
|
37
|
+
</div>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
</ThemeProvider>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleSheet, Pressable, Text, View, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { spacing, font, border } from '../../../native/tokens';
|
|
4
|
+
import { useTheme } from '../../../native/ThemeContext';
|
|
5
|
+
|
|
6
|
+
type ButtonVariant = 'default' | 'ghost';
|
|
7
|
+
type ButtonSize = 'small' | 'medium' | 'large';
|
|
8
|
+
|
|
9
|
+
export interface ButtonProps {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
variant?: ButtonVariant;
|
|
12
|
+
size?: ButtonSize;
|
|
13
|
+
iconStart?: ReactNode;
|
|
14
|
+
iconEnd?: ReactNode;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
onPress?: () => void;
|
|
17
|
+
accessibilityLabel?: string;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sizeTokens: Record<ButtonSize, { height: number; px: number; fontSize: number; radius: number }> = {
|
|
22
|
+
small: { height: 36, px: spacing.m, fontSize: font.size.bodyS, radius: border.radius.xs },
|
|
23
|
+
medium: { height: 48, px: spacing.m, fontSize: font.size.body, radius: border.radius.s },
|
|
24
|
+
large: { height: 56, px: spacing.xxl, fontSize: font.size.heading3, radius: border.radius.m },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const styles = StyleSheet.create({
|
|
28
|
+
pressable: {
|
|
29
|
+
flexDirection: 'row',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'center',
|
|
32
|
+
gap: spacing.xs,
|
|
33
|
+
alignSelf: 'flex-start',
|
|
34
|
+
},
|
|
35
|
+
label: {
|
|
36
|
+
fontFamily: font.family.body,
|
|
37
|
+
fontWeight: font.weight.semibold,
|
|
38
|
+
includeFontPadding: false,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export function Button({
|
|
43
|
+
children,
|
|
44
|
+
variant = 'default',
|
|
45
|
+
size = 'medium',
|
|
46
|
+
iconStart,
|
|
47
|
+
iconEnd,
|
|
48
|
+
disabled = false,
|
|
49
|
+
onPress,
|
|
50
|
+
accessibilityLabel,
|
|
51
|
+
style,
|
|
52
|
+
}: Readonly<ButtonProps>) {
|
|
53
|
+
const { colors } = useTheme();
|
|
54
|
+
const { height, px, fontSize, radius } = sizeTokens[size];
|
|
55
|
+
|
|
56
|
+
// Color model: filled = green bg, ghost = transparent bg + darker green border/text
|
|
57
|
+
const primaryColor = colors.colorGreen;
|
|
58
|
+
const onPrimaryColor = colors.colorOnGreen;
|
|
59
|
+
// Ghost uses colorGreenStrong for text/border — colorGreen (#00c950) fails 3:1 on white (2.15:1)
|
|
60
|
+
const ghostTextColor = colors.colorGreenStrong;
|
|
61
|
+
|
|
62
|
+
const containerStyle: ViewStyle = {
|
|
63
|
+
height,
|
|
64
|
+
paddingHorizontal: px,
|
|
65
|
+
borderRadius: radius,
|
|
66
|
+
opacity: disabled ? 0.4 : 1,
|
|
67
|
+
...(variant === 'default' && { backgroundColor: primaryColor }),
|
|
68
|
+
...(variant === 'ghost' && {
|
|
69
|
+
backgroundColor: 'transparent',
|
|
70
|
+
borderWidth: 1,
|
|
71
|
+
borderColor: ghostTextColor,
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const textColor = variant === 'default' ? onPrimaryColor : ghostTextColor;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Pressable
|
|
79
|
+
onPress={onPress}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
accessibilityRole="button"
|
|
82
|
+
accessibilityLabel={accessibilityLabel}
|
|
83
|
+
accessibilityState={{ disabled }}
|
|
84
|
+
style={({ pressed }) => [styles.pressable, containerStyle, pressed && { opacity: 0.75 }, style]}
|
|
85
|
+
>
|
|
86
|
+
{iconStart && <View importantForAccessibility="no-hide-descendants" accessibilityElementsHidden={true}>{iconStart}</View>}
|
|
87
|
+
{children && (
|
|
88
|
+
<Text style={[styles.label, { fontSize, color: textColor }]}>
|
|
89
|
+
{children}
|
|
90
|
+
</Text>
|
|
91
|
+
)}
|
|
92
|
+
{iconEnd && <View importantForAccessibility="no-hide-descendants">{iconEnd}</View>}
|
|
93
|
+
</Pressable>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { Button } from './Button';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Interaction/Button',
|
|
5
|
+
title: 'React/Interaction/Button',
|
|
6
6
|
component: Button,
|
|
7
7
|
argTypes: {
|
|
8
8
|
variant: { control: 'select', options: ['default', 'ghost'] },
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {ButtonHTMLAttributes, MouseEventHandler, ReactNode} from 'react';
|
|
2
2
|
import css from './Button.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
|
|
6
|
+
export interface ButtonProps extends WithClassName, ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
|
|
6
7
|
href?: string;
|
|
7
8
|
variant?: 'default' | 'ghost';
|
|
8
9
|
type?: 'button' | 'submit' | 'reset';
|
|
@@ -10,7 +11,6 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HT
|
|
|
10
11
|
iconStart?: ReactNode;
|
|
11
12
|
iconEnd?: ReactNode;
|
|
12
13
|
children?: ReactNode;
|
|
13
|
-
className?: string;
|
|
14
14
|
disabled?: boolean;
|
|
15
15
|
hasPulse?: boolean;
|
|
16
16
|
onClick?: MouseEventHandler<HTMLElement>;
|
|
@@ -20,6 +20,22 @@ Keyboard-first command palette for searching and executing actions. Commonly tri
|
|
|
20
20
|
|
|
21
21
|
<ArgTypes of={Stories} />
|
|
22
22
|
|
|
23
|
+
## CSS variables
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<table>
|
|
27
|
+
<thead>
|
|
28
|
+
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
<tr><td>`--command_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
|
|
32
|
+
<tr><td>`--command_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text and icon colour</td></tr>
|
|
33
|
+
<tr><td>`--command_shadow`</td><td>`var(--bds-shadow_xl)`</td><td>Panel box shadow</td></tr>
|
|
34
|
+
<tr><td>`--command_border`</td><td>`none`</td><td>Panel border</td></tr>
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
|
|
38
|
+
|
|
23
39
|
## Accessibility
|
|
24
40
|
|
|
25
41
|
- Input has `role="combobox"` with `aria-controls` pointing to the results list
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useId, KeyboardEvent, useMemo } from 'react';
|
|
2
2
|
import css from './Command.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
6
|
export interface CommandItem {
|
|
6
7
|
id: string;
|
|
@@ -11,12 +12,11 @@ export interface CommandItem {
|
|
|
11
12
|
onSelect: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
interface CommandProps {
|
|
15
|
+
interface CommandProps extends WithClassName {
|
|
15
16
|
isOpen: boolean;
|
|
16
17
|
onClose: () => void;
|
|
17
18
|
items: CommandItem[];
|
|
18
19
|
placeholder?: string;
|
|
19
|
-
className?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function Command({
|
|
@@ -3,10 +3,10 @@ import { ReactNode, useEffect, useRef } from 'react';
|
|
|
3
3
|
const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
4
4
|
import css from './Dialog.module.css';
|
|
5
5
|
import { cn } from '@boostdev/design-system-foundation';
|
|
6
|
+
import type { WithClassName } from '../../../types';
|
|
6
7
|
|
|
7
|
-
interface DialogProps {
|
|
8
|
+
interface DialogProps extends WithClassName {
|
|
8
9
|
children: ReactNode;
|
|
9
|
-
className?: string;
|
|
10
10
|
isOpen?: boolean;
|
|
11
11
|
onClose?: () => void;
|
|
12
12
|
}
|
|
@@ -28,6 +28,20 @@ Slide-in panel from the left or right edge of the viewport. Use for navigation,
|
|
|
28
28
|
|
|
29
29
|
<ArgTypes of={Stories} />
|
|
30
30
|
|
|
31
|
+
## CSS variables
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
<table>
|
|
35
|
+
<thead>
|
|
36
|
+
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
37
|
+
</thead>
|
|
38
|
+
<tbody>
|
|
39
|
+
<tr><td>`--drawer_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
|
|
40
|
+
<tr><td>`--drawer_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
|
|
44
|
+
|
|
31
45
|
## Accessibility
|
|
32
46
|
|
|
33
47
|
- Uses `<dialog>` internally — provides native focus trapping and `Escape` to close
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ReactNode, useEffect, useRef } from 'react';
|
|
2
2
|
import css from './Drawer.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
|
+
import type { WithClassName } from '../../../types';
|
|
4
5
|
|
|
5
|
-
interface DrawerProps {
|
|
6
|
+
interface DrawerProps extends WithClassName {
|
|
6
7
|
isOpen: boolean;
|
|
7
8
|
onClose: () => void;
|
|
8
9
|
title: ReactNode;
|
|
9
10
|
children: ReactNode;
|
|
10
11
|
side?: 'left' | 'right';
|
|
11
|
-
className?: string;
|
|
12
12
|
ariaLabel?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'react';
|
|
11
11
|
import css from './DropdownMenu.module.css';
|
|
12
12
|
import { cn } from '@boostdev/design-system-foundation';
|
|
13
|
+
import type { WithClassName } from '../../../types';
|
|
13
14
|
|
|
14
15
|
export interface DropdownMenuItem {
|
|
15
16
|
id: string;
|
|
@@ -20,11 +21,10 @@ export interface DropdownMenuItem {
|
|
|
20
21
|
separator?: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
interface DropdownMenuProps {
|
|
24
|
+
interface DropdownMenuProps extends WithClassName {
|
|
24
25
|
trigger: ReactElement;
|
|
25
26
|
items: DropdownMenuItem[];
|
|
26
27
|
placement?: 'bottom-start' | 'bottom-end';
|
|
27
|
-
className?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function DropdownMenu({
|
|
@@ -27,6 +27,23 @@ Floating panel anchored to a trigger element. Unlike `Tooltip`, popovers can con
|
|
|
27
27
|
|
|
28
28
|
<ArgTypes of={Stories} />
|
|
29
29
|
|
|
30
|
+
## CSS variables
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
<table>
|
|
34
|
+
<thead>
|
|
35
|
+
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
36
|
+
</thead>
|
|
37
|
+
<tbody>
|
|
38
|
+
<tr><td>`--popover_color`</td><td>`var(--bds-color_bg)`</td><td>Panel background colour</td></tr>
|
|
39
|
+
<tr><td>`--popover_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
|
|
40
|
+
<tr><td>`--popover_shadow`</td><td>`var(--bds-shadow_m)`</td><td>Panel box shadow</td></tr>
|
|
41
|
+
<tr><td>`--popover_border`</td><td>`none`</td><td>Panel border</td></tr>
|
|
42
|
+
<tr><td>`--popover_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Panel corner radius</td></tr>
|
|
43
|
+
</tbody>
|
|
44
|
+
</table>
|
|
45
|
+
|
|
46
|
+
|
|
30
47
|
## Accessibility
|
|
31
48
|
|
|
32
49
|
- Trigger element gets `aria-haspopup="true"` and `aria-expanded` toggled on open/close
|
|
@@ -5,8 +5,13 @@
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.panel {
|
|
8
|
+
/* Absolute positioning keeps the panel anchored to the wrapper/trigger and
|
|
9
|
+
scrolls with the page. CSS anchor positioning sets the exact placement;
|
|
10
|
+
@position-try fallbacks handle automatic repositioning on overflow. */
|
|
8
11
|
position: absolute;
|
|
9
|
-
|
|
12
|
+
margin: 0;
|
|
13
|
+
overflow-y: auto;
|
|
14
|
+
z-index: var(--popover_z-index, 100);
|
|
10
15
|
min-width: 12rem;
|
|
11
16
|
padding: var(--bds-space_m);
|
|
12
17
|
border: var(--popover_border, none);
|
|
@@ -22,23 +27,75 @@
|
|
|
22
27
|
font-size: var(--bds-font_size--body);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
/* Primary placements via CSS anchor positioning.
|
|
31
|
+
anchor() resolves relative to the element tagged with the matching anchor-name.
|
|
32
|
+
@position-try fallbacks are tried in order when the primary placement overflows. */
|
|
33
|
+
|
|
34
|
+
.panel[data-placement='bottom'] {
|
|
35
|
+
top: anchor(bottom);
|
|
36
|
+
left: anchor(left);
|
|
37
|
+
margin-top: var(--bds-space_xs, 8px);
|
|
38
|
+
position-try-fallbacks: --popover-top, --popover-right, --popover-left;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.panel[data-placement='top'] {
|
|
42
|
+
bottom: anchor(top);
|
|
43
|
+
left: anchor(left);
|
|
44
|
+
margin-bottom: var(--bds-space_xs, 8px);
|
|
45
|
+
position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.panel[data-placement='right'] {
|
|
49
|
+
top: anchor(top);
|
|
50
|
+
left: anchor(right);
|
|
51
|
+
margin-left: var(--bds-space_xs, 8px);
|
|
52
|
+
position-try-fallbacks: --popover-left, --popover-bottom, --popover-top;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.panel[data-placement='left'] {
|
|
56
|
+
top: anchor(top);
|
|
57
|
+
right: anchor(left);
|
|
58
|
+
margin-right: var(--bds-space_xs, 8px);
|
|
59
|
+
position-try-fallbacks: --popover-right, --popover-bottom, --popover-top;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Fallback placements — used by the browser (or polyfill) when the primary
|
|
63
|
+
placement would overflow the viewport. Each block resets all inset/margin
|
|
64
|
+
properties so nothing bleeds in from the primary placement. */
|
|
65
|
+
|
|
66
|
+
@position-try --popover-bottom {
|
|
67
|
+
top: anchor(bottom);
|
|
68
|
+
right: unset;
|
|
69
|
+
bottom: unset;
|
|
70
|
+
left: anchor(left);
|
|
71
|
+
margin: 0;
|
|
72
|
+
margin-top: var(--bds-space_xs, 8px);
|
|
28
73
|
}
|
|
29
74
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
75
|
+
@position-try --popover-top {
|
|
76
|
+
top: unset;
|
|
77
|
+
right: unset;
|
|
78
|
+
bottom: anchor(top);
|
|
79
|
+
left: anchor(left);
|
|
80
|
+
margin: 0;
|
|
81
|
+
margin-bottom: var(--bds-space_xs, 8px);
|
|
33
82
|
}
|
|
34
83
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
84
|
+
@position-try --popover-right {
|
|
85
|
+
top: anchor(top);
|
|
86
|
+
right: unset;
|
|
87
|
+
bottom: unset;
|
|
88
|
+
left: anchor(right);
|
|
89
|
+
margin: 0;
|
|
90
|
+
margin-left: var(--bds-space_xs, 8px);
|
|
38
91
|
}
|
|
39
92
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
93
|
+
@position-try --popover-left {
|
|
94
|
+
top: anchor(top);
|
|
95
|
+
right: anchor(left);
|
|
96
|
+
bottom: unset;
|
|
97
|
+
left: unset;
|
|
98
|
+
margin: 0;
|
|
99
|
+
margin-right: var(--bds-space_xs, 8px);
|
|
43
100
|
}
|
|
44
101
|
}
|