@boostdev/design-system-components 1.2.8 → 2.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 -3
- package/README.md +57 -38
- package/dist/client.cjs +52 -50
- package/dist/client.css +537 -524
- package/dist/client.d.cts +31 -1
- package/dist/client.d.ts +31 -1
- package/dist/client.js +52 -50
- package/dist/index.cjs +52 -50
- package/dist/index.css +537 -524
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +52 -50
- package/dist/native/index.cjs +5 -2
- package/dist/native/index.d.cts +3 -1
- package/dist/native/index.d.ts +3 -1
- package/dist/native/index.js +5 -2
- package/dist/web-components/{chunk-DZRSJGPB.js → chunk-3S42DZ7W.js} +1 -1
- package/dist/web-components/{chunk-3REOIRDW.js → chunk-N3TN6WCH.js} +26 -1
- package/dist/web-components/globals.js +1 -1
- package/dist/web-components/index.js +2 -2
- package/dist/web-components/interaction/bds-button.d.ts +7 -0
- package/dist/web-components/interaction/bds-button.js +1 -1
- package/package.json +3 -3
- package/src/components/interaction/Button/Button.mdx +84 -29
- package/src/components/interaction/Button/Button.module.css +25 -1
- package/src/components/interaction/Button/Button.native.mdx +33 -14
- package/src/components/interaction/Button/Button.native.spec.tsx +20 -0
- package/src/components/interaction/Button/Button.native.stories.tsx +110 -9
- package/src/components/interaction/Button/Button.native.tsx +13 -4
- package/src/components/interaction/Button/Button.spec.tsx +16 -0
- package/src/components/interaction/Button/Button.stories.tsx +134 -16
- package/src/components/interaction/Button/Button.tsx +32 -0
- package/src/components/interaction/Command/Command.module.css +1 -1
- package/src/components/interaction/Dialog/Dialog.module.css +1 -1
- package/src/components/interaction/Dialog/Dialog.native.mdx +2 -2
- package/src/components/interaction/Drawer/Drawer.module.css +1 -1
- package/src/components/interaction/Drawer/Drawer.native.mdx +2 -2
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +2 -2
- package/src/components/interaction/Popover/Popover.module.css +1 -1
- package/src/components/interaction/Popover/Popover.native.mdx +2 -2
- package/src/components/interaction/Rating/Rating.module.css +1 -1
- package/src/components/interaction/Rating/Rating.native.mdx +1 -1
- package/src/components/interaction/Toast/Toast.module.css +1 -1
- package/src/components/interaction/Toast/Toast.native.mdx +3 -3
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -1
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +1 -1
- package/src/components/interaction/form/Combobox/Combobox.module.css +1 -1
- package/src/components/interaction/form/Combobox/Combobox.native.mdx +1 -1
- package/src/components/interaction/form/FileInput/FileInput.module.css +1 -1
- package/src/components/interaction/form/FileInput/FileInput.native.mdx +1 -1
- package/src/components/interaction/form/FormInput/FormInput.module.css +1 -1
- package/src/components/interaction/form/FormInput/FormInput.native.mdx +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +1 -1
- package/src/components/interaction/form/Radio/Radio.module.css +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +1 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +1 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +1 -1
- package/src/components/interaction/form/Select/Select.module.css +1 -1
- package/src/components/interaction/form/Select/Select.native.mdx +1 -1
- package/src/components/interaction/form/Slider/Slider.module.css +1 -1
- package/src/components/interaction/form/Slider/Slider.native.mdx +1 -1
- package/src/components/interaction/form/Switch/Switch.module.css +1 -1
- package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
- package/src/components/interaction/form/Textarea/Textarea.native.mdx +1 -1
- package/src/components/interaction/form/atoms/InputContainer.module.css +1 -1
- package/src/components/interaction/form/atoms/Label.module.css +1 -1
- package/src/components/interaction/form/atoms/Message.module.css +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -1
- package/src/components/layout/Card/Card.module.css +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.module.css +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +46 -14
- package/src/components/layout/SectionHeader/SectionHeader.module.css +1 -1
- package/src/components/ui/Accordion/Accordion.module.css +1 -1
- package/src/components/ui/Accordion/Accordion.native.mdx +1 -1
- package/src/components/ui/Accordion/Accordion.native.stories.tsx +1 -1
- package/src/components/ui/Alert/Alert.module.css +1 -1
- package/src/components/ui/Alert/Alert.native.mdx +1 -1
- package/src/components/ui/Avatar/Avatar.module.css +1 -1
- package/src/components/ui/Avatar/Avatar.native.mdx +1 -1
- package/src/components/ui/Badge/Badge.module.css +1 -1
- package/src/components/ui/Badge/Badge.native.mdx +1 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.module.css +1 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +1 -1
- package/src/components/ui/Calendar/Calendar.module.css +1 -1
- package/src/components/ui/Calendar/Calendar.native.mdx +1 -1
- package/src/components/ui/Carousel/Carousel.module.css +1 -1
- package/src/components/ui/Carousel/Carousel.native.mdx +1 -1
- package/src/components/ui/Collapsible/Collapsible.module.css +1 -1
- package/src/components/ui/Collapsible/Collapsible.native.mdx +1 -1
- package/src/components/ui/DescriptionList/DescriptionList.module.css +1 -1
- package/src/components/ui/DescriptionList/DescriptionList.native.mdx +1 -1
- package/src/components/ui/Link/Link.module.css +1 -1
- package/src/components/ui/Link/Link.native.mdx +1 -1
- package/src/components/ui/Loading/Loading.module.css +1 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -1
- package/src/components/ui/Pagination/Pagination.module.css +1 -1
- package/src/components/ui/Pagination/Pagination.native.mdx +1 -1
- package/src/components/ui/Progress/Progress.module.css +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +1 -1
- package/src/components/ui/Separator/Separator.module.css +1 -1
- package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
- package/src/components/ui/SkipLink/SkipLink.module.css +1 -1
- package/src/components/ui/Table/Table.module.css +1 -1
- package/src/components/ui/Table/Table.native.mdx +1 -1
- package/src/components/ui/Tabs/Tabs.module.css +1 -1
- package/src/components/ui/Tabs/Tabs.native.mdx +1 -1
- package/src/components/ui/Tooltip/Tooltip.module.css +1 -1
- package/src/components/ui/Tooltip/Tooltip.native.mdx +1 -1
- package/src/components/ui/Typography/Typography.module.css +1 -1
- package/src/components/ui/Typography/Typography.native.mdx +1 -1
- package/src/css/bdc.css +1 -1
- package/src/css/index.css +1 -1
- package/src/stories/DesignSystem/Borders.mdx +3 -3
- package/src/stories/DesignSystem/Colors.mdx +1 -1
- package/src/stories/DesignSystem/DarkMode.mdx +3 -3
- package/src/stories/DesignSystem/Overview.mdx +16 -5
- package/src/stories/DesignSystem/Typography.mdx +1 -1
- package/src/stories/Introduction.mdx +4 -4
- package/src/stories/ReactNative.mdx +6 -6
- package/src/web-components/globals.ts +1 -1
- package/src/web-components/interaction/BdsAccordion.mdx +3 -3
- package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
- package/src/web-components/interaction/BdsButton.mdx +48 -16
- package/src/web-components/interaction/BdsButton.stories.tsx +171 -19
- package/src/web-components/interaction/BdsCollapsible.mdx +2 -2
- package/src/web-components/interaction/BdsDialog.mdx +2 -2
- package/src/web-components/interaction/BdsDrawer.mdx +2 -2
- package/src/web-components/interaction/BdsDropdownMenu.mdx +2 -2
- package/src/web-components/interaction/BdsPopover.mdx +1 -1
- package/src/web-components/interaction/BdsSkipLink.mdx +2 -2
- package/src/web-components/interaction/BdsTabs.mdx +2 -2
- package/src/web-components/interaction/BdsToastProvider.mdx +3 -3
- package/src/web-components/interaction/BdsTooltip.mdx +2 -2
- package/src/web-components/interaction/bds-button.spec.ts +35 -0
- package/src/web-components/interaction/bds-button.ts +28 -1
- package/src/web-components/interaction/form/BdsCheckbox.mdx +1 -1
- package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +1 -1
- package/src/web-components/interaction/form/BdsCombobox.mdx +1 -1
- package/src/web-components/interaction/form/BdsFileInput.mdx +1 -1
- package/src/web-components/interaction/form/BdsFormInput.mdx +1 -1
- package/src/web-components/interaction/form/BdsNumberInput.mdx +1 -1
- package/src/web-components/interaction/form/BdsRadio.mdx +1 -1
- package/src/web-components/interaction/form/BdsRadioGroup.mdx +1 -1
- package/src/web-components/interaction/form/BdsSegmentedControl.mdx +1 -1
- package/src/web-components/interaction/form/BdsSelect.mdx +1 -1
- package/src/web-components/interaction/form/BdsSlider.mdx +1 -1
- package/src/web-components/interaction/form/BdsSwitch.mdx +1 -1
- package/src/web-components/interaction/form/BdsTextarea.mdx +1 -1
- package/src/web-components/ui/BdsAlert.mdx +1 -1
- package/src/web-components/ui/BdsAvatar.mdx +1 -1
- package/src/web-components/ui/BdsBadge.mdx +1 -1
- package/src/web-components/ui/BdsBreadcrumb.mdx +1 -1
- package/src/web-components/ui/BdsButtonGroup.mdx +1 -1
- package/src/web-components/ui/BdsCalendar.mdx +1 -1
- package/src/web-components/ui/BdsCard.mdx +1 -1
- package/src/web-components/ui/BdsCarousel.mdx +1 -1
- package/src/web-components/ui/BdsDescriptionList.mdx +1 -1
- package/src/web-components/ui/BdsIconWrapper.mdx +1 -1
- package/src/web-components/ui/BdsLink.mdx +1 -1
- package/src/web-components/ui/BdsLoading.mdx +1 -1
- package/src/web-components/ui/BdsNotificationBanner.mdx +1 -1
- package/src/web-components/ui/BdsPagination.mdx +1 -1
- package/src/web-components/ui/BdsProgress.mdx +1 -1
- package/src/web-components/ui/BdsProgressCircle.mdx +1 -1
- package/src/web-components/ui/BdsRating.mdx +1 -1
- package/src/web-components/ui/BdsSectionHeader.mdx +1 -1
- package/src/web-components/ui/BdsSeparator.mdx +2 -2
- package/src/web-components/ui/BdsSkeleton.mdx +1 -1
- package/src/web-components/ui/BdsTable.mdx +1 -1
- package/src/web-components/ui/BdsTypography.mdx +1 -1
package/dist/native/index.cjs
CHANGED
|
@@ -2139,6 +2139,7 @@ function Button({
|
|
|
2139
2139
|
iconStart,
|
|
2140
2140
|
iconEnd,
|
|
2141
2141
|
disabled = false,
|
|
2142
|
+
isIconOnly = false,
|
|
2142
2143
|
onPress,
|
|
2143
2144
|
accessibilityLabel,
|
|
2144
2145
|
style
|
|
@@ -2151,9 +2152,11 @@ function Button({
|
|
|
2151
2152
|
const isOutline = variant === "outline" || variant === "ghost";
|
|
2152
2153
|
const containerStyle = {
|
|
2153
2154
|
height,
|
|
2154
|
-
paddingHorizontal: px,
|
|
2155
|
+
paddingHorizontal: isIconOnly ? import_native.spacing.xs : px,
|
|
2155
2156
|
borderRadius: radius,
|
|
2156
2157
|
opacity: disabled ? 0.4 : 1,
|
|
2158
|
+
// Icon-only: square with equal xs padding on both axes.
|
|
2159
|
+
...isIconOnly && { aspectRatio: 1, paddingVertical: import_native.spacing.xs },
|
|
2157
2160
|
...variant === "default" && { backgroundColor: primaryColor },
|
|
2158
2161
|
...isOutline && {
|
|
2159
2162
|
backgroundColor: "transparent",
|
|
@@ -2173,7 +2176,7 @@ function Button({
|
|
|
2173
2176
|
style: ({ pressed }) => [styles24.pressable, containerStyle, pressed && { opacity: 0.75 }, style],
|
|
2174
2177
|
children: [
|
|
2175
2178
|
iconStart && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native27.View, { importantForAccessibility: "no-hide-descendants", accessibilityElementsHidden: true, children: iconStart }),
|
|
2176
|
-
children && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native27.Text, { style: [styles24.label, { fontSize, color: textColor }], children }),
|
|
2179
|
+
children && (isIconOnly ? children : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native27.Text, { style: [styles24.label, { fontSize, color: textColor }], children })),
|
|
2177
2180
|
iconEnd && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native27.View, { importantForAccessibility: "no-hide-descendants", children: iconEnd })
|
|
2178
2181
|
]
|
|
2179
2182
|
}
|
package/dist/native/index.d.cts
CHANGED
|
@@ -293,11 +293,13 @@ interface ButtonProps {
|
|
|
293
293
|
iconStart?: ReactNode;
|
|
294
294
|
iconEnd?: ReactNode;
|
|
295
295
|
disabled?: boolean;
|
|
296
|
+
/** Forces a 1:1 aspect ratio with minimal padding for icon-only buttons. Requires `accessibilityLabel`. */
|
|
297
|
+
isIconOnly?: boolean;
|
|
296
298
|
onPress?: () => void;
|
|
297
299
|
accessibilityLabel?: string;
|
|
298
300
|
style?: StyleProp<ViewStyle>;
|
|
299
301
|
}
|
|
300
|
-
declare function Button({ children, variant, size, iconStart, iconEnd, disabled, onPress, accessibilityLabel, style, }: Readonly<ButtonProps>): react_jsx_runtime.JSX.Element;
|
|
302
|
+
declare function Button({ children, variant, size, iconStart, iconEnd, disabled, isIconOnly, onPress, accessibilityLabel, style, }: Readonly<ButtonProps>): react_jsx_runtime.JSX.Element;
|
|
301
303
|
|
|
302
304
|
interface RatingProps {
|
|
303
305
|
value: number;
|
package/dist/native/index.d.ts
CHANGED
|
@@ -293,11 +293,13 @@ interface ButtonProps {
|
|
|
293
293
|
iconStart?: ReactNode;
|
|
294
294
|
iconEnd?: ReactNode;
|
|
295
295
|
disabled?: boolean;
|
|
296
|
+
/** Forces a 1:1 aspect ratio with minimal padding for icon-only buttons. Requires `accessibilityLabel`. */
|
|
297
|
+
isIconOnly?: boolean;
|
|
296
298
|
onPress?: () => void;
|
|
297
299
|
accessibilityLabel?: string;
|
|
298
300
|
style?: StyleProp<ViewStyle>;
|
|
299
301
|
}
|
|
300
|
-
declare function Button({ children, variant, size, iconStart, iconEnd, disabled, onPress, accessibilityLabel, style, }: Readonly<ButtonProps>): react_jsx_runtime.JSX.Element;
|
|
302
|
+
declare function Button({ children, variant, size, iconStart, iconEnd, disabled, isIconOnly, onPress, accessibilityLabel, style, }: Readonly<ButtonProps>): react_jsx_runtime.JSX.Element;
|
|
301
303
|
|
|
302
304
|
interface RatingProps {
|
|
303
305
|
value: number;
|
package/dist/native/index.js
CHANGED
|
@@ -2087,6 +2087,7 @@ function Button({
|
|
|
2087
2087
|
iconStart,
|
|
2088
2088
|
iconEnd,
|
|
2089
2089
|
disabled = false,
|
|
2090
|
+
isIconOnly = false,
|
|
2090
2091
|
onPress,
|
|
2091
2092
|
accessibilityLabel,
|
|
2092
2093
|
style
|
|
@@ -2099,9 +2100,11 @@ function Button({
|
|
|
2099
2100
|
const isOutline = variant === "outline" || variant === "ghost";
|
|
2100
2101
|
const containerStyle = {
|
|
2101
2102
|
height,
|
|
2102
|
-
paddingHorizontal: px,
|
|
2103
|
+
paddingHorizontal: isIconOnly ? spacing.xs : px,
|
|
2103
2104
|
borderRadius: radius,
|
|
2104
2105
|
opacity: disabled ? 0.4 : 1,
|
|
2106
|
+
// Icon-only: square with equal xs padding on both axes.
|
|
2107
|
+
...isIconOnly && { aspectRatio: 1, paddingVertical: spacing.xs },
|
|
2105
2108
|
...variant === "default" && { backgroundColor: primaryColor },
|
|
2106
2109
|
...isOutline && {
|
|
2107
2110
|
backgroundColor: "transparent",
|
|
@@ -2121,7 +2124,7 @@ function Button({
|
|
|
2121
2124
|
style: ({ pressed }) => [styles24.pressable, containerStyle, pressed && { opacity: 0.75 }, style],
|
|
2122
2125
|
children: [
|
|
2123
2126
|
iconStart && /* @__PURE__ */ jsx27(View22, { importantForAccessibility: "no-hide-descendants", accessibilityElementsHidden: true, children: iconStart }),
|
|
2124
|
-
children && /* @__PURE__ */ jsx27(Text20, { style: [styles24.label, { fontSize, color: textColor }], children }),
|
|
2127
|
+
children && (isIconOnly ? children : /* @__PURE__ */ jsx27(Text20, { style: [styles24.label, { fontSize, color: textColor }], children })),
|
|
2125
2128
|
iconEnd && /* @__PURE__ */ jsx27(View22, { importantForAccessibility: "no-hide-descendants", children: iconEnd })
|
|
2126
2129
|
]
|
|
2127
2130
|
}
|
|
@@ -90,6 +90,23 @@ var BdsButton = class extends i2 {
|
|
|
90
90
|
animation: pulse 3s infinite;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/* Icon-only: square (1:1), equal xs padding on both axes — composes with
|
|
94
|
+
any variant/size. box-sizing: border-box keeps the outer box at
|
|
95
|
+
--button_height; without it, all: unset reverts to content-box and
|
|
96
|
+
padding-block would stretch the button past its declared height.
|
|
97
|
+
Slotted child fills the content area (button height minus the two
|
|
98
|
+
padding sides) so SVG icons scale with button size. */
|
|
99
|
+
.button.is-icon-only {
|
|
100
|
+
aspect-ratio: 1;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
padding: var(--bds-space_xs);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:host([is-icon-only]) ::slotted(*) {
|
|
106
|
+
inline-size: 100%;
|
|
107
|
+
block-size: 100%;
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
/* SVG icon colour */
|
|
94
111
|
.button svg {
|
|
95
112
|
--icon__stroke: currentcolor;
|
|
@@ -117,6 +134,12 @@ var BdsButton = class extends i2 {
|
|
|
117
134
|
transition: var(--bds-animation_transition);
|
|
118
135
|
}
|
|
119
136
|
|
|
137
|
+
/* Icon-only: zero out icon slot margins so icon centres */
|
|
138
|
+
.button.is-icon-only .icon-start.has-content,
|
|
139
|
+
.button.is-icon-only .icon-end.has-content {
|
|
140
|
+
margin-inline: 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
/* Hover icon animation */
|
|
121
144
|
@media (hover: hover) and (pointer: fine) {
|
|
122
145
|
.button:hover .icon-start svg,
|
|
@@ -164,6 +187,7 @@ var BdsButton = class extends i2 {
|
|
|
164
187
|
target: { type: String },
|
|
165
188
|
rel: { type: String },
|
|
166
189
|
hasPulse: { type: Boolean, attribute: "has-pulse", reflect: true },
|
|
190
|
+
isIconOnly: { type: Boolean, attribute: "is-icon-only", reflect: true },
|
|
167
191
|
type: { type: String },
|
|
168
192
|
ariaLabel: { type: String, attribute: "aria-label" },
|
|
169
193
|
// Internal state
|
|
@@ -176,6 +200,7 @@ var BdsButton = class extends i2 {
|
|
|
176
200
|
this.size = "medium";
|
|
177
201
|
this.disabled = false;
|
|
178
202
|
this.hasPulse = false;
|
|
203
|
+
this.isIconOnly = false;
|
|
179
204
|
this.type = "button";
|
|
180
205
|
this.ariaLabel = null;
|
|
181
206
|
this._iconStartFilled = false;
|
|
@@ -191,7 +216,7 @@ var BdsButton = class extends i2 {
|
|
|
191
216
|
}
|
|
192
217
|
}
|
|
193
218
|
get _classes() {
|
|
194
|
-
return `button ${this.variant} ${this.size}${this.hasPulse ? " has-pulse" : ""}`;
|
|
219
|
+
return `button ${this.variant} ${this.size}${this.hasPulse ? " has-pulse" : ""}${this.isIconOnly ? " is-icon-only" : ""}`;
|
|
195
220
|
}
|
|
196
221
|
_handleAnchorClick(e) {
|
|
197
222
|
if (this.disabled) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-3S42DZ7W.js";
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
} from "./chunk-UFFCKVZE.js";
|
|
70
70
|
import {
|
|
71
71
|
BdsButton
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-N3TN6WCH.js";
|
|
73
73
|
import {
|
|
74
74
|
BdsCollapsible
|
|
75
75
|
} from "./chunk-6I2DBFQ7.js";
|
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
BdsSectionHeader
|
|
102
102
|
} from "./chunk-Y4MAXTMA.js";
|
|
103
103
|
import "./chunk-QPOKHLMQ.js";
|
|
104
|
-
import "./chunk-
|
|
104
|
+
import "./chunk-3S42DZ7W.js";
|
|
105
105
|
import {
|
|
106
106
|
BdsAlert
|
|
107
107
|
} from "./chunk-BX3IPVOB.js";
|
|
@@ -14,6 +14,7 @@ type ButtonSize = 'small' | 'medium' | 'large';
|
|
|
14
14
|
* target — forwarded to <a> when href is set
|
|
15
15
|
* rel — forwarded to <a> when href is set
|
|
16
16
|
* has-pulse — boolean, enables the pulse ring animation
|
|
17
|
+
* is-icon-only — boolean, forces a 1:1 aspect ratio with minimal xs padding (requires aria-label)
|
|
17
18
|
* type — "button" (default) | "submit" | "reset"
|
|
18
19
|
*
|
|
19
20
|
* Slots:
|
|
@@ -59,6 +60,11 @@ declare class BdsButton extends LitElement {
|
|
|
59
60
|
attribute: string;
|
|
60
61
|
reflect: boolean;
|
|
61
62
|
};
|
|
63
|
+
isIconOnly: {
|
|
64
|
+
type: BooleanConstructor;
|
|
65
|
+
attribute: string;
|
|
66
|
+
reflect: boolean;
|
|
67
|
+
};
|
|
62
68
|
type: {
|
|
63
69
|
type: StringConstructor;
|
|
64
70
|
};
|
|
@@ -80,6 +86,7 @@ declare class BdsButton extends LitElement {
|
|
|
80
86
|
target: string | undefined;
|
|
81
87
|
rel: string | undefined;
|
|
82
88
|
hasPulse: boolean;
|
|
89
|
+
isIconOnly: boolean;
|
|
83
90
|
type: 'button' | 'submit' | 'reset';
|
|
84
91
|
ariaLabel: string | null;
|
|
85
92
|
private _iconStartFilled;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boostdev/design-system-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.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",
|
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
"changelog:unreleased": "pnpm exec git-cliff --unreleased"
|
|
285
285
|
},
|
|
286
286
|
"devDependencies": {
|
|
287
|
-
"@boostdev/design-system-foundation": "^
|
|
287
|
+
"@boostdev/design-system-foundation": "^2.0.0",
|
|
288
288
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
289
289
|
"@storybook/addon-docs": "8.6.14",
|
|
290
290
|
"@storybook/addon-essentials": "^8.6.14",
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
"zod": "^4.3.6"
|
|
320
320
|
},
|
|
321
321
|
"peerDependencies": {
|
|
322
|
-
"@boostdev/design-system-foundation": ">=
|
|
322
|
+
"@boostdev/design-system-foundation": ">=2.0.0",
|
|
323
323
|
"react": ">=18",
|
|
324
324
|
"react-dom": ">=18",
|
|
325
325
|
"react-native": ">=0.70"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
1
|
+
import { Meta, Canvas, ArgTypes, Controls } from '@storybook/blocks';
|
|
2
2
|
import * as Stories from './Button.stories';
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
@@ -7,52 +7,94 @@ import * as Stories from './Button.stories';
|
|
|
7
7
|
|
|
8
8
|
Primary interactive element for triggering actions. Renders as `<button>` or `<a>` depending on whether `href` is provided.
|
|
9
9
|
|
|
10
|
+
## Playground
|
|
11
|
+
|
|
12
|
+
Use the Controls panel below to try every prop live. `iconStart` and `iconEnd` are backed by a small set of example icons — in your app, pass any `ReactNode` (SVG, icon component, emoji, etc.).
|
|
13
|
+
|
|
14
|
+
<Canvas of={Stories.Playground} />
|
|
15
|
+
<Controls of={Stories.Playground} />
|
|
16
|
+
|
|
10
17
|
## When to use
|
|
11
|
-
- The main action on a page or dialog (`default`)
|
|
12
|
-
- Secondary or
|
|
18
|
+
- The main action on a page or dialog (`variant="default"`)
|
|
19
|
+
- Secondary or less emphatic actions (`variant="outline"`)
|
|
13
20
|
- Navigation that should look like an action (`href` → renders as `<a>`)
|
|
14
21
|
|
|
15
|
-
> **Form actions:** When using buttons in a form (submit, reset, cancel), always wrap them in a `ButtonContainer` component.
|
|
22
|
+
> **Form actions:** When using buttons in a form (submit, reset, cancel), always wrap them in a `ButtonContainer` component. It provides consistent spacing, alignment, and responsive layout. Use `variant="flow"` for step navigation or `variant="card"` for single-form submissions.
|
|
16
23
|
|
|
17
24
|
## When not to use
|
|
18
|
-
- Navigation to another page when it reads naturally as a link — use `Link` instead
|
|
19
|
-
-
|
|
25
|
+
- Navigation to another page when it reads naturally as a link — use `Link` instead.
|
|
26
|
+
- A purely icon-based action without a label — allowed, but always set `aria-label` **and** prefer `isIconOnly` so the shape reflects the intent.
|
|
20
27
|
|
|
21
|
-
##
|
|
28
|
+
## Variants
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
Two visual styles:
|
|
31
|
+
|
|
32
|
+
- **`default`** — filled with `--button_color`, text in `--button_on-color`. Hover inverts: background becomes page surface, text becomes `--bdc_color`-on-background. Use for primary actions.
|
|
33
|
+
- **`outline`** — transparent background, border and text both use `--button_color--on-bg` (a text-safe variant of the identity colour). Hover fills with `--button_color`. Use for secondary actions. `'ghost'` remains as a deprecated alias.
|
|
25
34
|
|
|
26
|
-
### Default
|
|
27
35
|
<Canvas of={Stories.Default} />
|
|
36
|
+
<Canvas of={Stories.Outline} />
|
|
37
|
+
|
|
38
|
+
## Sizes
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
<Canvas of={Stories.Ghost} />
|
|
40
|
+
Size controls both height and horizontal padding. `large` also bumps font-size to the h3 scale.
|
|
31
41
|
|
|
32
|
-
### Sizes
|
|
33
42
|
<Canvas of={Stories.Small} />
|
|
34
43
|
<Canvas of={Stories.Medium} />
|
|
35
44
|
<Canvas of={Stories.Large} />
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
## All variants × sizes
|
|
47
|
+
|
|
48
|
+
<Canvas of={Stories.AllVariants} />
|
|
49
|
+
|
|
50
|
+
## Icons
|
|
51
|
+
|
|
52
|
+
Pass any `ReactNode` to `iconStart` or `iconEnd`. Icons inherit the button's `currentColor`, so they work on both filled and outline variants automatically.
|
|
53
|
+
|
|
54
|
+
<Canvas of={Stories.WithIconStart} />
|
|
55
|
+
<Canvas of={Stories.WithIconEnd} />
|
|
56
|
+
<Canvas of={Stories.WithBothIcons} />
|
|
57
|
+
|
|
58
|
+
### Icon-only (1:1 aspect ratio)
|
|
59
|
+
|
|
60
|
+
Set `isIconOnly` to render a square button with equal `xs` padding on both axes — composable with any `variant`. The icon sits centred; the button's height (from `size`) determines both width and height. Always pair with `aria-label` — there's no visible text for assistive tech to read.
|
|
61
|
+
|
|
62
|
+
<Canvas of={Stories.IconOnly} />
|
|
63
|
+
<Canvas of={Stories.IconOnlyOutline} />
|
|
64
|
+
<Canvas of={Stories.AllIconOnly} />
|
|
65
|
+
|
|
66
|
+
## Rendered as a link
|
|
67
|
+
|
|
68
|
+
Provide `href` to render as an `<a>` with button styling. `disabled` on a link sets `aria-disabled="true"` and removes it from the tab order (the `href` is also dropped so the link is non-navigable while disabled).
|
|
69
|
+
|
|
38
70
|
<Canvas of={Stories.AsLink} />
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
## Pulse (CTA emphasis)
|
|
73
|
+
|
|
74
|
+
`hasPulse` adds a subtle scale-and-glow animation for call-to-action emphasis. Respects `prefers-reduced-motion`.
|
|
42
75
|
|
|
43
|
-
### Pulse (CTA emphasis)
|
|
44
76
|
<Canvas of={Stories.WithPulse} />
|
|
45
77
|
|
|
46
|
-
|
|
78
|
+
## Disabled
|
|
79
|
+
|
|
47
80
|
<Canvas of={Stories.Disabled} />
|
|
48
81
|
|
|
82
|
+
## HTML attribute forwarding
|
|
83
|
+
|
|
84
|
+
`Button` spreads all unrecognised props onto the underlying element (`<button>` when `href` is absent, `<a>` when `href` is provided). Every standard HTML attribute for the target element is accepted as a prop, including:
|
|
85
|
+
|
|
86
|
+
- **Form-related (on `<button>`)**: `type` (default `"button"` — set to `"submit"` for form submission or `"reset"` to reset), `form`, `formAction`, `formMethod`, `formNoValidate`, `formTarget`, `name`, `value`, `autoFocus`.
|
|
87
|
+
- **Link-related (on `<a>`)**: `download`, `hreflang`, `ping`, `referrerPolicy` (plus the explicit `target` and `rel` props).
|
|
88
|
+
- **Universal**: `id`, `style`, `tabIndex`, `title`, `role`, all `aria-*` attributes, all `data-*` attributes, and every DOM event handler (`onFocus`, `onBlur`, `onKeyDown`, `onMouseEnter`, etc.).
|
|
89
|
+
|
|
90
|
+
> **Gotcha:** the rendered DOM shows `type="button"` by default because that is the component default — it is **not** forced. Pass `type="submit"` to wire the button to a parent `<form>`'s `onSubmit` handler (preferred over an `onClick` shim, because it preserves Enter-to-submit, native form validation, and assistive-tech semantics).
|
|
91
|
+
|
|
49
92
|
## Props
|
|
50
93
|
|
|
51
94
|
<ArgTypes of={Stories} />
|
|
52
95
|
|
|
53
96
|
## CSS variables
|
|
54
97
|
|
|
55
|
-
|
|
56
98
|
<table>
|
|
57
99
|
<thead>
|
|
58
100
|
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
@@ -60,19 +102,32 @@ Primary interactive element for triggering actions. Renders as `<button>` or `<a
|
|
|
60
102
|
<tbody>
|
|
61
103
|
<tr><td>`--button_color`</td><td>`var(--bds-color_interactive)`</td><td>Identity colour — fills default, outlines outline. Set this to re-theme.</td></tr>
|
|
62
104
|
<tr><td>`--button_on-color`</td><td>`var(--bds-color_on-interactive)`</td><td>Text/icon colour on the button. Pair with `--button_color`.</td></tr>
|
|
63
|
-
<tr><td>`--button_color--on-bg`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Text-safe variant of `--button_color` for outline variant text/border
|
|
64
|
-
<tr><td>`--button_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Button border radius
|
|
65
|
-
<tr><td>`--
|
|
66
|
-
<tr><td>`--
|
|
67
|
-
<tr><td>`--
|
|
68
|
-
<tr><td>`--
|
|
105
|
+
<tr><td>`--button_color--on-bg`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Text-safe variant of `--button_color` for outline variant text/border.</td></tr>
|
|
106
|
+
<tr><td>`--button_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Button border radius.</td></tr>
|
|
107
|
+
<tr><td>`--button_height`</td><td>`3em` (medium)</td><td>Button height. Also drives the width when `isIconOnly` is set.</td></tr>
|
|
108
|
+
<tr><td>`--button_bg--hover`</td><td>`var(--bds-color_bg)`</td><td>Default variant background on hover.</td></tr>
|
|
109
|
+
<tr><td>`--button_text--hover`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Default variant text colour on hover.</td></tr>
|
|
110
|
+
<tr><td>`--button_bg--outline-hover`</td><td>`var(--button_color)`</td><td>Outline variant background on hover.</td></tr>
|
|
111
|
+
<tr><td>`--button_text--outline-hover`</td><td>`var(--button_on-color)`</td><td>Outline variant text colour on hover.</td></tr>
|
|
112
|
+
<tr><td>`--button_pulse-color`</td><td>`var(--button_color)`</td><td>Halo colour for the pulse animation.</td></tr>
|
|
69
113
|
</tbody>
|
|
70
114
|
</table>
|
|
71
115
|
|
|
116
|
+
### Re-theming
|
|
117
|
+
|
|
118
|
+
Change the two identity custom properties to re-theme both variants at once:
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
.danger-zone .button {
|
|
122
|
+
--button_color: var(--bds-color_error);
|
|
123
|
+
--button_on-color: var(--bds-color_on-error);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
72
126
|
|
|
73
127
|
## Accessibility
|
|
74
128
|
|
|
75
|
-
- Always provide children or `aria-label`
|
|
76
|
-
- Disabled state sets `aria-disabled="true"` and removes the element from the tab order
|
|
77
|
-
- When rendered as `<a>`, `href` must point to a valid destination
|
|
78
|
-
- `hasPulse` animation respects `prefers-reduced-motion
|
|
129
|
+
- Always provide children **or** `aria-label` — icon-only buttons without an accessible name are a WCAG failure.
|
|
130
|
+
- Disabled state sets `aria-disabled="true"` and removes the element from the tab order.
|
|
131
|
+
- When rendered as `<a>`, `href` must point to a valid destination.
|
|
132
|
+
- `hasPulse` animation respects `prefers-reduced-motion`.
|
|
133
|
+
- Minimum hit target is enforced via the size tokens — even `size="small"` at 2.25em font-size satisfies the 44 × 44px guideline at standard root-font sizes.
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
@layer component {
|
|
30
|
+
@layer boostdev.component {
|
|
31
31
|
.button,
|
|
32
32
|
.button[href] {
|
|
33
33
|
all: unset;
|
|
@@ -96,6 +96,25 @@
|
|
|
96
96
|
animation: pulse 3s infinite;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* Icon-only: square (1:1), equal xs padding on both axes — composes with
|
|
100
|
+
any variant/size. `box-sizing: border-box` keeps the outer box at
|
|
101
|
+
`--button_height`; without it, `all: unset` reverts to content-box and
|
|
102
|
+
`padding-block` would stretch the button past its declared height.
|
|
103
|
+
Direct child fills the content area (button height minus the two
|
|
104
|
+
padding sides) so SVG icons scale with button size.
|
|
105
|
+
Nested icon overrides live after .iconStart/.iconEnd below to satisfy
|
|
106
|
+
stylelint's no-descending-specificity rule. */
|
|
107
|
+
.button.--iconOnly {
|
|
108
|
+
aspect-ratio: 1;
|
|
109
|
+
box-sizing: border-box;
|
|
110
|
+
padding: var(--bds-space_xxs);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.button.--iconOnly > * {
|
|
114
|
+
inline-size: 100%;
|
|
115
|
+
block-size: 100%;
|
|
116
|
+
}
|
|
117
|
+
|
|
99
118
|
.button[href] {
|
|
100
119
|
text-decoration: none;
|
|
101
120
|
}
|
|
@@ -122,6 +141,11 @@
|
|
|
122
141
|
transition: var(--bds-animation_transition);
|
|
123
142
|
}
|
|
124
143
|
|
|
144
|
+
.button.--iconOnly .iconStart,
|
|
145
|
+
.button.--iconOnly .iconEnd {
|
|
146
|
+
margin-inline: 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
125
149
|
@media (hover: hover) and (pointer: fine) {
|
|
126
150
|
.button:hover .iconStart svg {
|
|
127
151
|
animation: fadeZoom 2s ease-out infinite;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
1
|
+
import { Meta, Canvas, ArgTypes, Controls } from '@storybook/blocks';
|
|
2
2
|
import * as Stories from './Button.native.stories';
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
@@ -12,7 +12,7 @@ React Native button component using `Pressable`. Matches the web `Button` API as
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
|
-
import { Button } from '@boostdev/components';
|
|
15
|
+
import { Button } from '@boostdev/design-system-components';
|
|
16
16
|
|
|
17
17
|
<Button variant="default" size="medium" onPress={() => console.log('pressed')}>
|
|
18
18
|
Save
|
|
@@ -22,30 +22,48 @@ import { Button } from '@boostdev/components';
|
|
|
22
22
|
Wrap your tree in `ThemeProvider` (once, at the app root):
|
|
23
23
|
|
|
24
24
|
```tsx
|
|
25
|
-
import { ThemeProvider } from '@boostdev/components/native/ThemeContext';
|
|
25
|
+
import { ThemeProvider } from '@boostdev/design-system-components/native/ThemeContext';
|
|
26
26
|
|
|
27
27
|
export default function App() {
|
|
28
28
|
return <ThemeProvider><YourApp /></ThemeProvider>;
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Playground
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
<
|
|
34
|
+
<Canvas of={Stories.Playground} />
|
|
35
|
+
<Controls of={Stories.Playground} />
|
|
36
|
+
|
|
37
|
+
## Variants
|
|
36
38
|
|
|
37
|
-
### Default
|
|
38
39
|
<Canvas of={Stories.Default} />
|
|
40
|
+
<Canvas of={Stories.Outline} />
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
<Canvas of={Stories.Ghost} />
|
|
42
|
+
## Sizes
|
|
42
43
|
|
|
43
|
-
### Sizes
|
|
44
44
|
<Canvas of={Stories.Small} />
|
|
45
45
|
<Canvas of={Stories.Medium} />
|
|
46
46
|
<Canvas of={Stories.Large} />
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
## Icons
|
|
49
|
+
|
|
50
|
+
<Canvas of={Stories.WithIconStart} />
|
|
51
|
+
<Canvas of={Stories.WithIconEnd} />
|
|
52
|
+
|
|
53
|
+
### Icon-only (1:1 aspect ratio)
|
|
54
|
+
|
|
55
|
+
Set `isIconOnly` to render a square button with equal `spacing.xs` padding on both axes. Composable with any `variant`. Always pair with `accessibilityLabel` for assistive tech.
|
|
56
|
+
|
|
57
|
+
<Canvas of={Stories.IconOnly} />
|
|
58
|
+
<Canvas of={Stories.IconOnlyOutline} />
|
|
59
|
+
<Canvas of={Stories.AllIconOnly} />
|
|
60
|
+
|
|
61
|
+
## All variants × sizes
|
|
62
|
+
|
|
63
|
+
<Canvas of={Stories.AllVariants} />
|
|
64
|
+
|
|
65
|
+
## Disabled
|
|
66
|
+
|
|
49
67
|
<Canvas of={Stories.Disabled} />
|
|
50
68
|
|
|
51
69
|
## Props
|
|
@@ -56,13 +74,14 @@ export default function App() {
|
|
|
56
74
|
|
|
57
75
|
| Prop | Type | Default | Description |
|
|
58
76
|
|------|------|---------|-------------|
|
|
59
|
-
| `variant` | `"default" \| "
|
|
60
|
-
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Height and padding
|
|
77
|
+
| `variant` | `"default" \| "outline"` | `"default"` | Visual style (`"ghost"` is a deprecated alias for `"outline"`) |
|
|
78
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Height and horizontal padding |
|
|
61
79
|
| `disabled` | boolean | `false` | Prevents interaction, reduces opacity |
|
|
62
80
|
| `onPress` | `() => void` | — | Called when the button is pressed |
|
|
63
81
|
| `iconStart` | `ReactNode` | — | Icon before the label |
|
|
64
82
|
| `iconEnd` | `ReactNode` | — | Icon after the label |
|
|
65
|
-
| `
|
|
83
|
+
| `isIconOnly` | boolean | `false` | 1:1 aspect ratio with equal `spacing.xs` padding on both axes. Requires `accessibilityLabel`. |
|
|
84
|
+
| `accessibilityLabel` | string | — | Screen-reader label (required for icon-only buttons) |
|
|
66
85
|
| `style` | `StyleProp<ViewStyle>` | — | Override container styles |
|
|
67
86
|
|
|
68
87
|
## Accessibility
|
|
@@ -32,4 +32,24 @@ describe('Button (native)', () => {
|
|
|
32
32
|
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
33
33
|
expect(screen.getByTestId('icon-end')).toBeInTheDocument();
|
|
34
34
|
});
|
|
35
|
+
|
|
36
|
+
it('renders icon-only button with accessibilityLabel and icon as children (not wrapped in Text)', () => {
|
|
37
|
+
renderWithTheme(
|
|
38
|
+
<Button isIconOnly accessibilityLabel="Confirm">
|
|
39
|
+
<span data-testid="icon-only">✓</span>
|
|
40
|
+
</Button>
|
|
41
|
+
);
|
|
42
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByTestId('icon-only')).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders icon-only with outline variant', () => {
|
|
47
|
+
renderWithTheme(
|
|
48
|
+
<Button isIconOnly variant="outline" accessibilityLabel="Confirm">
|
|
49
|
+
<span data-testid="icon-only-outline">✓</span>
|
|
50
|
+
</Button>
|
|
51
|
+
);
|
|
52
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByTestId('icon-only-outline')).toBeInTheDocument();
|
|
54
|
+
});
|
|
35
55
|
});
|