@arbor-education/design-system.components 1.0.5 → 2.0.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 (116) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/components/breadcrumbs/BreadcrumbCopyButton.d.ts.map +1 -1
  3. package/dist/components/breadcrumbs/BreadcrumbCopyButton.js +3 -2
  4. package/dist/components/breadcrumbs/BreadcrumbCopyButton.js.map +1 -1
  5. package/dist/components/breadcrumbs/BreadcrumbEllipsis.d.ts.map +1 -1
  6. package/dist/components/breadcrumbs/BreadcrumbEllipsis.js +2 -1
  7. package/dist/components/breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
  8. package/dist/components/button/Button.d.ts +17 -8
  9. package/dist/components/button/Button.d.ts.map +1 -1
  10. package/dist/components/button/Button.js +5 -6
  11. package/dist/components/button/Button.js.map +1 -1
  12. package/dist/components/button/Button.stories.d.ts +17 -36
  13. package/dist/components/button/Button.stories.d.ts.map +1 -1
  14. package/dist/components/button/Button.stories.js +165 -214
  15. package/dist/components/button/Button.stories.js.map +1 -1
  16. package/dist/components/button/Button.test.d.ts.map +1 -1
  17. package/dist/components/button/Button.test.js +16 -10
  18. package/dist/components/button/Button.test.js.map +1 -1
  19. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  20. package/dist/components/datePicker/DatePicker.js +2 -1
  21. package/dist/components/datePicker/DatePicker.js.map +1 -1
  22. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
  23. package/dist/components/dateTimePicker/DateTimePicker.js +2 -1
  24. package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
  25. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
  26. package/dist/components/dropdown/Dropdown.stories.js +5 -3
  27. package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
  28. package/dist/components/emptyState/EmptyState.stories.d.ts.map +1 -1
  29. package/dist/components/emptyState/EmptyState.stories.js +3 -2
  30. package/dist/components/emptyState/EmptyState.stories.js.map +1 -1
  31. package/dist/components/fileUpload/FileUpload.d.ts.map +1 -1
  32. package/dist/components/fileUpload/FileUpload.js +1 -1
  33. package/dist/components/fileUpload/FileUpload.js.map +1 -1
  34. package/dist/components/fileUpload/FileUpload.test.js +4 -4
  35. package/dist/components/fileUpload/FileUpload.test.js.map +1 -1
  36. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.d.ts.map +1 -1
  37. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.js +2 -1
  38. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.js.map +1 -1
  39. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  40. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -1
  41. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  42. package/dist/components/grid/Grid.stories.d.ts.map +1 -1
  43. package/dist/components/grid/Grid.stories.js +4 -3
  44. package/dist/components/grid/Grid.stories.js.map +1 -1
  45. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  46. package/dist/components/heading/Heading.stories.js +18 -14
  47. package/dist/components/heading/Heading.stories.js.map +1 -1
  48. package/dist/components/icon/Icon.stories.d.ts.map +1 -1
  49. package/dist/components/icon/Icon.stories.js +30 -31
  50. package/dist/components/icon/Icon.stories.js.map +1 -1
  51. package/dist/components/modal/ModalCloseButton.d.ts.map +1 -1
  52. package/dist/components/modal/ModalCloseButton.js +2 -1
  53. package/dist/components/modal/ModalCloseButton.js.map +1 -1
  54. package/dist/components/section/Section.stories.d.ts.map +1 -1
  55. package/dist/components/section/Section.stories.js +52 -25
  56. package/dist/components/section/Section.stories.js.map +1 -1
  57. package/dist/components/table/Table.stories.d.ts.map +1 -1
  58. package/dist/components/table/Table.stories.js +4 -6
  59. package/dist/components/table/Table.stories.js.map +1 -1
  60. package/dist/components/table/pagination/PageSizeSelector.d.ts.map +1 -1
  61. package/dist/components/table/pagination/PageSizeSelector.js +2 -1
  62. package/dist/components/table/pagination/PageSizeSelector.js.map +1 -1
  63. package/dist/components/table/pagination/PaginationControls.d.ts.map +1 -1
  64. package/dist/components/table/pagination/PaginationControls.js +3 -2
  65. package/dist/components/table/pagination/PaginationControls.js.map +1 -1
  66. package/dist/components/table/tableControls/BulkActionsDropdown.d.ts.map +1 -1
  67. package/dist/components/table/tableControls/BulkActionsDropdown.js +3 -2
  68. package/dist/components/table/tableControls/BulkActionsDropdown.js.map +1 -1
  69. package/dist/components/table/tableControls/TableControls.d.ts.map +1 -1
  70. package/dist/components/table/tableControls/TableControls.js +2 -1
  71. package/dist/components/table/tableControls/TableControls.js.map +1 -1
  72. package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
  73. package/dist/components/table/tableControls/TableSettingsDropdown.js +2 -1
  74. package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
  75. package/dist/components/toast/Toast.js +1 -1
  76. package/dist/components/toast/Toast.js.map +1 -1
  77. package/dist/components/tooltip/Tooltip.stories.js +5 -5
  78. package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
  79. package/dist/components/tooltip/TooltipWrapper.stories.js +2 -2
  80. package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
  81. package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts.map +1 -1
  82. package/dist/components/userDropdown/internal/UserDropdownTrigger.js +3 -2
  83. package/dist/components/userDropdown/internal/UserDropdownTrigger.js.map +1 -1
  84. package/dist/index.css +15 -15
  85. package/dist/index.css.map +1 -1
  86. package/package.json +1 -1
  87. package/src/components/breadcrumbs/BreadcrumbCopyButton.tsx +5 -2
  88. package/src/components/breadcrumbs/BreadcrumbEllipsis.tsx +5 -2
  89. package/src/components/breadcrumbs/breadcrumbs.scss +2 -2
  90. package/src/components/button/Button.stories.tsx +205 -230
  91. package/src/components/button/Button.test.tsx +36 -9
  92. package/src/components/button/Button.tsx +6 -16
  93. package/src/components/button/button.scss +8 -8
  94. package/src/components/datePicker/DatePicker.tsx +5 -3
  95. package/src/components/dateTimePicker/DateTimePicker.tsx +5 -3
  96. package/src/components/dropdown/Dropdown.stories.tsx +7 -3
  97. package/src/components/emptyState/EmptyState.stories.tsx +4 -2
  98. package/src/components/fileUpload/FileUpload.test.tsx +4 -4
  99. package/src/components/fileUpload/FileUpload.tsx +8 -4
  100. package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.tsx +3 -1
  101. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +3 -2
  102. package/src/components/grid/Grid.stories.tsx +17 -4
  103. package/src/components/heading/Heading.stories.tsx +23 -16
  104. package/src/components/icon/Icon.stories.tsx +43 -44
  105. package/src/components/modal/ModalCloseButton.tsx +5 -3
  106. package/src/components/section/Section.stories.tsx +86 -31
  107. package/src/components/table/Table.stories.tsx +19 -6
  108. package/src/components/table/pagination/PageSizeSelector.tsx +3 -1
  109. package/src/components/table/pagination/PaginationControls.tsx +17 -12
  110. package/src/components/table/tableControls/BulkActionsDropdown.tsx +3 -1
  111. package/src/components/table/tableControls/TableControls.tsx +31 -22
  112. package/src/components/table/tableControls/TableSettingsDropdown.tsx +7 -4
  113. package/src/components/toast/Toast.tsx +1 -1
  114. package/src/components/tooltip/Tooltip.stories.tsx +7 -7
  115. package/src/components/tooltip/TooltipWrapper.stories.tsx +4 -4
  116. package/src/components/userDropdown/internal/UserDropdownTrigger.tsx +2 -1
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
3
3
  import { useState, useRef } from 'react';
4
4
  import { Button } from './Button.js';
5
+ import { Icon } from '../icon/Icon.js';
5
6
  // ---------------------------------------------------------------------------
6
7
  // Component description — built as a joined array to avoid no-useless-escape
7
8
  // on backtick code spans inside template literals.
@@ -13,8 +14,9 @@ import { Button } from './Button.js';
13
14
  // ---------------------------------------------------------------------------
14
15
  const DESCRIPTION_INTRO = [
15
16
  'The **Button** is the primary interactive control in the Arbor design system. It wraps a native',
16
- '`<button>` element with `forwardRef` support, consistent design tokens, icon slots, and a full set',
17
- 'of variant and state modifiers.',
17
+ '`<button>` element with `forwardRef` support, consistent design tokens, and a full set',
18
+ 'of variant and state modifiers. Icons are composed via `children` using the `Icon` component,',
19
+ 'so consumers control icon placement, colour, and accessible text directly.',
18
20
  ].join('\n');
19
21
  const USAGE_GUIDANCE = [
20
22
  '### When to use',
@@ -57,9 +59,10 @@ const DEVELOPER_NOTES = [
57
59
  '### Critical gotchas',
58
60
  '',
59
61
  '#### 1. The `type` attribute trap',
60
- 'The `type` prop is **deliberately omitted** from the component\'s prop spread',
61
- '(`Omit<ButtonHTMLAttributes<HTMLButtonElement>, \'type\' | \'onClick\'>` on `Button.tsx` line 24).',
62
- 'This means the underlying `<button>` has no `type` attribute, and browsers default to',
62
+ '`type` is a fully typed prop (`\'button\' | \'submit\' | \'reset\'`, inherited from',
63
+ '`ButtonHTMLAttributes<HTMLButtonElement>`), but the component **does not default it**',
64
+ '— there is no `type = \'button\'` fallback in `Button.tsx`. This means that if you omit it,',
65
+ 'the underlying `<button>` has no `type` attribute, and browsers default to',
63
66
  '`type="submit"`. If this button lives inside a `<form>`, clicking it **will submit the form**',
64
67
  'unless you explicitly pass `type="button"`.',
65
68
  '',
@@ -71,17 +74,22 @@ const DEVELOPER_NOTES = [
71
74
  '<Button variant="secondary" type="button" onClick={handleCancel}>Cancel</Button>',
72
75
  '```',
73
76
  '',
74
- '#### 2. Icon-only accessibility',
75
- 'When `children` is omitted, the component enters icon-only mode (`ds-button--icon-only`). The icon',
76
- 'name becomes the default screen reader text, but icon names like `"3-dot"` or `"x"` are not',
77
- 'meaningful in context. Always provide `iconLeftScreenReaderText` or `iconRightScreenReaderText`.',
77
+ '#### 2. Icons are composed via `children`',
78
+ 'Icons are no longer separate props — render an `Icon` as part of `children`, placed before and/or',
79
+ 'after the label text. Set the explicit `isRound` prop for square, no-padding sizing when there is',
80
+ 'no visible label. Because the icon is just a child element, always pass `screenReaderText` on the',
81
+ '`Icon` yourself when it is the button\'s only accessible content — icon names like `"3-dot"` or',
82
+ '`"x"` are not meaningful in context. When an icon sits alongside a visible label (e.g. a trailing',
83
+ 'chevron), omit `screenReaderText` so it stays decorative and does not duplicate the label.',
78
84
  '',
79
85
  '```tsx',
80
- '// BAD — screen reader says "3-dot"',
81
- '<Button variant="secondary" iconRightName="3-dot" />',
86
+ '// BAD — no accessible name at all',
87
+ '<Button variant="secondary" isRound><Icon name="3-dot" /></Button>',
82
88
  '',
83
89
  '// GOOD',
84
- '<Button variant="secondary" iconRightName="3-dot" iconRightScreenReaderText="More actions" />',
90
+ '<Button variant="secondary" isRound>',
91
+ ' <Icon name="3-dot" screenReaderText="More actions" />',
92
+ '</Button>',
85
93
  '```',
86
94
  '',
87
95
  '---',
@@ -91,10 +99,32 @@ const DEVELOPER_NOTES = [
91
99
  '- Native `<button>` — focusable with Tab, activated with Space or Enter',
92
100
  '- Focus ring: 3px outline using `--focus-border` and `--color-brand-500` via `--focus-color-focus`',
93
101
  '- `disabled` removes the button from tab order AND blocks pointer events (`opacity: 0.5; cursor: not-allowed; pointer-events: none`)',
94
- '- Icon-only mode: provide `iconLeftScreenReaderText` / `iconRightScreenReaderText` for meaningful labels',
102
+ '- Icon-only mode (`isRound`): always pass `screenReaderText` on the child `Icon` — it is the button\'s only accessible label',
95
103
  '- The `error` prop is **purely visual** (adds a red border) — it carries no ARIA semantics.',
96
104
  ' The consumer is still responsible for communicating the error state to assistive tech — e.g.',
97
105
  ' setting `aria-invalid` on the associated input or wiring `aria-describedby` to an error message.',
106
+ '- `disabled` sets `pointer-events: none` via CSS, so `onClick` genuinely cannot fire while disabled —',
107
+ ' no defensive guard is needed inside the handler itself.',
108
+ '',
109
+ '---',
110
+ '',
111
+ '#### Focus, hover, and active are CSS-only',
112
+ 'There is no `focused` / `hovered` / `active` prop. Each variant defines its own `:focus`, `:hover`,',
113
+ 'and `:active` rules directly in `button.scss` — these are native pseudo-classes on the underlying',
114
+ '`<button>`, not something Storybook Controls can toggle. To see them, interact with the rendered',
115
+ 'button in the canvas (click, tab to it, or hover over it).',
116
+ '',
117
+ '---',
118
+ '',
119
+ '#### The `` `[key: `data-${string}`]: unknown` `` index signature',
120
+ '`ButtonProps` includes a narrow catch-all that matches **only** `data-*` keys, spread via `...rest`',
121
+ 'straight onto the native `<button>` element — the one open-ended attribute pattern with no fixed',
122
+ 'name list (`data-testid`, `data-component`, etc.). `aria-*` and all standard HTML attributes are',
123
+ 'separately and fully typed via `ButtonHTMLAttributes`, so they do not need this catch-all. Because',
124
+ 'the signature is scoped to `data-*`, a typo in a known prop name (e.g. `isRond` instead of `isRound`)',
125
+ 'is a **compile error**, not a silent unrecognised DOM attribute — this used to be a blanket',
126
+ '`[key: string]: unknown` that swallowed any misspelled or removed prop name; it was narrowed for',
127
+ 'exactly this reason.',
98
128
  '',
99
129
  '---',
100
130
  '',
@@ -176,10 +206,9 @@ const meta = {
176
206
  children: {
177
207
  control: 'text',
178
208
  description: [
179
- 'Button label content. Accepts any `React.ReactNode`.',
180
- 'When omitted (and at least one icon prop is provided), the button enters',
181
- 'icon-only mode (`ds-button--icon-only`) — square aspect ratio, no padding compensation needed.',
182
- 'Always pair icon-only mode with descriptive `iconLeftScreenReaderText` / `iconRightScreenReaderText`.',
209
+ 'Button label content. Accepts any `React.ReactNode`, including `Icon` elements placed before',
210
+ 'and/or after the label text — Button renders `children` as-is with no separate icon slots.',
211
+ 'For an icon-only button, set `isRound` and always pass `screenReaderText` on the `Icon`.',
183
212
  ].join(' '),
184
213
  table: {
185
214
  type: { summary: 'React.ReactNode' },
@@ -223,94 +252,16 @@ const meta = {
223
252
  defaultValue: { summary: 'true' },
224
253
  },
225
254
  },
226
- iconLeftName: {
227
- control: 'select',
228
- options: [
229
- undefined,
230
- 'plus',
231
- 'pencil',
232
- 'save',
233
- 'trash',
234
- 'download',
235
- 'upload',
236
- 'mail',
237
- 'search',
238
- 'info',
239
- 'check',
240
- 'x',
241
- 'arrow-left',
242
- 'arrow-right',
243
- 'settings',
244
- 'loader',
245
- '3-dot',
246
- 'user',
247
- 'send',
248
- ],
249
- description: [
250
- 'Name of an icon to render **before** the button label at 16px.',
251
- 'Renders an `<Icon>` component with `size={16}`.',
252
- 'Screen reader text defaults to the icon name — always override with `iconLeftScreenReaderText`',
253
- 'when the icon name is not meaningful in context (e.g. `"3-dot"`, `"x"`).',
254
- ].join(' '),
255
- table: {
256
- type: { summary: 'IconName' },
257
- defaultValue: { summary: 'undefined' },
258
- },
259
- },
260
- iconLeftScreenReaderText: {
261
- control: 'text',
262
- description: [
263
- 'Screen reader label for the left icon.',
264
- 'Defaults to the icon name when omitted.',
265
- 'In icon-only mode this becomes the accessible label for the entire button — make it descriptive.',
266
- ].join(' '),
267
- table: {
268
- type: { summary: 'string' },
269
- },
270
- },
271
- iconRightName: {
272
- control: 'select',
273
- options: [
274
- undefined,
275
- 'plus',
276
- 'pencil',
277
- 'save',
278
- 'trash',
279
- 'download',
280
- 'upload',
281
- 'mail',
282
- 'search',
283
- 'info',
284
- 'check',
285
- 'x',
286
- 'arrow-right',
287
- 'chevron-down',
288
- 'chevron-up',
289
- 'settings',
290
- 'loader',
291
- '3-dot',
292
- 'external-link',
293
- 'send',
294
- ],
295
- description: [
296
- 'Name of an icon to render **after** the button label at 16px.',
297
- 'Renders an `<Icon>` component with `size={16}`.',
298
- 'Screen reader text defaults to the icon name — always override with `iconRightScreenReaderText`.',
299
- ].join(' '),
300
- table: {
301
- type: { summary: 'IconName' },
302
- defaultValue: { summary: 'undefined' },
303
- },
304
- },
305
- iconRightScreenReaderText: {
306
- control: 'text',
255
+ isRound: {
256
+ control: 'boolean',
307
257
  description: [
308
- 'Screen reader label for the right icon.',
309
- 'Defaults to the icon name when omitted.',
310
- 'In icon-only mode this is the accessible label — always set it explicitly.',
258
+ 'Applies equal padding on all sides (`ds-button--round`) for a square, compact button.',
259
+ 'Commonly used for icon-only buttons — in that case, always pass `screenReaderText` on the child',
260
+ '`Icon`, since it becomes the button\'s only accessible label.',
311
261
  ].join(' '),
312
262
  table: {
313
- type: { summary: 'string' },
263
+ type: { summary: 'boolean' },
264
+ defaultValue: { summary: 'false' },
314
265
  },
315
266
  },
316
267
  borderless: {
@@ -392,7 +343,7 @@ const LoadingCompositionTemplate = () => {
392
343
  setDone(true);
393
344
  }, 2000);
394
345
  };
395
- return (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)', alignItems: 'center' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Button has no built-in loading state \u2014 compose it with disabled + a loader icon." }), done && (_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "Exported successfully!" })), _jsx(Button, { variant: "primary", type: "button", disabled: loading, iconLeftName: loading ? 'loader' : undefined, iconLeftScreenReaderText: loading ? 'Loading, please wait' : undefined, onClick: handleClick, children: loading ? 'Exporting...' : 'Export to CSV' })] }));
346
+ return (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)', alignItems: 'center' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Button has no built-in loading state \u2014 compose it with disabled + a loader icon." }), done && (_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "Exported successfully!" })), _jsxs(Button, { variant: "primary", type: "button", disabled: loading, onClick: handleClick, children: [loading && _jsx(Icon, { name: "loader", screenReaderText: "Loading, please wait" }), loading ? 'Exporting...' : 'Export to CSV'] })] }));
396
347
  };
397
348
  // ---------------------------------------------------------------------------
398
349
  // Stories
@@ -408,7 +359,7 @@ export const Default = withDescription({
408
359
  borderless: false,
409
360
  },
410
361
  render: args => _jsx(Button, { ...args }),
411
- }, 'The interactive canvas story — every prop is wired to the **Controls** panel. Use the controls to explore variants, sizes, icons, and states. Tip: try setting `iconLeftName` to `"save"` or `"plus"` to see the icon slot in action.');
362
+ }, 'The interactive canvas story — every prop is wired to the **Controls** panel. Use the controls to explore variants, sizes, and states. See the `WithIconLeft` / `WithIconRight` / `IconOnly` stories for icon composition, since icons are rendered via `children` rather than a Controls-editable prop.');
412
363
  export const Primary = withDescription({
413
364
  args: {
414
365
  variant: 'primary',
@@ -557,7 +508,7 @@ export const Dropdown = withDescription({
557
508
  args: {
558
509
  variant: 'dropdown',
559
510
  size: 'M',
560
- children: 'Select year group',
511
+ children: (_jsxs(_Fragment, { children: ["Select year group", _jsx(Icon, { name: "chevron-down" })] })),
561
512
  },
562
513
  parameters: {
563
514
  docs: {
@@ -565,11 +516,13 @@ export const Dropdown = withDescription({
565
516
  language: 'tsx',
566
517
  code: `
567
518
  import { Button } from '@arbor-education/design-system.components';
519
+ import { Icon } from '@arbor-education/design-system.components';
568
520
 
569
521
  function ButtonDropdownExample() {
570
522
  return (
571
- <Button variant="dropdown" size="M" iconRightName="chevron-down" iconRightScreenReaderText="Open dropdown">
523
+ <Button variant="dropdown" size="M">
572
524
  Select year group
525
+ <Icon name="chevron-down" />
573
526
  </Button>
574
527
  );
575
528
  }
@@ -583,8 +536,8 @@ export default ButtonDropdownExample;
583
536
  'The `dropdown` variant has `justify-content: space-between` and specific padding designed for use as a',
584
537
  'dropdown trigger — a trailing chevron icon aligns to the right edge automatically. Used internally by',
585
538
  '`SelectDropdown`, `ColourPickerDropdown`, and `UserDropdown`, and available to consumers for custom',
586
- 'dropdown triggers. Pair it with a chevron icon (e.g. `iconRightName="chevron-down"`) for the canonical',
587
- 'look.',
539
+ 'dropdown triggers. Pair it with a trailing chevron `Icon` (e.g. `<Icon name="chevron-down" />`) for the',
540
+ 'canonical look — it is decorative, so it does not need `screenReaderText` when a visible label is present.',
588
541
  ].join(' '));
589
542
  export const AllVariants = withDescription({
590
543
  render: () => (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xlarge)' }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Action hierarchy" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)', flexWrap: 'wrap' }, children: [_jsx(Button, { variant: "primary", type: "button", children: "Save attendance" }), _jsx(Button, { variant: "secondary", type: "button", children: "Cancel" }), _jsx(Button, { variant: "tertiary", type: "button", children: "Clear filters" })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-destructive-600)' }, children: "Destructive actions" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)', flexWrap: 'wrap' }, children: [_jsx(Button, { variant: "primary-destructive", type: "button", children: "Delete permanently" }), _jsx(Button, { variant: "secondary-destructive", type: "button", children: "Archive record" })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Specialised" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)', flexWrap: 'wrap' }, children: [_jsx(Button, { variant: "text-link", type: "button", children: "View full report" }), _jsx(Button, { variant: "dropdown", type: "button", children: "Select year group" })] })] })] })),
@@ -709,9 +662,7 @@ export const WithIconLeft = withDescription({
709
662
  args: {
710
663
  variant: 'primary',
711
664
  size: 'M',
712
- children: 'Export to CSV',
713
- iconLeftName: 'download',
714
- iconLeftScreenReaderText: 'Download',
665
+ children: (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "download" }), "Export to CSV"] })),
715
666
  },
716
667
  parameters: {
717
668
  docs: {
@@ -719,10 +670,12 @@ export const WithIconLeft = withDescription({
719
670
  language: 'tsx',
720
671
  code: `
721
672
  import { Button } from '@arbor-education/design-system.components';
673
+ import { Icon } from '@arbor-education/design-system.components';
722
674
 
723
675
  function ButtonWithIconLeftExample() {
724
676
  return (
725
- <Button variant="primary" size="M" iconLeftName="download" iconLeftScreenReaderText="Download">
677
+ <Button variant="primary" size="M">
678
+ <Icon name="download" />
726
679
  Export to CSV
727
680
  </Button>
728
681
  );
@@ -733,14 +686,12 @@ export default ButtonWithIconLeftExample;
733
686
  },
734
687
  },
735
688
  },
736
- }, 'Icon on the left of the label via `iconLeftName`. The icon renders at 16px. `iconLeftScreenReaderText` overrides the accessible label for the icon — here it is set to `"Download"` which is more natural than the icon name `"download"` (though similar in this case). Use left icons for actions where the icon reinforces the verb: download, add, send.');
689
+ }, 'Icon placed before the label by rendering an `Icon` as the first child. `Icon` defaults to `size={16}` and `color="currentColor"`, so it inherits the button\'s text colour automatically. The icon sits next to a visible label here, so it is left decorative (no `screenReaderText`). Use leading icons for actions where the icon reinforces the verb: download, add, send.');
737
690
  export const WithIconRight = withDescription({
738
691
  args: {
739
692
  variant: 'secondary',
740
693
  size: 'M',
741
- children: 'Send to parents',
742
- iconRightName: 'send',
743
- iconRightScreenReaderText: 'Send',
694
+ children: (_jsxs(_Fragment, { children: ["Send to parents", _jsx(Icon, { name: "send" })] })),
744
695
  },
745
696
  parameters: {
746
697
  docs: {
@@ -748,11 +699,13 @@ export const WithIconRight = withDescription({
748
699
  language: 'tsx',
749
700
  code: `
750
701
  import { Button } from '@arbor-education/design-system.components';
702
+ import { Icon } from '@arbor-education/design-system.components';
751
703
 
752
704
  function ButtonWithIconRightExample() {
753
705
  return (
754
- <Button variant="secondary" size="M" iconRightName="send" iconRightScreenReaderText="Send">
706
+ <Button variant="secondary" size="M">
755
707
  Send to parents
708
+ <Icon name="send" />
756
709
  </Button>
757
710
  );
758
711
  }
@@ -762,16 +715,12 @@ export default ButtonWithIconRightExample;
762
715
  },
763
716
  },
764
717
  },
765
- }, 'Icon on the right of the label via `iconRightName`. Right icons typically indicate direction or outcome: `arrow-right` for "next", `external-link` for "opens in new tab", `chevron-down` for "opens a menu". Pair right icons with the `dropdown` variant for SelectDropdown triggers.');
718
+ }, 'Icon placed after the label by rendering an `Icon` as the last child. Trailing icons typically indicate direction or outcome: `arrow-right` for "next", `external-link` for "opens in new tab", `chevron-down` for "opens a menu". Pair a trailing chevron `Icon` with the `dropdown` variant for SelectDropdown-style triggers.');
766
719
  export const WithBothIcons = withDescription({
767
720
  args: {
768
721
  variant: 'primary',
769
722
  size: 'M',
770
- children: 'Generate report',
771
- iconLeftName: 'sparkles',
772
- iconLeftScreenReaderText: 'AI generated',
773
- iconRightName: 'arrow-right',
774
- iconRightScreenReaderText: 'Continue',
723
+ children: (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "sparkles", screenReaderText: "AI generated" }), "Generate report", _jsx(Icon, { name: "arrow-right" })] })),
775
724
  },
776
725
  parameters: {
777
726
  docs: {
@@ -779,18 +728,14 @@ export const WithBothIcons = withDescription({
779
728
  language: 'tsx',
780
729
  code: `
781
730
  import { Button } from '@arbor-education/design-system.components';
731
+ import { Icon } from '@arbor-education/design-system.components';
782
732
 
783
733
  function ButtonWithBothIconsExample() {
784
734
  return (
785
- <Button
786
- variant="primary"
787
- size="M"
788
- iconLeftName="sparkles"
789
- iconLeftScreenReaderText="AI generated"
790
- iconRightName="arrow-right"
791
- iconRightScreenReaderText="Continue"
792
- >
735
+ <Button variant="primary" size="M">
736
+ <Icon name="sparkles" screenReaderText="AI generated" />
793
737
  Generate report
738
+ <Icon name="arrow-right" />
794
739
  </Button>
795
740
  );
796
741
  }
@@ -800,13 +745,13 @@ export default ButtonWithBothIconsExample;
800
745
  },
801
746
  },
802
747
  },
803
- }, 'Both icon slots used simultaneously. Left icon reinforces the action type (`sparkles` for AI-generated), right icon indicates direction or outcome (`arrow-right` for proceed). The dual-icon pattern is rare — only use it when both icons add distinct meaning. The label should be short enough that the button does not become unwieldy.');
748
+ }, 'Both a leading and trailing icon used simultaneously. The leading icon carries its own `screenReaderText` ("AI generated") because that meaning is not otherwise conveyed by the label — the trailing `arrow-right` is purely decorative (proceed/continue) so it has none. The dual-icon pattern is rare — only use it when both icons add distinct meaning, and keep the label short enough that the button does not become unwieldy.');
804
749
  export const IconOnly = withDescription({
805
750
  args: {
806
751
  variant: 'secondary',
807
752
  size: 'M',
808
- iconRightName: '3-dot',
809
- iconRightScreenReaderText: 'More actions',
753
+ isRound: true,
754
+ children: _jsx(Icon, { name: "3-dot", screenReaderText: "More actions" }),
810
755
  },
811
756
  parameters: {
812
757
  docs: {
@@ -814,9 +759,14 @@ export const IconOnly = withDescription({
814
759
  language: 'tsx',
815
760
  code: `
816
761
  import { Button } from '@arbor-education/design-system.components';
762
+ import { Icon } from '@arbor-education/design-system.components';
817
763
 
818
764
  function ButtonIconOnlyExample() {
819
- return <Button variant="secondary" size="M" iconRightName="3-dot" iconRightScreenReaderText="More actions" />;
765
+ return (
766
+ <Button variant="secondary" size="M" isRound>
767
+ <Icon name="3-dot" screenReaderText="More actions" />
768
+ </Button>
769
+ );
820
770
  }
821
771
 
822
772
  export default ButtonIconOnlyExample;
@@ -824,31 +774,32 @@ export default ButtonIconOnlyExample;
824
774
  },
825
775
  },
826
776
  },
827
- }, 'Icon-only mode is triggered automatically when `children` is omitted and at least one icon prop is provided. The component adds `ds-button--icon-only` for square sizing. **Always** provide `iconRightScreenReaderText` (or `iconLeftScreenReaderText`) — the icon name `"3-dot"` is meaningless to a screen reader. This story uses `"More actions"` which clearly communicates the button\'s purpose.');
777
+ }, 'Set `isRound` for square, no-padding sizing (`ds-button--round`) when there is no visible label. **Always** pass `screenReaderText` on the `Icon` in this mode — the icon name `"3-dot"` is meaningless to a screen reader, and with no other text content the button would otherwise have no accessible name at all. This story uses `"More actions"` which clearly communicates the button\'s purpose. Note: `isRound` already zeroes padding via CSS, so combining it with `hasHorizontalPadding={false}` is redundant — the two do not stack.');
828
778
  export const IconOnlyAccessibility = withDescription({
829
- render: () => (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xlarge)' }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-destructive-600)' }, children: "Bad \u2014 screen reader announces \"3-dot\" or \"x\" (the raw icon name)" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)' }, children: [_jsx(Button, { variant: "secondary", type: "button", iconRightName: "3-dot" }), _jsx(Button, { variant: "secondary", type: "button", iconRightName: "x" }), _jsx(Button, { variant: "secondary", type: "button", iconRightName: "pencil" })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "Good \u2014 screen reader announces a human-readable action name" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)' }, children: [_jsx(Button, { variant: "secondary", type: "button", iconRightName: "3-dot", iconRightScreenReaderText: "More actions" }), _jsx(Button, { variant: "secondary", type: "button", iconRightName: "x", iconRightScreenReaderText: "Dismiss" }), _jsx(Button, { variant: "secondary", type: "button", iconRightName: "pencil", iconRightScreenReaderText: "Edit student details" })] })] })] })),
779
+ render: () => (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xlarge)' }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-destructive-600)' }, children: "Bad \u2014 no accessible name at all (screenReaderText omitted)" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)' }, children: [_jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "3-dot" }) }), _jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "x" }) }), _jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "pencil" }) })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "Good \u2014 screen reader announces a human-readable action name" }), _jsxs("div", { style: { display: 'flex', gap: 'var(--spacing-large)' }, children: [_jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "3-dot", screenReaderText: "More actions" }) }), _jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "x", screenReaderText: "Dismiss" }) }), _jsx(Button, { variant: "secondary", type: "button", isRound: true, children: _jsx(Icon, { name: "pencil", screenReaderText: "Edit student details" }) })] })] })] })),
830
780
  parameters: {
831
781
  docs: {
832
782
  source: {
833
783
  language: 'tsx',
834
784
  code: `
835
785
  import { Button } from '@arbor-education/design-system.components';
786
+ import { Icon } from '@arbor-education/design-system.components';
836
787
 
837
788
  function ButtonIconOnlyAccessibilityExample() {
838
789
  return (
839
790
  <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
840
- {/* Bad — screen reader announces raw icon names */}
791
+ {/* Bad — no accessible name at all */}
841
792
  <div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
842
- <Button variant="secondary" type="button" iconRightName="3-dot" />
843
- <Button variant="secondary" type="button" iconRightName="x" />
844
- <Button variant="secondary" type="button" iconRightName="pencil" />
793
+ <Button variant="secondary" type="button" isRound><Icon name="3-dot" /></Button>
794
+ <Button variant="secondary" type="button" isRound><Icon name="x" /></Button>
795
+ <Button variant="secondary" type="button" isRound><Icon name="pencil" /></Button>
845
796
  </div>
846
797
 
847
798
  {/* Good — human-readable action names */}
848
799
  <div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
849
- <Button variant="secondary" type="button" iconRightName="3-dot" iconRightScreenReaderText="More actions" />
850
- <Button variant="secondary" type="button" iconRightName="x" iconRightScreenReaderText="Dismiss" />
851
- <Button variant="secondary" type="button" iconRightName="pencil" iconRightScreenReaderText="Edit student details" />
800
+ <Button variant="secondary" type="button" isRound><Icon name="3-dot" screenReaderText="More actions" /></Button>
801
+ <Button variant="secondary" type="button" isRound><Icon name="x" screenReaderText="Dismiss" /></Button>
802
+ <Button variant="secondary" type="button" isRound><Icon name="pencil" screenReaderText="Edit student details" /></Button>
852
803
  </div>
853
804
  </div>
854
805
  );
@@ -859,7 +810,7 @@ export default ButtonIconOnlyAccessibilityExample;
859
810
  },
860
811
  },
861
812
  },
862
- }, 'Icon-only accessibility contrast. The top row has no `iconRightScreenReaderText` override — screen readers announce the raw icon names (`"3-dot"`, `"x"`, `"pencil"`). The bottom row overrides with human-readable action names. Always provide `iconLeftScreenReaderText` or `iconRightScreenReaderText` when using icon-only buttons — the icon name is almost never descriptive enough in context.');
813
+ }, 'Icon-only accessibility contrast. The top row omits `screenReaderText` on the `Icon` — since Button no longer supplies a fallback, these buttons have **no accessible name at all**. The bottom row sets `screenReaderText` with human-readable action names. Always pass `screenReaderText` on the `Icon` when using `isRound` — the icon name is almost never descriptive enough in context, and unlike before, Button will not fall back to it for you.');
863
814
  export const Disabled = withDescription({
864
815
  args: {
865
816
  variant: 'primary',
@@ -908,64 +859,6 @@ export default ButtonErrorStateExample;
908
859
  },
909
860
  },
910
861
  }, 'The `error` prop adds a red border (`--color-semantic-destructive-500`) via `ds-button--error`. This is used by `SelectDropdown` and other form-field trigger buttons to indicate validation failure on the field. It is **purely visual** — no `aria-invalid` is set on the button itself. Consumers are responsible for communicating the error state to assistive tech (e.g. `aria-invalid` on the associated input).');
911
- export const Borderless = withDescription({
912
- args: {
913
- variant: 'tertiary',
914
- size: 'M',
915
- children: 'Settings',
916
- iconLeftName: 'settings',
917
- iconLeftScreenReaderText: 'Settings',
918
- borderless: true,
919
- },
920
- parameters: {
921
- docs: {
922
- source: {
923
- language: 'tsx',
924
- code: `
925
- import { Button } from '@arbor-education/design-system.components';
926
-
927
- function ButtonBorderlessExample() {
928
- return (
929
- <Button variant="tertiary" size="M" iconLeftName="settings" iconLeftScreenReaderText="Settings" borderless>
930
- Settings
931
- </Button>
932
- );
933
- }
934
-
935
- export default ButtonBorderlessExample;
936
- `.trim(),
937
- },
938
- },
939
- },
940
- }, 'The `borderless` prop removes the border entirely via `ds-button--borderless`. Useful in toolbar contexts where a visible border would add visual noise — for example, icon-adjacent utility buttons in a rich text editor toolbar or a data grid header. Pairs naturally with `variant="tertiary"` and an icon.');
941
- export const NoHorizontalPadding = withDescription({
942
- args: {
943
- variant: 'text-link',
944
- size: 'M',
945
- children: 'View full attendance report',
946
- hasHorizontalPadding: false,
947
- },
948
- parameters: {
949
- docs: {
950
- source: {
951
- language: 'tsx',
952
- code: `
953
- import { Button } from '@arbor-education/design-system.components';
954
-
955
- function ButtonNoHorizontalPaddingExample() {
956
- return (
957
- <Button variant="text-link" size="M" hasHorizontalPadding={false}>
958
- View full attendance report
959
- </Button>
960
- );
961
- }
962
-
963
- export default ButtonNoHorizontalPaddingExample;
964
- `.trim(),
965
- },
966
- },
967
- },
968
- }, '`hasHorizontalPadding={false}` removes horizontal padding via `ds-button--no-horizontal-padding`. The canonical use case is a `text-link` variant sitting inline in a form label or table cell, where the default padding would indent the text away from the surrounding content. Do not use this as a general layout escape hatch — it is specifically for flush-mounted inline actions.');
969
862
  export const FormTypeAttribute = withDescription({
970
863
  render: FormTypeAttributeTemplate,
971
864
  parameters: {
@@ -1023,15 +916,73 @@ export default FormTypeAttributeExample;
1023
916
  },
1024
917
  },
1025
918
  }, [
1026
- '**Critical gotcha demo.** The `type` prop is explicitly omitted from `ButtonProps` via',
1027
- '`Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "onClick">`. This means `<Button>` never',
1028
- 'sets a `type` attribute, and browsers default to `type="submit"`. Any `<Button>` inside a `<form>`',
1029
- 'will submit that form on click unless you pass `type="button"` explicitly.',
919
+ '**Critical gotcha demo.** `type` is a typed prop, but `Button` never defaults it — there is no',
920
+ '`type = \'button\'` fallback in the component. This means `<Button>` never sets a `type` attribute',
921
+ 'on its own, and browsers default to `type="submit"`. Any `<Button>` inside a `<form>` will submit',
922
+ 'that form on click unless you pass `type="button"` explicitly.',
1030
923
  '',
1031
924
  'This story shows a form with both patterns side by side. The "Cancel" button uses `type="button"`',
1032
925
  'so clicking it does not submit. The "Save attendance" button uses `type="submit"` to intentionally',
1033
926
  'submit. Click both and watch the status message.',
1034
927
  ].join(' '));
928
+ export const Borderless = withDescription({
929
+ args: {
930
+ variant: 'tertiary',
931
+ size: 'M',
932
+ borderless: true,
933
+ children: (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "settings" }), "Settings"] })),
934
+ },
935
+ parameters: {
936
+ docs: {
937
+ source: {
938
+ language: 'tsx',
939
+ code: `
940
+ import { Button } from '@arbor-education/design-system.components';
941
+ import { Icon } from '@arbor-education/design-system.components';
942
+
943
+ function ButtonBorderlessExample() {
944
+ return (
945
+ <Button variant="tertiary" size="M" borderless>
946
+ <Icon name="settings" />
947
+ Settings
948
+ </Button>
949
+ );
950
+ }
951
+
952
+ export default ButtonBorderlessExample;
953
+ `.trim(),
954
+ },
955
+ },
956
+ },
957
+ }, 'The `borderless` prop removes the border entirely via `ds-button--borderless`. Useful in toolbar contexts where a visible border would add visual noise — for example, icon-adjacent utility buttons in a rich text editor toolbar or a data grid header. Pairs naturally with `variant="tertiary"` and a leading icon.');
958
+ export const NoHorizontalPadding = withDescription({
959
+ args: {
960
+ variant: 'text-link',
961
+ size: 'M',
962
+ children: 'View full attendance report',
963
+ hasHorizontalPadding: false,
964
+ },
965
+ parameters: {
966
+ docs: {
967
+ source: {
968
+ language: 'tsx',
969
+ code: `
970
+ import { Button } from '@arbor-education/design-system.components';
971
+
972
+ function ButtonNoHorizontalPaddingExample() {
973
+ return (
974
+ <Button variant="text-link" size="M" hasHorizontalPadding={false}>
975
+ View full attendance report
976
+ </Button>
977
+ );
978
+ }
979
+
980
+ export default ButtonNoHorizontalPaddingExample;
981
+ `.trim(),
982
+ },
983
+ },
984
+ },
985
+ }, '`hasHorizontalPadding={false}` removes horizontal padding via `ds-button--no-horizontal-padding`. The canonical use case is a `text-link` variant sitting inline in a form label or table cell, where the default padding would indent the text away from the surrounding content. Do not use this as a general layout escape hatch — it is specifically for flush-mounted inline actions.');
1035
986
  export const LoadingComposition = withDescription({
1036
987
  render: LoadingCompositionTemplate,
1037
988
  parameters: {
@@ -1041,6 +992,7 @@ export const LoadingComposition = withDescription({
1041
992
  code: `
1042
993
  import { useState } from 'react';
1043
994
  import { Button } from '@arbor-education/design-system.components';
995
+ import { Icon } from '@arbor-education/design-system.components';
1044
996
 
1045
997
  function LoadingCompositionExample() {
1046
998
  const [loading, setLoading] = useState(false);
@@ -1064,10 +1016,9 @@ function LoadingCompositionExample() {
1064
1016
  variant="primary"
1065
1017
  type="button"
1066
1018
  disabled={loading}
1067
- iconLeftName={loading ? 'loader' : undefined}
1068
- iconLeftScreenReaderText={loading ? 'Loading, please wait' : undefined}
1069
1019
  onClick={handleClick}
1070
1020
  >
1021
+ {loading && <Icon name="loader" screenReaderText="Loading, please wait" />}
1071
1022
  {loading ? 'Exporting...' : 'Export to CSV'}
1072
1023
  </Button>
1073
1024
  </div>
@@ -1080,10 +1031,10 @@ export default LoadingCompositionExample;
1080
1031
  },
1081
1032
  },
1082
1033
  }, [
1083
- 'Button has **no built-in loading state** — compose one with `disabled`, `iconLeftName`, and a label change.',
1084
- 'This pattern uses `disabled={loading}` to block interaction during the async operation, `iconLeftName="loader"`',
1085
- 'to show a spinner icon, and `iconLeftScreenReaderText="Loading, please wait"` so screen reader users are',
1086
- 'informed. After 2 seconds the mock export resolves and the button returns to its default state.',
1034
+ 'Button has **no built-in loading state** — compose one with `disabled`, a conditional `Icon`, and a label change.',
1035
+ 'This pattern uses `disabled={loading}` to block interaction during the async operation, a conditionally',
1036
+ 'rendered `<Icon name="loader" screenReaderText="Loading, please wait" />` to show a spinner icon with an',
1037
+ 'accessible announcement. After 2 seconds the mock export resolves and the button returns to its default state.',
1087
1038
  '',
1088
1039
  'The `loader` icon (`LoaderCircle` from Lucide) does not spin by default — animate it with a CSS',
1089
1040
  '`animation: spin 1s linear infinite` keyframe on the `svg` within the button if your design requires it.',
@@ -1156,5 +1107,5 @@ export default ClickTrackingExample;
1156
1107
  },
1157
1108
  },
1158
1109
  },
1159
- }, 'Demonstrates that `onClick` fires correctly with each button activation (click, Space, Enter). The counter increments on every activation. Note `type="button"` — this story\'s button is not inside a form but it is good practice to always be explicit about `type` to avoid accidental form submissions.');
1110
+ }, 'Demonstrates that `onClick` fires correctly with each button activation (click, Space, Enter). The counter increments on every activation. Note `type="button"` — this story\'s button is not inside a form but it is good practice to always be explicit about `type` to avoid accidental form submissions. The `onClick` signature also accepts `...otherArgs: unknown[]` (non-standard) — this exists to support legacy callers that wrap `Button` and invoke the handler with extra arguments beyond the click event.');
1160
1111
  //# sourceMappingURL=Button.stories.js.map