@heyramzi/ui 2.19.2 → 2.21.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 (176) 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/dialog/index.js +3 -2
  10. package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
  11. package/dist/shadcn/sheet/index.js +3 -2
  12. package/dist/shadcn/skeleton/index.js +3 -2
  13. package/dist/status/index.js +1 -0
  14. package/dist/work/kanban-drag.js +13 -7
  15. package/package.json +1 -1
  16. package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
  17. package/src/ai/loading-state/PixelLoader.svelte +1 -1
  18. package/src/animations/dropdown.css +19 -10
  19. package/src/animations/modal.css +7 -2
  20. package/src/charts/charts.css +3 -5
  21. package/src/charts/dashboard/dashboard-section.svelte +1 -1
  22. package/src/charts/dashboard/data-table.ts +1 -2
  23. package/src/charts/dashboard/stat-tile.svelte +1 -1
  24. package/src/charts/number-chart.css +8 -7
  25. package/src/charts/pop-value.svelte +68 -0
  26. package/src/charts/primitives/chart-tooltip.svelte +1 -1
  27. package/src/charts/reference-line.css +1 -1
  28. package/src/charts/stat-card.css +4 -4
  29. package/src/components/copy/copy-button.svelte +11 -10
  30. package/src/components/data-table/row-arrow.svelte +1 -1
  31. package/src/components/data-table/row-context-menu.svelte +1 -1
  32. package/src/components/data-table/sortable-head.svelte +2 -2
  33. package/src/components/data-table/table-skeleton.svelte +5 -5
  34. package/src/components/docs/doc-page.svelte +1 -3
  35. package/src/components/docs/on-this-page.svelte +4 -4
  36. package/src/components/field-row/field-row.svelte +1 -1
  37. package/src/components/list-page/detail-empty.svelte +3 -8
  38. package/src/components/list-page/detail-header.svelte +1 -1
  39. package/src/components/list-page/detail-row.svelte +2 -1
  40. package/src/components/list-page/empty-state.svelte +15 -7
  41. package/src/components/list-page/filter-item.svelte +2 -2
  42. package/src/components/list-page/filter-popover.svelte +1 -1
  43. package/src/components/list-page/filter-section.ts +1 -4
  44. package/src/components/list-page/grouped-list.svelte +37 -8
  45. package/src/components/list-page/list-page-shell.svelte +1 -1
  46. package/src/components/list-page/list-table.ts +6 -6
  47. package/src/components/list-page/master-detail.svelte +1 -1
  48. package/src/components/list-page/queue-rail.ts +2 -2
  49. package/src/components/list-page/rail-empty.svelte +1 -1
  50. package/src/components/list-page/rail-row.svelte +5 -5
  51. package/src/components/open-in/open-in.svelte +2 -1
  52. package/src/components/picker/trigger.ts +6 -5
  53. package/src/components/section-card/section-card.svelte +1 -1
  54. package/src/components/section-header/section-header.svelte +4 -2
  55. package/src/components/section-header/section-label.svelte +2 -2
  56. package/src/components/segmented/animated-tabs.svelte +1 -1
  57. package/src/components/segmented/section-runs.svelte +1 -1
  58. package/src/components/segmented/segment.svelte +4 -6
  59. package/src/components/segmented/segmented-track.svelte +18 -52
  60. package/src/components/segmented/segmented.ts +5 -5
  61. package/src/components/table-view/table-view.svelte +43 -24
  62. package/src/components/tags-input/tags-input.svelte +17 -31
  63. package/src/components/work-parts/looks.ts +1 -1
  64. package/src/editor/content.css +3 -3
  65. package/src/editor/editor.svelte +1 -1
  66. package/src/inbox/thread-inbox.svelte +2 -9
  67. package/src/panel/side-panel.svelte +9 -4
  68. package/src/record/info-list.svelte +7 -8
  69. package/src/record/project-list.svelte +1 -1
  70. package/src/record/record-demo.svelte +2 -4
  71. package/src/record/record-shell.svelte +1 -1
  72. package/src/record/record-stats.svelte +9 -3
  73. package/src/record/steps-rail.svelte +7 -11
  74. package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
  75. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
  76. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
  77. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
  78. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
  79. package/src/shadcn/badge/badge.svelte +3 -1
  80. package/src/shadcn/bubble/bubble-content.svelte +1 -1
  81. package/src/shadcn/bubble/bubble.svelte +1 -1
  82. package/src/shadcn/button/button.svelte +11 -5
  83. package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
  84. package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
  85. package/src/shadcn/calendar/calendar.svelte +1 -1
  86. package/src/shadcn/card/card.svelte +2 -1
  87. package/src/shadcn/checkbox/checkbox.svelte +1 -1
  88. package/src/shadcn/code-block/code-block-actions.svelte +1 -1
  89. package/src/shadcn/command/command-dialog.svelte +2 -2
  90. package/src/shadcn/command/command-group.svelte +1 -1
  91. package/src/shadcn/command/command-input.svelte +1 -1
  92. package/src/shadcn/command/command-item.svelte +1 -1
  93. package/src/shadcn/dialog/dialog-body.svelte +22 -0
  94. package/src/shadcn/dialog/dialog-content.svelte +19 -4
  95. package/src/shadcn/dialog/dialog-footer.svelte +19 -1
  96. package/src/shadcn/dialog/dialog-header.svelte +6 -1
  97. package/src/shadcn/dialog/dialog-title.svelte +2 -1
  98. package/src/shadcn/dialog/index.ts +3 -0
  99. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
  100. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  101. package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
  102. package/src/shadcn/field/field-separator.svelte +1 -1
  103. package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
  104. package/src/shadcn/input/input.svelte +2 -2
  105. package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
  106. package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
  107. package/src/shadcn/popover/popover-content.svelte +1 -1
  108. package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
  109. package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
  110. package/src/shadcn/select/select-content.svelte +6 -2
  111. package/src/shadcn/select/select-item.svelte +1 -1
  112. package/src/shadcn/select/select-trigger.svelte +1 -1
  113. package/src/shadcn/sheet/index.ts +3 -0
  114. package/src/shadcn/sheet/sheet-body.svelte +22 -0
  115. package/src/shadcn/sheet/sheet-content.svelte +27 -5
  116. package/src/shadcn/sheet/sheet-footer.svelte +14 -2
  117. package/src/shadcn/sheet/sheet-header.svelte +1 -1
  118. package/src/shadcn/sheet/sheet-title.svelte +1 -1
  119. package/src/shadcn/sheet/sheet.css +11 -5
  120. package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
  121. package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
  122. package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
  123. package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
  124. package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
  125. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
  126. package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
  127. package/src/shadcn/sidebar/sidebar.svelte +1 -1
  128. package/src/shadcn/skeleton/index.ts +3 -0
  129. package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
  130. package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
  131. package/src/shadcn/skeleton/skeleton.svelte +5 -1
  132. package/src/shadcn/slider/slider.svelte +1 -1
  133. package/src/shadcn/switch/switch.svelte +2 -2
  134. package/src/shadcn/table/table-row.svelte +1 -1
  135. package/src/shadcn/tabs/tabs-list.svelte +1 -1
  136. package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
  137. package/src/shadcn/textarea/textarea.svelte +1 -1
  138. package/src/shadcn/toggle/toggle.svelte +2 -2
  139. package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
  140. package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
  141. package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
  142. package/src/shadcn/trace/trace.svelte +1 -1
  143. package/src/status/README.md +1 -1
  144. package/src/status/index.ts +1 -0
  145. package/src/status/status-badge.svelte +21 -7
  146. package/src/status/status-dot.svelte +6 -11
  147. package/src/status/status-pill.svelte +50 -0
  148. package/src/thread/thread.svelte +54 -136
  149. package/src/tokens/craft.css +88 -30
  150. package/src/tokens/palette.css +16 -9
  151. package/src/work/demo.svelte +48 -42
  152. package/src/work/detail/detail-demo.svelte +33 -24
  153. package/src/work/detail/task-chip.svelte +4 -4
  154. package/src/work/detail/task-detail.svelte +41 -108
  155. package/src/work/detail/task-property.svelte +7 -13
  156. package/src/work/detail/task-thread.svelte +48 -119
  157. package/src/work/docs/doc-editor-page.svelte +5 -4
  158. package/src/work/docs/doc-menu.svelte +9 -4
  159. package/src/work/docs/docs-demo.svelte +4 -4
  160. package/src/work/docs/edited-chip.svelte +11 -4
  161. package/src/work/gantt/gantt-bar.svelte +18 -14
  162. package/src/work/gantt/gantt-grid.svelte +7 -7
  163. package/src/work/gantt/gantt-group-row.svelte +7 -10
  164. package/src/work/gantt/gantt-header.svelte +3 -1
  165. package/src/work/gantt/gantt-task-row.svelte +19 -9
  166. package/src/work/gantt/gantt.css +4 -5
  167. package/src/work/inbox/inbox-card.svelte +32 -10
  168. package/src/work/inbox/inbox-draft.svelte +2 -2
  169. package/src/work/kanban-column.svelte +7 -15
  170. package/src/work/kanban-drag.ts +13 -8
  171. package/src/work/task-gantt.svelte +2 -1
  172. package/src/work/task-list.svelte +54 -53
  173. package/src/work/time/time-chip.svelte +16 -19
  174. package/src/work/time/time-dot.svelte +2 -1
  175. package/src/work/time/time-ring.svelte +2 -3
  176. package/src/work/time/time-tracker.svelte +72 -166
@@ -31,11 +31,13 @@
31
31
 
32
32
  /* Dark surfaces. Light pendants are surface-light, line-light and paper. */
33
33
  --brand-ink: oklch(0.145 0 0); /* Page background */
34
- --brand-surface: oklch(0.19 0 0); /* Cards, panels */
34
+ --brand-surface: oklch(0.2 0 0); /* Cards, panels: 0.055 over the page, the light step */
35
35
  --brand-line: oklch(0.29 0 0); /* Borders, dividers */
36
36
  --brand-raised: oklch(0.24 0 0); /* Hover surfaces, inputs, between surface and line */
37
37
  --brand-paper: oklch(0.97 0 0); /* Text on dark, light backgrounds */
38
- --brand-surface-light: oklch(0.99 0 0); /* Cards on a light page */
38
+ /* Two levels, grey canvas and white card: the 0.055 step dark uses, and muted text keeps 6.3:1. */
39
+ --brand-canvas-light: oklch(0.945 0 0); /* The page, the sidebar under it */
40
+ --brand-surface-light: oklch(1 0 0); /* Cards, popovers, the shell panel on a light page */
39
41
  --brand-line-light: oklch(0.91 0 0); /* Borders on a light page */
40
42
 
41
43
  /* The key button. Each theme has its own pair, since a hue lifted for dark needs dark text. */
@@ -111,12 +113,12 @@
111
113
  --black: oklch(0 0 0);
112
114
 
113
115
  /* === Sidebar Colors === */
114
- /* Sidebar is one shade darker than content — Linear-style depth */
115
- --sidebar: oklch(0.96 0 0);
116
+ /* The sidebar sits on the canvas, not on a third grey, and the active item is a card on it. */
117
+ --sidebar: var(--brand-canvas-light);
116
118
  --sidebar-foreground: oklch(0.45 0 0);
117
119
  --sidebar-primary: oklch(0.15 0 0);
118
120
  --sidebar-primary-foreground: oklch(0.98 0 0);
119
- --sidebar-accent: oklch(0.92 0 0);
121
+ --sidebar-accent: var(--brand-surface-light);
120
122
  --sidebar-accent-foreground: oklch(0.15 0 0);
121
123
  --sidebar-border: oklch(0.91 0 0);
122
124
  --sidebar-ring: var(--brand);
@@ -146,6 +148,7 @@
146
148
  --color-line: var(--brand-line);
147
149
  --color-raised: var(--brand-raised);
148
150
  --color-paper: var(--brand-paper);
151
+ --color-canvas-light: var(--brand-canvas-light);
149
152
  --color-surface-light: var(--brand-surface-light);
150
153
  --color-line-light: var(--brand-line-light);
151
154
 
@@ -216,7 +219,7 @@
216
219
  );
217
220
 
218
221
  /* Light theme color system: the light surfaces and lines of the brand roles */
219
- --color-background: var(--gray-50);
222
+ --color-background: var(--brand-canvas-light);
220
223
  --color-foreground: var(--gray-900);
221
224
  --color-foreground-muted: var(--gray-700);
222
225
 
@@ -342,14 +345,18 @@
342
345
  --color-destructive-foreground: var(--brand-paper);
343
346
 
344
347
  --color-border: var(--brand-line);
345
- --color-input: var(--brand-raised);
348
+ /* WHY not raised: raised equals muted, so an input's edge vanished on the card. Mixed from the
349
+ brand's own paper and surface so a brand that moves them keeps the step. Neutral defaults:
350
+ L 0.547, 3.7:1 on the card (L 0.2), 4.0:1 on the canvas (L 0.145), past the 3:1 for an edge.
351
+ Fills read it at /30 (L 0.30), a step over the card. */
352
+ --color-input: color-mix(in oklab, var(--brand-paper) 45%, var(--brand-surface));
346
353
  --color-ring: var(--brand-on-dark);
347
354
 
348
355
  /* The brand colour lifts for contrast on the dark page. */
349
356
  --color-brand: var(--brand-on-dark);
350
357
 
351
- /* Dark sidebar */
352
- --sidebar: var(--brand-surface);
358
+ /* Dark sidebar: on the page, like light, so the cards are the only raised level */
359
+ --sidebar: var(--brand-ink);
353
360
  --sidebar-foreground: var(--brand-paper);
354
361
  --sidebar-primary: var(--brand-on-dark);
355
362
  --sidebar-primary-foreground: oklch(1 0 0);
@@ -3,7 +3,19 @@
3
3
  import TaskList from "./task-list.svelte";
4
4
  import TaskGantt from "./task-gantt.svelte";
5
5
  import { SegmentedFilter } from "../components/segmented/index.js";
6
- import { ChartBarHorizontal, Kanban, ListBullets } from "phosphor-svelte";
6
+ import {
7
+ CalendarBlank,
8
+ CellSignalFull,
9
+ CellSignalLow,
10
+ CellSignalMedium,
11
+ ChartBarHorizontal,
12
+ Kanban,
13
+ ListBullets,
14
+ } from "phosphor-svelte";
15
+ import StatusPill from "../status/status-pill.svelte";
16
+ import { Card } from "../shadcn/card/index.js";
17
+ import WorkCard from "../components/work-parts/work-card.svelte";
18
+ import type { Tone } from "../lib/tone.js";
7
19
  import StatusBadge from "../status/status-badge.svelte";
8
20
  import StatusDot from "../status/status-dot.svelte";
9
21
  import { defineStatuses } from "../status/define-statuses.js";
@@ -161,11 +173,12 @@
161
173
  { id: "due", label: "Due date", compare: (a: Task, b: Task) => a.end.localeCompare(b.end) },
162
174
  ];
163
175
 
164
- const priorityClass: Record<Priority, string> = {
165
- low: "bg-muted text-muted-foreground",
166
- medium: "bg-warning/15 text-warning-700 dark:text-warning-400",
167
- high: "bg-destructive/15 text-destructive",
176
+ const priorityTone: Record<Priority, Tone> = {
177
+ low: "neutral",
178
+ medium: "warning",
179
+ high: "destructive",
168
180
  };
181
+ const priorityIcon = { low: CellSignalLow, medium: CellSignalMedium, high: CellSignalFull };
169
182
 
170
183
  const people: Record<string, string> = {
171
184
  Ada: "oklch(0.72 0.12 280)",
@@ -275,6 +288,14 @@
275
288
  "grid grid-cols-[minmax(0,1fr)_7rem_5.5rem_4.5rem] items-center gap-3 max-sm:grid-cols-[minmax(0,1fr)_4.5rem]";
276
289
  </script>
277
290
 
291
+ {#snippet priority(level: Priority)}
292
+ {@const Icon = priorityIcon[level]}
293
+ <StatusPill tone={priorityTone[level]} class="capitalize">
294
+ {#snippet icon()}<Icon weight="bold" aria-hidden="true" />{/snippet}
295
+ {level}
296
+ </StatusPill>
297
+ {/snippet}
298
+
278
299
  {#snippet avatar(name: string)}
279
300
  <span
280
301
  class="grid size-5 shrink-0 place-items-center rounded-full text-[10px] font-semibold text-white"
@@ -285,7 +306,7 @@
285
306
  </span>
286
307
  {/snippet}
287
308
 
288
- <div class="flex flex-col gap-6 rounded-xl border border-border bg-background p-4">
309
+ <Card class="gap-6 p-4">
289
310
  <div class="flex h-[600px] flex-col gap-3">
290
311
  <div class="flex flex-wrap items-center gap-2">
291
312
  <SegmentedFilter options={views} bind:value={view} ariaLabel="Task view" block={false} />
@@ -312,7 +333,7 @@
312
333
  <select
313
334
  id="tasks-sort"
314
335
  bind:value={sortKey}
315
- class="h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
336
+ class="h-8 rounded-md border border-border bg-card px-2 text-xs text-foreground"
316
337
  >
317
338
  <option value="manual">Manual</option>
318
339
  {#each sortKeys as key (key.id)}
@@ -338,15 +359,15 @@
338
359
  >
339
360
  {#snippet renderCard(task)}
340
361
  {@const d = due(task)}
341
- <article
342
- class="rounded-xl border border-border bg-card p-2.5 text-card-foreground shadow-sm"
343
- >
344
- <p class="text-sm leading-snug">{task.title}</p>
345
- <div class="mt-2.5 flex items-center gap-2 text-xs">
346
- {@render avatar(task.assignee)}
347
- <span class={d.late ? "font-medium text-destructive" : "text-muted-foreground"}
348
- >{d.label}</span
362
+ <WorkCard>
363
+ {task.title}
364
+ {#snippet meta()}
365
+ <span
366
+ class="flex items-center gap-1 {d.late ? 'font-medium text-destructive' : ''}"
349
367
  >
368
+ <CalendarBlank class="size-3.5" aria-hidden="true" />
369
+ {d.label}
370
+ </span>
350
371
  {#if tracking}
351
372
  <TimeChip
352
373
  minutes={minutesByTask[task.id] ?? 0}
@@ -354,15 +375,12 @@
354
375
  running={timer?.taskId === task.id}
355
376
  />
356
377
  {/if}
357
- <span
358
- class="ml-auto rounded-full px-1.5 py-0.5 font-medium capitalize {priorityClass[
359
- task.priority
360
- ]}"
361
- >
362
- {task.priority}
378
+ <span class="ms-auto flex items-center gap-1.5">
379
+ {@render priority(task.priority)}
380
+ {@render avatar(task.assignee)}
363
381
  </span>
364
- </div>
365
- </article>
382
+ {/snippet}
383
+ </WorkCard>
366
384
  {/snippet}
367
385
  </KanbanBoard>
368
386
  {:else if view === "list"}
@@ -386,7 +404,7 @@
386
404
  {/snippet}
387
405
  {#snippet renderRow(task)}
388
406
  {@const d = due(task)}
389
- <div class="{listGrid} h-10 px-2 text-sm">
407
+ <div class="{listGrid} px-2 text-sm">
390
408
  <span class="flex min-w-0 items-center gap-2.5">
391
409
  {#key task.status}
392
410
  <StatusDot {statuses} status={task.status} />
@@ -410,15 +428,7 @@
410
428
  {@render avatar(task.assignee)}
411
429
  <span class="truncate">{task.assignee}</span>
412
430
  </span>
413
- <span class="max-sm:hidden">
414
- <span
415
- class="rounded-full px-1.5 py-0.5 text-xs font-medium capitalize {priorityClass[
416
- task.priority
417
- ]}"
418
- >
419
- {task.priority}
420
- </span>
421
- </span>
431
+ <span class="max-sm:hidden">{@render priority(task.priority)}</span>
422
432
  <span
423
433
  class="text-right text-xs tabular-nums {d.late
424
434
  ? 'font-medium text-destructive'
@@ -469,13 +479,11 @@
469
479
  {#if tracking}
470
480
  <section class="flex flex-col gap-3 border-t border-border pt-4">
471
481
  <div class="flex items-center justify-between gap-3">
472
- <h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
473
- Task time
474
- </h3>
482
+ <h3 class="text-xs font-semibold text-muted-foreground">Task time</h3>
475
483
  <select
476
484
  bind:value={timeTaskId}
477
485
  aria-label="Task"
478
- class="h-8 max-w-[60%] rounded-md border border-border bg-background px-2 text-xs text-foreground"
486
+ class="h-8 max-w-[60%] rounded-md border border-border bg-card px-2 text-xs text-foreground"
479
487
  >
480
488
  {#each tasks as task (task.id)}
481
489
  <option value={task.id}>{task.title}</option>
@@ -483,7 +491,7 @@
483
491
  </select>
484
492
  </div>
485
493
  <TimeTracker
486
- class="mx-auto w-full max-w-xl"
494
+ class="w-full max-w-xl"
487
495
  entries={timeEntries}
488
496
  getId={(entry) => entry.id}
489
497
  getMinutes={(entry) => entry.minutes}
@@ -505,9 +513,7 @@
505
513
  {/if}
506
514
 
507
515
  <section class="flex flex-col gap-2 border-t border-border pt-4">
508
- <h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
509
- Status badges
510
- </h3>
516
+ <h3 class="text-xs font-semibold text-muted-foreground">Status badges</h3>
511
517
  <div class="flex flex-wrap items-center gap-2">
512
518
  {#each salesStatuses.ids as id (id)}
513
519
  <StatusBadge statuses={salesStatuses} status={id} />
@@ -522,7 +528,7 @@
522
528
  {/each}
523
529
  </ul>
524
530
  </section>
525
- </div>
531
+ </Card>
526
532
 
527
533
  <style>
528
534
  /* Switching views: the new one rises in with a touch of blur, the old one is gone at once. */
@@ -1,5 +1,17 @@
1
1
  <script lang="ts">
2
- import { CalendarBlank, CircleDashed, Flag, Folder, User } from "phosphor-svelte";
2
+ import {
3
+ CalendarBlank,
4
+ CircleDashed,
5
+ CircleHalf,
6
+ Flag,
7
+ Folder,
8
+ Timer,
9
+ User,
10
+ UserCircle,
11
+ } from "phosphor-svelte";
12
+ import { Button } from "../../shadcn/button/index.js";
13
+ import { Card } from "../../shadcn/card/index.js";
14
+ import * as Avatar from "../../shadcn/avatar/index.js";
3
15
  import TaskDetail from "./task-detail.svelte";
4
16
  import TaskProperty from "./task-property.svelte";
5
17
  import TaskChip from "./task-chip.svelte";
@@ -144,29 +156,27 @@
144
156
  <div class="flex flex-col gap-3">
145
157
  <div class="flex flex-wrap items-center gap-1.5" role="group" aria-label="Scenario">
146
158
  {#each scenarios as item (item.id)}
147
- <button
148
- type="button"
159
+ <Button
160
+ size="sm"
161
+ variant={scenario === item.id ? "default" : "outline"}
149
162
  aria-pressed={scenario === item.id}
150
163
  onclick={() => (scenario = item.id)}
151
- class="h-7 rounded-md border border-border px-2.5 text-[13px] font-medium transition-colors hover:bg-muted aria-pressed:bg-foreground aria-pressed:text-background"
152
164
  >
153
165
  {item.label}
154
- </button>
166
+ </Button>
155
167
  {/each}
156
- <button
157
- type="button"
168
+ <Button
169
+ size="sm"
170
+ variant={narrow ? "default" : "outline"}
171
+ class="ms-auto"
158
172
  aria-pressed={narrow}
159
173
  onclick={() => (narrow = !narrow)}
160
- class="ms-auto h-7 rounded-md border border-border px-2.5 text-[13px] font-medium transition-colors hover:bg-muted aria-pressed:bg-foreground aria-pressed:text-background"
161
174
  >
162
175
  390 wide
163
- </button>
176
+ </Button>
164
177
  </div>
165
178
 
166
- <div
167
- class="mx-auto w-full overflow-hidden rounded-xl border border-border bg-background"
168
- style:max-width={narrow ? "390px" : "none"}
169
- >
179
+ <Card class="mx-auto w-full gap-0 py-0" style="max-width: {narrow ? '390px' : 'none'}">
170
180
  <TaskDetail
171
181
  {title}
172
182
  onTitleChange={(next) => {
@@ -192,35 +202,34 @@
192
202
  <span class="tabular-nums">Created Sep 9, 2026</span>
193
203
  {/snippet}
194
204
  {#snippet properties()}
195
- <TaskProperty label="Status">
205
+ <TaskProperty label="Status" icon={CircleHalf}>
196
206
  <TaskChip aria-label="Status: {statusLabel}"
197
207
  ><StatusDot {statuses} {status} size={14} animate={false} />{statusLabel}</TaskChip
198
208
  >
199
209
  </TaskProperty>
200
- <TaskProperty label="Priority">
210
+ <TaskProperty label="Priority" icon={Flag}>
201
211
  <TaskChip><Flag weight="fill" class="text-destructive" aria-hidden="true" />High</TaskChip
202
212
  >
203
213
  </TaskProperty>
204
- <TaskProperty label="Assignee">
214
+ <TaskProperty label="Assignee" icon={UserCircle}>
205
215
  {#if assignee}
206
216
  <TaskChip>
207
- <span
208
- class="grid size-4 place-items-center rounded-full bg-muted text-[0px]"
209
- aria-hidden="true"><User /></span
210
- >
217
+ <Avatar.Root class="size-4" aria-hidden="true">
218
+ <Avatar.Fallback><User class="size-3" /></Avatar.Fallback>
219
+ </Avatar.Root>
211
220
  {assignee}
212
221
  </TaskChip>
213
222
  {:else}
214
223
  <TaskChip empty><CircleDashed aria-hidden="true" />Assign</TaskChip>
215
224
  {/if}
216
225
  </TaskProperty>
217
- <TaskProperty label="Due date">
226
+ <TaskProperty label="Due date" icon={CalendarBlank}>
218
227
  <TaskChip><CalendarBlank aria-hidden="true" />Oct 9, 2026</TaskChip>
219
228
  </TaskProperty>
220
- <TaskProperty label="Project">
229
+ <TaskProperty label="Project" icon={Folder}>
221
230
  <TaskChip><Folder aria-hidden="true" />Agency OS</TaskChip>
222
231
  </TaskProperty>
223
- <TaskProperty label="Time">
232
+ <TaskProperty label="Time" icon={Timer}>
224
233
  <TimeTracker
225
234
  {entries}
226
235
  running={timer}
@@ -242,5 +251,5 @@
242
251
  </TaskProperty>
243
252
  {/snippet}
244
253
  </TaskDetail>
245
- </div>
254
+ </Card>
246
255
  </div>
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import type { HTMLButtonAttributes } from "svelte/elements";
4
4
  import { cn } from "../../lib/utils.js";
5
+ import { buttonVariants } from "../../shadcn/button/index.js";
5
6
 
6
7
  interface Props extends Omit<HTMLButtonAttributes, "children"> {
7
8
  /** No value yet: the label is a prompt and recedes until hovered. */
@@ -12,13 +13,12 @@
12
13
  let { empty = false, class: className, children, ...rest }: Props = $props();
13
14
  </script>
14
15
 
15
- <!-- The one box every property value sits in: 28px, 40 under a coarse pointer. -->
16
+ <!-- The one box every property value sits in: a quiet ghost Button, 28px, 40 under a coarse pointer. -->
16
17
  <button
17
18
  type="button"
18
19
  class={cn(
19
- "inline-flex h-7 min-w-0 items-center gap-1.5 rounded-md ps-1.5 pe-2.5 text-[13px] whitespace-nowrap outline-none select-none any-pointer-coarse:h-10",
20
- "transition-colors hover:bg-muted data-[state=open]:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/50",
21
- "disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-3.5 [&_svg]:shrink-0",
20
+ buttonVariants({ variant: "ghost", size: "sm" }),
21
+ "min-w-0 justify-start ps-1.5 pe-2.5 text-(length:--text-control) font-normal data-[state=open]:bg-muted",
22
22
  empty && "text-muted-foreground hover:text-foreground",
23
23
  className,
24
24
  )}
@@ -2,6 +2,8 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import { Circle, CheckCircle, PencilSimple } from "phosphor-svelte";
4
4
  import { cn } from "../../lib/utils.js";
5
+ import { Button } from "../../shadcn/button/index.js";
6
+ import { Textarea } from "../../shadcn/textarea/index.js";
5
7
  import {
6
8
  DEFAULT_TASK_DETAIL_LABELS,
7
9
  type TaskComment,
@@ -118,13 +120,15 @@
118
120
  <header class="min-w-0 px-4 pt-6 @3xl:col-start-1 @3xl:px-8">
119
121
  <div class="mx-auto w-full max-w-184">
120
122
  <div class="flex items-start gap-2">
121
- <button
122
- type="button"
123
+ <Button
124
+ variant="ghost"
125
+ size="sm"
126
+ icon
123
127
  onclick={onToggleDone}
124
128
  disabled={!onToggleDone}
125
129
  aria-label={done ? labels.markIncomplete : labels.markComplete}
126
130
  class={cn(
127
- "tk-icon mt-px",
131
+ "mt-px disabled:opacity-100",
128
132
  done ? "text-success" : "text-muted-foreground/60 hover:text-foreground",
129
133
  )}
130
134
  >
@@ -133,30 +137,30 @@
133
137
  {:else}
134
138
  <Circle class="size-5" aria-hidden="true" />
135
139
  {/if}
136
- </button>
140
+ </Button>
137
141
 
138
142
  {#if editingTitle}
139
- <textarea
140
- bind:this={titleField}
143
+ <Textarea
144
+ bind:ref={titleField}
141
145
  bind:value={titleDraft}
142
146
  onkeydown={onTitleKeydown}
143
147
  onblur={commitTitle}
144
148
  rows={2}
145
149
  aria-label={labels.editTitle}
146
- class="-mx-2 -my-1 min-w-0 flex-1 resize-none rounded-md border border-input bg-transparent px-2 py-1 text-2xl leading-tight font-semibold tracking-tight outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
147
- ></textarea>
150
+ class="-mx-2 -my-1 min-h-0 min-w-0 flex-1 resize-none px-2 py-1 text-2xl leading-tight font-semibold tracking-tight md:text-2xl"
151
+ />
148
152
  {:else}
149
153
  <h1
150
154
  class="min-w-0 flex-1 text-2xl leading-tight font-semibold tracking-tight text-balance break-words"
151
155
  >
152
- <button
153
- type="button"
156
+ <Button
157
+ variant="text"
154
158
  onclick={startTitle}
155
159
  disabled={!onTitleChange}
156
160
  aria-label={labels.editTitle}
157
161
  class={cn(
158
- "group -mx-2 -my-1 rounded-md px-2 py-1 text-start outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
159
- onTitleChange && "hover:bg-muted/60",
162
+ "group -mx-2 -my-1 px-2 py-1 text-2xl leading-tight font-semibold tracking-tight disabled:opacity-100",
163
+ !onTitleChange && "hover:bg-transparent",
160
164
  closed && "text-muted-foreground",
161
165
  )}
162
166
  >
@@ -167,7 +171,7 @@
167
171
  aria-hidden="true"
168
172
  />
169
173
  {/if}
170
- </button>
174
+ </Button>
171
175
  </h1>
172
176
  {/if}
173
177
  </div>
@@ -203,58 +207,56 @@
203
207
  <div class="flex h-7 items-center justify-between">
204
208
  <h2 class="text-xs font-medium text-muted-foreground">{labels.description}</h2>
205
209
  {#if !editingDescription && description && onDescriptionChange}
206
- <button
207
- type="button"
208
- class="tk-icon"
210
+ <Button
211
+ variant="ghost"
212
+ size="sm"
213
+ icon
214
+ class="text-muted-foreground"
209
215
  onclick={startDescription}
210
216
  aria-label={labels.editDescription}
211
217
  >
212
- <PencilSimple class="size-3.5" aria-hidden="true" />
213
- </button>
218
+ <PencilSimple aria-hidden="true" />
219
+ </Button>
214
220
  {/if}
215
221
  </div>
216
222
 
217
223
  {#if editingDescription}
218
- <textarea
224
+ <Textarea
219
225
  bind:value={descriptionDraft}
220
226
  rows={8}
221
227
  placeholder={labels.addDescription}
222
228
  aria-label={labels.description}
223
- class="w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm leading-relaxed outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
224
- ></textarea>
229
+ class="resize-y px-3 leading-relaxed"
230
+ />
225
231
  <div class="mt-1 flex justify-end gap-1.5">
226
- <button type="button" class="tk-button" onclick={() => (editingDescription = false)}
227
- >{labels.cancel}</button
228
- >
229
- <button
230
- type="button"
231
- class="tk-button tk-primary"
232
- onclick={saveDescription}
233
- disabled={savingDescription}
232
+ <Button variant="ghost" size="sm" onclick={() => (editingDescription = false)}
233
+ >{labels.cancel}</Button
234
234
  >
235
+ <Button size="sm" onclick={saveDescription} disabled={savingDescription}>
235
236
  {savingDescription ? labels.saving : labels.save}
236
- </button>
237
+ </Button>
237
238
  </div>
238
239
  {:else if description}
239
- <button
240
- type="button"
240
+ <Button
241
+ variant="text"
241
242
  onclick={startDescription}
242
243
  disabled={!onDescriptionChange}
243
244
  class={cn(
244
- "-mx-3 block w-[calc(100%+1.5rem)] rounded-md px-3 py-1.5 text-start text-sm leading-relaxed whitespace-pre-wrap text-pretty outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
245
- onDescriptionChange && "hover:bg-muted/50",
245
+ "-mx-3 block w-[calc(100%+1.5rem)] px-3 py-1.5 leading-relaxed font-normal whitespace-pre-wrap text-pretty disabled:opacity-100",
246
+ !onDescriptionChange && "hover:bg-transparent",
246
247
  )}
247
248
  >
248
249
  {description}
249
- </button>
250
+ </Button>
250
251
  {:else if onDescriptionChange}
251
- <button
252
- type="button"
252
+ <Button
253
+ variant="dashed"
254
+ size="sm"
253
255
  onclick={startDescription}
254
- class="flex h-7 w-full items-center rounded-md border border-dashed border-border px-2.5 text-[13px] text-muted-foreground transition-colors outline-none hover:border-muted-foreground/40 hover:bg-muted/40 hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
256
+ class="w-full justify-start text-(length:--text-control) font-normal"
255
257
  >
256
258
  {labels.addDescription}
257
- </button>
259
+ </Button>
258
260
  {/if}
259
261
  </div>
260
262
 
@@ -274,72 +276,3 @@
274
276
  </section>
275
277
  </div>
276
278
  </div>
277
-
278
- <style>
279
- .tk-icon {
280
- display: grid;
281
- place-items: center;
282
- width: 1.75rem;
283
- height: 1.75rem;
284
- flex: none;
285
- border-radius: var(--radius-md, 0.5rem);
286
- outline: none;
287
- transition:
288
- background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out),
289
- color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
290
- }
291
-
292
- @media (hover: hover) and (pointer: fine) {
293
- .tk-icon:hover:not(:disabled) {
294
- background: var(--color-muted);
295
- }
296
- }
297
-
298
- .tk-icon:focus-visible,
299
- .tk-button:focus-visible {
300
- box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-ring) 50%, transparent);
301
- }
302
-
303
- .tk-button {
304
- height: 1.75rem;
305
- padding-inline: 0.75rem;
306
- border-radius: var(--radius-md, 0.5rem);
307
- font-size: 0.8125rem;
308
- font-weight: 500;
309
- outline: none;
310
- transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
311
- }
312
-
313
- @media (hover: hover) and (pointer: fine) {
314
- .tk-button:hover:not(:disabled) {
315
- background: var(--color-muted);
316
- }
317
- }
318
-
319
- .tk-button:disabled {
320
- opacity: 0.5;
321
- pointer-events: none;
322
- }
323
-
324
- .tk-primary {
325
- background: var(--color-primary);
326
- color: var(--color-primary-foreground);
327
- }
328
-
329
- @media (hover: hover) and (pointer: fine) {
330
- .tk-primary:hover:not(:disabled) {
331
- background: color-mix(in oklab, var(--color-primary) 90%, transparent);
332
- }
333
- }
334
-
335
- @media (any-pointer: coarse) {
336
- .tk-icon {
337
- width: 2.5rem;
338
- height: 2.5rem;
339
- }
340
-
341
- .tk-button {
342
- height: 2.5rem;
343
- }
344
- }
345
- </style>
@@ -1,23 +1,17 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from "svelte";
2
+ import type { Component, Snippet } from "svelte";
3
+ import PropertyRow from "../../components/work-parts/property-row.svelte";
3
4
 
4
5
  interface Props {
5
6
  label: string;
7
+ /** A Phosphor icon before the label, shown when the rail is wide. */
8
+ icon?: Component<{ class?: string; "aria-hidden"?: boolean | "true" | "false" }>;
6
9
  children: Snippet;
7
10
  }
8
11
 
9
- let { label, children }: Props = $props();
12
+ let { label, icon, children }: Props = $props();
10
13
  </script>
11
14
 
12
- <!-- A labelled row in the rail. In the narrow chip row the label hides and
15
+ <!-- A labelled row in the rail: icon, label, value. In the narrow chip row the label hides and
13
16
  names the group for assistive tech. -->
14
- <div
15
- class="flex min-h-7 items-center @3xl:grid @3xl:grid-cols-[6rem_minmax(0,1fr)]"
16
- role="group"
17
- aria-label={label}
18
- >
19
- <span class="hidden text-xs font-medium text-muted-foreground @3xl:block" aria-hidden="true"
20
- >{label}</span
21
- >
22
- <div class="flex min-w-0 items-center">{@render children()}</div>
23
- </div>
17
+ <PropertyRow {label} {icon} layout="responsive">{@render children()}</PropertyRow>