@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
@@ -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 BreadcrumbItem {
4
5
  label: string;
5
6
  href?: string;
7
+ /** Leading icon — a home mark on the first crumb, say. */
8
+ icon?: Snippet;
6
9
  }
7
10
  interface Props extends HTMLAttributes<HTMLElement> {
8
11
  items: BreadcrumbItem[];
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
  import Spinner from './Spinner.svelte';
5
+ import { iconMd, iconSm } from './icon.js';
5
6
  import {
6
7
  focusRing,
7
8
  toneBorderSoft,
@@ -17,7 +18,7 @@
17
18
  export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
18
19
  export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
19
20
 
20
- interface Props extends HTMLButtonAttributes {
21
+ interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
21
22
  variant?: ButtonVariant;
22
23
  tone?: Tone;
23
24
  size?: ButtonSize;
@@ -28,7 +29,19 @@
28
29
  loading?: boolean;
29
30
  /** Stretch to the container's full width. */
30
31
  block?: boolean;
31
- children: Snippet;
32
+ /** Leading icon. The spinner takes its place while the button is busy. */
33
+ icon?: Snippet;
34
+ /** Trailing icon. */
35
+ iconEnd?: Snippet;
36
+ /** Square button with no label — pass an `aria-label`. */
37
+ iconOnly?: boolean;
38
+ /**
39
+ * Return a promise — a save, a submit, a fetch — and the button spins and
40
+ * locks itself until it settles, without any state in the parent. It
41
+ * unlocks on a rejection too, so catch failures inside the handler.
42
+ */
43
+ onclick?: (event: MouseEvent) => unknown;
44
+ children?: Snippet;
32
45
  }
33
46
 
34
47
  let {
@@ -38,6 +51,10 @@
38
51
  href,
39
52
  loading = false,
40
53
  block = false,
54
+ icon,
55
+ iconEnd,
56
+ iconOnly = false,
57
+ onclick,
41
58
  disabled = false,
42
59
  type = 'button',
43
60
  class: className = '',
@@ -55,6 +72,13 @@
55
72
  xl: 'h-12 px-6 text-base'
56
73
  };
57
74
 
75
+ const squares: Record<ButtonSize, string> = {
76
+ sm: 'size-8',
77
+ md: 'size-10',
78
+ lg: 'size-11',
79
+ xl: 'size-12'
80
+ };
81
+
58
82
  const variantClass = $derived.by(() => {
59
83
  switch (variant) {
60
84
  case 'primary':
@@ -74,7 +98,24 @@
74
98
  });
75
99
 
76
100
  const isLink = $derived(variant === 'link');
77
- const inert = $derived(disabled || loading);
101
+
102
+ /** Set by an `onclick` that returned a promise, on top of the `loading` prop. */
103
+ let pending = $state(false);
104
+ const busy = $derived(loading || pending);
105
+ const inert = $derived(disabled || busy);
106
+
107
+ const iconClass = $derived(size === 'sm' ? iconSm : iconMd);
108
+
109
+ async function handleClick(event: MouseEvent) {
110
+ const result = onclick?.(event);
111
+ // Anything else is a plain handler: nothing to wait for, nothing to lock.
112
+ if (!(result instanceof Promise)) return;
113
+ pending = true;
114
+ // The handler owns its own errors — catch them there to show a message.
115
+ // All the button needs to know is that the work has settled.
116
+ await result.catch(() => {});
117
+ pending = false;
118
+ }
78
119
  </script>
79
120
 
80
121
  <svelte:element
@@ -83,14 +124,24 @@
83
124
  type={href ? undefined : type}
84
125
  disabled={href ? undefined : inert}
85
126
  aria-disabled={href && inert ? 'true' : undefined}
86
- aria-busy={loading ? 'true' : undefined}
127
+ aria-busy={busy ? 'true' : undefined}
87
128
  role={href ? 'button' : undefined}
88
- class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink ? '' : sizes[size]}
129
+ class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink
130
+ ? ''
131
+ : iconOnly
132
+ ? squares[size]
133
+ : sizes[size]}
89
134
  {block ? 'w-full' : ''} {className}"
90
135
  {...rest}
136
+ onclick={handleClick}
91
137
  >
92
- {#if loading}
138
+ {#if busy}
93
139
  <Spinner size={size === 'sm' ? 'xs' : 'sm'} label="" />
140
+ {:else if icon}
141
+ <span class={iconClass} aria-hidden="true">{@render icon()}</span>
142
+ {/if}
143
+ {@render children?.()}
144
+ {#if iconEnd && !iconOnly}
145
+ <span class={iconClass} aria-hidden="true">{@render iconEnd()}</span>
94
146
  {/if}
95
- {@render children()}
96
147
  </svelte:element>
@@ -3,7 +3,7 @@ import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  import { type Tone } from '../../core/tones.js';
4
4
  export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
5
5
  export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
6
- interface Props extends HTMLButtonAttributes {
6
+ interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
7
7
  variant?: ButtonVariant;
8
8
  tone?: Tone;
9
9
  size?: ButtonSize;
@@ -14,7 +14,19 @@ interface Props extends HTMLButtonAttributes {
14
14
  loading?: boolean;
15
15
  /** Stretch to the container's full width. */
16
16
  block?: boolean;
17
- children: Snippet;
17
+ /** Leading icon. The spinner takes its place while the button is busy. */
18
+ icon?: Snippet;
19
+ /** Trailing icon. */
20
+ iconEnd?: Snippet;
21
+ /** Square button with no label — pass an `aria-label`. */
22
+ iconOnly?: boolean;
23
+ /**
24
+ * Return a promise — a save, a submit, a fetch — and the button spins and
25
+ * locks itself until it settles, without any state in the parent. It
26
+ * unlocks on a rejection too, so catch failures inside the handler.
27
+ */
28
+ onclick?: (event: MouseEvent) => unknown;
29
+ children?: Snippet;
18
30
  }
19
31
  declare const Button: import("svelte").Component<Props, {}, "">;
20
32
  type Button = ReturnType<typeof Button>;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { toneBorderLeft, toneHoverBorder, toneSurface, type Tone } from '../../core/tones.js';
5
+ import { iconLg } from './icon.js';
5
6
 
6
7
  export type CardVariant = 'outline' | 'filled' | 'tinted';
7
8
 
@@ -15,6 +16,8 @@
15
16
  padded?: boolean;
16
17
  title?: string;
17
18
  eyebrow?: string;
19
+ /** Leading icon above the title. Ignored when you pass `header`. */
20
+ icon?: Snippet;
18
21
  header?: Snippet;
19
22
  footer?: Snippet;
20
23
  children: Snippet;
@@ -28,6 +31,7 @@
28
31
  padded = true,
29
32
  title,
30
33
  eyebrow,
34
+ icon,
31
35
  header,
32
36
  footer,
33
37
  class: className = '',
@@ -51,11 +55,12 @@
51
55
  {className}"
52
56
  {...rest}
53
57
  >
54
- {#if header || title || eyebrow}
58
+ {#if header || title || eyebrow || icon}
55
59
  <div class="flex flex-col gap-1 {padded ? 'px-5 pt-5' : ''}">
56
60
  {#if header}
57
61
  {@render header()}
58
62
  {:else}
63
+ {#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
59
64
  {#if eyebrow}
60
65
  <span class="font-sans text-xs font-semibold text-text-muted">{eyebrow}</span>
61
66
  {/if}
@@ -12,6 +12,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
12
12
  padded?: boolean;
13
13
  title?: string;
14
14
  eyebrow?: string;
15
+ /** Leading icon above the title. Ignored when you pass `header`. */
16
+ icon?: Snippet;
15
17
  header?: Snippet;
16
18
  footer?: Snippet;
17
19
  children: Snippet;
@@ -3,10 +3,13 @@
3
3
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
4
  import Checkbox from './Checkbox.svelte';
5
5
  import { toneBorder, toneSurface, 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
  }
@@ -78,6 +81,9 @@
78
81
  : 'border-hairline bg-bg hover:border-hairline-strong'}
79
82
  {disabled ? 'pointer-events-none opacity-50' : ''}"
80
83
  >
84
+ {#if option.icon}
85
+ <span class={iconLg}>{@render option.icon()}</span>
86
+ {/if}
81
87
  <Checkbox
82
88
  {checked}
83
89
  {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;
@@ -237,6 +241,7 @@
237
241
  {index === active ? toneSoft[tone] : 'text-text-secondary'}"
238
242
  >
239
243
  <span class="flex items-center gap-2">
244
+ {#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
240
245
  <span class="min-w-0 flex-1 truncate">{option.label}</span>
241
246
  {#if option.value === value}
242
247
  <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 {
@@ -214,6 +219,7 @@
214
219
  {item.disabled ? 'pointer-events-none opacity-40' : ''}
215
220
  {index === active ? toneSoft[tone] : 'text-text-secondary'}"
216
221
  >
222
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
217
223
  <span class="flex min-w-0 flex-1 flex-col gap-0.5">
218
224
  <span class="truncate">{item.label}</span>
219
225
  {#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-base font-semibold text-text">{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[];
@@ -57,11 +60,12 @@
57
60
  href={item.href}
58
61
  target={item.external ? '_blank' : undefined}
59
62
  rel={item.external ? 'noreferrer' : undefined}
60
- class="font-sans text-[13px] text-text-muted transition-colors duration-150 ease-brand-out {toneHoverText[
63
+ class="inline-flex items-center gap-2 font-sans text-[13px] text-text-muted transition-colors duration-150 ease-brand-out {toneHoverText[
61
64
  tone
62
65
  ]}"
63
66
  >
64
- {item.label}
67
+ {#if item.icon}<span class={iconSm}>{@render item.icon()}</span
68
+ >{/if}{item.label}
65
69
  {#if item.external}<span aria-hidden="true" class="text-[0.85em]">↗</span
66
70
  >{/if}
67
71
  </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>`. */
@@ -55,7 +56,7 @@
55
56
  />
56
57
  </svg>
57
58
  {:else if icon}
58
- <span class="flex size-3.5 shrink-0 items-center justify-center">{@render icon()}</span>
59
+ <span class={iconMd}>{@render icon()}</span>
59
60
  {/if}
60
61
  <span class="min-w-0 flex-1 truncate">{@render children()}</span>
61
62
  {#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 px-6 pt-5 pb-3">
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-base font-semibold text-text">{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)}
@@ -273,6 +278,7 @@
273
278
  </svg>
274
279
  {/if}
275
280
  </span>
281
+ {#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
276
282
  <span class="flex min-w-0 flex-1 flex-col gap-0.5">
277
283
  <span class="truncate">{option.label}</span>
278
284
  {#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, toneSoft, toneText, type Tone } from '../../core/tones.js';
19
22
  import Drawer from './Drawer.svelte';
20
23
  import Dropdown from './Dropdown.svelte';
@@ -84,6 +87,7 @@
84
87
  ? `${toneSoft[tone]}`
85
88
  : 'text-text-secondary hover:bg-bg-inset hover:text-text'}"
86
89
  >
90
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
87
91
  {item.label}
88
92
  <svg class="size-3" viewBox="0 0 16 16" fill="none" aria-hidden="true">
89
93
  <path
@@ -96,7 +100,12 @@
96
100
  </button>
97
101
  {/snippet}
98
102
  {#each item.items as child (child.label)}
99
- <MenuItem href={child.href} disabled={child.disabled} selected={child.active}>
103
+ <MenuItem
104
+ href={child.href}
105
+ disabled={child.disabled}
106
+ selected={child.active}
107
+ icon={child.icon}
108
+ >
100
109
  {child.label}
101
110
  </MenuItem>
102
111
  {/each}
@@ -111,6 +120,7 @@
111
120
  ? 'pointer-events-none opacity-50'
112
121
  : ''}"
113
122
  >
123
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
114
124
  {item.label}
115
125
  {#if item.badge !== undefined}
116
126
  <span class="text-[11px] text-text-muted tabular-nums">{item.badge}</span>
@@ -163,7 +173,10 @@
163
173
  ? 'pointer-events-none opacity-50'
164
174
  : ''}"
165
175
  >
166
- {item.label}
176
+ <span class="flex min-w-0 items-center gap-2.5">
177
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
178
+ {item.label}
179
+ </span>
167
180
  {#if item.badge !== undefined}
168
181
  <span class="text-[11px] text-text-muted tabular-nums">{item.badge}</span>
169
182
  {/if}
@@ -175,11 +188,12 @@
175
188
  <a
176
189
  href={child.href}
177
190
  onclick={() => (menuOpen = false)}
178
- class="block rounded-md px-3 py-2 font-sans text-[13px] text-text-muted hover:bg-bg-inset {toneText[
191
+ class="flex items-center gap-2.5 rounded-md px-3 py-2 font-sans text-[13px] text-text-muted hover:bg-bg-inset {toneText[
179
192
  tone
180
193
  ]}"
181
194
  >
182
- {child.label}
195
+ {#if child.icon}<span class={iconMd}>{@render child.icon()}</span
196
+ >{/if}{child.label}
183
197
  </a>
184
198
  </li>
185
199
  {/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;