@heyramzi/ui 2.20.0 → 2.21.1

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 (193) hide show
  1. package/README.md +2 -0
  2. package/dist/charts/dashboard/data-table.js +1 -2
  3. package/dist/components/list-page/filter-section.js +1 -1
  4. package/dist/components/list-page/list-table.js +5 -6
  5. package/dist/components/list-page/queue-rail.js +2 -1
  6. package/dist/components/picker/trigger.js +3 -3
  7. package/dist/components/segmented/segmented.js +5 -5
  8. package/dist/components/work-parts/looks.js +1 -1
  9. package/dist/shadcn/alert/alert-variants.js +9 -0
  10. package/dist/shadcn/alert/index.js +1 -1
  11. package/dist/shadcn/badge/badge-variants.js +17 -0
  12. package/dist/shadcn/badge/index.js +1 -1
  13. package/dist/shadcn/dialog/index.js +3 -2
  14. package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
  15. package/dist/shadcn/sheet/index.js +3 -2
  16. package/dist/shadcn/skeleton/index.js +3 -2
  17. package/dist/shadcn/toggle/index.js +1 -1
  18. package/dist/shadcn/toggle/toggle-variants.js +20 -0
  19. package/dist/status/index.js +2 -0
  20. package/dist/status/pill-tint.js +13 -0
  21. package/dist/work/kanban-drag.js +13 -7
  22. package/package.json +23 -23
  23. package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
  24. package/src/ai/loading-state/PixelLoader.svelte +1 -1
  25. package/src/animations/dropdown.css +19 -10
  26. package/src/animations/modal.css +7 -2
  27. package/src/charts/charts.css +3 -5
  28. package/src/charts/dashboard/dashboard-section.svelte +1 -1
  29. package/src/charts/dashboard/data-table.ts +1 -2
  30. package/src/charts/dashboard/stat-tile.svelte +1 -1
  31. package/src/charts/number-chart.css +8 -7
  32. package/src/charts/pop-value.svelte +68 -0
  33. package/src/charts/primitives/chart-tooltip.svelte +1 -1
  34. package/src/charts/reference-line.css +1 -1
  35. package/src/charts/stat-card.css +4 -4
  36. package/src/components/copy/copy-button.svelte +11 -10
  37. package/src/components/data-table/row-arrow.svelte +1 -1
  38. package/src/components/data-table/row-context-menu.svelte +1 -1
  39. package/src/components/data-table/sortable-head.svelte +2 -2
  40. package/src/components/data-table/table-skeleton.svelte +5 -5
  41. package/src/components/docs/doc-page.svelte +1 -3
  42. package/src/components/docs/on-this-page.svelte +4 -4
  43. package/src/components/field-row/field-row.svelte +1 -1
  44. package/src/components/list-page/detail-empty.svelte +3 -8
  45. package/src/components/list-page/detail-header.svelte +1 -1
  46. package/src/components/list-page/detail-row.svelte +2 -1
  47. package/src/components/list-page/empty-state.svelte +15 -7
  48. package/src/components/list-page/filter-item.svelte +2 -2
  49. package/src/components/list-page/filter-popover.svelte +1 -1
  50. package/src/components/list-page/filter-section.ts +1 -4
  51. package/src/components/list-page/grouped-list.svelte +37 -8
  52. package/src/components/list-page/list-page-shell.svelte +1 -1
  53. package/src/components/list-page/list-table.ts +6 -6
  54. package/src/components/list-page/master-detail.svelte +1 -1
  55. package/src/components/list-page/queue-rail.ts +2 -2
  56. package/src/components/list-page/rail-empty.svelte +1 -1
  57. package/src/components/list-page/rail-row.svelte +5 -5
  58. package/src/components/open-in/open-in.svelte +2 -1
  59. package/src/components/picker/trigger.ts +6 -5
  60. package/src/components/section-card/section-card.svelte +1 -1
  61. package/src/components/section-header/section-header.svelte +4 -2
  62. package/src/components/section-header/section-label.svelte +2 -2
  63. package/src/components/segmented/animated-tabs.svelte +1 -1
  64. package/src/components/segmented/section-runs.svelte +1 -1
  65. package/src/components/segmented/segment.svelte +4 -6
  66. package/src/components/segmented/segmented-track.svelte +18 -52
  67. package/src/components/segmented/segmented.ts +5 -5
  68. package/src/components/table-view/table-view.svelte +43 -24
  69. package/src/components/tags-input/tags-input.svelte +17 -31
  70. package/src/components/work-parts/looks.ts +1 -1
  71. package/src/editor/content.css +3 -3
  72. package/src/editor/editor.svelte +1 -1
  73. package/src/inbox/thread-inbox.svelte +2 -9
  74. package/src/panel/side-panel.svelte +9 -4
  75. package/src/record/info-list.svelte +7 -8
  76. package/src/record/project-list.svelte +1 -1
  77. package/src/record/record-demo.svelte +2 -4
  78. package/src/record/record-rail.svelte +5 -1
  79. package/src/record/record-shell.svelte +1 -1
  80. package/src/record/record-stats.svelte +9 -3
  81. package/src/record/steps-rail.svelte +7 -11
  82. package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
  83. package/src/shadcn/alert/alert-variants.ts +17 -0
  84. package/src/shadcn/alert/alert.svelte +2 -16
  85. package/src/shadcn/alert/index.ts +1 -1
  86. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
  87. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
  88. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
  89. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
  90. package/src/shadcn/badge/badge-variants.ts +23 -0
  91. package/src/shadcn/badge/badge.svelte +2 -20
  92. package/src/shadcn/badge/index.ts +1 -1
  93. package/src/shadcn/bubble/bubble-content.svelte +1 -1
  94. package/src/shadcn/bubble/bubble.svelte +1 -1
  95. package/src/shadcn/button/button.svelte +11 -5
  96. package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
  97. package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
  98. package/src/shadcn/calendar/calendar.svelte +1 -1
  99. package/src/shadcn/card/card.svelte +2 -1
  100. package/src/shadcn/checkbox/checkbox.svelte +1 -1
  101. package/src/shadcn/code-block/code-block-actions.svelte +1 -1
  102. package/src/shadcn/command/command-dialog.svelte +2 -2
  103. package/src/shadcn/command/command-group.svelte +1 -1
  104. package/src/shadcn/command/command-input.svelte +1 -1
  105. package/src/shadcn/command/command-item.svelte +1 -1
  106. package/src/shadcn/dialog/dialog-body.svelte +22 -0
  107. package/src/shadcn/dialog/dialog-content.svelte +19 -4
  108. package/src/shadcn/dialog/dialog-footer.svelte +19 -1
  109. package/src/shadcn/dialog/dialog-header.svelte +6 -1
  110. package/src/shadcn/dialog/dialog-title.svelte +2 -1
  111. package/src/shadcn/dialog/index.ts +3 -0
  112. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
  113. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  114. package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
  115. package/src/shadcn/field/field-separator.svelte +1 -1
  116. package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
  117. package/src/shadcn/input/input.svelte +2 -2
  118. package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
  119. package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
  120. package/src/shadcn/popover/popover-content.svelte +1 -1
  121. package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
  122. package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
  123. package/src/shadcn/select/select-content.svelte +6 -2
  124. package/src/shadcn/select/select-item.svelte +1 -1
  125. package/src/shadcn/select/select-trigger.svelte +1 -1
  126. package/src/shadcn/sheet/index.ts +3 -0
  127. package/src/shadcn/sheet/sheet-body.svelte +22 -0
  128. package/src/shadcn/sheet/sheet-content.svelte +27 -5
  129. package/src/shadcn/sheet/sheet-footer.svelte +14 -2
  130. package/src/shadcn/sheet/sheet-header.svelte +1 -1
  131. package/src/shadcn/sheet/sheet-title.svelte +1 -1
  132. package/src/shadcn/sheet/sheet.css +11 -5
  133. package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
  134. package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
  135. package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
  136. package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
  137. package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
  138. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
  139. package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
  140. package/src/shadcn/sidebar/sidebar.svelte +1 -1
  141. package/src/shadcn/skeleton/index.ts +3 -0
  142. package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
  143. package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
  144. package/src/shadcn/skeleton/skeleton.svelte +5 -1
  145. package/src/shadcn/slider/slider.svelte +1 -1
  146. package/src/shadcn/switch/switch.svelte +2 -2
  147. package/src/shadcn/table/table-row.svelte +1 -1
  148. package/src/shadcn/tabs/tabs-list.svelte +1 -1
  149. package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
  150. package/src/shadcn/textarea/textarea.svelte +1 -1
  151. package/src/shadcn/toggle/index.ts +1 -1
  152. package/src/shadcn/toggle/toggle-variants.ts +26 -0
  153. package/src/shadcn/toggle/toggle.svelte +7 -25
  154. package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
  155. package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
  156. package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
  157. package/src/shadcn/trace/trace.svelte +1 -1
  158. package/src/status/README.md +1 -1
  159. package/src/status/index.ts +2 -0
  160. package/src/status/pill-tint.ts +16 -0
  161. package/src/status/status-badge.svelte +21 -7
  162. package/src/status/status-dot.svelte +6 -11
  163. package/src/status/status-pill.svelte +40 -0
  164. package/src/svelte-reexports.test.ts +43 -0
  165. package/src/thread/thread.svelte +54 -136
  166. package/src/tokens/craft.css +55 -36
  167. package/src/tokens/palette.css +16 -9
  168. package/src/work/demo.svelte +48 -42
  169. package/src/work/detail/detail-demo.svelte +33 -24
  170. package/src/work/detail/task-chip.svelte +4 -4
  171. package/src/work/detail/task-detail.svelte +41 -108
  172. package/src/work/detail/task-property.svelte +7 -13
  173. package/src/work/detail/task-thread.svelte +48 -119
  174. package/src/work/docs/doc-editor-page.svelte +5 -4
  175. package/src/work/docs/doc-menu.svelte +9 -4
  176. package/src/work/docs/docs-demo.svelte +4 -4
  177. package/src/work/docs/edited-chip.svelte +11 -4
  178. package/src/work/gantt/gantt-bar.svelte +18 -14
  179. package/src/work/gantt/gantt-grid.svelte +7 -7
  180. package/src/work/gantt/gantt-group-row.svelte +7 -10
  181. package/src/work/gantt/gantt-header.svelte +3 -1
  182. package/src/work/gantt/gantt-task-row.svelte +19 -9
  183. package/src/work/gantt/gantt.css +4 -5
  184. package/src/work/inbox/inbox-card.svelte +32 -10
  185. package/src/work/inbox/inbox-draft.svelte +2 -2
  186. package/src/work/kanban-column.svelte +7 -15
  187. package/src/work/kanban-drag.ts +13 -8
  188. package/src/work/task-gantt.svelte +2 -1
  189. package/src/work/task-list.svelte +54 -53
  190. package/src/work/time/time-chip.svelte +16 -19
  191. package/src/work/time/time-dot.svelte +2 -1
  192. package/src/work/time/time-ring.svelte +2 -3
  193. package/src/work/time/time-tracker.svelte +72 -166
@@ -14,6 +14,7 @@
14
14
  import type { Tone } from "../lib/tone.js";
15
15
  import { cn } from "../lib/utils.js";
16
16
  import StatusBadge from "../status/status-badge.svelte";
17
+ import PopValue from "../charts/pop-value.svelte";
17
18
 
18
19
  // =========================
19
20
  // PROPS
@@ -113,7 +114,8 @@
113
114
  side="right"
114
115
  bind:ref={panel}
115
116
  tabindex={-1}
116
- class={cn("flex w-full flex-col gap-0 p-0", WIDTH[width], bare && "overflow-hidden")}
117
+ floating
118
+ class={cn("flex w-full flex-col gap-0 overflow-hidden p-0", WIDTH[width])}
117
119
  {onOpenAutoFocus}
118
120
  >
119
121
  {#if bare}
@@ -165,11 +167,14 @@
165
167
  <div class="min-w-0">
166
168
  <dt class="text-xs text-muted-foreground">{stat.label}</dt>
167
169
  <dd
168
- class={cn("text-sm font-medium tabular-nums", STAT_TONE[stat.tone ?? "default"])}
170
+ class={cn(
171
+ "text-lg leading-tight font-semibold tabular-nums",
172
+ STAT_TONE[stat.tone ?? "default"],
173
+ )}
169
174
  >
170
- {stat.value}
175
+ <PopValue value={stat.value} />
171
176
  {#if stat.sub}
172
- <span class="text-xs font-normal text-muted-foreground">{stat.sub}</span>
177
+ <span class="ms-1 text-xs font-normal text-muted-foreground">{stat.sub}</span>
173
178
  {/if}
174
179
  </dd>
175
180
  </div>
@@ -171,15 +171,16 @@
171
171
  {/each}
172
172
  </span>
173
173
  {:else if row.tone && row.value}
174
- <StatusBadge tone={row.tone} capitalize={false} class="w-fit max-w-full">{row.value}</StatusBadge>
174
+ <StatusBadge tone={row.tone} capitalize={false} class="w-fit max-w-full"
175
+ >{row.value}</StatusBadge
176
+ >
175
177
  {:else if row.href && row.value}
176
178
  <a
177
179
  href={row.href}
178
180
  title={row.value}
179
181
  class={cn(
180
182
  "min-w-0 truncate text-sm font-medium underline-offset-2 hover:underline",
181
- clamp &&
182
- "rounded-xs focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
183
+ clamp && "rounded-xs focus-visible:shadow-(--ring-focus) focus-visible:outline-none",
183
184
  )}>{row.value}</a
184
185
  >
185
186
  {:else if row.value}
@@ -299,12 +300,10 @@
299
300
  {:else if shown.length > 0}
300
301
  <div class={cn("flex flex-col gap-2", className)}>
301
302
  {#if title}
302
- <h2 class="text-xs font-medium tracking-wide text-muted-foreground uppercase">{title}</h2>
303
+ <h2 class="text-xs font-medium text-muted-foreground">{title}</h2>
303
304
  {/if}
304
305
  <!-- Labels size to their text, capped at 45%; the dt's min-w-20 is the 5rem floor. min() can't take max-content, so a min() template is dropped whole. -->
305
- <dl
306
- class="grid grid-cols-[fit-content(45%)_minmax(0,1fr)] items-start gap-x-4 gap-y-2.5"
307
- >
306
+ <dl class="grid grid-cols-[fit-content(45%)_minmax(0,1fr)] items-start gap-x-4 gap-y-2.5">
308
307
  {#each shown as row, index (row.divider ? `divider-${index}` : keyOf(row))}
309
308
  {#if row.divider}
310
309
  <div class="col-span-2 -my-0.5 border-t border-border/50"></div>
@@ -355,7 +354,7 @@
355
354
  <Select.Trigger
356
355
  size="sm"
357
356
  aria-label={t.edit(row.label)}
358
- class="-ml-1.5 min-h-7 h-auto w-fit max-w-full gap-1.5 border-transparent bg-transparent px-1.5 shadow-none transition-colors hover:bg-muted/60 focus-visible:ring-3 data-[state=open]:bg-muted/60"
357
+ class="-ml-1.5 min-h-7 h-auto w-fit max-w-full gap-1.5 border-transparent bg-transparent px-1.5 shadow-none transition-colors hover:bg-muted/60 focus-visible:shadow-(--ring-focus) data-[state=open]:bg-muted/60"
359
358
  >
360
359
  <span data-slot="select-value" class="flex min-w-0 items-center gap-1.5">
361
360
  {@render display(row)}
@@ -122,7 +122,7 @@
122
122
  aria-current={active ? "page" : undefined}
123
123
  class={cn(
124
124
  "flex flex-col gap-1.5 rounded-lg px-2 py-2 transition-colors any-pointer-coarse:py-3",
125
- "outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
125
+ "outline-none focus-visible:shadow-(--ring-focus)",
126
126
  active ? "bg-muted" : "hover:bg-muted/60",
127
127
  )}
128
128
  >
@@ -183,7 +183,7 @@
183
183
  </div>
184
184
 
185
185
  <div class={narrow ? "mx-auto w-full max-w-[390px]" : "w-full"}>
186
- <div class="overflow-hidden rounded-xl border border-border">
186
+ <div class="overflow-hidden rounded-xl shadow-card">
187
187
  {#if fixture === "project"}
188
188
  <RecordShell
189
189
  bind:tab
@@ -320,9 +320,7 @@
320
320
  {#snippet rail()}
321
321
  <div class="flex flex-col gap-6">
322
322
  <section class="flex flex-col gap-2">
323
- <h2 class="px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
324
- Projects
325
- </h2>
323
+ <h2 class="px-2 text-xs font-medium text-muted-foreground">Projects</h2>
326
324
  <ProjectList projects={summaries} {stages} today={DEMO_TODAY} {locale} />
327
325
  </section>
328
326
  <FieldSection
@@ -51,7 +51,11 @@
51
51
 
52
52
  <!-- A record's side rail with tabs: the fields in one, a thread or a log in the next,
53
53
  so the thread sits beside the record and not a screen below it. One tab draws no strip. -->
54
- <Tabs.Root value={open} onValueChange={(next) => (value = next)} class={cn("min-w-0 gap-0", className)}>
54
+ <Tabs.Root
55
+ value={open}
56
+ onValueChange={(next) => (value = next)}
57
+ class={cn("min-w-0 gap-0", className)}
58
+ >
55
59
  {#if tabs.length > 1}
56
60
  <div class={cn("pb-3", listClass)}>
57
61
  <Tabs.List size="sm" class="w-full" aria-label={label}>
@@ -62,7 +62,7 @@
62
62
  <!-- =========================== -->
63
63
  <!-- WHY a container query: the record sits in a page with its own sidebar, so the
64
64
  viewport says nothing about how much room it has. -->
65
- <div class={cn("@container flex min-w-0 flex-col bg-background", className)}>
65
+ <div class={cn("@container flex min-w-0 flex-col bg-card", className)}>
66
66
  {@render header()}
67
67
  {@render banner?.()}
68
68
 
@@ -3,6 +3,7 @@
3
3
  // IMPORTS
4
4
  // =========================
5
5
  import { cn } from "../lib/utils.js";
6
+ import PopValue from "../charts/pop-value.svelte";
6
7
  import type { PanelStat } from "../panel/side-panel-types.js";
7
8
  import { gutterClass } from "./gutter.js";
8
9
 
@@ -36,10 +37,15 @@
36
37
  {#each stats as stat (stat.label)}
37
38
  <div class="min-w-0">
38
39
  <dt class="text-xs text-muted-foreground">{stat.label}</dt>
39
- <dd class={cn("text-sm font-medium tabular-nums", TONE[stat.tone ?? "default"])}>
40
- {stat.value}
40
+ <dd
41
+ class={cn(
42
+ "text-lg leading-tight font-semibold tabular-nums",
43
+ TONE[stat.tone ?? "default"],
44
+ )}
45
+ >
46
+ <PopValue value={stat.value} />
41
47
  {#if stat.sub}
42
- <span class="text-xs font-normal text-muted-foreground">{stat.sub}</span>
48
+ <span class="ms-1 text-xs font-normal text-muted-foreground">{stat.sub}</span>
43
49
  {/if}
44
50
  </dd>
45
51
  </div>
@@ -245,21 +245,21 @@
245
245
  <Check class="size-2.5" weight="bold" />
246
246
  </span>
247
247
  {:else if kind === "current"}
248
- <span class="rounded-full bg-background p-0.75">
248
+ <span class="rounded-full bg-card p-0.75">
249
249
  <span
250
250
  class="block size-2 rounded-full bg-[color:var(--tone-accent,var(--color-primary))] ring-3 ring-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_20%,transparent)]"
251
251
  ></span>
252
252
  </span>
253
253
  {:else if kind === "late"}
254
- <span class="size-2 rounded-full border-2 border-warning bg-background"></span>
254
+ <span class="size-2 rounded-full border-2 border-warning bg-card"></span>
255
255
  {:else}
256
- <span class="size-2 rounded-full border border-muted-foreground/40 bg-background"></span>
256
+ <span class="size-2 rounded-full border border-muted-foreground/40 bg-card"></span>
257
257
  {/if}
258
258
  {/snippet}
259
259
 
260
260
  <div class={cn("flex flex-col", className)}>
261
261
  <div class="mb-2 flex min-h-7 items-center gap-2 px-2">
262
- <h2 class="text-xs font-medium tracking-wide text-muted-foreground uppercase">{t.steps}</h2>
262
+ <h2 class="text-xs font-medium text-muted-foreground">{t.steps}</h2>
263
263
  {#if !loading}
264
264
  <span class="text-xs tabular-nums text-muted-foreground">
265
265
  {t.stepsCount(progress.done, progress.total)}
@@ -290,9 +290,7 @@
290
290
  <Popover.Content align="end" class="w-64 p-1">
291
291
  {#each menuBuckets as bucket (bucket.key)}
292
292
  {#if bucket.label}
293
- <p
294
- class="px-2 pt-2 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase"
295
- >
293
+ <p class="px-2 pt-2 pb-1 text-xs font-medium text-muted-foreground">
296
294
  {bucket.label}
297
295
  </p>
298
296
  {/if}
@@ -342,9 +340,7 @@
342
340
  {#each buckets as bucket (bucket.key)}
343
341
  <div class="flex flex-col">
344
342
  {#if bucket.label}
345
- <h3
346
- class="mb-1 px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase select-none"
347
- >
343
+ <h3 class="mb-1 px-2 text-xs font-medium text-muted-foreground select-none">
348
344
  {bucket.label}
349
345
  </h3>
350
346
  {/if}
@@ -375,7 +371,7 @@
375
371
  draggable={canReorder && !renaming}
376
372
  class={cn(
377
373
  "relative flex min-h-9 w-full items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors any-pointer-coarse:min-h-11 any-pointer-coarse:py-3",
378
- "outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
374
+ "outline-none focus-visible:shadow-(--ring-focus)",
379
375
  canReorder && "active:cursor-grabbing",
380
376
  active ? "bg-muted" : "hover:bg-muted/60",
381
377
  )}
@@ -24,7 +24,7 @@
24
24
  data-slot="accordion-trigger"
25
25
  bind:ref
26
26
  class={cn(
27
- "focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-md py-4 text-left text-sm font-medium hover:underline focus-visible:ring-3 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-[color,background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none disabled:pointer-events-none disabled:opacity-50",
27
+ " focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-md py-4 text-left text-sm font-medium hover:underline focus-visible:shadow-(--ring-focus) **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-[color,background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none disabled:pointer-events-none disabled:opacity-50",
28
28
  className,
29
29
  )}
30
30
  {...restProps}
@@ -0,0 +1,17 @@
1
+ import { type VariantProps, tv } from "tailwind-variants";
2
+
3
+ export const alertVariants = tv({
4
+ base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
5
+ variants: {
6
+ variant: {
7
+ default: "bg-card text-card-foreground",
8
+ destructive:
9
+ "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ variant: "default",
14
+ },
15
+ });
16
+
17
+ export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
@@ -1,21 +1,7 @@
1
1
  <script lang="ts" module>
2
- import { type VariantProps, tv } from "tailwind-variants";
2
+ import { alertVariants, type AlertVariant } from "./alert-variants.js";
3
3
 
4
- export const alertVariants = tv({
5
- base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
6
- variants: {
7
- variant: {
8
- default: "bg-card text-card-foreground",
9
- destructive:
10
- "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
11
- },
12
- },
13
- defaultVariants: {
14
- variant: "default",
15
- },
16
- });
17
-
18
- export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
4
+ export { alertVariants, type AlertVariant };
19
5
  </script>
20
6
 
21
7
  <script lang="ts">
@@ -1,7 +1,7 @@
1
1
  import Root from "./alert.svelte";
2
2
  import Description from "./alert-description.svelte";
3
3
  import Title from "./alert-title.svelte";
4
- export { alertVariants, type AlertVariant } from "./alert.svelte";
4
+ export { alertVariants, type AlertVariant } from "./alert-variants.js";
5
5
 
6
6
  export {
7
7
  Root,
@@ -21,7 +21,11 @@
21
21
  bind:ref
22
22
  data-slot="alert-dialog-content"
23
23
  class={cn(
24
- "t-modal bg-background fixed inset-0 z-50 m-auto grid h-fit w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg",
24
+ // The dialog's surface: white popover, the edge and depth from `shadow-edge-overlay`,
25
+ // the overlay radius. `overflow-hidden` lets a banded footer follow the bottom corners.
26
+ "t-modal bg-popover text-popover-foreground shadow-edge-overlay fixed inset-0 z-50 m-auto grid h-fit w-full max-w-[calc(100%-2rem)] gap-4 overflow-hidden rounded-2xl text-sm outline-none sm:max-w-md",
27
+ // Same padding variable as the dialog, so `AlertDialog.Footer band` reads one number.
28
+ "[--dialog-pad:1.25rem] sm:[--dialog-pad:1.5rem] p-(--dialog-pad)",
25
29
  className,
26
30
  )}
27
31
  {...restProps}
@@ -6,14 +6,24 @@
6
6
  ref = $bindable(null),
7
7
  class: className,
8
8
  children,
9
+ band = false,
9
10
  ...restProps
10
- }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
12
+ /** A hairline and a quiet surface out to both edges and the bottom corners. Last child only. */
13
+ band?: boolean;
14
+ } = $props();
11
15
  </script>
12
16
 
13
17
  <div
14
18
  bind:this={ref}
15
19
  data-slot="alert-dialog-footer"
16
- class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
20
+ data-band={band || undefined}
21
+ class={cn(
22
+ "flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end",
23
+ "data-band:border-border data-band:bg-muted/50 data-band:mt-2 data-band:border-t",
24
+ "data-band:mx-[calc(-1*var(--dialog-pad))] data-band:mb-[calc(-1*var(--dialog-pad))] data-band:px-(--dialog-pad) data-band:py-4",
25
+ className,
26
+ )}
17
27
  {...restProps}
18
28
  >
19
29
  {@render children?.()}
@@ -13,7 +13,7 @@
13
13
  <div
14
14
  bind:this={ref}
15
15
  data-slot="alert-dialog-header"
16
- class={cn("flex flex-col gap-2 text-center sm:text-start", className)}
16
+ class={cn("flex flex-col gap-1.5 text-center sm:text-start", className)}
17
17
  {...restProps}
18
18
  >
19
19
  {@render children?.()}
@@ -12,6 +12,6 @@
12
12
  <AlertDialogPrimitive.Title
13
13
  bind:ref
14
14
  data-slot="alert-dialog-title"
15
- class={cn("text-lg font-semibold", className)}
15
+ class={cn("text-base leading-6 font-semibold tracking-tight", className)}
16
16
  {...restProps}
17
17
  />
@@ -0,0 +1,23 @@
1
+ import { type VariantProps, tv } from "tailwind-variants";
2
+
3
+ export const badgeVariants = tv({
4
+ // The chip radius, not a pill: DESIGN.md keeps `rounded-full` for status and counts,
5
+ // which `StatusBadge` owns. Tabular figures so a count that ticks doesn't shift.
6
+ base: "h-5 gap-1 rounded-sm border border-transparent px-2 py-0.5 text-xs font-medium tabular-nums has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit max-w-full shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-[background-color,border-color,color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] [&>svg]:pointer-events-none",
7
+ variants: {
8
+ // Three weights a label can carry: filled, tinted, outlined. Severity is the
9
+ // `tone` prop, which reads `TONE_CLASS`, not a variant.
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
12
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
13
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
14
+ // For a consumer that wants one "this is wrong" badge without a tone prop.
15
+ destructive: "bg-destructive/10 text-destructive-ink [a]:hover:bg-destructive/20",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: "default",
20
+ },
21
+ });
22
+
23
+ export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
@@ -1,25 +1,7 @@
1
1
  <script lang="ts" module>
2
- import { type VariantProps, tv } from "tailwind-variants";
2
+ import { badgeVariants, type BadgeVariant } from "./badge-variants.js";
3
3
 
4
- export const badgeVariants = tv({
5
- base: "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-xs font-medium has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit max-w-full shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-[background-color,border-color,color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none",
6
- variants: {
7
- // Three weights a label can carry: filled, tinted, outlined. Severity is the
8
- // `tone` prop, which reads `TONE_CLASS`, not a variant.
9
- variant: {
10
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
11
- secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
12
- outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
13
- // For a consumer that wants one "this is wrong" badge without a tone prop.
14
- destructive: "bg-destructive/10 text-destructive-ink [a]:hover:bg-destructive/20",
15
- },
16
- },
17
- defaultVariants: {
18
- variant: "default",
19
- },
20
- });
21
-
22
- export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
4
+ export { badgeVariants, type BadgeVariant };
23
5
  </script>
24
6
 
25
7
  <script lang="ts">
@@ -1,2 +1,2 @@
1
1
  export { default as Badge } from "./badge.svelte";
2
- export { badgeVariants, type BadgeVariant } from "./badge.svelte";
2
+ export { badgeVariants, type BadgeVariant } from "./badge-variants.js";
@@ -14,7 +14,7 @@
14
14
 
15
15
  const mergedProps = $derived({
16
16
  class: cn(
17
- "cn-bubble-content w-fit max-w-full min-w-0 overflow-hidden wrap-break-word [button]:text-left [button,a]:transition-colors rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50 group-data-[align=end]/bubble:self-end",
17
+ "cn-bubble-content w-fit max-w-full min-w-0 overflow-hidden wrap-break-word [button]:text-left [button,a]:transition-colors rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:shadow-(--ring-focus) group-data-[align=end]/bubble:self-end",
18
18
  className,
19
19
  ),
20
20
  "data-slot": "bubble-content",
@@ -14,7 +14,7 @@
14
14
  tinted:
15
15
  "cn-bubble-variant-tinted *:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
16
16
  outline:
17
- "cn-bubble-variant-outline *:data-[slot=bubble-content]:bg-background *:data-[slot=bubble-content]:border-border [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
17
+ "cn-bubble-variant-outline *:data-[slot=bubble-content]:bg-card *:data-[slot=bubble-content]:border-border [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
18
18
  ghost:
19
19
  "cn-bubble-variant-ghost *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50 border-none",
20
20
  destructive:
@@ -4,7 +4,9 @@
4
4
  import { type VariantProps, tv } from "tailwind-variants";
5
5
 
6
6
  export const buttonVariants = tv({
7
- base: "t-press focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
7
+ // Focus is the kit's one ring, `--ring-focus`, written as a utility because the
8
+ // variant shadows (`shadow-key`, `shadow-btn`) would otherwise cover the base-layer rule.
9
+ base: "t-press focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
8
10
  variants: {
9
11
  // Five roles: fill, outline, ghost, destructive, and dashed for an empty
10
12
  // slot. `secondary` and `link` went on 4 Sep 2026, one real caller between
@@ -13,14 +15,18 @@
13
15
  default:
14
16
  "bg-primary text-primary-foreground shadow-key hover:bg-primary/90 active:shadow-none [a]:hover:bg-primary/80",
15
17
  outline:
16
- "border-border bg-background shadow-btn hover:bg-muted hover:text-foreground active:shadow-none dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
18
+ // `bg-card`, not `bg-background`: the background is the grey canvas now, so an
19
+ // outline button on a white panel read as a grey slab.
20
+ "border-border bg-card shadow-btn hover:bg-muted hover:border-foreground/15 hover:text-foreground active:shadow-none dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
21
+ // A foreground wash, not `bg-muted`: muted is L 0.94 and the canvas 0.945, so a
22
+ // ghost hover on the page drew nothing. The wash reads on canvas and card alike.
17
23
  ghost:
18
- "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
24
+ "hover:bg-foreground/6 hover:text-foreground aria-expanded:bg-foreground/6 aria-expanded:text-foreground",
19
25
  destructive:
20
- "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive dark:text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
26
+ "bg-destructive/10 hover:bg-destructive/20 dark:bg-destructive/20 text-destructive dark:text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
21
27
  // Quiet text that opens an editor or a menu: a ghost that wraps to as many lines as its
22
28
  // label needs, aligned left, never a fixed-height square.
23
- text: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
29
+ text: "hover:bg-foreground/6 hover:text-foreground aria-expanded:bg-foreground/6 aria-expanded:text-foreground",
24
30
  // WHY a variant and not a class every call site writes: "add another
25
31
 
26
32
  // row", "add a step", "attach a call" is one affordance, and it was hand-drawn eleven times across the app with four radii, three text sizes and three different hover hues.
@@ -14,7 +14,7 @@
14
14
 
15
15
  <span
16
16
  class={cn(
17
- "has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
17
+ "has-focus:border-ring border-input relative flex rounded-md border shadow-field has-focus:shadow-(--ring-focus)",
18
18
  className,
19
19
  )}
20
20
  >
@@ -13,7 +13,7 @@
13
13
 
14
14
  <span
15
15
  class={cn(
16
- "has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
16
+ "has-focus:border-ring border-input relative flex rounded-md border shadow-field has-focus:shadow-(--ring-focus)",
17
17
  className,
18
18
  )}
19
19
  >
@@ -59,7 +59,7 @@ get along, so we shut typescript up by casting `value` to `never`.
59
59
  {weekStartsOn}
60
60
  {fixedWeeks}
61
61
  class={cn(
62
- "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
62
+ "bg-card group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
63
63
  className,
64
64
  )}
65
65
  {locale}
@@ -16,7 +16,8 @@
16
16
  data-slot="card"
17
17
  data-size={size}
18
18
  class={cn(
19
- "ring-foreground/10 bg-card text-card-foreground gap-6 overflow-hidden rounded-xl py-6 text-sm shadow-card ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
19
+ // `shadow-card` draws its own hairline, so a ring on top made a 2px edge.
20
+ "bg-card text-card-foreground gap-6 overflow-hidden rounded-xl py-6 text-sm shadow-card has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
20
21
  className,
21
22
  )}
22
23
  {...restProps}
@@ -16,7 +16,7 @@
16
16
  bind:ref
17
17
  data-slot="checkbox"
18
18
  class={cn(
19
- "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-sm border shadow-btn transition-[background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 any-pointer-coarse:after:-inset-[15px] disabled:cursor-not-allowed disabled:opacity-50",
19
+ "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 not-data-checked:enabled:hover:border-foreground/30 focus-visible:border-ring focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-sm border shadow-btn transition-[background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] group-has-disabled/field:opacity-50 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 any-pointer-coarse:after:-inset-[15px] disabled:cursor-not-allowed disabled:opacity-50",
20
20
  className,
21
21
  )}
22
22
  bind:checked
@@ -14,7 +14,7 @@
14
14
  bind:this={ref}
15
15
  data-slot="code-block-actions"
16
16
  class={cn(
17
- "-my-1 -me-1.5 gap-1 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:top-3 [[data-slot=code-block]>&]:end-3 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-2 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10 ms-auto flex shrink-0 items-center [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 [[data-slot=code-block]>&]:pointer-coarse:opacity-100",
17
+ "-my-1 -me-1.5 gap-1 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:top-3 [[data-slot=code-block]>&]:end-3 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-2 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:*:[button]:bg-card [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10 ms-auto flex shrink-0 items-center [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 [[data-slot=code-block]>&]:pointer-coarse:opacity-100",
18
18
  className
19
19
  )}
20
20
  {...restProps}
@@ -37,13 +37,13 @@
37
37
  // WHY: search is the one dialog that must sit above the keyboard rather
38
38
 
39
39
  // than under it, so it stays top-anchored on phones instead of taking the shared bottom-sheet position, and hugs the safe area at the top.
40
- "rounded-xl! top-[max(0.5rem,env(safe-area-inset-top))] right-2 bottom-auto left-2 w-auto max-h-[70svh] translate-y-0 gap-0 overflow-hidden p-0 sm:p-0",
40
+ "rounded-2xl! top-[max(0.5rem,env(safe-area-inset-top))] right-2 bottom-auto left-2 w-auto max-h-[70svh] translate-y-0 gap-0 overflow-hidden p-0 sm:p-0",
41
41
  // WHY no border and no card background here: the palette is a floating
42
42
 
43
43
  // object, so its edge is a lit rim and a cast shadow rather than a drawn line, and the surface itself belongs to the Command root beneath so its translucency
44
44
 
45
45
  // can read the dimmed page through it.
46
- "border-0 bg-transparent ring-1 ring-foreground/15 shadow-overlay",
46
+ "border-0 bg-transparent shadow-edge-overlay",
47
47
  // WHY a third of the way down rather than centred: the eye is already at
48
48
 
49
49
  // the top of the screen when the shortcut is pressed, and a list that grows downward should not push its first row past the fold.
@@ -31,7 +31,7 @@
31
31
  >
32
32
  {#if heading}
33
33
  <CommandPrimitive.GroupHeading
34
- class="text-muted-foreground px-2.5 pt-2.5 pb-1.5 text-xs font-medium tracking-[0.09em] uppercase"
34
+ class="text-muted-foreground px-2.5 pt-2.5 pb-1.5 text-xs font-medium"
35
35
  >
36
36
  {heading}
37
37
  </CommandPrimitive.GroupHeading>
@@ -49,7 +49,7 @@
49
49
  "caret-brand placeholder:text-muted-foreground/70 w-full bg-transparent outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
50
50
  // A combobox popover's search line is 36px, measured; `lg` is the
51
51
  // standalone palette header and keeps its own larger rhythm.
52
- size === "lg" ? "py-3.5 text-[15px]" : "py-2 text-[13px]",
52
+ size === "lg" ? "py-3.5 text-[15px]" : "py-2 text-(length:--text-control)",
53
53
  className,
54
54
  )}
55
55
  bind:ref
@@ -22,7 +22,7 @@
22
22
  // at half strength is a two-percent lightness step on the dark surface, so the one row the arrow keys are on was the hardest row to find.
23
23
 
24
24
  // A foreground wash plus an inset hairline works in both modes from one rule.
25
- "group/command-item data-selected:bg-foreground/6 data-selected:ring-foreground/10 data-selected:text-foreground data-selected:*:[svg]:text-foreground relative flex min-h-8 cursor-default items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] outline-hidden transition-colors duration-[var(--duration-fast)] select-none active:translate-y-px data-selected:ring-1 data-selected:ring-inset data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
25
+ "group/command-item data-selected:bg-foreground/6 data-selected:ring-foreground/10 data-selected:text-foreground data-selected:*:[svg]:text-foreground relative flex min-h-8 cursor-default items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-(length:--text-control) outline-hidden select-none active:translate-y-px data-selected:ring-1 data-selected:ring-inset data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
26
26
  className,
27
27
  )}
28
28
  {...restProps}
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "../../lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <!-- The fields between the header and the footer. No padding of its own: the content's
14
+ `--dialog-pad` already lines it up with the title. -->
15
+ <div
16
+ bind:this={ref}
17
+ data-slot="dialog-body"
18
+ class={cn("flex min-w-0 flex-col gap-4", className)}
19
+ {...restProps}
20
+ >
21
+ {@render children?.()}
22
+ </div>