@nqmcreative/ui 0.2.2 → 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 (189) hide show
  1. package/README.md +15 -15
  2. package/cli/index.mjs +81 -42
  3. package/dist/core/index.d.ts +1 -1
  4. package/dist/core/index.js +1 -1
  5. package/dist/core/toast.svelte.d.ts +3 -0
  6. package/dist/core/trigger.d.ts +6 -0
  7. package/dist/core/trigger.js +13 -0
  8. package/dist/styles/matte/AccordionItem.svelte +5 -0
  9. package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
  10. package/dist/styles/matte/Avatar.svelte +15 -0
  11. package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
  12. package/dist/styles/matte/Badge.svelte +5 -0
  13. package/dist/styles/matte/Badge.svelte.d.ts +2 -0
  14. package/dist/styles/matte/Breadcrumb.svelte +11 -4
  15. package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
  16. package/dist/styles/matte/Button.svelte +62 -11
  17. package/dist/styles/matte/Button.svelte.d.ts +14 -2
  18. package/dist/styles/matte/Card.svelte +9 -4
  19. package/dist/styles/matte/Card.svelte.d.ts +2 -0
  20. package/dist/styles/matte/CheckboxGroup.svelte +6 -0
  21. package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
  22. package/dist/styles/matte/Combobox.svelte +7 -2
  23. package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
  24. package/dist/styles/matte/CommandPalette.svelte +38 -7
  25. package/dist/styles/matte/CommandPalette.svelte.d.ts +8 -0
  26. package/dist/styles/matte/DatePicker.svelte +2 -2
  27. package/dist/styles/matte/Drawer.svelte +7 -2
  28. package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
  29. package/dist/styles/matte/Dropzone.svelte +1 -1
  30. package/dist/styles/matte/FeatureCard.svelte +1 -1
  31. package/dist/styles/matte/Footer.svelte +6 -2
  32. package/dist/styles/matte/Footer.svelte.d.ts +2 -0
  33. package/dist/styles/matte/Input.svelte +4 -4
  34. package/dist/styles/matte/Link.svelte +9 -0
  35. package/dist/styles/matte/Link.svelte.d.ts +4 -0
  36. package/dist/styles/matte/MenuItem.svelte +3 -2
  37. package/dist/styles/matte/Modal.svelte +8 -3
  38. package/dist/styles/matte/Modal.svelte.d.ts +2 -0
  39. package/dist/styles/matte/MultiSelect.svelte +7 -1
  40. package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
  41. package/dist/styles/matte/Navbar.svelte +18 -4
  42. package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
  43. package/dist/styles/matte/PricingCard.svelte +19 -11
  44. package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
  45. package/dist/styles/matte/RadioGroup.svelte +6 -0
  46. package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
  47. package/dist/styles/matte/SegmentedControl.svelte +17 -3
  48. package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
  49. package/dist/styles/matte/Select.svelte +1 -1
  50. package/dist/styles/matte/Sidebar.svelte +76 -26
  51. package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
  52. package/dist/styles/matte/Stat.svelte +8 -1
  53. package/dist/styles/matte/Stat.svelte.d.ts +3 -0
  54. package/dist/styles/matte/StatsBand.svelte +1 -1
  55. package/dist/styles/matte/Steps.svelte +7 -0
  56. package/dist/styles/matte/Steps.svelte.d.ts +3 -0
  57. package/dist/styles/matte/Table.svelte +154 -98
  58. package/dist/styles/matte/Table.svelte.d.ts +12 -0
  59. package/dist/styles/matte/Tabs.svelte +5 -0
  60. package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
  61. package/dist/styles/matte/Testimonial.svelte +2 -2
  62. package/dist/styles/matte/Textarea.svelte +1 -1
  63. package/dist/styles/matte/Toaster.svelte +3 -0
  64. package/dist/styles/matte/icon.d.ts +21 -0
  65. package/dist/styles/matte/icon.js +21 -0
  66. package/dist/styles/matte/index.d.ts +1 -3
  67. package/dist/styles/matte/index.js +0 -2
  68. package/dist/styles/paper/AccordionItem.svelte +5 -0
  69. package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
  70. package/dist/styles/paper/Avatar.svelte +15 -0
  71. package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
  72. package/dist/styles/paper/Badge.svelte +5 -0
  73. package/dist/styles/paper/Badge.svelte.d.ts +2 -0
  74. package/dist/styles/paper/Breadcrumb.svelte +8 -4
  75. package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
  76. package/dist/styles/paper/Button.svelte +58 -7
  77. package/dist/styles/paper/Button.svelte.d.ts +14 -2
  78. package/dist/styles/paper/Card.svelte +6 -1
  79. package/dist/styles/paper/Card.svelte.d.ts +2 -0
  80. package/dist/styles/paper/CheckboxGroup.svelte +6 -0
  81. package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
  82. package/dist/styles/paper/Combobox.svelte +5 -0
  83. package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
  84. package/dist/styles/paper/CommandPalette.svelte +31 -2
  85. package/dist/styles/paper/CommandPalette.svelte.d.ts +8 -0
  86. package/dist/styles/paper/Drawer.svelte +5 -0
  87. package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
  88. package/dist/styles/paper/Footer.svelte +6 -2
  89. package/dist/styles/paper/Footer.svelte.d.ts +2 -0
  90. package/dist/styles/paper/Link.svelte +9 -0
  91. package/dist/styles/paper/Link.svelte.d.ts +4 -0
  92. package/dist/styles/paper/MenuItem.svelte +2 -1
  93. package/dist/styles/paper/Modal.svelte +14 -1
  94. package/dist/styles/paper/Modal.svelte.d.ts +2 -0
  95. package/dist/styles/paper/MultiSelect.svelte +7 -1
  96. package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
  97. package/dist/styles/paper/Navbar.svelte +18 -4
  98. package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
  99. package/dist/styles/paper/PricingCard.svelte +23 -15
  100. package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
  101. package/dist/styles/paper/RadioGroup.svelte +6 -0
  102. package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
  103. package/dist/styles/paper/SegmentedControl.svelte +16 -2
  104. package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
  105. package/dist/styles/paper/Sidebar.svelte +76 -26
  106. package/dist/styles/paper/Sidebar.svelte.d.ts +15 -1
  107. package/dist/styles/paper/Stat.svelte +8 -1
  108. package/dist/styles/paper/Stat.svelte.d.ts +3 -0
  109. package/dist/styles/paper/Steps.svelte +7 -0
  110. package/dist/styles/paper/Steps.svelte.d.ts +3 -0
  111. package/dist/styles/paper/Table.svelte +153 -97
  112. package/dist/styles/paper/Table.svelte.d.ts +12 -0
  113. package/dist/styles/paper/Tabs.svelte +5 -0
  114. package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
  115. package/dist/styles/paper/Toaster.svelte +3 -0
  116. package/dist/styles/paper/fonts.css +34 -7
  117. package/dist/styles/paper/icon.d.ts +21 -0
  118. package/dist/styles/paper/icon.js +21 -0
  119. package/dist/styles/paper/index.d.ts +1 -3
  120. package/dist/styles/paper/index.js +0 -2
  121. package/dist/styles/paper/theme.css +11 -8
  122. package/dist/styles/sprout/AccordionItem.svelte +5 -0
  123. package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
  124. package/dist/styles/sprout/Avatar.svelte +15 -0
  125. package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
  126. package/dist/styles/sprout/Badge.svelte +5 -0
  127. package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
  128. package/dist/styles/sprout/Breadcrumb.svelte +8 -4
  129. package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
  130. package/dist/styles/sprout/Button.svelte +58 -7
  131. package/dist/styles/sprout/Button.svelte.d.ts +14 -2
  132. package/dist/styles/sprout/Card.svelte +6 -1
  133. package/dist/styles/sprout/Card.svelte.d.ts +2 -0
  134. package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
  135. package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
  136. package/dist/styles/sprout/Combobox.svelte +5 -0
  137. package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
  138. package/dist/styles/sprout/CommandPalette.svelte +31 -2
  139. package/dist/styles/sprout/CommandPalette.svelte.d.ts +8 -0
  140. package/dist/styles/sprout/Drawer.svelte +5 -0
  141. package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
  142. package/dist/styles/sprout/Footer.svelte +6 -2
  143. package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
  144. package/dist/styles/sprout/Link.svelte +9 -0
  145. package/dist/styles/sprout/Link.svelte.d.ts +4 -0
  146. package/dist/styles/sprout/MenuItem.svelte +2 -1
  147. package/dist/styles/sprout/Modal.svelte +14 -1
  148. package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
  149. package/dist/styles/sprout/MultiSelect.svelte +7 -1
  150. package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
  151. package/dist/styles/sprout/Navbar.svelte +18 -4
  152. package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
  153. package/dist/styles/sprout/PricingCard.svelte +23 -15
  154. package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
  155. package/dist/styles/sprout/RadioGroup.svelte +6 -0
  156. package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
  157. package/dist/styles/sprout/SegmentedControl.svelte +16 -2
  158. package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
  159. package/dist/styles/sprout/Sidebar.svelte +76 -26
  160. package/dist/styles/sprout/Sidebar.svelte.d.ts +15 -1
  161. package/dist/styles/sprout/Stat.svelte +8 -1
  162. package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
  163. package/dist/styles/sprout/Steps.svelte +7 -0
  164. package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
  165. package/dist/styles/sprout/Table.svelte +153 -97
  166. package/dist/styles/sprout/Table.svelte.d.ts +12 -0
  167. package/dist/styles/sprout/Tabs.svelte +5 -0
  168. package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
  169. package/dist/styles/sprout/Toaster.svelte +3 -0
  170. package/dist/styles/sprout/fonts.css +7 -8
  171. package/dist/styles/sprout/icon.d.ts +21 -0
  172. package/dist/styles/sprout/icon.js +21 -0
  173. package/dist/styles/sprout/index.d.ts +1 -3
  174. package/dist/styles/sprout/index.js +0 -2
  175. package/dist/styles/sprout/theme.css +6 -6
  176. package/package.json +2 -26
  177. package/registry.json +3 -36
  178. package/dist/styles/matte/NumberInput.svelte +0 -96
  179. package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
  180. package/dist/styles/matte/PasswordInput.svelte +0 -95
  181. package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
  182. package/dist/styles/paper/NumberInput.svelte +0 -96
  183. package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
  184. package/dist/styles/paper/PasswordInput.svelte +0 -100
  185. package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
  186. package/dist/styles/sprout/NumberInput.svelte +0 -97
  187. package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
  188. package/dist/styles/sprout/PasswordInput.svelte +0 -101
  189. package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
@@ -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 = '',
@@ -49,11 +53,12 @@
49
53
  {className}"
50
54
  {...rest}
51
55
  >
52
- {#if header || title || eyebrow}
53
- <div class="flex flex-col gap-1 {padded ? 'px-6 pt-6' : ''}">
56
+ {#if header || title || eyebrow || icon}
57
+ <div class="flex flex-col gap-1 {padded ? 'px-5 pt-5' : ''}">
54
58
  {#if header}
55
59
  {@render header()}
56
60
  {:else}
61
+ {#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
57
62
  {#if eyebrow}
58
63
  <span class="font-mono text-xs tracking-wide text-text-muted uppercase">{eyebrow}</span>
59
64
  {/if}
@@ -64,12 +69,12 @@
64
69
  </div>
65
70
  {/if}
66
71
 
67
- <div class="flex-1 {padded ? (header || title || eyebrow ? 'px-6 pt-4 pb-6' : 'p-6') : ''}">
72
+ <div class="flex-1 {padded ? (header || title || eyebrow ? 'px-5 pt-3.5 pb-5' : 'p-5') : ''}">
68
73
  {@render children()}
69
74
  </div>
70
75
 
71
76
  {#if footer}
72
- <div class="border-t border-hairline {padded ? 'px-6 py-4' : ''}">
77
+ <div class="border-t border-hairline {padded ? 'px-5 py-3.5' : ''}">
73
78
  {@render footer()}
74
79
  </div>
75
80
  {/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 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;
@@ -142,7 +146,7 @@
142
146
 
143
147
  <div
144
148
  bind:this={wrapper}
145
- class="relative inline-flex w-full items-center gap-2 border bg-bg pr-2 pl-4 transition-colors duration-150 ease-brand-out
149
+ class="relative inline-flex w-full items-center gap-2 border bg-bg pr-2 pl-3.5 transition-colors duration-150 ease-brand-out
146
150
  {invalid ? 'border-danger' : `border-hairline ${toneFocusWithinBorder[tone]}`}
147
151
  {disabled ? 'pointer-events-none opacity-50' : ''} {className}"
148
152
  >
@@ -166,7 +170,7 @@
166
170
  }}
167
171
  onfocus={openList}
168
172
  onkeydown={onKeydown}
169
- class="h-11 w-full min-w-0 bg-transparent font-sans text-[15px] text-text placeholder:text-text-muted focus:outline-none"
173
+ class="h-10 w-full min-w-0 bg-transparent font-sans text-[15px] text-text placeholder:text-text-muted focus:outline-none"
170
174
  />
171
175
 
172
176
  {#if name}<input type="hidden" {name} {value} />{/if}
@@ -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. */
@@ -21,6 +25,8 @@
21
25
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
22
26
  import { useLocale } from '../../core/locale.svelte.js';
23
27
  import { toneSoft, type Tone } from '../../core/tones.js';
28
+ import { isTypingTarget } from '../../core/trigger.js';
29
+ import { iconMd } from './icon.js';
24
30
  import Kbd from './Kbd.svelte';
25
31
 
26
32
  interface Props {
@@ -34,6 +40,11 @@
34
40
  * Pass `null` to wire your own trigger only.
35
41
  */
36
42
  hotkey?: string | null;
43
+ /**
44
+ * Key that opens the palette on its own, pressed anywhere the reader
45
+ * isn't typing. Default `'/'`. Pass `null` to turn it off.
46
+ */
47
+ quickKey?: string | null;
37
48
  onselect?: (item: CommandItem) => void;
38
49
  class?: string;
39
50
  }
@@ -45,6 +56,7 @@
45
56
  emptyText,
46
57
  tone = 'brand',
47
58
  hotkey = 'k',
59
+ quickKey = '/',
48
60
  onselect,
49
61
  class: className = ''
50
62
  }: Props = $props();
@@ -82,11 +94,27 @@
82
94
  });
83
95
 
84
96
  $effect(() => {
85
- if (!hotkey) return;
97
+ if (!hotkey && !quickKey) return;
86
98
  const onKeydown = (event: KeyboardEvent) => {
87
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === hotkey.toLowerCase()) {
99
+ const key = event.key.toLowerCase();
100
+
101
+ if ((event.metaKey || event.ctrlKey) && key === hotkey?.toLowerCase()) {
88
102
  event.preventDefault();
89
103
  open = !open;
104
+ return;
105
+ }
106
+
107
+ // A bare key can only open the palette, never close it: while it is
108
+ // open the keystroke belongs to the query field.
109
+ if (
110
+ key === quickKey?.toLowerCase() &&
111
+ !event.metaKey &&
112
+ !event.ctrlKey &&
113
+ !event.altKey &&
114
+ !isTypingTarget(event.target)
115
+ ) {
116
+ event.preventDefault();
117
+ open = true;
90
118
  }
91
119
  };
92
120
  window.addEventListener('keydown', onKeydown);
@@ -138,7 +166,7 @@
138
166
  class="mx-auto mt-[12vh] mb-auto w-[min(36rem,calc(100vw-2rem))] max-w-none border border-hairline bg-bg p-0 text-text backdrop:bg-text/40 {className}"
139
167
  >
140
168
  <div use:focusTrap={{ autofocus: false }} onkeydown={onKeydown} role="none" class="flex flex-col">
141
- <div class="flex items-center gap-3 border-b border-hairline px-4">
169
+ <div class="flex items-center gap-3 border-b border-hairline px-3.5">
142
170
  <svg
143
171
  class="size-4 shrink-0 text-text-muted"
144
172
  viewBox="0 0 16 16"
@@ -156,7 +184,7 @@
156
184
  autocomplete="off"
157
185
  aria-label={placeholder ?? t.current.commandPlaceholder}
158
186
  placeholder={placeholder ?? t.current.commandPlaceholder}
159
- class="h-12 w-full min-w-0 bg-transparent font-sans text-[15px] text-text placeholder:text-text-muted focus:outline-none"
187
+ class="h-11 w-full min-w-0 bg-transparent font-sans text-[15px] text-text placeholder:text-text-muted focus:outline-none"
160
188
  />
161
189
  <Kbd>esc</Kbd>
162
190
  </div>
@@ -168,13 +196,15 @@
168
196
  class="max-h-80 overflow-y-auto py-1"
169
197
  >
170
198
  {#if flat.length === 0}
171
- <p class="px-4 py-10 text-center font-sans text-sm text-text-muted">
199
+ <p class="px-3.5 py-10 text-center font-sans text-sm text-text-muted">
172
200
  {emptyText ?? t.current.noResults}
173
201
  </p>
174
202
  {:else}
175
203
  {#each groups as [group, groupItems] (group)}
176
204
  {#if group}
177
- <p class="px-4 pt-3 pb-1 font-mono text-[10px] tracking-wide text-text-muted uppercase">
205
+ <p
206
+ class="px-3.5 pt-3 pb-1 font-mono text-[10px] tracking-wide text-text-muted uppercase"
207
+ >
178
208
  {group}
179
209
  </p>
180
210
  {/if}
@@ -189,10 +219,11 @@
189
219
  aria-disabled={item.disabled}
190
220
  onclick={() => run(item)}
191
221
  onpointermove={() => (cursor.index = index)}
192
- class="flex cursor-pointer items-center gap-3 px-4 py-2.5 font-sans text-[14px] transition-colors duration-100
222
+ class="flex cursor-pointer items-center gap-3 px-3.5 py-2 font-sans text-[14px] transition-colors duration-100
193
223
  {item.disabled ? 'pointer-events-none opacity-40' : ''}
194
224
  {index === active ? toneSoft[tone] : 'text-text-secondary'}"
195
225
  >
226
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
196
227
  <span class="flex min-w-0 flex-1 flex-col gap-0.5">
197
228
  <span class="truncate">{item.label}</span>
198
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. */
@@ -24,6 +27,11 @@ interface Props {
24
27
  * Pass `null` to wire your own trigger only.
25
28
  */
26
29
  hotkey?: string | null;
30
+ /**
31
+ * Key that opens the palette on its own, pressed anywhere the reader
32
+ * isn't typing. Default `'/'`. Pass `null` to turn it off.
33
+ */
34
+ quickKey?: string | null;
27
35
  onselect?: (item: CommandItem) => void;
28
36
  class?: string;
29
37
  }
@@ -94,7 +94,7 @@
94
94
 
95
95
  <div
96
96
  bind:this={wrapper}
97
- class="inline-flex w-full items-center gap-1 border bg-bg pr-1 pl-4 transition-colors duration-150 ease-brand-out
97
+ class="inline-flex w-full items-center gap-1 border bg-bg pr-1 pl-3.5 transition-colors duration-150 ease-brand-out
98
98
  {invalid ? 'border-danger' : `border-hairline ${toneFocusWithinBorder[tone]}`}
99
99
  {disabled ? 'pointer-events-none opacity-50' : ''} {className}"
100
100
  >
@@ -118,7 +118,7 @@
118
118
  }}
119
119
  onblur={commit}
120
120
  onkeydown={onKeydown}
121
- class="h-11 w-full min-w-0 bg-transparent font-sans text-[15px] text-text tabular-nums placeholder:text-text-muted focus:outline-none"
121
+ class="h-10 w-full min-w-0 bg-transparent font-sans text-[15px] text-text tabular-nums placeholder:text-text-muted focus:outline-none"
122
122
  />
123
123
 
124
124
  {#if name}<input type="hidden" {name} {value} />{/if}
@@ -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,
@@ -91,7 +95,8 @@
91
95
  >
92
96
  <div class="flex h-full flex-col">
93
97
  {#if title || dismissible}
94
- <div class="flex items-start gap-4 border-b border-hairline px-6 py-5">
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>
@@ -116,7 +121,7 @@
116
121
  {/if}
117
122
 
118
123
  <div
119
- class="flex-1 overflow-y-auto px-6 py-5 font-sans text-[15px] leading-relaxed text-text-secondary"
124
+ class="flex-1 overflow-y-auto px-5 py-4 font-sans text-[15px] leading-relaxed text-text-secondary"
120
125
  >
121
126
  {@render children()}
122
127
  </div>
@@ -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;
@@ -173,7 +173,7 @@
173
173
  <ul class="flex flex-col border border-hairline">
174
174
  {#each files as file, i (file.name + file.size + i)}
175
175
  <li
176
- class="flex items-center gap-3 border-b border-hairline px-4 py-3 font-sans last:border-b-0"
176
+ class="flex items-center gap-3 border-b border-hairline px-3.5 py-2.5 font-sans last:border-b-0"
177
177
  >
178
178
  <span class="min-w-0 flex-1 truncate text-sm text-text">{file.name}</span>
179
179
  <span class="shrink-0 font-mono text-xs text-text-muted">{formatSize(file.size)}</span>
@@ -29,7 +29,7 @@
29
29
  <svelte:element
30
30
  this={href ? 'a' : 'div'}
31
31
  {href}
32
- class="group flex flex-col gap-3 bg-bg p-6 {href
32
+ class="group flex flex-col gap-3 bg-bg p-5 {href
33
33
  ? `transition-colors duration-150 ease-brand-out ${focusRing} ${toneRing[tone]}`
34
34
  : ''} {className}"
35
35
  {...rest}
@@ -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 {
@@ -30,14 +30,14 @@
30
30
  }: Props = $props();
31
31
 
32
32
  const sizes: Record<InputSize, string> = {
33
- sm: 'h-9 text-[13px]',
34
- md: 'h-11 text-[15px]',
35
- lg: 'h-12 text-base'
33
+ sm: 'h-8 text-[13px]',
34
+ md: 'h-10 text-[15px]',
35
+ lg: 'h-11 text-base'
36
36
  };
37
37
  </script>
38
38
 
39
39
  <span
40
- class="inline-flex w-full items-center gap-2 border bg-bg px-4 transition-colors duration-150 ease-brand-out
40
+ class="inline-flex w-full items-center gap-2 border bg-bg px-3.5 transition-colors duration-150 ease-brand-out
41
41
  {sizes[size]}
42
42
  {invalid
43
43
  ? 'border-danger focus-within:border-danger'
@@ -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>`. */
@@ -37,7 +38,7 @@
37
38
  disabled={href ? undefined : disabled}
38
39
  aria-disabled={disabled ? 'true' : undefined}
39
40
  tabindex={disabled ? undefined : -1}
40
- class="flex w-full items-center gap-2.5 px-3 py-2 text-left text-[14px] transition-colors duration-100 ease-brand-out focus:outline-none disabled:pointer-events-none disabled:opacity-40
41
+ class="flex w-full items-center gap-2.5 px-3 py-1.5 text-left text-[14px] transition-colors duration-100 ease-brand-out focus:outline-none disabled:pointer-events-none disabled:opacity-40
41
42
  {tone === 'neutral'
42
43
  ? 'text-text-secondary hover:bg-bg-inset focus-visible:bg-bg-inset'
43
44
  : `${toneText[tone]} ${toneSoftHover[tone]}`}
@@ -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,
@@ -74,7 +78,8 @@
74
78
  >
75
79
  <div class="flex flex-col">
76
80
  {#if title || closable}
77
- <div class="flex items-start gap-4 border-b border-hairline px-6 py-5">
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>
@@ -98,12 +103,12 @@
98
103
  </div>
99
104
  {/if}
100
105
 
101
- <div class="px-6 py-5 font-sans text-[15px] leading-relaxed text-text-secondary">
106
+ <div class="px-5 py-4 font-sans text-[15px] leading-relaxed text-text-secondary">
102
107
  {@render children()}
103
108
  </div>
104
109
 
105
110
  {#if footer}
106
- <div class="flex items-center justify-end gap-3 border-t border-hairline px-6 py-4">
111
+ <div class="flex items-center justify-end gap-3 border-t border-hairline px-5 py-3.5">
107
112
  {@render footer()}
108
113
  </div>
109
114
  {/if}
@@ -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;