@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.
Files changed (176) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +57 -38
  3. package/dist/client.cjs +52 -50
  4. package/dist/client.css +537 -524
  5. package/dist/client.d.cts +31 -1
  6. package/dist/client.d.ts +31 -1
  7. package/dist/client.js +52 -50
  8. package/dist/index.cjs +52 -50
  9. package/dist/index.css +537 -524
  10. package/dist/index.d.cts +31 -1
  11. package/dist/index.d.ts +31 -1
  12. package/dist/index.js +52 -50
  13. package/dist/native/index.cjs +5 -2
  14. package/dist/native/index.d.cts +3 -1
  15. package/dist/native/index.d.ts +3 -1
  16. package/dist/native/index.js +5 -2
  17. package/dist/web-components/{chunk-DZRSJGPB.js → chunk-3S42DZ7W.js} +1 -1
  18. package/dist/web-components/{chunk-3REOIRDW.js → chunk-N3TN6WCH.js} +26 -1
  19. package/dist/web-components/globals.js +1 -1
  20. package/dist/web-components/index.js +2 -2
  21. package/dist/web-components/interaction/bds-button.d.ts +7 -0
  22. package/dist/web-components/interaction/bds-button.js +1 -1
  23. package/package.json +3 -3
  24. package/src/components/interaction/Button/Button.mdx +84 -29
  25. package/src/components/interaction/Button/Button.module.css +25 -1
  26. package/src/components/interaction/Button/Button.native.mdx +33 -14
  27. package/src/components/interaction/Button/Button.native.spec.tsx +20 -0
  28. package/src/components/interaction/Button/Button.native.stories.tsx +110 -9
  29. package/src/components/interaction/Button/Button.native.tsx +13 -4
  30. package/src/components/interaction/Button/Button.spec.tsx +16 -0
  31. package/src/components/interaction/Button/Button.stories.tsx +134 -16
  32. package/src/components/interaction/Button/Button.tsx +32 -0
  33. package/src/components/interaction/Command/Command.module.css +1 -1
  34. package/src/components/interaction/Dialog/Dialog.module.css +1 -1
  35. package/src/components/interaction/Dialog/Dialog.native.mdx +2 -2
  36. package/src/components/interaction/Drawer/Drawer.module.css +1 -1
  37. package/src/components/interaction/Drawer/Drawer.native.mdx +2 -2
  38. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -1
  39. package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +2 -2
  40. package/src/components/interaction/Popover/Popover.module.css +1 -1
  41. package/src/components/interaction/Popover/Popover.native.mdx +2 -2
  42. package/src/components/interaction/Rating/Rating.module.css +1 -1
  43. package/src/components/interaction/Rating/Rating.native.mdx +1 -1
  44. package/src/components/interaction/Toast/Toast.module.css +1 -1
  45. package/src/components/interaction/Toast/Toast.native.mdx +3 -3
  46. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -1
  47. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +1 -1
  48. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +1 -1
  49. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +1 -1
  50. package/src/components/interaction/form/Combobox/Combobox.module.css +1 -1
  51. package/src/components/interaction/form/Combobox/Combobox.native.mdx +1 -1
  52. package/src/components/interaction/form/FileInput/FileInput.module.css +1 -1
  53. package/src/components/interaction/form/FileInput/FileInput.native.mdx +1 -1
  54. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -1
  55. package/src/components/interaction/form/FormInput/FormInput.native.mdx +1 -1
  56. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  57. package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +1 -1
  58. package/src/components/interaction/form/Radio/Radio.module.css +1 -1
  59. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +1 -1
  60. package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +1 -1
  61. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +1 -1
  62. package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +1 -1
  63. package/src/components/interaction/form/Select/Select.module.css +1 -1
  64. package/src/components/interaction/form/Select/Select.native.mdx +1 -1
  65. package/src/components/interaction/form/Slider/Slider.module.css +1 -1
  66. package/src/components/interaction/form/Slider/Slider.native.mdx +1 -1
  67. package/src/components/interaction/form/Switch/Switch.module.css +1 -1
  68. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
  69. package/src/components/interaction/form/Textarea/Textarea.native.mdx +1 -1
  70. package/src/components/interaction/form/atoms/InputContainer.module.css +1 -1
  71. package/src/components/interaction/form/atoms/Label.module.css +1 -1
  72. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  73. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -1
  74. package/src/components/layout/Card/Card.module.css +1 -1
  75. package/src/components/layout/IconWrapper/IconWrapper.module.css +1 -1
  76. package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +46 -14
  77. package/src/components/layout/SectionHeader/SectionHeader.module.css +1 -1
  78. package/src/components/ui/Accordion/Accordion.module.css +1 -1
  79. package/src/components/ui/Accordion/Accordion.native.mdx +1 -1
  80. package/src/components/ui/Accordion/Accordion.native.stories.tsx +1 -1
  81. package/src/components/ui/Alert/Alert.module.css +1 -1
  82. package/src/components/ui/Alert/Alert.native.mdx +1 -1
  83. package/src/components/ui/Avatar/Avatar.module.css +1 -1
  84. package/src/components/ui/Avatar/Avatar.native.mdx +1 -1
  85. package/src/components/ui/Badge/Badge.module.css +1 -1
  86. package/src/components/ui/Badge/Badge.native.mdx +1 -1
  87. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +1 -1
  88. package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +1 -1
  89. package/src/components/ui/Calendar/Calendar.module.css +1 -1
  90. package/src/components/ui/Calendar/Calendar.native.mdx +1 -1
  91. package/src/components/ui/Carousel/Carousel.module.css +1 -1
  92. package/src/components/ui/Carousel/Carousel.native.mdx +1 -1
  93. package/src/components/ui/Collapsible/Collapsible.module.css +1 -1
  94. package/src/components/ui/Collapsible/Collapsible.native.mdx +1 -1
  95. package/src/components/ui/DescriptionList/DescriptionList.module.css +1 -1
  96. package/src/components/ui/DescriptionList/DescriptionList.native.mdx +1 -1
  97. package/src/components/ui/Link/Link.module.css +1 -1
  98. package/src/components/ui/Link/Link.native.mdx +1 -1
  99. package/src/components/ui/Loading/Loading.module.css +1 -1
  100. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -1
  101. package/src/components/ui/Pagination/Pagination.module.css +1 -1
  102. package/src/components/ui/Pagination/Pagination.native.mdx +1 -1
  103. package/src/components/ui/Progress/Progress.module.css +1 -1
  104. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
  105. package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +1 -1
  106. package/src/components/ui/Separator/Separator.module.css +1 -1
  107. package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
  108. package/src/components/ui/SkipLink/SkipLink.module.css +1 -1
  109. package/src/components/ui/Table/Table.module.css +1 -1
  110. package/src/components/ui/Table/Table.native.mdx +1 -1
  111. package/src/components/ui/Tabs/Tabs.module.css +1 -1
  112. package/src/components/ui/Tabs/Tabs.native.mdx +1 -1
  113. package/src/components/ui/Tooltip/Tooltip.module.css +1 -1
  114. package/src/components/ui/Tooltip/Tooltip.native.mdx +1 -1
  115. package/src/components/ui/Typography/Typography.module.css +1 -1
  116. package/src/components/ui/Typography/Typography.native.mdx +1 -1
  117. package/src/css/bdc.css +1 -1
  118. package/src/css/index.css +1 -1
  119. package/src/stories/DesignSystem/Borders.mdx +3 -3
  120. package/src/stories/DesignSystem/Colors.mdx +1 -1
  121. package/src/stories/DesignSystem/DarkMode.mdx +3 -3
  122. package/src/stories/DesignSystem/Overview.mdx +16 -5
  123. package/src/stories/DesignSystem/Typography.mdx +1 -1
  124. package/src/stories/Introduction.mdx +4 -4
  125. package/src/stories/ReactNative.mdx +6 -6
  126. package/src/web-components/globals.ts +1 -1
  127. package/src/web-components/interaction/BdsAccordion.mdx +3 -3
  128. package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
  129. package/src/web-components/interaction/BdsButton.mdx +48 -16
  130. package/src/web-components/interaction/BdsButton.stories.tsx +171 -19
  131. package/src/web-components/interaction/BdsCollapsible.mdx +2 -2
  132. package/src/web-components/interaction/BdsDialog.mdx +2 -2
  133. package/src/web-components/interaction/BdsDrawer.mdx +2 -2
  134. package/src/web-components/interaction/BdsDropdownMenu.mdx +2 -2
  135. package/src/web-components/interaction/BdsPopover.mdx +1 -1
  136. package/src/web-components/interaction/BdsSkipLink.mdx +2 -2
  137. package/src/web-components/interaction/BdsTabs.mdx +2 -2
  138. package/src/web-components/interaction/BdsToastProvider.mdx +3 -3
  139. package/src/web-components/interaction/BdsTooltip.mdx +2 -2
  140. package/src/web-components/interaction/bds-button.spec.ts +35 -0
  141. package/src/web-components/interaction/bds-button.ts +28 -1
  142. package/src/web-components/interaction/form/BdsCheckbox.mdx +1 -1
  143. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +1 -1
  144. package/src/web-components/interaction/form/BdsCombobox.mdx +1 -1
  145. package/src/web-components/interaction/form/BdsFileInput.mdx +1 -1
  146. package/src/web-components/interaction/form/BdsFormInput.mdx +1 -1
  147. package/src/web-components/interaction/form/BdsNumberInput.mdx +1 -1
  148. package/src/web-components/interaction/form/BdsRadio.mdx +1 -1
  149. package/src/web-components/interaction/form/BdsRadioGroup.mdx +1 -1
  150. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +1 -1
  151. package/src/web-components/interaction/form/BdsSelect.mdx +1 -1
  152. package/src/web-components/interaction/form/BdsSlider.mdx +1 -1
  153. package/src/web-components/interaction/form/BdsSwitch.mdx +1 -1
  154. package/src/web-components/interaction/form/BdsTextarea.mdx +1 -1
  155. package/src/web-components/ui/BdsAlert.mdx +1 -1
  156. package/src/web-components/ui/BdsAvatar.mdx +1 -1
  157. package/src/web-components/ui/BdsBadge.mdx +1 -1
  158. package/src/web-components/ui/BdsBreadcrumb.mdx +1 -1
  159. package/src/web-components/ui/BdsButtonGroup.mdx +1 -1
  160. package/src/web-components/ui/BdsCalendar.mdx +1 -1
  161. package/src/web-components/ui/BdsCard.mdx +1 -1
  162. package/src/web-components/ui/BdsCarousel.mdx +1 -1
  163. package/src/web-components/ui/BdsDescriptionList.mdx +1 -1
  164. package/src/web-components/ui/BdsIconWrapper.mdx +1 -1
  165. package/src/web-components/ui/BdsLink.mdx +1 -1
  166. package/src/web-components/ui/BdsLoading.mdx +1 -1
  167. package/src/web-components/ui/BdsNotificationBanner.mdx +1 -1
  168. package/src/web-components/ui/BdsPagination.mdx +1 -1
  169. package/src/web-components/ui/BdsProgress.mdx +1 -1
  170. package/src/web-components/ui/BdsProgressCircle.mdx +1 -1
  171. package/src/web-components/ui/BdsRating.mdx +1 -1
  172. package/src/web-components/ui/BdsSectionHeader.mdx +1 -1
  173. package/src/web-components/ui/BdsSeparator.mdx +2 -2
  174. package/src/web-components/ui/BdsSkeleton.mdx +1 -1
  175. package/src/web-components/ui/BdsTable.mdx +1 -1
  176. package/src/web-components/ui/BdsTypography.mdx +1 -1
@@ -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
  }
@@ -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;
@@ -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;
@@ -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
  }
@@ -30,7 +30,7 @@ var bdcCss = `
30
30
  initial-value: 0 0 0 0 transparent;
31
31
  }
32
32
 
33
- @layer component {
33
+ @layer boostdev.component {
34
34
  :root {
35
35
  --bdc-outline_border: none;
36
36
 
@@ -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-DZRSJGPB.js";
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-3REOIRDW.js";
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-DZRSJGPB.js";
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;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsButton
3
- } from "../chunk-3REOIRDW.js";
3
+ } from "../chunk-N3TN6WCH.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsButton
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "1.2.8",
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": "^1.0.6",
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": ">=1.0.6",
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 ghost actions (`ghost`)
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. This provides consistent spacing, alignment, and responsive layout for form actions. Use `variant="flow"` for step navigation or `variant="card"` for single-form submissions.
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
- - When the action is purely icon-based without a label — ensure `aria-label` is set
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
- ## Examples
28
+ ## Variants
22
29
 
23
- ### All variants & sizes
24
- <Canvas of={Stories.AllVariants} />
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
- ### Ghost
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
- ### As a link
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
- ### With icons
41
- <Canvas of={Stories.WithIcons} />
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
- ### Disabled
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</td></tr>
64
- <tr><td>`--button_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Button border radius</td></tr>
65
- <tr><td>`--button_bg--hover`</td><td>`var(--bds-color_bg)`</td><td>Default variant background on hover</td></tr>
66
- <tr><td>`--button_text--hover`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Default variant text colour on hover</td></tr>
67
- <tr><td>`--button_bg--outline-hover`</td><td>`var(--button_color)`</td><td>Outline variant background on hover</td></tr>
68
- <tr><td>`--button_text--outline-hover`</td><td>`var(--button_on-color)`</td><td>Outline variant text colour on hover</td></tr>
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` for icon-only buttons
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
- ## Examples
32
+ ## Playground
33
33
 
34
- ### All variants & sizes
35
- <Canvas of={Stories.AllVariants} />
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
- ### Ghost
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
- ### Disabled
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" \| "ghost"` | `"default"` | Visual style |
60
- | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Height and padding scale |
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
- | `accessibilityLabel` | string | | Screen reader label (required for icon-only buttons) |
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
  });