@arbor-education/design-system.components 1.0.4 → 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/.github/workflows/fail-on-no-changeset.yml +33 -1
- package/CHANGELOG.md +17 -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
|
@@ -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,
|
|
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
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
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.
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
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 —
|
|
81
|
-
'<Button variant="secondary"
|
|
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"
|
|
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:
|
|
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
|
-
'
|
|
181
|
-
'icon-only
|
|
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
|
-
|
|
227
|
-
control: '
|
|
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
|
-
'
|
|
309
|
-
'
|
|
310
|
-
'
|
|
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: '
|
|
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!" })),
|
|
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,
|
|
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:
|
|
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"
|
|
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
|
|
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:
|
|
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"
|
|
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
|
|
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:
|
|
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"
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
809
|
-
|
|
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
|
|
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
|
-
}, '
|
|
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
|
|
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 —
|
|
791
|
+
{/* Bad — no accessible name at all */}
|
|
841
792
|
<div style={{ display: 'flex', gap: 'var(--spacing-large)' }}>
|
|
842
|
-
<Button variant="secondary" type="button"
|
|
843
|
-
<Button variant="secondary" type="button"
|
|
844
|
-
<Button variant="secondary" type="button"
|
|
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"
|
|
850
|
-
<Button variant="secondary" type="button"
|
|
851
|
-
<Button variant="secondary" type="button"
|
|
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
|
|
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.**
|
|
1027
|
-
'`
|
|
1028
|
-
'
|
|
1029
|
-
'
|
|
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`, `
|
|
1084
|
-
'This pattern uses `disabled={loading}` to block interaction during the async operation,
|
|
1085
|
-
'
|
|
1086
|
-
'
|
|
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
|