@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.
- package/CHANGELOG.md +11 -0
- package/dist/components/breadcrumbs/BreadcrumbCopyButton.d.ts.map +1 -1
- package/dist/components/breadcrumbs/BreadcrumbCopyButton.js +3 -2
- package/dist/components/breadcrumbs/BreadcrumbCopyButton.js.map +1 -1
- package/dist/components/breadcrumbs/BreadcrumbEllipsis.d.ts.map +1 -1
- package/dist/components/breadcrumbs/BreadcrumbEllipsis.js +2 -1
- package/dist/components/breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
- package/dist/components/button/Button.d.ts +17 -8
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +5 -6
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/button/Button.stories.d.ts +17 -36
- package/dist/components/button/Button.stories.d.ts.map +1 -1
- package/dist/components/button/Button.stories.js +165 -214
- package/dist/components/button/Button.stories.js.map +1 -1
- package/dist/components/button/Button.test.d.ts.map +1 -1
- package/dist/components/button/Button.test.js +16 -10
- package/dist/components/button/Button.test.js.map +1 -1
- package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.js +2 -1
- package/dist/components/datePicker/DatePicker.js.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.js +2 -1
- package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +5 -3
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/emptyState/EmptyState.stories.d.ts.map +1 -1
- package/dist/components/emptyState/EmptyState.stories.js +3 -2
- package/dist/components/emptyState/EmptyState.stories.js.map +1 -1
- package/dist/components/fileUpload/FileUpload.d.ts.map +1 -1
- package/dist/components/fileUpload/FileUpload.js +1 -1
- package/dist/components/fileUpload/FileUpload.js.map +1 -1
- package/dist/components/fileUpload/FileUpload.test.js +4 -4
- package/dist/components/fileUpload/FileUpload.test.js.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.js +2 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
- package/dist/components/grid/Grid.stories.d.ts.map +1 -1
- package/dist/components/grid/Grid.stories.js +4 -3
- package/dist/components/grid/Grid.stories.js.map +1 -1
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +18 -14
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +30 -31
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/modal/ModalCloseButton.d.ts.map +1 -1
- package/dist/components/modal/ModalCloseButton.js +2 -1
- package/dist/components/modal/ModalCloseButton.js.map +1 -1
- package/dist/components/section/Section.stories.d.ts.map +1 -1
- package/dist/components/section/Section.stories.js +52 -25
- package/dist/components/section/Section.stories.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +4 -6
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/pagination/PageSizeSelector.d.ts.map +1 -1
- package/dist/components/table/pagination/PageSizeSelector.js +2 -1
- package/dist/components/table/pagination/PageSizeSelector.js.map +1 -1
- package/dist/components/table/pagination/PaginationControls.d.ts.map +1 -1
- package/dist/components/table/pagination/PaginationControls.js +3 -2
- package/dist/components/table/pagination/PaginationControls.js.map +1 -1
- package/dist/components/table/tableControls/BulkActionsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/BulkActionsDropdown.js +3 -2
- package/dist/components/table/tableControls/BulkActionsDropdown.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableControls.js +2 -1
- package/dist/components/table/tableControls/TableControls.js.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.js +2 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
- package/dist/components/toast/Toast.js +1 -1
- package/dist/components/toast/Toast.js.map +1 -1
- package/dist/components/tooltip/Tooltip.stories.js +5 -5
- package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
- package/dist/components/tooltip/TooltipWrapper.stories.js +2 -2
- package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
- package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts.map +1 -1
- package/dist/components/userDropdown/internal/UserDropdownTrigger.js +3 -2
- package/dist/components/userDropdown/internal/UserDropdownTrigger.js.map +1 -1
- package/dist/index.css +15 -15
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
- package/src/components/breadcrumbs/BreadcrumbCopyButton.tsx +5 -2
- package/src/components/breadcrumbs/BreadcrumbEllipsis.tsx +5 -2
- package/src/components/breadcrumbs/breadcrumbs.scss +2 -2
- package/src/components/button/Button.stories.tsx +205 -230
- package/src/components/button/Button.test.tsx +36 -9
- package/src/components/button/Button.tsx +6 -16
- package/src/components/button/button.scss +8 -8
- package/src/components/datePicker/DatePicker.tsx +5 -3
- package/src/components/dateTimePicker/DateTimePicker.tsx +5 -3
- package/src/components/dropdown/Dropdown.stories.tsx +7 -3
- package/src/components/emptyState/EmptyState.stories.tsx +4 -2
- package/src/components/fileUpload/FileUpload.test.tsx +4 -4
- package/src/components/fileUpload/FileUpload.tsx +8 -4
- package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.tsx +3 -1
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +3 -2
- package/src/components/grid/Grid.stories.tsx +17 -4
- package/src/components/heading/Heading.stories.tsx +23 -16
- package/src/components/icon/Icon.stories.tsx +43 -44
- package/src/components/modal/ModalCloseButton.tsx +5 -3
- package/src/components/section/Section.stories.tsx +86 -31
- package/src/components/table/Table.stories.tsx +19 -6
- package/src/components/table/pagination/PageSizeSelector.tsx +3 -1
- package/src/components/table/pagination/PaginationControls.tsx +17 -12
- package/src/components/table/tableControls/BulkActionsDropdown.tsx +3 -1
- package/src/components/table/tableControls/TableControls.tsx +31 -22
- package/src/components/table/tableControls/TableSettingsDropdown.tsx +7 -4
- package/src/components/toast/Toast.tsx +1 -1
- package/src/components/tooltip/Tooltip.stories.tsx +7 -7
- package/src/components/tooltip/TooltipWrapper.stories.tsx +4 -4
- package/src/components/userDropdown/internal/UserDropdownTrigger.tsx +2 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@storybook/addon-docs/blocks';
|
|
11
11
|
import { useState, useRef } from 'react';
|
|
12
12
|
import { Button } from './Button.js';
|
|
13
|
+
import { Icon } from 'Components/icon/Icon';
|
|
13
14
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Component description — built as a joined array to avoid no-useless-escape
|
|
@@ -23,8 +24,9 @@ import { Button } from './Button.js';
|
|
|
23
24
|
|
|
24
25
|
const DESCRIPTION_INTRO = [
|
|
25
26
|
'The **Button** is the primary interactive control in the Arbor design system. It wraps a native',
|
|
26
|
-
'`<button>` element with `forwardRef` support, consistent design tokens,
|
|
27
|
-
'of variant and state modifiers.',
|
|
27
|
+
'`<button>` element with `forwardRef` support, consistent design tokens, and a full set',
|
|
28
|
+
'of variant and state modifiers. Icons are composed via `children` using the `Icon` component,',
|
|
29
|
+
'so consumers control icon placement, colour, and accessible text directly.',
|
|
28
30
|
].join('\n');
|
|
29
31
|
|
|
30
32
|
const USAGE_GUIDANCE = [
|
|
@@ -69,9 +71,10 @@ const DEVELOPER_NOTES = [
|
|
|
69
71
|
'### Critical gotchas',
|
|
70
72
|
'',
|
|
71
73
|
'#### 1. The `type` attribute trap',
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
74
|
+
'`type` is a fully typed prop (`\'button\' | \'submit\' | \'reset\'`, inherited from',
|
|
75
|
+
'`ButtonHTMLAttributes<HTMLButtonElement>`), but the component **does not default it**',
|
|
76
|
+
'— there is no `type = \'button\'` fallback in `Button.tsx`. This means that if you omit it,',
|
|
77
|
+
'the underlying `<button>` has no `type` attribute, and browsers default to',
|
|
75
78
|
'`type="submit"`. If this button lives inside a `<form>`, clicking it **will submit the form**',
|
|
76
79
|
'unless you explicitly pass `type="button"`.',
|
|
77
80
|
'',
|
|
@@ -83,17 +86,22 @@ const DEVELOPER_NOTES = [
|
|
|
83
86
|
'<Button variant="secondary" type="button" onClick={handleCancel}>Cancel</Button>',
|
|
84
87
|
'```',
|
|
85
88
|
'',
|
|
86
|
-
'#### 2.
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
89
|
+
'#### 2. Icons are composed via `children`',
|
|
90
|
+
'Icons are no longer separate props — render an `Icon` as part of `children`, placed before and/or',
|
|
91
|
+
'after the label text. Set the explicit `isRound` prop for square, no-padding sizing when there is',
|
|
92
|
+
'no visible label. Because the icon is just a child element, always pass `screenReaderText` on the',
|
|
93
|
+
'`Icon` yourself when it is the button\'s only accessible content — icon names like `"3-dot"` or',
|
|
94
|
+
'`"x"` are not meaningful in context. When an icon sits alongside a visible label (e.g. a trailing',
|
|
95
|
+
'chevron), omit `screenReaderText` so it stays decorative and does not duplicate the label.',
|
|
90
96
|
'',
|
|
91
97
|
'```tsx',
|
|
92
|
-
'// BAD —
|
|
93
|
-
'<Button variant="secondary"
|
|
98
|
+
'// BAD — no accessible name at all',
|
|
99
|
+
'<Button variant="secondary" isRound><Icon name="3-dot" /></Button>',
|
|
94
100
|
'',
|
|
95
101
|
'// GOOD',
|
|
96
|
-
'<Button variant="secondary"
|
|
102
|
+
'<Button variant="secondary" isRound>',
|
|
103
|
+
' <Icon name="3-dot" screenReaderText="More actions" />',
|
|
104
|
+
'</Button>',
|
|
97
105
|
'```',
|
|
98
106
|
'',
|
|
99
107
|
'---',
|
|
@@ -103,10 +111,32 @@ const DEVELOPER_NOTES = [
|
|
|
103
111
|
'- Native `<button>` — focusable with Tab, activated with Space or Enter',
|
|
104
112
|
'- Focus ring: 3px outline using `--focus-border` and `--color-brand-500` via `--focus-color-focus`',
|
|
105
113
|
'- `disabled` removes the button from tab order AND blocks pointer events (`opacity: 0.5; cursor: not-allowed; pointer-events: none`)',
|
|
106
|
-
'- Icon-only mode:
|
|
114
|
+
'- Icon-only mode (`isRound`): always pass `screenReaderText` on the child `Icon` — it is the button\'s only accessible label',
|
|
107
115
|
'- The `error` prop is **purely visual** (adds a red border) — it carries no ARIA semantics.',
|
|
108
116
|
' The consumer is still responsible for communicating the error state to assistive tech — e.g.',
|
|
109
117
|
' setting `aria-invalid` on the associated input or wiring `aria-describedby` to an error message.',
|
|
118
|
+
'- `disabled` sets `pointer-events: none` via CSS, so `onClick` genuinely cannot fire while disabled —',
|
|
119
|
+
' no defensive guard is needed inside the handler itself.',
|
|
120
|
+
'',
|
|
121
|
+
'---',
|
|
122
|
+
'',
|
|
123
|
+
'#### Focus, hover, and active are CSS-only',
|
|
124
|
+
'There is no `focused` / `hovered` / `active` prop. Each variant defines its own `:focus`, `:hover`,',
|
|
125
|
+
'and `:active` rules directly in `button.scss` — these are native pseudo-classes on the underlying',
|
|
126
|
+
'`<button>`, not something Storybook Controls can toggle. To see them, interact with the rendered',
|
|
127
|
+
'button in the canvas (click, tab to it, or hover over it).',
|
|
128
|
+
'',
|
|
129
|
+
'---',
|
|
130
|
+
'',
|
|
131
|
+
'#### The `` `[key: `data-${string}`]: unknown` `` index signature',
|
|
132
|
+
'`ButtonProps` includes a narrow catch-all that matches **only** `data-*` keys, spread via `...rest`',
|
|
133
|
+
'straight onto the native `<button>` element — the one open-ended attribute pattern with no fixed',
|
|
134
|
+
'name list (`data-testid`, `data-component`, etc.). `aria-*` and all standard HTML attributes are',
|
|
135
|
+
'separately and fully typed via `ButtonHTMLAttributes`, so they do not need this catch-all. Because',
|
|
136
|
+
'the signature is scoped to `data-*`, a typo in a known prop name (e.g. `isRond` instead of `isRound`)',
|
|
137
|
+
'is a **compile error**, not a silent unrecognised DOM attribute — this used to be a blanket',
|
|
138
|
+
'`[key: string]: unknown` that swallowed any misspelled or removed prop name; it was narrowed for',
|
|
139
|
+
'exactly this reason.',
|
|
110
140
|
'',
|
|
111
141
|
'---',
|
|
112
142
|
'',
|
|
@@ -209,10 +239,9 @@ const meta = {
|
|
|
209
239
|
children: {
|
|
210
240
|
control: 'text',
|
|
211
241
|
description: [
|
|
212
|
-
'Button label content. Accepts any `React.ReactNode
|
|
213
|
-
'
|
|
214
|
-
'icon-only
|
|
215
|
-
'Always pair icon-only mode with descriptive `iconLeftScreenReaderText` / `iconRightScreenReaderText`.',
|
|
242
|
+
'Button label content. Accepts any `React.ReactNode`, including `Icon` elements placed before',
|
|
243
|
+
'and/or after the label text — Button renders `children` as-is with no separate icon slots.',
|
|
244
|
+
'For an icon-only button, set `isRound` and always pass `screenReaderText` on the `Icon`.',
|
|
216
245
|
].join(' '),
|
|
217
246
|
table: {
|
|
218
247
|
type: { summary: 'React.ReactNode' },
|
|
@@ -256,94 +285,16 @@ const meta = {
|
|
|
256
285
|
defaultValue: { summary: 'true' },
|
|
257
286
|
},
|
|
258
287
|
},
|
|
259
|
-
|
|
260
|
-
control: '
|
|
261
|
-
options: [
|
|
262
|
-
undefined,
|
|
263
|
-
'plus',
|
|
264
|
-
'pencil',
|
|
265
|
-
'save',
|
|
266
|
-
'trash',
|
|
267
|
-
'download',
|
|
268
|
-
'upload',
|
|
269
|
-
'mail',
|
|
270
|
-
'search',
|
|
271
|
-
'info',
|
|
272
|
-
'check',
|
|
273
|
-
'x',
|
|
274
|
-
'arrow-left',
|
|
275
|
-
'arrow-right',
|
|
276
|
-
'settings',
|
|
277
|
-
'loader',
|
|
278
|
-
'3-dot',
|
|
279
|
-
'user',
|
|
280
|
-
'send',
|
|
281
|
-
],
|
|
282
|
-
description: [
|
|
283
|
-
'Name of an icon to render **before** the button label at 16px.',
|
|
284
|
-
'Renders an `<Icon>` component with `size={16}`.',
|
|
285
|
-
'Screen reader text defaults to the icon name — always override with `iconLeftScreenReaderText`',
|
|
286
|
-
'when the icon name is not meaningful in context (e.g. `"3-dot"`, `"x"`).',
|
|
287
|
-
].join(' '),
|
|
288
|
-
table: {
|
|
289
|
-
type: { summary: 'IconName' },
|
|
290
|
-
defaultValue: { summary: 'undefined' },
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
iconLeftScreenReaderText: {
|
|
294
|
-
control: 'text',
|
|
295
|
-
description: [
|
|
296
|
-
'Screen reader label for the left icon.',
|
|
297
|
-
'Defaults to the icon name when omitted.',
|
|
298
|
-
'In icon-only mode this becomes the accessible label for the entire button — make it descriptive.',
|
|
299
|
-
].join(' '),
|
|
300
|
-
table: {
|
|
301
|
-
type: { summary: 'string' },
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
iconRightName: {
|
|
305
|
-
control: 'select',
|
|
306
|
-
options: [
|
|
307
|
-
undefined,
|
|
308
|
-
'plus',
|
|
309
|
-
'pencil',
|
|
310
|
-
'save',
|
|
311
|
-
'trash',
|
|
312
|
-
'download',
|
|
313
|
-
'upload',
|
|
314
|
-
'mail',
|
|
315
|
-
'search',
|
|
316
|
-
'info',
|
|
317
|
-
'check',
|
|
318
|
-
'x',
|
|
319
|
-
'arrow-right',
|
|
320
|
-
'chevron-down',
|
|
321
|
-
'chevron-up',
|
|
322
|
-
'settings',
|
|
323
|
-
'loader',
|
|
324
|
-
'3-dot',
|
|
325
|
-
'external-link',
|
|
326
|
-
'send',
|
|
327
|
-
],
|
|
328
|
-
description: [
|
|
329
|
-
'Name of an icon to render **after** the button label at 16px.',
|
|
330
|
-
'Renders an `<Icon>` component with `size={16}`.',
|
|
331
|
-
'Screen reader text defaults to the icon name — always override with `iconRightScreenReaderText`.',
|
|
332
|
-
].join(' '),
|
|
333
|
-
table: {
|
|
334
|
-
type: { summary: 'IconName' },
|
|
335
|
-
defaultValue: { summary: 'undefined' },
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
iconRightScreenReaderText: {
|
|
339
|
-
control: 'text',
|
|
288
|
+
isRound: {
|
|
289
|
+
control: 'boolean',
|
|
340
290
|
description: [
|
|
341
|
-
'
|
|
342
|
-
'
|
|
343
|
-
'
|
|
291
|
+
'Applies equal padding on all sides (`ds-button--round`) for a square, compact button.',
|
|
292
|
+
'Commonly used for icon-only buttons — in that case, always pass `screenReaderText` on the child',
|
|
293
|
+
'`Icon`, since it becomes the button\'s only accessible label.',
|
|
344
294
|
].join(' '),
|
|
345
295
|
table: {
|
|
346
|
-
type: { summary: '
|
|
296
|
+
type: { summary: 'boolean' },
|
|
297
|
+
defaultValue: { summary: 'false' },
|
|
347
298
|
},
|
|
348
299
|
},
|
|
349
300
|
borderless: {
|
|
@@ -518,10 +469,9 @@ const LoadingCompositionTemplate = () => {
|
|
|
518
469
|
variant="primary"
|
|
519
470
|
type="button"
|
|
520
471
|
disabled={loading}
|
|
521
|
-
iconLeftName={loading ? 'loader' : undefined}
|
|
522
|
-
iconLeftScreenReaderText={loading ? 'Loading, please wait' : undefined}
|
|
523
472
|
onClick={handleClick}
|
|
524
473
|
>
|
|
474
|
+
{loading && <Icon name="loader" screenReaderText="Loading, please wait" />}
|
|
525
475
|
{loading ? 'Exporting...' : 'Export to CSV'}
|
|
526
476
|
</Button>
|
|
527
477
|
</div>
|
|
@@ -545,7 +495,7 @@ export const Default: Story = withDescription(
|
|
|
545
495
|
},
|
|
546
496
|
render: args => <Button {...args} />,
|
|
547
497
|
},
|
|
548
|
-
'The interactive canvas story — every prop is wired to the **Controls** panel. Use the controls to explore variants, sizes,
|
|
498
|
+
'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.',
|
|
549
499
|
);
|
|
550
500
|
|
|
551
501
|
export const Primary: Story = withDescription(
|
|
@@ -721,7 +671,12 @@ export const Dropdown: Story = withDescription(
|
|
|
721
671
|
args: {
|
|
722
672
|
variant: 'dropdown',
|
|
723
673
|
size: 'M',
|
|
724
|
-
children:
|
|
674
|
+
children: (
|
|
675
|
+
<>
|
|
676
|
+
Select year group
|
|
677
|
+
<Icon name="chevron-down" />
|
|
678
|
+
</>
|
|
679
|
+
),
|
|
725
680
|
},
|
|
726
681
|
parameters: {
|
|
727
682
|
docs: {
|
|
@@ -729,11 +684,13 @@ export const Dropdown: Story = withDescription(
|
|
|
729
684
|
language: 'tsx',
|
|
730
685
|
code: `
|
|
731
686
|
import { Button } from '@arbor-education/design-system.components';
|
|
687
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
732
688
|
|
|
733
689
|
function ButtonDropdownExample() {
|
|
734
690
|
return (
|
|
735
|
-
<Button variant="dropdown" size="M"
|
|
691
|
+
<Button variant="dropdown" size="M">
|
|
736
692
|
Select year group
|
|
693
|
+
<Icon name="chevron-down" />
|
|
737
694
|
</Button>
|
|
738
695
|
);
|
|
739
696
|
}
|
|
@@ -748,8 +705,8 @@ export default ButtonDropdownExample;
|
|
|
748
705
|
'The `dropdown` variant has `justify-content: space-between` and specific padding designed for use as a',
|
|
749
706
|
'dropdown trigger — a trailing chevron icon aligns to the right edge automatically. Used internally by',
|
|
750
707
|
'`SelectDropdown`, `ColourPickerDropdown`, and `UserDropdown`, and available to consumers for custom',
|
|
751
|
-
'dropdown triggers. Pair it with a chevron
|
|
752
|
-
'look.',
|
|
708
|
+
'dropdown triggers. Pair it with a trailing chevron `Icon` (e.g. `<Icon name="chevron-down" />`) for the',
|
|
709
|
+
'canonical look — it is decorative, so it does not need `screenReaderText` when a visible label is present.',
|
|
753
710
|
].join(' '),
|
|
754
711
|
);
|
|
755
712
|
|
|
@@ -940,9 +897,12 @@ export const WithIconLeft: Story = withDescription(
|
|
|
940
897
|
args: {
|
|
941
898
|
variant: 'primary',
|
|
942
899
|
size: 'M',
|
|
943
|
-
children:
|
|
944
|
-
|
|
945
|
-
|
|
900
|
+
children: (
|
|
901
|
+
<>
|
|
902
|
+
<Icon name="download" />
|
|
903
|
+
Export to CSV
|
|
904
|
+
</>
|
|
905
|
+
),
|
|
946
906
|
},
|
|
947
907
|
parameters: {
|
|
948
908
|
docs: {
|
|
@@ -950,10 +910,12 @@ export const WithIconLeft: Story = withDescription(
|
|
|
950
910
|
language: 'tsx',
|
|
951
911
|
code: `
|
|
952
912
|
import { Button } from '@arbor-education/design-system.components';
|
|
913
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
953
914
|
|
|
954
915
|
function ButtonWithIconLeftExample() {
|
|
955
916
|
return (
|
|
956
|
-
<Button variant="primary" size="M"
|
|
917
|
+
<Button variant="primary" size="M">
|
|
918
|
+
<Icon name="download" />
|
|
957
919
|
Export to CSV
|
|
958
920
|
</Button>
|
|
959
921
|
);
|
|
@@ -965,7 +927,7 @@ export default ButtonWithIconLeftExample;
|
|
|
965
927
|
},
|
|
966
928
|
},
|
|
967
929
|
},
|
|
968
|
-
'Icon
|
|
930
|
+
'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.',
|
|
969
931
|
);
|
|
970
932
|
|
|
971
933
|
export const WithIconRight: Story = withDescription(
|
|
@@ -973,9 +935,12 @@ export const WithIconRight: Story = withDescription(
|
|
|
973
935
|
args: {
|
|
974
936
|
variant: 'secondary',
|
|
975
937
|
size: 'M',
|
|
976
|
-
children:
|
|
977
|
-
|
|
978
|
-
|
|
938
|
+
children: (
|
|
939
|
+
<>
|
|
940
|
+
Send to parents
|
|
941
|
+
<Icon name="send" />
|
|
942
|
+
</>
|
|
943
|
+
),
|
|
979
944
|
},
|
|
980
945
|
parameters: {
|
|
981
946
|
docs: {
|
|
@@ -983,11 +948,13 @@ export const WithIconRight: Story = withDescription(
|
|
|
983
948
|
language: 'tsx',
|
|
984
949
|
code: `
|
|
985
950
|
import { Button } from '@arbor-education/design-system.components';
|
|
951
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
986
952
|
|
|
987
953
|
function ButtonWithIconRightExample() {
|
|
988
954
|
return (
|
|
989
|
-
<Button variant="secondary" size="M"
|
|
955
|
+
<Button variant="secondary" size="M">
|
|
990
956
|
Send to parents
|
|
957
|
+
<Icon name="send" />
|
|
991
958
|
</Button>
|
|
992
959
|
);
|
|
993
960
|
}
|
|
@@ -998,7 +965,7 @@ export default ButtonWithIconRightExample;
|
|
|
998
965
|
},
|
|
999
966
|
},
|
|
1000
967
|
},
|
|
1001
|
-
'Icon
|
|
968
|
+
'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.',
|
|
1002
969
|
);
|
|
1003
970
|
|
|
1004
971
|
export const WithBothIcons: Story = withDescription(
|
|
@@ -1006,11 +973,13 @@ export const WithBothIcons: Story = withDescription(
|
|
|
1006
973
|
args: {
|
|
1007
974
|
variant: 'primary',
|
|
1008
975
|
size: 'M',
|
|
1009
|
-
children:
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
976
|
+
children: (
|
|
977
|
+
<>
|
|
978
|
+
<Icon name="sparkles" screenReaderText="AI generated" />
|
|
979
|
+
Generate report
|
|
980
|
+
<Icon name="arrow-right" />
|
|
981
|
+
</>
|
|
982
|
+
),
|
|
1014
983
|
},
|
|
1015
984
|
parameters: {
|
|
1016
985
|
docs: {
|
|
@@ -1018,18 +987,14 @@ export const WithBothIcons: Story = withDescription(
|
|
|
1018
987
|
language: 'tsx',
|
|
1019
988
|
code: `
|
|
1020
989
|
import { Button } from '@arbor-education/design-system.components';
|
|
990
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
1021
991
|
|
|
1022
992
|
function ButtonWithBothIconsExample() {
|
|
1023
993
|
return (
|
|
1024
|
-
<Button
|
|
1025
|
-
|
|
1026
|
-
size="M"
|
|
1027
|
-
iconLeftName="sparkles"
|
|
1028
|
-
iconLeftScreenReaderText="AI generated"
|
|
1029
|
-
iconRightName="arrow-right"
|
|
1030
|
-
iconRightScreenReaderText="Continue"
|
|
1031
|
-
>
|
|
994
|
+
<Button variant="primary" size="M">
|
|
995
|
+
<Icon name="sparkles" screenReaderText="AI generated" />
|
|
1032
996
|
Generate report
|
|
997
|
+
<Icon name="arrow-right" />
|
|
1033
998
|
</Button>
|
|
1034
999
|
);
|
|
1035
1000
|
}
|
|
@@ -1040,7 +1005,7 @@ export default ButtonWithBothIconsExample;
|
|
|
1040
1005
|
},
|
|
1041
1006
|
},
|
|
1042
1007
|
},
|
|
1043
|
-
'Both icon
|
|
1008
|
+
'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.',
|
|
1044
1009
|
);
|
|
1045
1010
|
|
|
1046
1011
|
export const IconOnly: Story = withDescription(
|
|
@@ -1048,8 +1013,8 @@ export const IconOnly: Story = withDescription(
|
|
|
1048
1013
|
args: {
|
|
1049
1014
|
variant: 'secondary',
|
|
1050
1015
|
size: 'M',
|
|
1051
|
-
|
|
1052
|
-
|
|
1016
|
+
isRound: true,
|
|
1017
|
+
children: <Icon name="3-dot" screenReaderText="More actions" />,
|
|
1053
1018
|
},
|
|
1054
1019
|
parameters: {
|
|
1055
1020
|
docs: {
|
|
@@ -1057,9 +1022,14 @@ export const IconOnly: Story = withDescription(
|
|
|
1057
1022
|
language: 'tsx',
|
|
1058
1023
|
code: `
|
|
1059
1024
|
import { Button } from '@arbor-education/design-system.components';
|
|
1025
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
1060
1026
|
|
|
1061
1027
|
function ButtonIconOnlyExample() {
|
|
1062
|
-
return
|
|
1028
|
+
return (
|
|
1029
|
+
<Button variant="secondary" size="M" isRound>
|
|
1030
|
+
<Icon name="3-dot" screenReaderText="More actions" />
|
|
1031
|
+
</Button>
|
|
1032
|
+
);
|
|
1063
1033
|
}
|
|
1064
1034
|
|
|
1065
1035
|
export default ButtonIconOnlyExample;
|
|
@@ -1068,7 +1038,7 @@ export default ButtonIconOnlyExample;
|
|
|
1068
1038
|
},
|
|
1069
1039
|
},
|
|
1070
1040
|
},
|
|
1071
|
-
'
|
|
1041
|
+
'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.',
|
|
1072
1042
|
);
|
|
1073
1043
|
|
|
1074
1044
|
export const IconOnlyAccessibility: Story = withDescription(
|
|
@@ -1077,13 +1047,12 @@ export const IconOnlyAccessibility: Story = withDescription(
|
|
|
1077
1047
|
<div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xlarge)' }}>
|
|
1078
1048
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
1079
1049
|
<p style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
|
|
1080
|
-
Bad —
|
|
1050
|
+
Bad — no accessible name at all (screenReaderText omitted)
|
|
1081
1051
|
</p>
|
|
1082
1052
|
<div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
|
|
1083
|
-
|
|
1084
|
-
<Button variant="secondary" type="button"
|
|
1085
|
-
<Button variant="secondary" type="button"
|
|
1086
|
-
<Button variant="secondary" type="button" iconRightName="pencil" />
|
|
1053
|
+
<Button variant="secondary" type="button" isRound><Icon name="3-dot" /></Button>
|
|
1054
|
+
<Button variant="secondary" type="button" isRound><Icon name="x" /></Button>
|
|
1055
|
+
<Button variant="secondary" type="button" isRound><Icon name="pencil" /></Button>
|
|
1087
1056
|
</div>
|
|
1088
1057
|
</div>
|
|
1089
1058
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
@@ -1091,9 +1060,9 @@ export const IconOnlyAccessibility: Story = withDescription(
|
|
|
1091
1060
|
Good — screen reader announces a human-readable action name
|
|
1092
1061
|
</p>
|
|
1093
1062
|
<div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
|
|
1094
|
-
<Button variant="secondary" type="button"
|
|
1095
|
-
<Button variant="secondary" type="button"
|
|
1096
|
-
<Button variant="secondary" type="button"
|
|
1063
|
+
<Button variant="secondary" type="button" isRound><Icon name="3-dot" screenReaderText="More actions" /></Button>
|
|
1064
|
+
<Button variant="secondary" type="button" isRound><Icon name="x" screenReaderText="Dismiss" /></Button>
|
|
1065
|
+
<Button variant="secondary" type="button" isRound><Icon name="pencil" screenReaderText="Edit student details" /></Button>
|
|
1097
1066
|
</div>
|
|
1098
1067
|
</div>
|
|
1099
1068
|
</div>
|
|
@@ -1104,22 +1073,23 @@ export const IconOnlyAccessibility: Story = withDescription(
|
|
|
1104
1073
|
language: 'tsx',
|
|
1105
1074
|
code: `
|
|
1106
1075
|
import { Button } from '@arbor-education/design-system.components';
|
|
1076
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
1107
1077
|
|
|
1108
1078
|
function ButtonIconOnlyAccessibilityExample() {
|
|
1109
1079
|
return (
|
|
1110
1080
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
1111
|
-
{/* Bad —
|
|
1081
|
+
{/* Bad — no accessible name at all */}
|
|
1112
1082
|
<div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
|
|
1113
|
-
<Button variant="secondary" type="button"
|
|
1114
|
-
<Button variant="secondary" type="button"
|
|
1115
|
-
<Button variant="secondary" type="button"
|
|
1083
|
+
<Button variant="secondary" type="button" isRound><Icon name="3-dot" /></Button>
|
|
1084
|
+
<Button variant="secondary" type="button" isRound><Icon name="x" /></Button>
|
|
1085
|
+
<Button variant="secondary" type="button" isRound><Icon name="pencil" /></Button>
|
|
1116
1086
|
</div>
|
|
1117
1087
|
|
|
1118
1088
|
{/* Good — human-readable action names */}
|
|
1119
1089
|
<div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
|
|
1120
|
-
<Button variant="secondary" type="button"
|
|
1121
|
-
<Button variant="secondary" type="button"
|
|
1122
|
-
<Button variant="secondary" type="button"
|
|
1090
|
+
<Button variant="secondary" type="button" isRound><Icon name="3-dot" screenReaderText="More actions" /></Button>
|
|
1091
|
+
<Button variant="secondary" type="button" isRound><Icon name="x" screenReaderText="Dismiss" /></Button>
|
|
1092
|
+
<Button variant="secondary" type="button" isRound><Icon name="pencil" screenReaderText="Edit student details" /></Button>
|
|
1123
1093
|
</div>
|
|
1124
1094
|
</div>
|
|
1125
1095
|
);
|
|
@@ -1131,7 +1101,7 @@ export default ButtonIconOnlyAccessibilityExample;
|
|
|
1131
1101
|
},
|
|
1132
1102
|
},
|
|
1133
1103
|
},
|
|
1134
|
-
'Icon-only accessibility contrast. The top row
|
|
1104
|
+
'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.',
|
|
1135
1105
|
);
|
|
1136
1106
|
|
|
1137
1107
|
export const Disabled: Story = withDescription(
|
|
@@ -1190,72 +1160,6 @@ export default ButtonErrorStateExample;
|
|
|
1190
1160
|
'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).',
|
|
1191
1161
|
);
|
|
1192
1162
|
|
|
1193
|
-
export const Borderless: Story = withDescription(
|
|
1194
|
-
{
|
|
1195
|
-
args: {
|
|
1196
|
-
variant: 'tertiary',
|
|
1197
|
-
size: 'M',
|
|
1198
|
-
children: 'Settings',
|
|
1199
|
-
iconLeftName: 'settings',
|
|
1200
|
-
iconLeftScreenReaderText: 'Settings',
|
|
1201
|
-
borderless: true,
|
|
1202
|
-
},
|
|
1203
|
-
parameters: {
|
|
1204
|
-
docs: {
|
|
1205
|
-
source: {
|
|
1206
|
-
language: 'tsx',
|
|
1207
|
-
code: `
|
|
1208
|
-
import { Button } from '@arbor-education/design-system.components';
|
|
1209
|
-
|
|
1210
|
-
function ButtonBorderlessExample() {
|
|
1211
|
-
return (
|
|
1212
|
-
<Button variant="tertiary" size="M" iconLeftName="settings" iconLeftScreenReaderText="Settings" borderless>
|
|
1213
|
-
Settings
|
|
1214
|
-
</Button>
|
|
1215
|
-
);
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
export default ButtonBorderlessExample;
|
|
1219
|
-
`.trim(),
|
|
1220
|
-
},
|
|
1221
|
-
},
|
|
1222
|
-
},
|
|
1223
|
-
},
|
|
1224
|
-
'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.',
|
|
1225
|
-
);
|
|
1226
|
-
|
|
1227
|
-
export const NoHorizontalPadding: Story = withDescription(
|
|
1228
|
-
{
|
|
1229
|
-
args: {
|
|
1230
|
-
variant: 'text-link',
|
|
1231
|
-
size: 'M',
|
|
1232
|
-
children: 'View full attendance report',
|
|
1233
|
-
hasHorizontalPadding: false,
|
|
1234
|
-
},
|
|
1235
|
-
parameters: {
|
|
1236
|
-
docs: {
|
|
1237
|
-
source: {
|
|
1238
|
-
language: 'tsx',
|
|
1239
|
-
code: `
|
|
1240
|
-
import { Button } from '@arbor-education/design-system.components';
|
|
1241
|
-
|
|
1242
|
-
function ButtonNoHorizontalPaddingExample() {
|
|
1243
|
-
return (
|
|
1244
|
-
<Button variant="text-link" size="M" hasHorizontalPadding={false}>
|
|
1245
|
-
View full attendance report
|
|
1246
|
-
</Button>
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
export default ButtonNoHorizontalPaddingExample;
|
|
1251
|
-
`.trim(),
|
|
1252
|
-
},
|
|
1253
|
-
},
|
|
1254
|
-
},
|
|
1255
|
-
},
|
|
1256
|
-
'`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.',
|
|
1257
|
-
);
|
|
1258
|
-
|
|
1259
1163
|
export const FormTypeAttribute: Story = withDescription(
|
|
1260
1164
|
{
|
|
1261
1165
|
render: FormTypeAttributeTemplate,
|
|
@@ -1315,10 +1219,10 @@ export default FormTypeAttributeExample;
|
|
|
1315
1219
|
},
|
|
1316
1220
|
},
|
|
1317
1221
|
[
|
|
1318
|
-
'**Critical gotcha demo.**
|
|
1319
|
-
'`
|
|
1320
|
-
'
|
|
1321
|
-
'
|
|
1222
|
+
'**Critical gotcha demo.** `type` is a typed prop, but `Button` never defaults it — there is no',
|
|
1223
|
+
'`type = \'button\'` fallback in the component. This means `<Button>` never sets a `type` attribute',
|
|
1224
|
+
'on its own, and browsers default to `type="submit"`. Any `<Button>` inside a `<form>` will submit',
|
|
1225
|
+
'that form on click unless you pass `type="button"` explicitly.',
|
|
1322
1226
|
'',
|
|
1323
1227
|
'This story shows a form with both patterns side by side. The "Cancel" button uses `type="button"`',
|
|
1324
1228
|
'so clicking it does not submit. The "Save attendance" button uses `type="submit"` to intentionally',
|
|
@@ -1326,6 +1230,77 @@ export default FormTypeAttributeExample;
|
|
|
1326
1230
|
].join(' '),
|
|
1327
1231
|
);
|
|
1328
1232
|
|
|
1233
|
+
export const Borderless: Story = withDescription(
|
|
1234
|
+
{
|
|
1235
|
+
args: {
|
|
1236
|
+
variant: 'tertiary',
|
|
1237
|
+
size: 'M',
|
|
1238
|
+
borderless: true,
|
|
1239
|
+
children: (
|
|
1240
|
+
<>
|
|
1241
|
+
<Icon name="settings" />
|
|
1242
|
+
Settings
|
|
1243
|
+
</>
|
|
1244
|
+
),
|
|
1245
|
+
},
|
|
1246
|
+
parameters: {
|
|
1247
|
+
docs: {
|
|
1248
|
+
source: {
|
|
1249
|
+
language: 'tsx',
|
|
1250
|
+
code: `
|
|
1251
|
+
import { Button } from '@arbor-education/design-system.components';
|
|
1252
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
1253
|
+
|
|
1254
|
+
function ButtonBorderlessExample() {
|
|
1255
|
+
return (
|
|
1256
|
+
<Button variant="tertiary" size="M" borderless>
|
|
1257
|
+
<Icon name="settings" />
|
|
1258
|
+
Settings
|
|
1259
|
+
</Button>
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
export default ButtonBorderlessExample;
|
|
1264
|
+
`.trim(),
|
|
1265
|
+
},
|
|
1266
|
+
},
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
'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.',
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
export const NoHorizontalPadding: Story = withDescription(
|
|
1273
|
+
{
|
|
1274
|
+
args: {
|
|
1275
|
+
variant: 'text-link',
|
|
1276
|
+
size: 'M',
|
|
1277
|
+
children: 'View full attendance report',
|
|
1278
|
+
hasHorizontalPadding: false,
|
|
1279
|
+
},
|
|
1280
|
+
parameters: {
|
|
1281
|
+
docs: {
|
|
1282
|
+
source: {
|
|
1283
|
+
language: 'tsx',
|
|
1284
|
+
code: `
|
|
1285
|
+
import { Button } from '@arbor-education/design-system.components';
|
|
1286
|
+
|
|
1287
|
+
function ButtonNoHorizontalPaddingExample() {
|
|
1288
|
+
return (
|
|
1289
|
+
<Button variant="text-link" size="M" hasHorizontalPadding={false}>
|
|
1290
|
+
View full attendance report
|
|
1291
|
+
</Button>
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
export default ButtonNoHorizontalPaddingExample;
|
|
1296
|
+
`.trim(),
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
'`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.',
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1329
1304
|
export const LoadingComposition: Story = withDescription(
|
|
1330
1305
|
{
|
|
1331
1306
|
render: LoadingCompositionTemplate,
|
|
@@ -1336,6 +1311,7 @@ export const LoadingComposition: Story = withDescription(
|
|
|
1336
1311
|
code: `
|
|
1337
1312
|
import { useState } from 'react';
|
|
1338
1313
|
import { Button } from '@arbor-education/design-system.components';
|
|
1314
|
+
import { Icon } from '@arbor-education/design-system.components';
|
|
1339
1315
|
|
|
1340
1316
|
function LoadingCompositionExample() {
|
|
1341
1317
|
const [loading, setLoading] = useState(false);
|
|
@@ -1359,10 +1335,9 @@ function LoadingCompositionExample() {
|
|
|
1359
1335
|
variant="primary"
|
|
1360
1336
|
type="button"
|
|
1361
1337
|
disabled={loading}
|
|
1362
|
-
iconLeftName={loading ? 'loader' : undefined}
|
|
1363
|
-
iconLeftScreenReaderText={loading ? 'Loading, please wait' : undefined}
|
|
1364
1338
|
onClick={handleClick}
|
|
1365
1339
|
>
|
|
1340
|
+
{loading && <Icon name="loader" screenReaderText="Loading, please wait" />}
|
|
1366
1341
|
{loading ? 'Exporting...' : 'Export to CSV'}
|
|
1367
1342
|
</Button>
|
|
1368
1343
|
</div>
|
|
@@ -1376,10 +1351,10 @@ export default LoadingCompositionExample;
|
|
|
1376
1351
|
},
|
|
1377
1352
|
},
|
|
1378
1353
|
[
|
|
1379
|
-
'Button has **no built-in loading state** — compose one with `disabled`, `
|
|
1380
|
-
'This pattern uses `disabled={loading}` to block interaction during the async operation,
|
|
1381
|
-
'
|
|
1382
|
-
'
|
|
1354
|
+
'Button has **no built-in loading state** — compose one with `disabled`, a conditional `Icon`, and a label change.',
|
|
1355
|
+
'This pattern uses `disabled={loading}` to block interaction during the async operation, a conditionally',
|
|
1356
|
+
'rendered `<Icon name="loader" screenReaderText="Loading, please wait" />` to show a spinner icon with an',
|
|
1357
|
+
'accessible announcement. After 2 seconds the mock export resolves and the button returns to its default state.',
|
|
1383
1358
|
'',
|
|
1384
1359
|
'The `loader` icon (`LoaderCircle` from Lucide) does not spin by default — animate it with a CSS',
|
|
1385
1360
|
'`animation: spin 1s linear infinite` keyframe on the `svg` within the button if your design requires it.',
|
|
@@ -1460,5 +1435,5 @@ export default ClickTrackingExample;
|
|
|
1460
1435
|
},
|
|
1461
1436
|
},
|
|
1462
1437
|
},
|
|
1463
|
-
'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.',
|
|
1438
|
+
'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.',
|
|
1464
1439
|
);
|