@lyeve-labs/ui-kit 0.31.0 → 0.32.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 (112) hide show
  1. package/README.md +9 -9
  2. package/dist/components/AccordionItem.svelte +1 -1
  3. package/dist/components/AccountMenu.svelte +2 -2
  4. package/dist/components/AccountMenu.svelte.d.ts +1 -1
  5. package/dist/components/Alert.svelte +2 -2
  6. package/dist/components/Alert.svelte.d.ts +1 -1
  7. package/dist/components/AppShell.svelte +7 -7
  8. package/dist/components/AppShell.svelte.d.ts +2 -2
  9. package/dist/components/AuthShell.svelte +1 -1
  10. package/dist/components/AuthShell.svelte.d.ts +1 -1
  11. package/dist/components/Autocomplete.svelte +1 -1
  12. package/dist/components/Badge.svelte +2 -2
  13. package/dist/components/Banner.svelte +1 -1
  14. package/dist/components/Banner.svelte.d.ts +1 -1
  15. package/dist/components/Button.svelte +5 -5
  16. package/dist/components/Button.svelte.d.ts +1 -1
  17. package/dist/components/Card.svelte +2 -2
  18. package/dist/components/Checkbox.svelte +3 -3
  19. package/dist/components/CheckoutSteps.svelte +54 -0
  20. package/dist/components/CheckoutSteps.svelte.d.ts +12 -0
  21. package/dist/components/Collapsible.svelte +1 -1
  22. package/dist/components/Collapsible.svelte.d.ts +1 -1
  23. package/dist/components/CopyField.svelte +2 -2
  24. package/dist/components/Field.svelte +1 -1
  25. package/dist/components/Field.svelte.d.ts +1 -1
  26. package/dist/components/FileInput.svelte +1 -1
  27. package/dist/components/FormMessage.svelte +4 -4
  28. package/dist/components/Logo.svelte +1 -1
  29. package/dist/components/Logo.svelte.d.ts +1 -1
  30. package/dist/components/MultiSelect.svelte +1 -1
  31. package/dist/components/PageHeader.svelte +2 -2
  32. package/dist/components/PageShell.svelte +4 -4
  33. package/dist/components/PageShell.svelte.d.ts +4 -4
  34. package/dist/components/Pagination.svelte +2 -2
  35. package/dist/components/Panel.svelte +1 -1
  36. package/dist/components/Panel.svelte.d.ts +1 -1
  37. package/dist/components/PasswordInput.svelte +5 -2
  38. package/dist/components/PluginCart.svelte +339 -0
  39. package/dist/components/PluginCart.svelte.d.ts +45 -0
  40. package/dist/components/Radio.svelte +2 -2
  41. package/dist/components/SearchInput.svelte +5 -4
  42. package/dist/components/SegmentedControl.svelte +4 -4
  43. package/dist/components/SegmentedControl.svelte.d.ts +2 -2
  44. package/dist/components/Select.svelte +5 -5
  45. package/dist/components/Select.svelte.d.ts +4 -4
  46. package/dist/components/SidebarNav.svelte +5 -5
  47. package/dist/components/Stat.svelte +1 -1
  48. package/dist/components/Stat.svelte.d.ts +1 -1
  49. package/dist/components/Table.svelte +4 -4
  50. package/dist/components/Tabs.svelte +2 -2
  51. package/dist/components/ThemeToggle.svelte +1 -1
  52. package/dist/components/TimePicker.svelte +2 -2
  53. package/dist/components/Toggle.svelte +3 -3
  54. package/dist/components/Toolbar.svelte +1 -1
  55. package/dist/components/Toolbar.svelte.d.ts +1 -1
  56. package/dist/components/TreeView.svelte +1 -1
  57. package/dist/components/dialog/Dialog.svelte +1 -1
  58. package/dist/components/dialog/DialogContainer.svelte +1 -1
  59. package/dist/components/dialog/dialog-manager.svelte.d.ts +3 -3
  60. package/dist/components/dialog/dialog-manager.svelte.js +7 -7
  61. package/dist/components/dialog/types.d.ts +3 -3
  62. package/dist/index.d.ts +7 -3
  63. package/dist/index.js +8 -5
  64. package/dist/internal/calendar.d.ts +1 -1
  65. package/dist/internal/calendar.js +2 -2
  66. package/dist/internal/choice.d.ts +6 -6
  67. package/dist/internal/choice.js +6 -6
  68. package/dist/internal/copy.svelte.d.ts +2 -2
  69. package/dist/internal/copy.svelte.js +2 -2
  70. package/dist/internal/field.d.ts +11 -11
  71. package/dist/internal/field.js +12 -12
  72. package/dist/internal/filter.d.ts +3 -3
  73. package/dist/internal/filter.js +3 -3
  74. package/dist/internal/href.d.ts +1 -1
  75. package/dist/internal/href.js +1 -1
  76. package/dist/internal/layout.d.ts +7 -7
  77. package/dist/internal/layout.js +7 -7
  78. package/dist/internal/listbox.svelte.d.ts +1 -1
  79. package/dist/internal/listbox.svelte.js +3 -3
  80. package/dist/internal/nav-expansion.svelte.d.ts +1 -1
  81. package/dist/internal/nav-expansion.svelte.js +2 -2
  82. package/dist/internal/nav-tree.d.ts +3 -3
  83. package/dist/internal/nav-tree.js +3 -3
  84. package/dist/internal/number.d.ts +1 -1
  85. package/dist/internal/number.js +1 -1
  86. package/dist/internal/overlay.d.ts +3 -3
  87. package/dist/internal/overlay.js +3 -3
  88. package/dist/internal/panel.d.ts +5 -5
  89. package/dist/internal/panel.js +5 -5
  90. package/dist/internal/pricing.d.ts +110 -0
  91. package/dist/internal/pricing.js +77 -0
  92. package/dist/internal/rollup.d.ts +3 -3
  93. package/dist/internal/rollup.js +3 -3
  94. package/dist/internal/scroll-edges.d.ts +4 -4
  95. package/dist/internal/scroll-edges.js +5 -5
  96. package/dist/internal/time.d.ts +3 -3
  97. package/dist/internal/time.js +3 -3
  98. package/dist/internal/tone.d.ts +3 -3
  99. package/dist/internal/tone.js +3 -3
  100. package/dist/internal/touch.d.ts +1 -1
  101. package/dist/internal/touch.js +1 -1
  102. package/dist/internal/tree.d.ts +2 -2
  103. package/dist/internal/tree.js +2 -2
  104. package/dist/motion.d.ts +3 -3
  105. package/dist/motion.js +4 -4
  106. package/dist/stores/toast.svelte.d.ts +1 -1
  107. package/dist/styles/theme.css +45 -45
  108. package/dist/utils/cn.d.ts +1 -1
  109. package/dist/utils/theme.d.ts +6 -6
  110. package/dist/utils/theme.js +10 -10
  111. package/package.json +1 -1
  112. package/src/lib/styles/theme.css +45 -45
package/README.md CHANGED
@@ -28,16 +28,16 @@ No config file, no theme provider, no setup ceremony.
28
28
 
29
29
  ## What's in the box
30
30
 
31
- - **69 components:** buttons, inputs, modals, drawers, tabs, tables, toasts, the works.
31
+ - **71 components:** buttons, inputs, modals, drawers, tabs, tables, toasts, the works.
32
32
  - **Two themes:** Soft Dark (default) and Soft Light, switched by a single `data-theme` attribute on `<html>`.
33
- - **One CSS file:** `@lyeve-labs/ui-kit/styles.css` declares every token; the rest is just Tailwind.
33
+ - **One CSS file:** `@lyeve-labs/ui-kit/styles.css` declares every token. The rest is just Tailwind.
34
34
  - **Svelte 5 native:** built on runes and snippets, fully typed end-to-end.
35
35
  - **No surprises:** a `<Button />` is a `<button>`, an `<Input />` is an `<input>`. Markup matches the preview.
36
36
 
37
37
  ## Component list
38
38
 
39
39
  <details>
40
- <summary>69 components, organized by purpose</summary>
40
+ <summary>71 components, organized by purpose</summary>
41
41
 
42
42
  **Layout and structure**
43
43
  Card, Panel, AppShell, AuthShell, PageShell, PageHeader, SectionHeading, Divider, Accordion, AccordionItem, Collapsible, Table, DescriptionList, Toolbar, TreeView
@@ -55,7 +55,7 @@ Modal, Drawer, Tooltip
55
55
  Dialog, DialogContainer, ConfirmDialog (plus the `openDialog` and `confirm` services)
56
56
 
57
57
  **Feedback and status**
58
- Alert, Banner, Badge, Tag, Indicator, Progress, Spinner, Skeleton, EmptyState, Stat, Kbd, CopyButton
58
+ Alert, Banner, Badge, Tag, Indicator, Progress, Spinner, Skeleton, CheckoutSteps, EmptyState, PluginCart, Stat, Kbd, CopyButton
59
59
 
60
60
  **Media**
61
61
  Avatar, AvatarGroup, Logo
@@ -175,7 +175,7 @@ in `styles.css` beside the palette:
175
175
 
176
176
  | Token | Value | For |
177
177
  | --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------- |
178
- | `--duration-fast` | 120ms | a state change in place: colour, opacity, border, shadow, focus |
178
+ | `--duration-fast` | 120ms | a state change in place: color, opacity, border, shadow, focus |
179
179
  | `--duration-base` | 200ms | movement in place (a knob, a chevron, a collapse) and a small surface arriving (menu, popover, tooltip, toast) |
180
180
  | `--duration-slow` | 320ms | a large surface arriving (dialog, drawer) |
181
181
  | `--duration-progress` | 500ms | a value moving (a progress bar's width) |
@@ -185,7 +185,7 @@ in `styles.css` beside the palette:
185
185
 
186
186
  An exit runs one rung faster than its entrance, on the exit curve. The
187
187
  `transition-*` utilities default to the fast rung and the move curve, so
188
- `transition-colors` on its own is complete; `duration-base`, `duration-slow`,
188
+ `transition-colors` on its own is complete. `duration-base`, `duration-slow`,
189
189
  `duration-progress`, `ease-enter`, `ease-exit` and `ease-move` are utilities
190
190
  for the rest. A surface of your own that mounts and unmounts enters and
191
191
  leaves through the same presets the kit's overlays use:
@@ -225,7 +225,7 @@ A page picks a role and `PageShell` picks the cap:
225
225
  | `wide` | 1536px | a data page whose table needs the room |
226
226
  | `full` | none | a canvas or a split pane |
227
227
 
228
- A surface lifted off the page - `Modal`, `Drawer`, a dialog - takes a rung of
228
+ A surface lifted off the page (`Modal`, `Drawer`, a dialog) takes a rung of
229
229
  one shared ladder, so the same form is the same size whichever of the three a
230
230
  page opens it in: `sm` 448px, `md` 576px, `lg` 704px, `xl` 896px, and `full`
231
231
  1088px for a dialog holding a table.
@@ -253,7 +253,7 @@ This repo is a single-purpose component library. Nothing but `src/lib/`.
253
253
  ```
254
254
  src/
255
255
  └── lib/ # → published as @lyeve-labs/ui-kit
256
- ├── components/ # 69 .svelte files
256
+ ├── components/ # 71 .svelte files
257
257
  ├── stores/ # toast.svelte.ts
258
258
  ├── styles/ # theme.css (the one stylesheet)
259
259
  ├── utils/ # cn.ts, theme.ts
@@ -263,7 +263,7 @@ src/
263
263
  ## Versioning
264
264
 
265
265
  `@lyeve-labs/ui-kit` follows [SemVer](https://semver.org). While under `1.0`,
266
- breaking changes bump the **minor** version; additive changes bump the **patch**.
266
+ breaking changes bump the **minor** version. Additive changes bump the **patch**.
267
267
  Every release is logged in [`CHANGELOG.md`](CHANGELOG.md) and on the docs site.
268
268
 
269
269
  Maintainers: the release workflow is [`CONTRIBUTING.md`](CONTRIBUTING.md#releases).
@@ -59,7 +59,7 @@
59
59
  <!--
60
60
  A panel opens to the height of its own content, which no fixed value knows.
61
61
  Animating grid-template-rows from 0fr to 1fr resolves to that height in CSS
62
- alone - no measuring, no reflow on every frame, and nothing to get wrong when
62
+ alone: no measuring, no reflow on every frame, and nothing to get wrong when
63
63
  the content changes. The inner element carries the overflow so the text is
64
64
  clipped rather than spilling while the row grows.
65
65
  -->
@@ -22,7 +22,7 @@
22
22
  import type { AccentTone } from '../internal/tone.js';
23
23
 
24
24
  interface Props {
25
- /** The line people recognise themselves by: a display name, or the email. */
25
+ /** The line people recognize themselves by: a display name, or the email. */
26
26
  name: string;
27
27
  /** Under it. The email when the name is a name, otherwise a role or a plan. */
28
28
  secondary?: string;
@@ -74,7 +74,7 @@
74
74
  /**
75
75
  * A `details` closes on its own summary and on nothing else, so without this
76
76
  * the panel stays open behind whatever the reader does next. Escape and an
77
- * outside click are both what a menu owes; they are enhancement, and the
77
+ * outside click are both what a menu owes. They are enhancement, and the
78
78
  * disclosure still works without either.
79
79
  */
80
80
  $effect(() => {
@@ -18,7 +18,7 @@
18
18
  import type { Snippet } from 'svelte';
19
19
  import type { AccentTone } from '../internal/tone.js';
20
20
  interface Props {
21
- /** The line people recognise themselves by: a display name, or the email. */
21
+ /** The line people recognize themselves by: a display name, or the email. */
22
22
  name: string;
23
23
  /** Under it. The email when the name is a name, otherwise a role or a plan. */
24
24
  secondary?: string;
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * A confirmation clears itself. A warning and a failure stay until
30
30
  * dismissed, so this is off by default and a danger alert never takes it.
31
- * Pass it wherever the alert reports the outcome of a submit; leave it off
31
+ * Pass it wherever the alert reports the outcome of a submit. Leave it off
32
32
  * where a success tone states a standing condition, which does not stop
33
33
  * being true after five seconds.
34
34
  */
@@ -60,7 +60,7 @@
60
60
 
61
61
  /**
62
62
  * A failure and a warning interrupt, because the reader has to act. Anything
63
- * else waits for a pause. FormMessage already splits them this way; Alert
63
+ * else waits for a pause. FormMessage already splits them this way. Alert
64
64
  * announced every tone assertively, so a green confirmation cut across
65
65
  * whatever was being read. An alert that also clears itself must not be
66
66
  * assertive: it would interrupt to say something and then take it away.
@@ -9,7 +9,7 @@ interface Props {
9
9
  *
10
10
  * A confirmation clears itself. A warning and a failure stay until
11
11
  * dismissed, so this is off by default and a danger alert never takes it.
12
- * Pass it wherever the alert reports the outcome of a submit; leave it off
12
+ * Pass it wherever the alert reports the outcome of a submit. Leave it off
13
13
  * where a success tone states a standing condition, which does not stop
14
14
  * being true after five seconds.
15
15
  */
@@ -4,7 +4,7 @@
4
4
  * while the sidebar is the 56px icon rail, so a SidebarNav takes it as
5
5
  * `collapsed`, a brand row renders its mark without the wordmark, and a
6
6
  * footer band renders what fits. A snippet that ignores the argument still
7
- * renders; it is simply drawn at 56px and clipped.
7
+ * renders. It is simply drawn at 56px and clipped.
8
8
  */
9
9
  export interface SidebarState {
10
10
  rail: boolean;
@@ -83,7 +83,7 @@
83
83
  *
84
84
  * The rail is automatic between md: and lg:, where the 224px column left a
85
85
  * 768px window 496px for the page. A page that owns the viewport - a
86
- * canvas, a split pane - asks for it at every width with this; the labels
86
+ * canvas, a split pane - asks for it at every width with this. The labels
87
87
  * still come back under a pointer or the keyboard. Below md: the sidebar
88
88
  * is the drawer whatever this says.
89
89
  */
@@ -136,7 +136,7 @@
136
136
  const drawerOpen = $derived(isMobile && navOpen);
137
137
  /**
138
138
  * Collapsing is a desktop gesture. Below md: the same aside is the drawer,
139
- * so honouring `collapsed` there would leave the hamburger opening nothing.
139
+ * so honoring `collapsed` there would leave the hamburger opening nothing.
140
140
  */
141
141
  const railHidden = $derived(collapsible && collapsed && !isMobile);
142
142
  /** Same rule for the header: a desktop gesture, and the drawer's button stays. */
@@ -148,7 +148,7 @@
148
148
  * While the sidebar is the rail it is 56px wide and the nav is told so. A
149
149
  * pointer over it, or focus inside it, opens it to the full column over the
150
150
  * page rather than beside it, so the page keeps its width and the reader
151
- * still gets every label; it closes when the pointer or the focus leaves.
151
+ * still gets every label. It closes when the pointer or the focus leaves.
152
152
  * State rather than a hover rule, because the nav snippet has to be told
153
153
  * the labels are wanted, and a stylesheet cannot tell it.
154
154
  */
@@ -174,7 +174,7 @@
174
174
  };
175
175
  });
176
176
 
177
- /** Focus moving from one rail link to the next stays inside; only leaving the rail closes it. */
177
+ /** Focus moving from one rail link to the next stays inside. Only leaving the rail closes it. */
178
178
  function onRailFocusOut(event: FocusEvent) {
179
179
  const next = event.relatedTarget;
180
180
  if (
@@ -218,7 +218,7 @@
218
218
  {/if}
219
219
 
220
220
  <!--
221
- One aside at every width. Below md: it sits inside a positioned dialog; above
221
+ One aside at every width. Below md: it sits inside a positioned dialog. Above
222
222
  it, that wrapper is the column it has always been. Rendering a second copy
223
223
  for the drawer would put every nav link in the page twice, which is what a
224
224
  strict-mode locator trips on and what a screen reader reads out.
@@ -226,7 +226,7 @@
226
226
  The dialog role is on the wrapper and not on the aside: an aside is a
227
227
  complementary landmark, and the a11y gate rejects a non-interactive element
228
228
  taking an interactive role. use:overlay is what makes aria-modal true rather
229
- than merely claimed - it moves focus in, keeps Tab inside, and hands focus
229
+ than merely claimed. It moves focus in, keeps Tab inside, and hands focus
230
230
  back to the hamburger on close.
231
231
  -->
232
232
  {#if drawerOpen}
@@ -3,7 +3,7 @@
3
3
  * while the sidebar is the 56px icon rail, so a SidebarNav takes it as
4
4
  * `collapsed`, a brand row renders its mark without the wordmark, and a
5
5
  * footer band renders what fits. A snippet that ignores the argument still
6
- * renders; it is simply drawn at 56px and clipped.
6
+ * renders. It is simply drawn at 56px and clipped.
7
7
  */
8
8
  export interface SidebarState {
9
9
  rail: boolean;
@@ -68,7 +68,7 @@ interface Props {
68
68
  *
69
69
  * The rail is automatic between md: and lg:, where the 224px column left a
70
70
  * 768px window 496px for the page. A page that owns the viewport - a
71
- * canvas, a split pane - asks for it at every width with this; the labels
71
+ * canvas, a split pane - asks for it at every width with this. The labels
72
72
  * still come back under a pointer or the keyboard. Below md: the sidebar
73
73
  * is the drawer whatever this says.
74
74
  */
@@ -11,7 +11,7 @@
11
11
  description?: string;
12
12
  /** Where the lockup links. Left unset, it is a mark and not a link. */
13
13
  href?: string;
14
- /** `md` is a form; `lg` is a walkthrough with more than one column. */
14
+ /** `md` is a form. `lg` is a walkthrough with more than one column. */
15
15
  width?: Width;
16
16
  /** Controls at the top end of the column: a theme toggle, a language switch. */
17
17
  actions?: Snippet;
@@ -6,7 +6,7 @@ interface Props {
6
6
  description?: string;
7
7
  /** Where the lockup links. Left unset, it is a mark and not a link. */
8
8
  href?: string;
9
- /** `md` is a form; `lg` is a walkthrough with more than one column. */
9
+ /** `md` is a form. `lg` is a walkthrough with more than one column. */
10
10
  width?: Width;
11
11
  /** Controls at the top end of the column: a theme toggle, a language switch. */
12
12
  actions?: Snippet;
@@ -210,7 +210,7 @@
210
210
  the field. The padding is taken back off the horizontal axis only: the
211
211
  box is positioned from its right edge, so a negative right margin holds
212
212
  the glyph exactly where it was, while a negative top margin would fight
213
- the translate that centres it and lift it 6px off the middle of the
213
+ the translate that centers it and lift it 6px off the middle of the
214
214
  field.
215
215
  -->
216
216
  <button
@@ -42,14 +42,14 @@
42
42
  Under a Table's default `overflow-wrap: anywhere` a status rendered as
43
43
  `dra ft` and a role as `sup er_a dmi n`. The label sits in a truncating
44
44
  span so a badge whose caller caps its width ends in an ellipsis instead of
45
- painting past its own border; with no cap the span never shrinks.
45
+ painting past its own border. With no cap the span never shrinks.
46
46
 
47
47
  The svg rules are what let a caller put an icon in the label. Preflight
48
48
  makes every svg a block, and a block inside the label takes a line of its
49
49
  own, so an icon and its text stacked and the badge rendered two rows tall
50
50
  inside a pill. A flex row here would fix the stacking and lose the
51
51
  ellipsis, which is the thing the span exists for, so the icon goes back to
52
- being inline and sits on the text's optical centre. -->
52
+ being inline and sits on the text's optical center. -->
53
53
  <span
54
54
  class="inline-flex max-w-full items-center font-medium whitespace-nowrap rounded-full border {tones[
55
55
  tone
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * A confirmation clears itself. A warning and a failure stay until
20
20
  * dismissed, so this is off by default. Pass it wherever the banner
21
- * reports the outcome of a submit; leave it off where it states a standing
21
+ * reports the outcome of a submit. Leave it off where it states a standing
22
22
  * condition, which does not stop being true after five seconds.
23
23
  */
24
24
  autoDismiss?: number | boolean;
@@ -8,7 +8,7 @@ interface Props {
8
8
  *
9
9
  * A confirmation clears itself. A warning and a failure stay until
10
10
  * dismissed, so this is off by default. Pass it wherever the banner
11
- * reports the outcome of a submit; leave it off where it states a standing
11
+ * reports the outcome of a submit. Leave it off where it states a standing
12
12
  * condition, which does not stop being true after five seconds.
13
13
  */
14
14
  autoDismiss?: number | boolean;
@@ -30,7 +30,7 @@
30
30
  /**
31
31
  * The words shown on hover and focus. Defaults to the button's own
32
32
  * aria-label, so an icon-only button names itself to a sighted reader the
33
- * way it already does to a screen reader; a string replaces those words,
33
+ * way it already does to a screen reader. A string replaces those words,
34
34
  * and `false` keeps the button silent.
35
35
  *
36
36
  * Thirty-five icon-only actions across one console had a name and no
@@ -65,14 +65,14 @@
65
65
  );
66
66
 
67
67
  /**
68
- * Colour per variant, in three states: at rest, under a pointer, and held.
68
+ * Color per variant, in three states: at rest, under a pointer, and held.
69
69
  *
70
70
  * The pressed step is the one a touch screen depends on. `hover:` compiles
71
71
  * inside `@media (hover: hover)`, which is false on a finger, so a tap ran
72
72
  * rest to rest with the action already fired and the control never
73
73
  * acknowledged the press. `active:` matches under a finger as well as under a
74
74
  * mouse, and Tailwind emits it after `hover:` at equal specificity, so the
75
- * held colour wins on a device that has both.
75
+ * held color wins on a device that has both.
76
76
  *
77
77
  * primary and violet had a pressed state already and neither one showed:
78
78
  * `active:bg-brand` on a `bg-brand` button and `active:brightness-100` on an
@@ -107,7 +107,7 @@
107
107
  * whitespace-nowrap: a label never breaks inside itself. Under a Table's
108
108
  * `overflow-wrap: anywhere` a two-word ghost button in a flex row rendered
109
109
  * on two lines, and a status badge beside it as `dra ft`. A caller that
110
- * caps the width wraps its label in a `truncate` span; the button cannot
110
+ * caps the width wraps its label in a `truncate` span. The button cannot
111
111
  * do that for it, because the label and any icon before it are flex items
112
112
  * and text-overflow does not reach into one.
113
113
  *
@@ -164,7 +164,7 @@
164
164
  {/snippet}
165
165
 
166
166
  <!-- The hint repeats the name, so it does not describe the control a second
167
- time; the aria-label already announces it. The wrapper takes the full
167
+ time. The aria-label already announces it. The wrapper takes the full
168
168
  width along with the button, or a full button would shrink to its text. -->
169
169
  {#if hintText}
170
170
  <Tooltip text={hintText} describe={false} class={full ? 'w-full' : ''}>
@@ -19,7 +19,7 @@ interface Props extends Forwarded {
19
19
  /**
20
20
  * The words shown on hover and focus. Defaults to the button's own
21
21
  * aria-label, so an icon-only button names itself to a sighted reader the
22
- * way it already does to a screen reader; a string replaces those words,
22
+ * way it already does to a screen reader. A string replaces those words,
23
23
  * and `false` keeps the button silent.
24
24
  *
25
25
  * Thirty-five icon-only actions across one console had a name and no
@@ -58,7 +58,7 @@
58
58
  * you pressed one. It took `role="button"` and `tabindex="0"` from `onclick`
59
59
  * alone, so it entered the tab order and then ignored Enter and Space unless
60
60
  * the page happened to pass its own `onkeydown`. Native button semantics are
61
- * the contract here, so the component honours them itself and still forwards
61
+ * the contract here, so the component honors them itself and still forwards
62
62
  * whatever the consumer supplied.
63
63
  */
64
64
  function activate(e: KeyboardEvent) {
@@ -79,7 +79,7 @@
79
79
  </script>
80
80
 
81
81
  <!-- tabindex is only set alongside role="button" (when onclick is provided),
82
- so the element is interactive; the compiler can't narrow the dynamic role. -->
82
+ so the element is interactive. The compiler can't narrow the dynamic role. -->
83
83
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
84
84
  <div
85
85
  data-print="keep"
@@ -79,7 +79,7 @@
79
79
  : undefined,
80
80
  );
81
81
 
82
- // A part-checked box is on, not a third colour, so it paints the filled box
82
+ // A part-checked box is on, not a third color, so it paints the filled box
83
83
  // and tints the card exactly as a checked one does.
84
84
  const on = $derived(checked || indeterminate);
85
85
 
@@ -91,8 +91,8 @@
91
91
  function handleChange(e: Event & { currentTarget: HTMLInputElement }) {
92
92
  // Disabled is enforced here as well as on the input. The native attribute
93
93
  // is what stops a real click, and it is the only thing that does, so a
94
- // change arriving any other way - a synthetic event, a script driving the
95
- // node - would still move the bound value with nothing on screen to say so.
94
+ // change arriving any other way (a synthetic event, a script driving the
95
+ // node) would still move the bound value with nothing on screen to say so.
96
96
  if (disabled) return;
97
97
  // The DOM clears indeterminate on the first click. Leaving the prop set
98
98
  // would repaint the mixed bar over a box the user has just ticked.
@@ -0,0 +1,54 @@
1
+ <!--
2
+ Where the reader is in a purchase, on every surface the purchase crosses.
3
+
4
+ Buying here spans two sites: the price page configures, the portal takes the
5
+ account and the payment. The hop between them read as arriving somewhere
6
+ else, so the same four labels are rendered on both, from one component, and
7
+ the reader can see that step three follows step two.
8
+
9
+ It is an ordered list, and the step in progress carries aria-current, so the
10
+ same information reaches a reader who cannot see the styling.
11
+ -->
12
+ <script lang="ts">
13
+ import { cn } from '../utils/cn.js';
14
+
15
+ interface Props {
16
+ /** In order. Four is the most that stays readable on a phone. */
17
+ steps: string[];
18
+ /** Zero-based index of the step in progress. */
19
+ current: number;
20
+ /** Names the list for assistive technology. */
21
+ label?: string;
22
+ class?: string;
23
+ }
24
+
25
+ let { steps, current, label = 'Checkout progress', class: klass = '' }: Props = $props();
26
+ </script>
27
+
28
+ <nav aria-label={label} class={cn('w-full', klass)}>
29
+ <ol class="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs sm:text-sm">
30
+ {#each steps as step, i (step)}
31
+ {@const done = i < current}
32
+ {@const here = i === current}
33
+ <li class="flex items-center gap-2" aria-current={here ? 'step' : undefined}>
34
+ <span
35
+ class={cn(
36
+ 'flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-xs font-semibold transition-colors',
37
+ done && 'bg-brand/20 text-brand',
38
+ here && 'bg-brand text-ink',
39
+ !done && !here && 'bg-surface-2 text-faint',
40
+ )}
41
+ aria-hidden="true"
42
+ >
43
+ {i + 1}
44
+ </span>
45
+ <span class={cn(here ? 'font-medium text-fg' : done ? 'text-muted' : 'text-faint')}>
46
+ {step}
47
+ </span>
48
+ {#if i < steps.length - 1}
49
+ <span class="mx-1 text-faint" aria-hidden="true">/</span>
50
+ {/if}
51
+ </li>
52
+ {/each}
53
+ </ol>
54
+ </nav>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ /** In order. Four is the most that stays readable on a phone. */
3
+ steps: string[];
4
+ /** Zero-based index of the step in progress. */
5
+ current: number;
6
+ /** Names the list for assistive technology. */
7
+ label?: string;
8
+ class?: string;
9
+ }
10
+ declare const CheckoutSteps: import("svelte").Component<Props, {}, "">;
11
+ type CheckoutSteps = ReturnType<typeof CheckoutSteps>;
12
+ export default CheckoutSteps;
@@ -10,7 +10,7 @@
10
10
  * share one value.
11
11
  *
12
12
  * Accordion is the component for a set of these. It decides which of its
13
- * items is open, and an item asks it; this decides nothing beyond itself.
13
+ * items is open, and an item asks it. This decides nothing beyond itself.
14
14
  */
15
15
  import type { Component, Snippet } from 'svelte';
16
16
  import { TOUCH_GROW } from '../internal/touch.js';
@@ -9,7 +9,7 @@
9
9
  * share one value.
10
10
  *
11
11
  * Accordion is the component for a set of these. It decides which of its
12
- * items is open, and an item asks it; this decides nothing beyond itself.
12
+ * items is open, and an item asks it. This decides nothing beyond itself.
13
13
  */
14
14
  import type { Component, Snippet } from 'svelte';
15
15
  interface Props {
@@ -87,7 +87,7 @@
87
87
  inside it, so the caller's actions, the reveal and the copy all fit
88
88
  without the value running under them and without measuring anything.
89
89
  The frame takes the focus ring and the brand border from the input
90
- inside it; a focused button draws its own ring and not the frame's. -->
90
+ inside it. A focused button draws its own ring and not the frame's. -->
91
91
  <div
92
92
  class="flex h-control w-full items-center gap-0.5 rounded-lg border bg-surface-2 pe-1 transition-colors border-line-strong has-[input:focus]:border-brand has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-inset has-[input:focus-visible]:ring-brand"
93
93
  >
@@ -130,7 +130,7 @@
130
130
 
131
131
  <!-- Mounted empty for the life of the field, so a copy is a change a screen
132
132
  reader announces rather than a region that arrives already full. The
133
- check is the sighted confirmation; only a failure is shown in words. -->
133
+ check is the sighted confirmation. Only a failure is shown in words. -->
134
134
  <p
135
135
  role="status"
136
136
  aria-live="polite"
@@ -3,7 +3,7 @@
3
3
  * What Field hands the control it wraps.
4
4
  *
5
5
  * The wiring is the whole point of the component. A consumer building a
6
- * control the kit does not ship - a colour well, a code editor, a third
6
+ * control the kit does not ship - a color well, a code editor, a third
7
7
  * party map picker - cannot reach internal/field.ts, so it hand-copies the
8
8
  * class strings and stops there: a form in a consuming app spelled
9
9
  * `class="flex flex-col gap-1.5"` inline, which is the literal value of
@@ -2,7 +2,7 @@
2
2
  * What Field hands the control it wraps.
3
3
  *
4
4
  * The wiring is the whole point of the component. A consumer building a
5
- * control the kit does not ship - a colour well, a code editor, a third
5
+ * control the kit does not ship - a color well, a code editor, a third
6
6
  * party map picker - cannot reach internal/field.ts, so it hand-copies the
7
7
  * class strings and stops there: a form in a consuming app spelled
8
8
  * `class="flex flex-col gap-1.5"` inline, which is the literal value of
@@ -53,7 +53,7 @@
53
53
 
54
54
  <!-- The dashed border is the only thing marking this region as a control, so
55
55
  it rests on line-strong: line reads 1.25:1 and failed SC 1.4.11. The
56
- hover is full-strength brand, because a focus-adjacent colour dropped to
56
+ hover is full-strength brand, because a focus-adjacent color dropped to
57
57
  half alpha reads as a weaker affordance for no reason a user can infer. -->
58
58
  <label
59
59
  class="flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl
@@ -14,10 +14,10 @@
14
14
  let { tone = 'danger', class: klass = '', children }: Props = $props();
15
15
 
16
16
  /**
17
- * The message colour, stated once.
17
+ * The message color, stated once.
18
18
  *
19
19
  * Eleven pages rendered a submit outcome into a bare paragraph and picked
20
- * their own size and colour for it, so the same failure read as three
20
+ * their own size and color for it, so the same failure read as three
21
21
  * different things depending on which settings page the user was on.
22
22
  */
23
23
  const tones: Record<Tone, string> = {
@@ -50,7 +50,7 @@
50
50
  </script>
51
51
 
52
52
  <!--
53
- The tones differ in more than colour. A failure takes role="alert", which
53
+ The tones differ in more than color. A failure takes role="alert", which
54
54
  interrupts whatever the reader is doing, because the submit did not go
55
55
  through and the user has to act on it. A confirmation takes aria-live
56
56
  "polite" and waits for a pause, because the work is done and nothing is being
@@ -64,7 +64,7 @@
64
64
  {#if filled}
65
65
  <!-- Stroked SVG, not a literal cross or tick: a font glyph lands at
66
66
  whatever weight the reader's font gives it, which sat visibly lighter
67
- than every other icon in the library. Colour alone would also be the
67
+ than every other icon in the library. Color alone would also be the
68
68
  only thing telling a failure from a confirmation. -->
69
69
  <svg
70
70
  class="mt-0.5 shrink-0"
@@ -12,7 +12,7 @@
12
12
  * for that reason, and none belongs here.
13
13
  *
14
14
  * Every face and the wordmark paint from the mark-* tokens, which carry the
15
- * brand book's colours per theme: the same lockup the documentation site
15
+ * brand book's colors per theme: the same lockup the documentation site
16
16
  * ships as a static file. They are separate from the brand ramp because that
17
17
  * ramp darkens for text contrast in the light theme and inverts the mark's
18
18
  * lighting there.
@@ -11,7 +11,7 @@
11
11
  * for that reason, and none belongs here.
12
12
  *
13
13
  * Every face and the wordmark paint from the mark-* tokens, which carry the
14
- * brand book's colours per theme: the same lockup the documentation site
14
+ * brand book's colors per theme: the same lockup the documentation site
15
15
  * ships as a static file. They are separate from the brand ramp because that
16
16
  * ramp darkens for text contrast in the light theme and inverts the mark's
17
17
  * lighting there.
@@ -215,7 +215,7 @@
215
215
  exception, and this one sits inside the trigger. The padding is
216
216
  taken straight back off as margin, so the chip is laid out
217
217
  exactly as it was and only the reachable area grew. The glyph
218
- is the same 12px cross Autocomplete clears its value with; it
218
+ is the same 12px cross Autocomplete clears its value with. It
219
219
  was drawn a pixel smaller here for no reason anyone recorded.
220
220
  -->
221
221
  <svg
@@ -49,7 +49,7 @@
49
49
  one of them, while this rendered at 24px, a size the ramp does not
50
50
  contain.
51
51
 
52
- text-h2 and not text-h1. The brand ramp is a marketing ramp - it runs
52
+ text-h2 and not text-h1. The brand ramp is a marketing ramp: it runs
53
53
  64 / 44 / 32 / 22 / 16 and holds nothing between 16 and 22, which is
54
54
  where a console's own type lives. A 44px page title over a 14px table on
55
55
  a 390px screen is the ramp applied rather than adopted. 32px is the step
@@ -74,7 +74,7 @@
74
74
  {#if description && !compact}<p class="mt-1 text-sm text-muted">{description}</p>{/if}
75
75
  </div>
76
76
  <!-- The slot wraps in two steps. It sits beside the title while both fit
77
- and drops under it, at the end edge, when they do not; wider than the
77
+ and drops under it, at the end edge, when they do not. Wider than the
78
78
  row on its own, it wraps its controls inside itself, each line ending at
79
79
  the end edge, in the order they were given. It used to be `shrink-0`,
80
80
  which sizes a flex item to its content and so cannot wrap even when the