@nqmcreative/ui 0.2.3 → 0.3.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 (170) hide show
  1. package/README.md +15 -15
  2. package/cli/index.mjs +81 -42
  3. package/dist/core/toast.svelte.d.ts +3 -0
  4. package/dist/styles/matte/AccordionItem.svelte +5 -0
  5. package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
  6. package/dist/styles/matte/Avatar.svelte +15 -0
  7. package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
  8. package/dist/styles/matte/Badge.svelte +5 -0
  9. package/dist/styles/matte/Badge.svelte.d.ts +2 -0
  10. package/dist/styles/matte/Breadcrumb.svelte +11 -4
  11. package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
  12. package/dist/styles/matte/Button.svelte +58 -7
  13. package/dist/styles/matte/Button.svelte.d.ts +14 -2
  14. package/dist/styles/matte/Card.svelte +6 -1
  15. package/dist/styles/matte/Card.svelte.d.ts +2 -0
  16. package/dist/styles/matte/CheckboxGroup.svelte +6 -0
  17. package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
  18. package/dist/styles/matte/Combobox.svelte +5 -0
  19. package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
  20. package/dist/styles/matte/CommandPalette.svelte +6 -0
  21. package/dist/styles/matte/CommandPalette.svelte.d.ts +3 -0
  22. package/dist/styles/matte/Drawer.svelte +5 -0
  23. package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
  24. package/dist/styles/matte/Footer.svelte +6 -2
  25. package/dist/styles/matte/Footer.svelte.d.ts +2 -0
  26. package/dist/styles/matte/Link.svelte +9 -0
  27. package/dist/styles/matte/Link.svelte.d.ts +4 -0
  28. package/dist/styles/matte/MenuItem.svelte +2 -1
  29. package/dist/styles/matte/Modal.svelte +5 -0
  30. package/dist/styles/matte/Modal.svelte.d.ts +2 -0
  31. package/dist/styles/matte/MultiSelect.svelte +7 -1
  32. package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
  33. package/dist/styles/matte/Navbar.svelte +18 -4
  34. package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
  35. package/dist/styles/matte/PricingCard.svelte +16 -8
  36. package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
  37. package/dist/styles/matte/RadioGroup.svelte +6 -0
  38. package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
  39. package/dist/styles/matte/SegmentedControl.svelte +16 -2
  40. package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
  41. package/dist/styles/matte/Sidebar.svelte +2 -1
  42. package/dist/styles/matte/Stat.svelte +8 -1
  43. package/dist/styles/matte/Stat.svelte.d.ts +3 -0
  44. package/dist/styles/matte/Steps.svelte +7 -0
  45. package/dist/styles/matte/Steps.svelte.d.ts +3 -0
  46. package/dist/styles/matte/Table.svelte +153 -97
  47. package/dist/styles/matte/Table.svelte.d.ts +12 -0
  48. package/dist/styles/matte/Tabs.svelte +5 -0
  49. package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
  50. package/dist/styles/matte/Toaster.svelte +3 -0
  51. package/dist/styles/matte/icon.d.ts +21 -0
  52. package/dist/styles/matte/icon.js +21 -0
  53. package/dist/styles/matte/index.d.ts +0 -2
  54. package/dist/styles/matte/index.js +0 -2
  55. package/dist/styles/paper/AccordionItem.svelte +5 -0
  56. package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
  57. package/dist/styles/paper/Avatar.svelte +15 -0
  58. package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
  59. package/dist/styles/paper/Badge.svelte +5 -0
  60. package/dist/styles/paper/Badge.svelte.d.ts +2 -0
  61. package/dist/styles/paper/Breadcrumb.svelte +8 -4
  62. package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
  63. package/dist/styles/paper/Button.svelte +58 -7
  64. package/dist/styles/paper/Button.svelte.d.ts +14 -2
  65. package/dist/styles/paper/Card.svelte +6 -1
  66. package/dist/styles/paper/Card.svelte.d.ts +2 -0
  67. package/dist/styles/paper/CheckboxGroup.svelte +6 -0
  68. package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
  69. package/dist/styles/paper/Combobox.svelte +5 -0
  70. package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
  71. package/dist/styles/paper/CommandPalette.svelte +6 -0
  72. package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -0
  73. package/dist/styles/paper/Drawer.svelte +5 -0
  74. package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
  75. package/dist/styles/paper/Footer.svelte +6 -2
  76. package/dist/styles/paper/Footer.svelte.d.ts +2 -0
  77. package/dist/styles/paper/Link.svelte +9 -0
  78. package/dist/styles/paper/Link.svelte.d.ts +4 -0
  79. package/dist/styles/paper/MenuItem.svelte +2 -1
  80. package/dist/styles/paper/Modal.svelte +5 -0
  81. package/dist/styles/paper/Modal.svelte.d.ts +2 -0
  82. package/dist/styles/paper/MultiSelect.svelte +7 -1
  83. package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
  84. package/dist/styles/paper/Navbar.svelte +18 -4
  85. package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
  86. package/dist/styles/paper/PricingCard.svelte +23 -15
  87. package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
  88. package/dist/styles/paper/RadioGroup.svelte +6 -0
  89. package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
  90. package/dist/styles/paper/SegmentedControl.svelte +16 -2
  91. package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
  92. package/dist/styles/paper/Sidebar.svelte +2 -1
  93. package/dist/styles/paper/Stat.svelte +8 -1
  94. package/dist/styles/paper/Stat.svelte.d.ts +3 -0
  95. package/dist/styles/paper/Steps.svelte +7 -0
  96. package/dist/styles/paper/Steps.svelte.d.ts +3 -0
  97. package/dist/styles/paper/Table.svelte +153 -97
  98. package/dist/styles/paper/Table.svelte.d.ts +12 -0
  99. package/dist/styles/paper/Tabs.svelte +5 -0
  100. package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
  101. package/dist/styles/paper/Toaster.svelte +3 -0
  102. package/dist/styles/paper/icon.d.ts +21 -0
  103. package/dist/styles/paper/icon.js +21 -0
  104. package/dist/styles/paper/index.d.ts +0 -2
  105. package/dist/styles/paper/index.js +0 -2
  106. package/dist/styles/sprout/AccordionItem.svelte +5 -0
  107. package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
  108. package/dist/styles/sprout/Avatar.svelte +15 -0
  109. package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
  110. package/dist/styles/sprout/Badge.svelte +5 -0
  111. package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
  112. package/dist/styles/sprout/Breadcrumb.svelte +8 -4
  113. package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
  114. package/dist/styles/sprout/Button.svelte +58 -7
  115. package/dist/styles/sprout/Button.svelte.d.ts +14 -2
  116. package/dist/styles/sprout/Card.svelte +6 -1
  117. package/dist/styles/sprout/Card.svelte.d.ts +2 -0
  118. package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
  119. package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
  120. package/dist/styles/sprout/Combobox.svelte +5 -0
  121. package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
  122. package/dist/styles/sprout/CommandPalette.svelte +6 -0
  123. package/dist/styles/sprout/CommandPalette.svelte.d.ts +3 -0
  124. package/dist/styles/sprout/Drawer.svelte +5 -0
  125. package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
  126. package/dist/styles/sprout/Footer.svelte +6 -2
  127. package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
  128. package/dist/styles/sprout/Link.svelte +9 -0
  129. package/dist/styles/sprout/Link.svelte.d.ts +4 -0
  130. package/dist/styles/sprout/MenuItem.svelte +2 -1
  131. package/dist/styles/sprout/Modal.svelte +5 -0
  132. package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
  133. package/dist/styles/sprout/MultiSelect.svelte +7 -1
  134. package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
  135. package/dist/styles/sprout/Navbar.svelte +18 -4
  136. package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
  137. package/dist/styles/sprout/PricingCard.svelte +23 -15
  138. package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
  139. package/dist/styles/sprout/RadioGroup.svelte +6 -0
  140. package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
  141. package/dist/styles/sprout/SegmentedControl.svelte +16 -2
  142. package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
  143. package/dist/styles/sprout/Sidebar.svelte +2 -1
  144. package/dist/styles/sprout/Stat.svelte +8 -1
  145. package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
  146. package/dist/styles/sprout/Steps.svelte +7 -0
  147. package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
  148. package/dist/styles/sprout/Table.svelte +153 -97
  149. package/dist/styles/sprout/Table.svelte.d.ts +12 -0
  150. package/dist/styles/sprout/Tabs.svelte +5 -0
  151. package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
  152. package/dist/styles/sprout/Toaster.svelte +3 -0
  153. package/dist/styles/sprout/icon.d.ts +21 -0
  154. package/dist/styles/sprout/icon.js +21 -0
  155. package/dist/styles/sprout/index.d.ts +0 -2
  156. package/dist/styles/sprout/index.js +0 -2
  157. package/package.json +2 -26
  158. package/registry.json +1 -35
  159. package/dist/styles/matte/NumberInput.svelte +0 -96
  160. package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
  161. package/dist/styles/matte/PasswordInput.svelte +0 -95
  162. package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
  163. package/dist/styles/paper/NumberInput.svelte +0 -96
  164. package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
  165. package/dist/styles/paper/PasswordInput.svelte +0 -100
  166. package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
  167. package/dist/styles/sprout/NumberInput.svelte +0 -97
  168. package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
  169. package/dist/styles/sprout/PasswordInput.svelte +0 -101
  170. package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
@@ -3,10 +3,13 @@
3
3
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
4
  import Checkbox from './Checkbox.svelte';
5
5
  import type { Tone } from '../../core/tones.js';
6
+ import { iconLg } from './icon.js';
6
7
 
7
8
  export interface CheckboxOption {
8
9
  value: string;
9
10
  label: string;
11
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
12
+ icon?: Snippet;
10
13
  description?: string;
11
14
  disabled?: boolean;
12
15
  }
@@ -76,6 +79,9 @@
76
79
  {checked ? 'border-text bg-bg-alt' : 'border-hairline hover:border-hairline-strong'}
77
80
  {disabled ? 'pointer-events-none opacity-50' : ''}"
78
81
  >
82
+ {#if option.icon}
83
+ <span class={iconLg}>{@render option.icon()}</span>
84
+ {/if}
79
85
  <Checkbox
80
86
  {checked}
81
87
  {disabled}
@@ -4,6 +4,8 @@ import type { Tone } from '../../core/tones.js';
4
4
  export interface CheckboxOption {
5
5
  value: string;
6
6
  label: string;
7
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
8
+ icon?: Snippet;
7
9
  description?: string;
8
10
  disabled?: boolean;
9
11
  }
@@ -1,13 +1,17 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import { anchored } from '../../core/actions/anchor.js';
3
4
  import { clickOutside, portal } from '../../core/actions/dismissable.js';
4
5
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
5
6
  import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
6
7
  import { useLocale } from '../../core/locale.svelte.js';
8
+ import { iconMd } from './icon.js';
7
9
 
8
10
  export interface ComboboxOption {
9
11
  value: string;
10
12
  label: string;
13
+ /** Leading icon, 16px. */
14
+ icon?: Snippet;
11
15
  /** Second line under the label. */
12
16
  description?: string;
13
17
  disabled?: boolean;
@@ -234,6 +238,7 @@
234
238
  {index === active ? toneSoft[tone] : 'text-text-secondary'}"
235
239
  >
236
240
  <span class="flex items-center gap-2">
241
+ {#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
237
242
  <span class="min-w-0 flex-1 truncate">{option.label}</span>
238
243
  {#if option.value === value}
239
244
  <svg
@@ -1,7 +1,10 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import { type Tone } from '../../core/tones.js';
2
3
  export interface ComboboxOption {
3
4
  value: string;
4
5
  label: string;
6
+ /** Leading icon, 16px. */
7
+ icon?: Snippet;
5
8
  /** Second line under the label. */
6
9
  description?: string;
7
10
  disabled?: boolean;
@@ -1,7 +1,11 @@
1
1
  <script module lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
2
4
  export interface CommandItem {
3
5
  id: string;
4
6
  label: string;
7
+ /** Leading icon, 16px. */
8
+ icon?: Snippet;
5
9
  /** Second line under the label. */
6
10
  description?: string;
7
11
  /** Items sharing a group are rendered under one heading. */
@@ -22,6 +26,7 @@
22
26
  import { useLocale } from '../../core/locale.svelte.js';
23
27
  import { toneSoft, type Tone } from '../../core/tones.js';
24
28
  import { isTypingTarget } from '../../core/trigger.js';
29
+ import { iconMd } from './icon.js';
25
30
  import Kbd from './Kbd.svelte';
26
31
 
27
32
  interface Props {
@@ -218,6 +223,7 @@
218
223
  {item.disabled ? 'pointer-events-none opacity-40' : ''}
219
224
  {index === active ? toneSoft[tone] : 'text-text-secondary'}"
220
225
  >
226
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
221
227
  <span class="flex min-w-0 flex-1 flex-col gap-0.5">
222
228
  <span class="truncate">{item.label}</span>
223
229
  {#if item.description}
@@ -1,6 +1,9 @@
1
+ import type { Snippet } from 'svelte';
1
2
  export interface CommandItem {
2
3
  id: string;
3
4
  label: string;
5
+ /** Leading icon, 16px. */
6
+ icon?: Snippet;
4
7
  /** Second line under the label. */
5
8
  description?: string;
6
9
  /** Items sharing a group are rendered under one heading. */
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { iconLg } from './icon.js';
3
4
  import type { HTMLDialogAttributes } from 'svelte/elements';
4
5
  import { useLocale } from '../../core/locale.svelte.js';
5
6
  import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
@@ -15,6 +16,8 @@
15
16
  size?: DrawerSize;
16
17
  title?: string;
17
18
  description?: string;
19
+ /** Leading icon beside the title. */
20
+ icon?: Snippet;
18
21
  dismissible?: boolean;
19
22
  footer?: Snippet;
20
23
  onclose?: () => void;
@@ -27,6 +30,7 @@
27
30
  size = 'md',
28
31
  title,
29
32
  description,
33
+ icon,
30
34
  dismissible = true,
31
35
  footer,
32
36
  onclose,
@@ -92,6 +96,7 @@
92
96
  <div class="flex h-full flex-col">
93
97
  {#if title || dismissible}
94
98
  <div class="flex items-start gap-4 border-b border-hairline px-5 py-4">
99
+ {#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
95
100
  <div class="flex min-w-0 flex-1 flex-col gap-1">
96
101
  {#if title}
97
102
  <h2 class="font-heading text-lg font-medium tracking-tight">{title}</h2>
@@ -10,6 +10,8 @@ interface Props extends HTMLDialogAttributes {
10
10
  size?: DrawerSize;
11
11
  title?: string;
12
12
  description?: string;
13
+ /** Leading icon beside the title. */
14
+ icon?: Snippet;
13
15
  dismissible?: boolean;
14
16
  footer?: Snippet;
15
17
  onclose?: () => void;
@@ -2,6 +2,8 @@
2
2
  export interface FooterLink {
3
3
  label: string;
4
4
  href?: string;
5
+ /** Leading icon, 14px. */
6
+ icon?: Snippet;
5
7
  external?: boolean;
6
8
  }
7
9
 
@@ -14,6 +16,7 @@
14
16
  <script lang="ts">
15
17
  import type { Snippet } from 'svelte';
16
18
  import { toneHoverText, type Tone } from '../../core/tones.js';
19
+ import { iconSm } from './icon.js';
17
20
 
18
21
  interface Props {
19
22
  columns?: FooterColumn[];
@@ -59,11 +62,12 @@
59
62
  href={item.href}
60
63
  target={item.external ? '_blank' : undefined}
61
64
  rel={item.external ? 'noreferrer' : undefined}
62
- class="font-sans text-sm text-text-secondary transition-colors duration-150 ease-brand-out {toneHoverText[
65
+ class="inline-flex items-center gap-2 font-sans text-sm text-text-secondary transition-colors duration-150 ease-brand-out {toneHoverText[
63
66
  tone
64
67
  ]}"
65
68
  >
66
- {item.label}
69
+ {#if item.icon}<span class={iconSm}>{@render item.icon()}</span
70
+ >{/if}{item.label}
67
71
  {#if item.external}<span aria-hidden="true" class="text-[0.85em]">↗</span
68
72
  >{/if}
69
73
  </a>
@@ -1,6 +1,8 @@
1
1
  export interface FooterLink {
2
2
  label: string;
3
3
  href?: string;
4
+ /** Leading icon, 14px. */
5
+ icon?: Snippet;
4
6
  external?: boolean;
5
7
  }
6
8
  export interface FooterColumn {
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAnchorAttributes } from 'svelte/elements';
4
4
  import { focusRing, toneHoverText, toneRing, toneText, type Tone } from '../../core/tones.js';
5
+ import { iconSm } from './icon.js';
5
6
 
6
7
  interface Props extends HTMLAnchorAttributes {
7
8
  tone?: Tone;
@@ -11,6 +12,10 @@
11
12
  external?: boolean;
12
13
  /** Inherit the surrounding text colour instead of the tone. */
13
14
  muted?: boolean;
15
+ /** Leading icon, 14px. */
16
+ icon?: Snippet;
17
+ /** Trailing icon, before the external mark. */
18
+ iconEnd?: Snippet;
14
19
  children: Snippet;
15
20
  }
16
21
 
@@ -19,6 +24,8 @@
19
24
  underline = 'hover',
20
25
  external = false,
21
26
  muted = false,
27
+ icon,
28
+ iconEnd,
22
29
  class: className = '',
23
30
  children,
24
31
  ...rest
@@ -39,6 +46,8 @@
39
46
  {underlines[underline]} {focusRing} {toneRing[tone]} {className}"
40
47
  {...rest}
41
48
  >
49
+ {#if icon}<span class={iconSm}>{@render icon()}</span>{/if}
42
50
  {@render children()}
51
+ {#if iconEnd}<span class={iconSm}>{@render iconEnd()}</span>{/if}
43
52
  {#if external}<span aria-hidden="true" class="text-[0.85em]">↗</span>{/if}
44
53
  </a>
@@ -9,6 +9,10 @@ interface Props extends HTMLAnchorAttributes {
9
9
  external?: boolean;
10
10
  /** Inherit the surrounding text colour instead of the tone. */
11
11
  muted?: boolean;
12
+ /** Leading icon, 14px. */
13
+ icon?: Snippet;
14
+ /** Trailing icon, before the external mark. */
15
+ iconEnd?: Snippet;
12
16
  children: Snippet;
13
17
  }
14
18
  declare const Link: import("svelte").Component<Props, {}, "">;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
  import { toneSoftHover, toneText, type Tone } from '../../core/tones.js';
5
+ import { iconMd } from './icon.js';
5
6
 
6
7
  interface Props extends HTMLButtonAttributes {
7
8
  /** Renders an `<a>` instead of a `<button>`. */
@@ -49,7 +50,7 @@
49
50
  <path d="m2 7 3.2 3.2L12 3.6" stroke="currentColor" stroke-width="1.6" />
50
51
  </svg>
51
52
  {:else if icon}
52
- <span class="flex size-3.5 shrink-0 items-center justify-center">{@render icon()}</span>
53
+ <span class={iconMd}>{@render icon()}</span>
53
54
  {/if}
54
55
  <span class="min-w-0 flex-1 truncate">{@render children()}</span>
55
56
  {#if shortcut}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { iconLg } from './icon.js';
3
4
  import type { HTMLDialogAttributes } from 'svelte/elements';
4
5
  import { useLocale } from '../../core/locale.svelte.js';
5
6
  import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
@@ -11,6 +12,8 @@
11
12
  open?: boolean;
12
13
  title?: string;
13
14
  description?: string;
15
+ /** Leading icon beside the title. */
16
+ icon?: Snippet;
14
17
  size?: ModalSize;
15
18
  /** Clicking the backdrop or pressing Escape closes the dialog. */
16
19
  dismissible?: boolean;
@@ -26,6 +29,7 @@
26
29
  open = $bindable(false),
27
30
  title,
28
31
  description,
32
+ icon,
29
33
  size = 'md',
30
34
  dismissible = true,
31
35
  showClose,
@@ -75,6 +79,7 @@
75
79
  <div class="flex flex-col">
76
80
  {#if title || closable}
77
81
  <div class="flex items-start gap-4 border-b border-hairline px-5 py-4">
82
+ {#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
78
83
  <div class="flex min-w-0 flex-1 flex-col gap-1">
79
84
  {#if title}
80
85
  <h2 class="font-heading text-lg font-medium tracking-tight">{title}</h2>
@@ -6,6 +6,8 @@ interface Props extends HTMLDialogAttributes {
6
6
  open?: boolean;
7
7
  title?: string;
8
8
  description?: string;
9
+ /** Leading icon beside the title. */
10
+ icon?: Snippet;
9
11
  size?: ModalSize;
10
12
  /** Clicking the backdrop or pressing Escape closes the dialog. */
11
13
  dismissible?: boolean;
@@ -1,7 +1,11 @@
1
1
  <script module lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
2
4
  export interface MultiSelectOption {
3
5
  value: string;
4
6
  label: string;
7
+ /** Leading icon, 16px. */
8
+ icon?: Snippet;
5
9
  description?: string;
6
10
  disabled?: boolean;
7
11
  group?: string;
@@ -13,6 +17,7 @@
13
17
  import { clickOutside, portal } from '../../core/actions/dismissable.js';
14
18
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
15
19
  import { useLocale } from '../../core/locale.svelte.js';
20
+ import { iconMd, iconSm } from './icon.js';
16
21
  import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
17
22
 
18
23
  interface Props {
@@ -153,7 +158,7 @@
153
158
  tone
154
159
  ]}"
155
160
  >
156
- {option.label}
161
+ {#if option.icon}<span class={iconSm}>{@render option.icon()}</span>{/if}{option.label}
157
162
  <button
158
163
  type="button"
159
164
  onclick={() => remove(option)}
@@ -259,6 +264,7 @@
259
264
  </svg>
260
265
  {/if}
261
266
  </span>
267
+ {#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
262
268
  <span class="flex min-w-0 flex-1 flex-col gap-0.5">
263
269
  <span class="truncate">{option.label}</span>
264
270
  {#if option.description}
@@ -1,6 +1,9 @@
1
+ import type { Snippet } from 'svelte';
1
2
  export interface MultiSelectOption {
2
3
  value: string;
3
4
  label: string;
5
+ /** Leading icon, 16px. */
6
+ icon?: Snippet;
4
7
  description?: string;
5
8
  disabled?: boolean;
6
9
  group?: string;
@@ -2,6 +2,8 @@
2
2
  export interface NavItem {
3
3
  label: string;
4
4
  href?: string;
5
+ /** Leading icon, 16px. */
6
+ icon?: Snippet;
5
7
  /** Marks the current page — also sets `aria-current`. */
6
8
  active?: boolean;
7
9
  disabled?: boolean;
@@ -15,6 +17,7 @@
15
17
  <script lang="ts">
16
18
  import type { Snippet } from 'svelte';
17
19
  import { useLocale } from '../../core/locale.svelte.js';
20
+ import { iconMd } from './icon.js';
18
21
  import { focusRing, toneRing, toneText, type Tone } from '../../core/tones.js';
19
22
  import Drawer from './Drawer.svelte';
20
23
  import Dropdown from './Dropdown.svelte';
@@ -82,6 +85,7 @@
82
85
  ? toneText[tone]
83
86
  : 'text-text-secondary hover:text-text'}"
84
87
  >
88
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
85
89
  {item.label}
86
90
  <svg class="size-3" viewBox="0 0 16 16" fill="none" aria-hidden="true">
87
91
  <path
@@ -94,7 +98,12 @@
94
98
  </button>
95
99
  {/snippet}
96
100
  {#each item.items as child (child.label)}
97
- <MenuItem href={child.href} disabled={child.disabled} selected={child.active}>
101
+ <MenuItem
102
+ href={child.href}
103
+ disabled={child.disabled}
104
+ selected={child.active}
105
+ icon={child.icon}
106
+ >
98
107
  {child.label}
99
108
  </MenuItem>
100
109
  {/each}
@@ -109,6 +118,7 @@
109
118
  ? 'pointer-events-none opacity-50'
110
119
  : ''}"
111
120
  >
121
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
112
122
  {item.label}
113
123
  {#if item.badge !== undefined}
114
124
  <span class="font-mono text-[11px] text-text-muted">{item.badge}</span>
@@ -154,7 +164,10 @@
154
164
  ? toneText[tone]
155
165
  : 'text-text-secondary'} {item.disabled ? 'pointer-events-none opacity-50' : ''}"
156
166
  >
157
- {item.label}
167
+ <span class="flex min-w-0 items-center gap-2.5">
168
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
169
+ {item.label}
170
+ </span>
158
171
  {#if item.badge !== undefined}
159
172
  <span class="font-mono text-[11px] text-text-muted">{item.badge}</span>
160
173
  {/if}
@@ -166,9 +179,10 @@
166
179
  <a
167
180
  href={child.href}
168
181
  onclick={() => (menuOpen = false)}
169
- class="block py-2 font-sans text-sm text-text-muted"
182
+ class="flex items-center gap-2.5 py-2 font-sans text-sm text-text-muted"
170
183
  >
171
- {child.label}
184
+ {#if child.icon}<span class={iconMd}>{@render child.icon()}</span
185
+ >{/if}{child.label}
172
186
  </a>
173
187
  </li>
174
188
  {/each}
@@ -1,6 +1,8 @@
1
1
  export interface NavItem {
2
2
  label: string;
3
3
  href?: string;
4
+ /** Leading icon, 16px. */
5
+ icon?: Snippet;
4
6
  /** Marks the current page — also sets `aria-current`. */
5
7
  active?: boolean;
6
8
  disabled?: boolean;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { toneBorder, toneFill, toneSoft, toneText, type Tone } from '../../core/tones.js';
5
+ import { iconMd } from './icon.js';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
8
  name: string;
@@ -12,6 +13,8 @@
12
13
  description?: string;
13
14
  /** One line each. Rendered with a tick. */
14
15
  features?: string[];
16
+ /** Replaces the tick beside every feature. */
17
+ featureIcon?: Snippet;
15
18
  /** Pill in the top corner, e.g. "Most popular". */
16
19
  badge?: string;
17
20
  /** Thicker border and a tinted header — for the plan you want chosen. */
@@ -29,6 +32,7 @@
29
32
  period,
30
33
  description,
31
34
  features = [],
35
+ featureIcon,
32
36
  badge,
33
37
  featured = false,
34
38
  tone = 'brand',
@@ -83,14 +87,18 @@
83
87
  <ul class="flex flex-1 flex-col gap-3 border-t border-hairline p-5">
84
88
  {#each features as feature (feature)}
85
89
  <li class="flex items-start gap-3 font-sans text-sm text-text-secondary">
86
- <svg
87
- class="mt-0.5 size-4 shrink-0 {toneText[tone]}"
88
- viewBox="0 0 16 16"
89
- fill="none"
90
- aria-hidden="true"
91
- >
92
- <path d="m3 8 3.2 3.2L13 4.4" stroke="currentColor" stroke-width="1.6" />
93
- </svg>
90
+ {#if featureIcon}
91
+ <span class="mt-0.5 {iconMd}">{@render featureIcon()}</span>
92
+ {:else}
93
+ <svg
94
+ class="mt-0.5 size-4 shrink-0 {toneText[tone]}"
95
+ viewBox="0 0 16 16"
96
+ fill="none"
97
+ aria-hidden="true"
98
+ >
99
+ <path d="m3 8 3.2 3.2L13 4.4" stroke="currentColor" stroke-width="1.6" />
100
+ </svg>
101
+ {/if}
94
102
  <span class="leading-snug">{feature}</span>
95
103
  </li>
96
104
  {/each}
@@ -10,6 +10,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
10
10
  description?: string;
11
11
  /** One line each. Rendered with a tick. */
12
12
  features?: string[];
13
+ /** Replaces the tick beside every feature. */
14
+ featureIcon?: Snippet;
13
15
  /** Pill in the top corner, e.g. "Most popular". */
14
16
  badge?: string;
15
17
  /** Thicker border and a tinted header — for the plan you want chosen. */
@@ -3,10 +3,13 @@
3
3
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
4
  import Radio from './Radio.svelte';
5
5
  import type { Tone } from '../../core/tones.js';
6
+ import { iconLg } from './icon.js';
6
7
 
7
8
  export interface RadioOption {
8
9
  value: string;
9
10
  label: string;
11
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
12
+ icon?: Snippet;
10
13
  description?: string;
11
14
  disabled?: boolean;
12
15
  }
@@ -60,6 +63,9 @@
60
63
  {value === option.value ? 'border-text bg-bg-alt' : 'border-hairline hover:border-hairline-strong'}
61
64
  {option.disabled ? 'pointer-events-none opacity-50' : ''}"
62
65
  >
66
+ {#if option.icon}
67
+ <span class={iconLg}>{@render option.icon()}</span>
68
+ {/if}
63
69
  <Radio
64
70
  bind:group={value}
65
71
  value={option.value}
@@ -4,6 +4,8 @@ import type { Tone } from '../../core/tones.js';
4
4
  export interface RadioOption {
5
5
  value: string;
6
6
  label: string;
7
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
8
+ icon?: Snippet;
7
9
  description?: string;
8
10
  disabled?: boolean;
9
11
  }
@@ -1,10 +1,14 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
5
+ import { iconMd } from './icon.js';
4
6
 
5
7
  export interface SegmentOption {
6
8
  value: string;
7
9
  label: string;
10
+ /** Leading icon, 16px. */
11
+ icon?: Snippet;
8
12
  disabled?: boolean;
9
13
  }
10
14
 
@@ -20,6 +24,8 @@
20
24
  tone?: Tone;
21
25
  /** Spread the segments evenly across the full width. */
22
26
  fullWidth?: boolean;
27
+ /** Icons only — each option's `label` becomes its accessible name. */
28
+ iconOnly?: boolean;
23
29
  label?: string;
24
30
  disabled?: boolean;
25
31
  onchange?: (value: string) => void;
@@ -32,6 +38,7 @@
32
38
  size = 'md',
33
39
  tone = 'brand',
34
40
  fullWidth = false,
41
+ iconOnly = false,
35
42
  label,
36
43
  disabled = false,
37
44
  class: className = '',
@@ -44,6 +51,11 @@
44
51
  md: 'h-9 px-3.5 text-sm'
45
52
  };
46
53
 
54
+ const squares: Record<SegmentedSize, string> = {
55
+ sm: 'size-8 text-[13px]',
56
+ md: 'size-9 text-sm'
57
+ };
58
+
47
59
  function select(option: SegmentOption) {
48
60
  if (option.disabled || disabled) return;
49
61
  value = option.value;
@@ -74,14 +86,16 @@
74
86
  type="button"
75
87
  role="radio"
76
88
  aria-checked={value === option.value}
89
+ aria-label={iconOnly ? option.label : undefined}
77
90
  disabled={option.disabled || disabled}
78
91
  tabindex={value === option.value ? 0 : -1}
79
92
  onclick={() => select(option)}
80
93
  class="inline-flex items-center justify-center font-sans whitespace-nowrap transition-colors duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-40
81
- {sizes[size]} {focusRing} {toneRing[tone]}
94
+ {iconOnly ? squares[size] : sizes[size]} {focusRing} {toneRing[tone]}
82
95
  {value === option.value ? `${toneSoft[tone]} font-medium` : 'text-text-secondary hover:text-text'}"
83
96
  >
84
- {option.label}
97
+ {#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
98
+ {#if !iconOnly}{option.label}{/if}
85
99
  </button>
86
100
  {/each}
87
101
  {#if name}<input type="hidden" {name} {value} />{/if}
@@ -1,8 +1,11 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { HTMLAttributes } from 'svelte/elements';
2
3
  import { type Tone } from '../../core/tones.js';
3
4
  export interface SegmentOption {
4
5
  value: string;
5
6
  label: string;
7
+ /** Leading icon, 16px. */
8
+ icon?: Snippet;
6
9
  disabled?: boolean;
7
10
  }
8
11
  export type SegmentedSize = 'sm' | 'md';
@@ -16,6 +19,8 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> {
16
19
  tone?: Tone;
17
20
  /** Spread the segments evenly across the full width. */
18
21
  fullWidth?: boolean;
22
+ /** Icons only — each option's `label` becomes its accessible name. */
23
+ iconOnly?: boolean;
19
24
  label?: string;
20
25
  disabled?: boolean;
21
26
  onchange?: (value: string) => void;
@@ -36,6 +36,7 @@
36
36
 
37
37
  <script lang="ts">
38
38
  import { useLocale } from '../../core/locale.svelte.js';
39
+ import { iconMd } from './icon.js';
39
40
  import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
40
41
 
41
42
  interface Props {
@@ -126,7 +127,7 @@
126
127
  <!-- One box for every glyph, so rows line up whether or not an item has one. -->
127
128
  {#snippet glyph(item: SidebarItem)}
128
129
  {#if item.icon}
129
- <span class="grid size-4 shrink-0 place-items-center *:size-4">{@render item.icon()}</span>
130
+ <span class={iconMd}>{@render item.icon()}</span>
130
131
  {/if}
131
132
  {/snippet}
132
133
 
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import { toneText, type Tone } from '../../core/tones.js';
5
+ import { iconMd } from './icon.js';
4
6
 
5
7
  export type StatTrend = 'up' | 'down' | 'flat';
6
8
 
@@ -11,6 +13,8 @@
11
13
  delta?: string;
12
14
  trend?: StatTrend;
13
15
  hint?: string;
16
+ /** Leading icon on the label row. */
17
+ icon?: Snippet;
14
18
  /** Colour of the value itself. Leave unset for plain `text`. */
15
19
  tone?: Tone;
16
20
  }
@@ -21,6 +25,7 @@
21
25
  delta,
22
26
  trend = 'flat',
23
27
  hint,
28
+ icon,
24
29
  tone,
25
30
  class: className = '',
26
31
  ...rest
@@ -31,7 +36,9 @@
31
36
  </script>
32
37
 
33
38
  <div class="flex flex-col gap-1 font-sans {className}" {...rest}>
34
- <span class="font-mono text-xs tracking-wide text-text-muted uppercase">{label}</span>
39
+ <span class="flex items-center gap-2 font-mono text-xs tracking-wide text-text-muted uppercase"
40
+ >{#if icon}<span class={iconMd}>{@render icon()}</span>{/if}{label}</span
41
+ >
35
42
  <div class="flex items-baseline gap-2">
36
43
  <span
37
44
  class="font-heading text-3xl font-medium tracking-tight {tone ? toneText[tone] : 'text-text'}"