@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
@@ -1,4 +1,7 @@
1
1
  <script lang="ts" generics="T">
2
+ import { ganttLooks } from "../../components/work-parts/looks.js";
3
+ import { Button } from "../../shadcn/button/index.js";
4
+ import { cn } from "../../lib/utils.js";
2
5
  import type { Snippet } from "svelte";
3
6
  import { CaretRight, FlagPennant } from "phosphor-svelte";
4
7
  import type { GanttChange, GanttLabelContext } from "../task-types.js";
@@ -79,12 +82,14 @@
79
82
  >
80
83
  {#if nested}
81
84
  {#if row.hasChildren}
82
- <button
83
- type="button"
85
+ <Button
86
+ variant="ghost"
87
+ size="sm"
88
+ icon
84
89
  aria-expanded={row.open}
85
90
  aria-label="{row.open ? 'Fold' : 'Unfold'} {title}"
86
91
  onclick={() => onToggle(row.id)}
87
- class="-ml-1 grid size-5 shrink-0 place-items-center rounded-md text-muted-foreground outline-none transition-colors duration-[var(--duration-quick,150ms)] hover:bg-foreground/5 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/60"
92
+ class="-ml-1.5 size-6 text-muted-foreground"
88
93
  >
89
94
  <span
90
95
  class="g-chevron size-3.5 [&>svg]:size-full"
@@ -93,7 +98,7 @@
93
98
  >
94
99
  <CaretRight weight="bold" />
95
100
  </span>
96
- </button>
101
+ </Button>
97
102
  {:else}
98
103
  <span class="-ml-1 size-5 shrink-0" aria-hidden="true"></span>
99
104
  {/if}
@@ -117,7 +122,10 @@
117
122
  type="button"
118
123
  aria-label="{title}, {describe(span)}"
119
124
  title="{title}, {describe(span)}"
120
- class="g-flag absolute top-1/2 grid size-6 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
125
+ class={cn(
126
+ "g-flag absolute top-1/2 grid size-6 -translate-x-1/2 -translate-y-1/2 place-items-center outline-none focus-visible:shadow-(--ring-focus)",
127
+ ganttLooks.mark,
128
+ )}
121
129
  style:--g-hue={hue}
122
130
  style:left="{dayX(layout, span.end) + layout.dayWidth / 2}px"
123
131
  onclick={() => onSelect?.(row.id)}
@@ -135,7 +143,10 @@
135
143
  tabindex="0"
136
144
  aria-label="{title}, {describe(span)}"
137
145
  title="{title}, {describe(span)}"
138
- class="g-derived g-enter absolute top-1/2 -translate-y-1/2 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
146
+ class={cn(
147
+ "g-derived g-enter absolute top-1/2 -translate-y-1/2 outline-none focus-visible:shadow-(--ring-focus)",
148
+ ganttLooks.mark,
149
+ )}
139
150
  style:--g-hue={hue}
140
151
  style:--g-i={Math.min(row.order, staggerCap)}
141
152
  style:left="{dayX(layout, span.start) + layout.inset}px"
@@ -172,7 +183,7 @@
172
183
 
173
184
  <style>
174
185
  .g-row {
175
- transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
186
+ transition: background-color var(--duration-quick) var(--ease-smooth-out);
176
187
  }
177
188
 
178
189
  .g-row-hot {
@@ -180,8 +191,7 @@
180
191
  }
181
192
 
182
193
  .g-chevron {
183
- transition: rotate var(--duration-fast, 250ms)
184
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
194
+ transition: rotate var(--duration-fast) var(--ease-smooth-out);
185
195
  }
186
196
 
187
197
  .g-chevron-open {
@@ -1,18 +1,17 @@
1
1
  /* Shared by the bar, the derived bar and the group summary: they all slide in the same way. */
2
2
  .g-enter {
3
- animation: g-bar-in var(--duration-slow, 300ms)
4
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
5
- animation-delay: calc(var(--g-i, 0) * var(--duration-stagger, 40ms));
3
+ animation: g-bar-in var(--duration-slow) var(--ease-smooth-out) backwards;
4
+ animation-delay: calc(var(--g-i, 0) * var(--duration-stagger));
6
5
  }
7
6
 
8
7
  @keyframes g-bar-in {
9
8
  from {
10
9
  opacity: 0;
11
- clip-path: inset(0 100% 0 0 round 8px);
10
+ clip-path: inset(0 100% 0 0 round var(--radius-lg));
12
11
  }
13
12
  to {
14
13
  opacity: 1;
15
- clip-path: inset(0 0 0 0 round 8px);
14
+ clip-path: inset(0 0 0 0 round var(--radius-lg));
16
15
  }
17
16
  }
18
17
 
@@ -26,6 +26,9 @@
26
26
  inboxStates,
27
27
  } from "./inbox-statuses.js";
28
28
  import InboxTraceView from "./inbox-trace.svelte";
29
+ import { TagChip } from "../../components/chip/index.js";
30
+ import { workLooks } from "../../components/work-parts/looks.js";
31
+ import { FolderSimple } from "phosphor-svelte";
29
32
  import type { InboxDraftEdits, InboxItem, InboxVerdict } from "./inbox-types.js";
30
33
 
31
34
  interface Props {
@@ -173,8 +176,9 @@
173
176
  );
174
177
  </script>
175
178
 
176
- <!-- WHY a bordered card only while it waits: a decided or automatic item is a
177
- line in a log, so it loses the border and the queue stays scannable. -->
179
+ <!-- WHY a card only while it waits: a decided or automatic item is a line in a log, so it
180
+ drops to the grey ground and the queue stays scannable. The card step draws its own
181
+ hairline, so a border on top of it would show a 2px edge. -->
178
182
  <!-- WHY a focusable article: j and k move focus card to card, and a screen reader
179
183
  reads the card as one labelled unit. -->
180
184
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
@@ -186,12 +190,10 @@
186
190
  onfocusin={onselect}
187
191
  onpointerdown={onselect}
188
192
  class={cn(
189
- "flex flex-col gap-3 rounded-xl p-3 outline-none transition-[background-color,border-color,box-shadow] duration-[var(--duration-quick,120ms)] ease-[var(--ease-smooth-out,ease-out)]",
190
- "focus-visible:ring-ring/50 focus-visible:ring-3",
191
- open
192
- ? "border-border bg-card border shadow-[var(--elev-card,none)]"
193
- : "bg-muted/30 border border-transparent",
194
- selected && open && "border-ring",
193
+ "flex flex-col gap-3 p-3 outline-none transition-[background-color,box-shadow] duration-quick ease-smooth-out motion-reduce:transition-none",
194
+ "focus-visible:shadow-(--ring-focus)",
195
+ open ? workLooks.panel : cn(workLooks.card, "bg-muted/30"),
196
+ selected && open && "ring-ring ring-1",
195
197
  )}
196
198
  >
197
199
  <header class="flex items-start gap-2.5">
@@ -214,7 +216,7 @@
214
216
  {#if facts.command}
215
217
  <div class="flex flex-col items-start gap-1">
216
218
  <!-- The padding sits on a wrapper: on the clamped box itself, the 4th line showed through it. -->
217
- <div class="bg-muted/50 w-full rounded-md px-2.5 py-2">
219
+ <div class={cn(workLooks.inset, "w-full px-2.5 py-2")}>
218
220
  <pre
219
221
  class={cn(
220
222
  "text-foreground font-mono text-xs leading-5 break-words whitespace-pre-wrap",
@@ -263,6 +265,26 @@
263
265
  {SOURCE_LABEL[item.trace.source.type]}
264
266
  </span>
265
267
  {/if}
268
+ <span class={FACT}>
269
+ <!-- The linked record is a chip inside the line, a link when the project has a page. -->
270
+ {#if item.project.href}
271
+ <a
272
+ href={item.project.href}
273
+ class={workLooks.chipLink}
274
+ aria-label="Project: {item.project.name}"
275
+ >
276
+ <TagChip class="hover:bg-muted">
277
+ {#snippet icon()}<FolderSimple aria-hidden="true" />{/snippet}
278
+ {item.project.name}
279
+ </TagChip>
280
+ </a>
281
+ {:else}
282
+ <TagChip>
283
+ {#snippet icon()}<FolderSimple aria-hidden="true" />{/snippet}
284
+ {item.project.name}
285
+ </TagChip>
286
+ {/if}
287
+ </span>
266
288
  {#if time}
267
289
  <span class={cn(FACT, "tabular-nums")}>{time}</span>
268
290
  {/if}
@@ -323,7 +345,7 @@
323
345
  onkeydown={onNoteKeydown}
324
346
  placeholder="Tell the agent what to do instead"
325
347
  aria-label="Note for the agent"
326
- class="bg-background min-h-20 text-sm"
348
+ class="bg-card min-h-20 text-sm"
327
349
  />
328
350
  <div class="flex flex-wrap items-center gap-2">
329
351
  <Button size="sm" onclick={submitSteer} disabled={!note.trim()}>Send to agent</Button>
@@ -68,7 +68,7 @@
68
68
  oninput={(event) => write({ subject: event.currentTarget.value })}
69
69
  aria-label="Subject"
70
70
  {disabled}
71
- class="bg-background"
71
+ class="bg-card"
72
72
  />
73
73
  <Textarea
74
74
  bind:ref={bodyEl}
@@ -76,7 +76,7 @@
76
76
  oninput={(event) => write({ body: event.currentTarget.value })}
77
77
  aria-label="Email body"
78
78
  {disabled}
79
- class="bg-background min-h-28 text-sm"
79
+ class="bg-card min-h-28 text-sm"
80
80
  />
81
81
  <p class="text-muted-foreground text-xs" aria-live="polite">
82
82
  {edited ? "Edited by you. Send uses your text." : "The agent's draft. Edit it before you send."}
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import type { HTMLAttributes } from "svelte/elements";
4
4
  import { cn } from "../lib/utils.js";
5
+ import WorkWell from "../components/work-parts/work-well.svelte";
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
8
  /** Any CSS color. Only shows while a card is held over the column. */
@@ -34,13 +35,6 @@
34
35
  bodyClass,
35
36
  ...restProps
36
37
  }: Props = $props();
37
-
38
- // A column rests neutral. Its hue shows only while a card is held over it.
39
- let style = $derived(
40
- hue && active
41
- ? `background: color-mix(in oklch, ${hue} 8%, var(--color-muted, transparent)); outline: 1px solid color-mix(in oklch, ${hue} 30%, transparent);`
42
- : undefined,
43
- );
44
38
  </script>
45
39
 
46
40
  <div
@@ -53,14 +47,12 @@
53
47
  className,
54
48
  )}
55
49
  >
56
- <div
50
+ <WorkWell
57
51
  role="region"
58
52
  aria-label={ariaLabel}
59
- class={cn(
60
- "flex min-h-0 flex-1 flex-col gap-1.5 overflow-y-auto overflow-x-hidden rounded-xl bg-muted/50 p-1.5 outline-offset-[-1px] transition-[background-color,outline-color] duration-[var(--duration-quick,120ms)] ease-[var(--ease-smooth-out,ease-out)] dark:bg-muted/30",
61
- bodyClass,
62
- )}
63
- {style}
53
+ {hue}
54
+ {active}
55
+ class={cn("flex-1", bodyClass)}
64
56
  {ondragover}
65
57
  {ondragleave}
66
58
  {ondrop}
@@ -69,7 +61,7 @@
69
61
  {@render header()}
70
62
  </div>
71
63
  {@render children()}
72
- </div>
64
+ </WorkWell>
73
65
  </div>
74
66
 
75
67
  <style>
@@ -79,7 +71,7 @@
79
71
  flex: 1 1 0;
80
72
  min-width: var(--kanban-column-min, 280px);
81
73
  max-width: var(--kanban-column-max, 420px);
82
- transition: opacity var(--duration-quick, 120ms) var(--ease-smooth-out, ease-out);
74
+ transition: opacity var(--duration-quick) var(--ease-smooth-out);
83
75
  }
84
76
 
85
77
  /* A board under 40rem wide (a phone, a side panel) shows one column and a peek of the next, as 82vw did on a phone. */
@@ -1,15 +1,17 @@
1
- const FRAME_PAD = 16;
1
+ const FRAME_PAD = 24;
2
2
 
3
3
  /**
4
- * Native drag shows a flat, faded snapshot. This hands the browser a tilted
5
- * copy with a lifted shadow instead. Call it from `ondragstart`.
4
+ * Native drag shows a flat, faded snapshot. This hands the browser a lifted copy instead: the
5
+ * raised elevation step and a 2% grow, so the held card reads as picked up. The browser moves the
6
+ * image with the pointer itself, so there's no transition while it moves. Call it from
7
+ * `ondragstart`. `tilt` rotates the copy in degrees, 0 by default.
6
8
  */
7
- export function liftDragImage(e: DragEvent, tilt = -3): void {
9
+ export function liftDragImage(e: DragEvent, tilt = 0): void {
8
10
  const source = e.currentTarget as HTMLElement | null;
9
11
  if (!source || !e.dataTransfer) return;
10
12
  const rect = source.getBoundingClientRect();
11
13
 
12
- // The padded frame leaves room for the tilt and shadow, or the snapshot crops them.
14
+ // The padded frame leaves room for the shadow and the grow, or the snapshot crops them.
13
15
  const frame = document.createElement("div");
14
16
  Object.assign(frame.style, {
15
17
  position: "fixed",
@@ -20,13 +22,16 @@ export function liftDragImage(e: DragEvent, tilt = -3): void {
20
22
  });
21
23
 
22
24
  const ghost = source.cloneNode(true) as HTMLElement;
25
+ // The copy's first child is the card the consumer drew, so the shadow sits on its corners.
26
+ const card = (ghost.firstElementChild as HTMLElement | null) ?? ghost;
23
27
  Object.assign(ghost.style, {
24
28
  width: `${rect.width}px`,
25
29
  opacity: "1",
26
- rotate: `${tilt}deg`,
27
- filter:
28
- "drop-shadow(0 2px 4px rgb(35 35 35 / 0.08)) drop-shadow(0 10px 18px rgb(35 35 35 / 0.14))",
30
+ rotate: tilt ? `${tilt}deg` : "",
31
+ // 2 minus the tooltip's shrink step is a 1.02 grow, kept on the motion scale.
32
+ scale: "calc(2 - var(--scale-small))",
29
33
  });
34
+ card.style.boxShadow = "var(--elev-raised)";
30
35
  frame.appendChild(ghost);
31
36
  document.body.appendChild(frame);
32
37
  e.dataTransfer.setDragImage(
@@ -1,4 +1,5 @@
1
1
  <script lang="ts" generics="T">
2
+ import { workLooks } from "../components/work-parts/looks.js";
2
3
  import type { Snippet } from "svelte";
3
4
  import { cn } from "../lib/utils.js";
4
5
  import { dayNumber, isoFromDay, todayIso } from "./dates.js";
@@ -193,7 +194,7 @@
193
194
  bind:this={scroller}
194
195
  class={cn(
195
196
  "task-gantt relative min-h-0 flex-1 overflow-auto overscroll-x-contain",
196
- flat ? "bg-background" : "rounded-xl border border-border bg-card text-card-foreground",
197
+ flat ? "bg-card" : workLooks.panel,
197
198
  drag && "select-none",
198
199
  className,
199
200
  )}
@@ -2,11 +2,15 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import { flip } from "svelte/animate";
4
4
  import { cubicOut } from "svelte/easing";
5
+ import { durationMs } from "../lib/motion.js";
5
6
  import { liftDragImage } from "./kanban-drag.js";
6
7
  import { boardOrderAt } from "./board-order.js";
7
8
  import { cn } from "../lib/utils.js";
8
9
  import { createKanbanDnd } from "./use-kanban-dnd.svelte.js";
9
10
  import { CaretRight } from "phosphor-svelte";
11
+ import { Button } from "../shadcn/button/index.js";
12
+ import GroupHead from "../components/work-parts/group-head.svelte";
13
+ import { workLooks } from "../components/work-parts/looks.js";
10
14
  import type {
11
15
  KanbanColumn as KanbanColumnDef,
12
16
  KanbanMove,
@@ -55,6 +59,10 @@
55
59
  /** Group ids that start folded. */
56
60
  collapsed?: string[];
57
61
  emptyLabel?: string;
62
+ /** Shows a plus in each default group head that adds a row to that group. */
63
+ onAdd?: (groupId: string) => void;
64
+ /** The plus button's accessible name, followed by the group label. */
65
+ addLabel?: string;
58
66
  isDroppable?: (groupId: string) => boolean;
59
67
  /** Drops the card chrome (border, radius, fill) so the list sits flat on the page. Group dividers stay. */
60
68
  flat?: boolean;
@@ -83,6 +91,8 @@
83
91
  collapsible = true,
84
92
  collapsed = [],
85
93
  emptyLabel = "Nothing here yet",
94
+ onAdd,
95
+ addLabel = "Add a task",
86
96
  isDroppable,
87
97
  flat = false,
88
98
  class: className,
@@ -222,20 +232,22 @@
222
232
  >
223
233
  <div
224
234
  class={cn(
225
- "sticky z-10 flex h-10 items-center gap-2 px-3 backdrop-blur-sm",
226
- flat ? "bg-background/90" : "bg-card/90",
235
+ "hover-row sticky z-10 flex h-10 items-center gap-2 px-3 backdrop-blur-sm",
236
+ flat ? "bg-card/90" : "bg-card/90",
227
237
  depth === 0 && (head ? "top-9" : "top-0"),
228
238
  )}
229
239
  style:top={depth > 0 ? `${(head ? 2.25 : 0) + depth * 2.5}rem` : undefined}
230
240
  style:padding-inline-start={depth > 0 ? `${0.75 + depth * 1.25}rem` : undefined}
231
241
  >
232
242
  {#if collapsible}
233
- <button
234
- type="button"
243
+ <Button
244
+ variant="ghost"
245
+ size="sm"
246
+ icon
235
247
  aria-expanded={open}
236
248
  aria-label="{open ? 'Fold' : 'Unfold'} {group.label}"
237
249
  onclick={() => toggle(group.id)}
238
- class="-ml-1 grid size-6 place-items-center rounded-md text-muted-foreground outline-none transition-colors duration-[var(--duration-quick,150ms)] hover:bg-foreground/5 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/60"
250
+ class="-ml-1.5 text-muted-foreground"
239
251
  >
240
252
  <span
241
253
  class="tl-chevron size-3.5 [&>svg]:size-full"
@@ -244,23 +256,21 @@
244
256
  >
245
257
  <CaretRight weight="bold" />
246
258
  </span>
247
- </button>
259
+ </Button>
248
260
  {/if}
249
261
  {#if groupHeader}
250
262
  {@render groupHeader(group, context)}
251
263
  {:else}
252
- <span class="size-2 shrink-0 rounded-full" style:background={hue}></span>
253
- <span class="truncate text-sm font-medium text-foreground">{group.label}</span>
254
- <span
255
- class="grid h-5 min-w-5 place-items-center rounded-sm bg-foreground/5 px-1 text-xs font-medium text-muted-foreground tabular-nums"
256
- >
257
- {count.toLocaleString()}
258
- </span>
264
+ <GroupHead
265
+ label={group.label}
266
+ {hue}
267
+ {count}
268
+ onAdd={onAdd ? () => onAdd(group.id) : undefined}
269
+ {addLabel}
270
+ />
259
271
  {/if}
260
272
  {#if actions}
261
- <div
262
- class="tl-actions ms-auto flex shrink-0 items-center gap-1 opacity-0 transition-opacity duration-[var(--duration-quick,150ms)] focus-within:opacity-100 any-pointer-coarse:opacity-100"
263
- >
273
+ <div class="tl-actions row-action ms-auto flex shrink-0 items-center gap-1">
264
274
  {@render actions(group, context)}
265
275
  </div>
266
276
  {/if}
@@ -286,10 +296,10 @@
286
296
  : undefined)}
287
297
  aria-keyshortcuts={draggable ? "Alt+ArrowUp Alt+ArrowDown" : undefined}
288
298
  draggable={draggable ? "true" : undefined}
289
- animate:flip={{ duration: 250, easing: cubicOut }}
299
+ animate:flip={{ duration: () => durationMs("fast"), easing: cubicOut }}
290
300
  ondragstart={draggable
291
301
  ? (e) => {
292
- liftDragImage(e, -1);
302
+ liftDragImage(e);
293
303
  dnd.onDragStart(e, id);
294
304
  }
295
305
  : undefined}
@@ -300,7 +310,9 @@
300
310
  onkeydown={draggable ? (e) => onRowKeyDown(e, id) : undefined}
301
311
  style:--tl-i={Math.min(index, STAGGER_CAP)}
302
312
  class={cn(
303
- "tl-row relative mx-1.5 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring/60",
313
+ "tl-row relative mx-1.5 outline-none focus-visible:shadow-(--ring-focus)",
314
+ workLooks.row,
315
+ workLooks.rowHeight,
304
316
  draggable && "cursor-grab active:cursor-grabbing",
305
317
  dropBefore && "tl-drop-before",
306
318
  dnd.isDragSource(id) && "opacity-40",
@@ -319,9 +331,7 @@
319
331
  {/if}
320
332
 
321
333
  {#if rows.length === 0 && children.length === 0 && !active}
322
- <p
323
- class="mx-1.5 rounded-lg border border-dashed border-border px-3 py-3 text-xs text-muted-foreground"
324
- >
334
+ <p class={cn(workLooks.rowHole, "mx-1.5 px-3 py-3 text-xs text-muted-foreground")}>
325
335
  {emptyLabel}
326
336
  </p>
327
337
  {/if}
@@ -338,7 +348,7 @@
338
348
  <div
339
349
  class={cn(
340
350
  "task-list flex min-h-0 flex-1 flex-col overflow-y-auto",
341
- flat ? "bg-background" : "rounded-xl border border-border bg-card text-card-foreground",
351
+ flat ? "bg-card" : workLooks.panel,
342
352
  className,
343
353
  )}
344
354
  >
@@ -346,7 +356,7 @@
346
356
  <div
347
357
  class={cn(
348
358
  "sticky top-0 z-20 flex h-9 shrink-0 items-center border-b border-border text-xs font-medium text-muted-foreground backdrop-blur-sm",
349
- flat ? "bg-background/90" : "bg-card/90",
359
+ flat ? "bg-card/90" : "bg-card/90",
350
360
  )}
351
361
  >
352
362
  {@render head()}
@@ -360,17 +370,7 @@
360
370
 
361
371
  <style>
362
372
  .tl-group {
363
- transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
364
- }
365
-
366
- .tl-group :global(.tl-actions:focus-within) {
367
- opacity: 1;
368
- }
369
-
370
- @media (hover: hover) and (pointer: fine) {
371
- .tl-group:hover :global(.tl-actions) {
372
- opacity: 1;
373
- }
373
+ transition: background-color var(--duration-quick) var(--ease-smooth-out);
374
374
  }
375
375
 
376
376
  .tl-group-active {
@@ -378,8 +378,7 @@
378
378
  }
379
379
 
380
380
  .tl-chevron {
381
- transition: rotate var(--duration-fast, 250ms)
382
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
381
+ transition: rotate var(--duration-fast) var(--ease-smooth-out);
383
382
  }
384
383
 
385
384
  .tl-chevron-open {
@@ -392,9 +391,8 @@
392
391
  grid-template-rows: 0fr;
393
392
  opacity: 0;
394
393
  transition:
395
- grid-template-rows var(--duration-fast, 250ms)
396
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
397
- opacity var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
394
+ grid-template-rows var(--duration-fast) var(--ease-smooth-out),
395
+ opacity var(--duration-quick) var(--ease-smooth-out);
398
396
  }
399
397
 
400
398
  .tl-fold-open {
@@ -408,15 +406,14 @@
408
406
  }
409
407
 
410
408
  .tl-row {
411
- animation: tl-row-in var(--duration-slow, 300ms)
412
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
413
- animation-delay: calc(var(--tl-i, 0) * var(--duration-stagger, 40ms));
414
- transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
409
+ animation: tl-row-in var(--duration-slow) var(--ease-smooth-out) backwards;
410
+ animation-delay: calc(var(--tl-i, 0) * var(--duration-stagger));
411
+ transition: background-color var(--duration-quick) var(--ease-smooth-out);
415
412
  }
416
413
 
417
414
  @media (hover: hover) and (pointer: fine) {
418
415
  .tl-row:hover {
419
- background: color-mix(in oklab, var(--color-foreground, currentColor) 4%, transparent);
416
+ background: color-mix(in oklab, var(--color-foreground) 4%, transparent);
420
417
  }
421
418
  }
422
419
 
@@ -428,9 +425,8 @@
428
425
  left: 0.5rem;
429
426
  right: 0.5rem;
430
427
  height: 2px;
431
- border-radius: 1px;
432
428
  background: var(--tl-hue);
433
- animation: tl-line-in var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out) both;
429
+ animation: tl-line-in var(--duration-quick) var(--ease-smooth-out) both;
434
430
  }
435
431
 
436
432
  /* A ring where the line starts, so the drop point reads at a glance. */
@@ -441,10 +437,15 @@
441
437
  left: 0.25rem;
442
438
  width: 8px;
443
439
  height: 8px;
444
- border-radius: 999px;
445
- border: 2px solid var(--tl-hue);
446
- background: var(--color-card, white);
447
- animation: tl-line-in var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out) both;
440
+ /* A ring drawn as a gradient, so the line needs no radius or border of its own. */
441
+ background: radial-gradient(
442
+ circle closest-side,
443
+ var(--color-card) calc(50% - 0.5px),
444
+ var(--tl-hue) 50%,
445
+ var(--tl-hue) calc(100% - 0.5px),
446
+ transparent 100%
447
+ );
448
+ animation: tl-line-in var(--duration-quick) var(--ease-smooth-out) both;
448
449
  }
449
450
 
450
451
  .tl-drop-before::before {
@@ -466,8 +467,8 @@
466
467
  @keyframes tl-row-in {
467
468
  from {
468
469
  opacity: 0;
469
- transform: translateY(var(--distance-micro, 4px));
470
- filter: blur(var(--blur-small, 2px));
470
+ transform: translateY(var(--distance-micro));
471
+ filter: blur(var(--blur-small));
471
472
  }
472
473
  }
473
474
 
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { cn } from "../../lib/utils.js";
3
- import { TONE_CLASS, TONE_TEXT } from "../../lib/tone.js";
3
+ import { TONE_TEXT } from "../../lib/tone.js";
4
+ import StatusPill from "../../status/status-pill.svelte";
4
5
  import { budgetTone, formatMinutes } from "./duration.js";
5
6
  import TimeDot from "./time-dot.svelte";
6
7
  import TimeRing from "./time-ring.svelte";
@@ -34,27 +35,23 @@
34
35
  );
35
36
  </script>
36
37
 
38
+ <!-- Logged time is a status of the task's budget, so it's a StatusPill: the icon sits in the tint. -->
37
39
  {#if visible}
38
- <span
40
+ <StatusPill
39
41
  role="img"
40
42
  aria-label={spoken}
41
- class={cn(
42
- "inline-flex h-5 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md border px-1.5 text-xs font-medium tabular-nums",
43
- running
44
- ? TONE_CLASS.accent
45
- : tone === "neutral"
46
- ? "border-transparent text-muted-foreground"
47
- : TONE_CLASS[tone],
48
- className,
49
- )}
43
+ tone={running ? "accent" : tone}
44
+ class={cn("tabular-nums", className)}
50
45
  >
51
- {#if running}
52
- <TimeDot running size={6} />
53
- {:else if estimate}
54
- <TimeRing value={minutes} max={estimate} size={12} stroke={6} />
55
- {:else}
56
- <span class="size-3 shrink-0 [&>svg]:size-full" aria-hidden="true"><Clock /></span>
57
- {/if}
46
+ {#snippet icon()}
47
+ {#if running}
48
+ <TimeDot running size={6} />
49
+ {:else if estimate}
50
+ <TimeRing value={minutes} max={estimate} size={12} stroke={6} />
51
+ {:else}
52
+ <Clock aria-hidden="true" />
53
+ {/if}
54
+ {/snippet}
58
55
  <span aria-hidden="true">
59
56
  {formatMinutes(minutes)}{#if estimate}<span
60
57
  class={cn(
@@ -65,5 +62,5 @@
65
62
  / {formatMinutes(estimate)}</span
66
63
  >{/if}
67
64
  </span>
68
- </span>
65
+ </StatusPill>
69
66
  {/if}
@@ -24,7 +24,8 @@
24
24
  flex: none;
25
25
  width: var(--time-dot-size);
26
26
  height: var(--time-dot-size);
27
- border-radius: 9999px;
27
+ /* A dot, the one round shape DESIGN rule 15 allows for a status. */
28
+ border-radius: 50%;
28
29
  background: currentColor;
29
30
  }
30
31
 
@@ -65,9 +65,8 @@
65
65
  <style>
66
66
  .time-ring-arc {
67
67
  transition:
68
- stroke-dasharray var(--duration-slow, 300ms)
69
- var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
70
- color var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
68
+ stroke-dasharray var(--duration-slow) var(--ease-smooth-out),
69
+ color var(--duration-fast) var(--ease-smooth-out);
71
70
  }
72
71
 
73
72
  .time-ring-arc.is-empty {