@heyramzi/ui 3.1.0 → 3.3.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 (172) hide show
  1. package/dist/components/list-page/toolbar-density.js +20 -1
  2. package/dist/lib/sliding-pill.js +2 -2
  3. package/dist/record/measure-clamp.js +23 -0
  4. package/dist/shadcn/button-group/index.js +6 -0
  5. package/dist/shadcn/checkbox/checkbox-box.js +3 -0
  6. package/dist/shadcn/drawer/index.js +14 -0
  7. package/dist/shadcn/input-group/index.js +9 -0
  8. package/dist/shadcn/item/index.js +13 -0
  9. package/dist/shadcn/native-select/index.js +6 -0
  10. package/dist/shadcn/pagination/index.js +12 -0
  11. package/package.json +7 -2
  12. package/scripts/sync-shadcn.mjs +0 -6
  13. package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
  14. package/src/canvas/pen/PenOverlay.svelte +1 -30
  15. package/src/canvas/screen/PartView.svelte +13 -8
  16. package/src/canvas/screen/ScreenBody.svelte +18 -11
  17. package/src/canvas/ui/BarTooltip.svelte +46 -0
  18. package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
  19. package/src/canvas/ui/CanvasToolbar.svelte +170 -156
  20. package/src/canvas/ui/StylePanel.svelte +179 -102
  21. package/src/canvas/ui/canvas-ui.css +12 -76
  22. package/src/charts/dashboard/data-table.svelte +80 -80
  23. package/src/charts/dashboard/hint.svelte +34 -0
  24. package/src/charts/dashboard/hint.svelte.d.ts +10 -0
  25. package/src/charts/dashboard/rating-chip.svelte +4 -3
  26. package/src/charts/legend.css +0 -8
  27. package/src/charts/legend.svelte +26 -30
  28. package/src/components/copy/copy-button.svelte +10 -2
  29. package/src/components/copy/copy-button.test.ts +7 -0
  30. package/src/components/data-table/table-skeleton.svelte +15 -7
  31. package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
  32. package/src/components/docs/on-this-page.svelte +2 -2
  33. package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
  34. package/src/components/list-page/filter-item.svelte +20 -7
  35. package/src/components/list-page/filter-popover.svelte +57 -17
  36. package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
  37. package/src/components/list-page/grouped-list.svelte +41 -23
  38. package/src/components/list-page/list-page-shell.svelte +155 -69
  39. package/src/components/list-page/list-page-shell.svelte.d.ts +6 -1
  40. package/src/components/list-page/toolbar-density.ts +24 -1
  41. package/src/components/picker/picker.svelte +8 -3
  42. package/src/components/picker/picker.svelte.d.ts +2 -1
  43. package/src/components/segmented/segmented-filter.svelte +19 -10
  44. package/src/components/segmented/segmented-track.svelte +11 -3
  45. package/src/components/table-view/table-view.svelte +22 -6
  46. package/src/components/tags-input/tags-input.svelte +94 -77
  47. package/src/components/toolbar/toolbar.svelte +2 -1
  48. package/src/components/toolbar/toolbar.test.ts +5 -4
  49. package/src/components/work-parts/group-head.svelte +28 -9
  50. package/src/components/work-parts/group-head.svelte.d.ts +5 -2
  51. package/src/editor/editor-toolbar.svelte +223 -193
  52. package/src/editor/editor.svelte +56 -38
  53. package/src/editor/image-toolbar.svelte +99 -37
  54. package/src/editor/image-toolbar.svelte.d.ts +4 -5
  55. package/src/editor/url-dialog.svelte +96 -0
  56. package/src/editor/url-dialog.svelte.d.ts +15 -0
  57. package/src/effects/theme-toggle/ThemeToggle.css +8 -8
  58. package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
  59. package/src/inbox/thread-inbox.svelte +9 -1
  60. package/src/lib/live-region.svelte +12 -4
  61. package/src/lib/live-region.svelte.d.ts +5 -1
  62. package/src/lib/sliding-pill.ts +3 -2
  63. package/src/record/info-list.svelte +33 -59
  64. package/src/record/measure-clamp.ts +31 -0
  65. package/src/record/record-shell.svelte +38 -39
  66. package/src/record/record-timeline.svelte +10 -5
  67. package/src/record/steps-rail.svelte +50 -36
  68. package/src/record/where-we-are.svelte +5 -19
  69. package/src/shadcn/button-group/button-group-separator.svelte +23 -0
  70. package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
  71. package/src/shadcn/button-group/button-group-text.svelte +28 -0
  72. package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
  73. package/src/shadcn/button-group/button-group.svelte +46 -0
  74. package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
  75. package/src/shadcn/button-group/index.ts +15 -0
  76. package/src/shadcn/checkbox/checkbox-box.ts +4 -0
  77. package/src/shadcn/checkbox/checkbox.svelte +2 -4
  78. package/src/shadcn/drawer/drawer-close.svelte +7 -0
  79. package/src/shadcn/drawer/drawer-close.svelte.d.ts +4 -0
  80. package/src/shadcn/drawer/drawer-content.svelte +36 -0
  81. package/src/shadcn/drawer/drawer-content.svelte.d.ts +10 -0
  82. package/src/shadcn/drawer/drawer-description.svelte +17 -0
  83. package/src/shadcn/drawer/drawer-description.svelte.d.ts +4 -0
  84. package/src/shadcn/drawer/drawer-footer.svelte +20 -0
  85. package/src/shadcn/drawer/drawer-footer.svelte.d.ts +5 -0
  86. package/src/shadcn/drawer/drawer-header.svelte +23 -0
  87. package/src/shadcn/drawer/drawer-header.svelte.d.ts +5 -0
  88. package/src/shadcn/drawer/drawer-nested.svelte +12 -0
  89. package/src/shadcn/drawer/drawer-nested.svelte.d.ts +3 -0
  90. package/src/shadcn/drawer/drawer-overlay.svelte +20 -0
  91. package/src/shadcn/drawer/drawer-overlay.svelte.d.ts +4 -0
  92. package/src/shadcn/drawer/drawer-portal.svelte +7 -0
  93. package/src/shadcn/drawer/drawer-portal.svelte.d.ts +3 -0
  94. package/src/shadcn/drawer/drawer-title.svelte +17 -0
  95. package/src/shadcn/drawer/drawer-title.svelte.d.ts +4 -0
  96. package/src/shadcn/drawer/drawer-trigger.svelte +7 -0
  97. package/src/shadcn/drawer/drawer-trigger.svelte.d.ts +4 -0
  98. package/src/shadcn/drawer/drawer.svelte +12 -0
  99. package/src/shadcn/drawer/drawer.svelte.d.ts +3 -0
  100. package/src/shadcn/drawer/index.ts +38 -0
  101. package/src/shadcn/input-group/index.ts +22 -0
  102. package/src/shadcn/input-group/input-group-addon.svelte +51 -0
  103. package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
  104. package/src/shadcn/input-group/input-group-button.svelte +49 -0
  105. package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
  106. package/src/shadcn/input-group/input-group-input.svelte +20 -0
  107. package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
  108. package/src/shadcn/input-group/input-group-text.svelte +19 -0
  109. package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
  110. package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
  111. package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
  112. package/src/shadcn/input-group/input-group.svelte +24 -0
  113. package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
  114. package/src/shadcn/item/index.ts +34 -0
  115. package/src/shadcn/item/item-actions.svelte +20 -0
  116. package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
  117. package/src/shadcn/item/item-content.svelte +23 -0
  118. package/src/shadcn/item/item-content.svelte.d.ts +5 -0
  119. package/src/shadcn/item/item-description.svelte +23 -0
  120. package/src/shadcn/item/item-description.svelte.d.ts +5 -0
  121. package/src/shadcn/item/item-footer.svelte +20 -0
  122. package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
  123. package/src/shadcn/item/item-group.svelte +21 -0
  124. package/src/shadcn/item/item-group.svelte.d.ts +5 -0
  125. package/src/shadcn/item/item-header.svelte +20 -0
  126. package/src/shadcn/item/item-header.svelte.d.ts +5 -0
  127. package/src/shadcn/item/item-media.svelte +42 -0
  128. package/src/shadcn/item/item-media.svelte.d.ts +17 -0
  129. package/src/shadcn/item/item-separator.svelte +19 -0
  130. package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
  131. package/src/shadcn/item/item-title.svelte +20 -0
  132. package/src/shadcn/item/item-title.svelte.d.ts +5 -0
  133. package/src/shadcn/item/item.svelte +61 -0
  134. package/src/shadcn/item/item.svelte.d.ts +28 -0
  135. package/src/shadcn/native-select/index.ts +13 -0
  136. package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
  137. package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
  138. package/src/shadcn/native-select/native-select-option.svelte +20 -0
  139. package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
  140. package/src/shadcn/native-select/native-select.svelte +45 -0
  141. package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
  142. package/src/shadcn/pagination/index.ts +31 -0
  143. package/src/shadcn/pagination/pagination-content.svelte +20 -0
  144. package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
  145. package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
  146. package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
  147. package/src/shadcn/pagination/pagination-item.svelte +14 -0
  148. package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
  149. package/src/shadcn/pagination/pagination-link.svelte +41 -0
  150. package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
  151. package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
  152. package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
  153. package/src/shadcn/pagination/pagination-next.svelte +26 -0
  154. package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
  155. package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
  156. package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
  157. package/src/shadcn/pagination/pagination-previous.svelte +30 -0
  158. package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
  159. package/src/shadcn/pagination/pagination.svelte +27 -0
  160. package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
  161. package/src/slides/Controls.svelte +39 -36
  162. package/src/slides/Deck.svelte +11 -8
  163. package/src/thread/thread.svelte +15 -8
  164. package/src/work/detail/task-detail.svelte +1 -1
  165. package/src/work/gantt/gantt-task-row.svelte +3 -6
  166. package/src/work/inbox/inbox-card.svelte +7 -5
  167. package/src/work/inbox/inbox-trace.svelte +14 -10
  168. package/src/work/kanban-board.svelte +40 -25
  169. package/src/work/kanban-hidden-rail.svelte +67 -65
  170. package/src/work/task-list.svelte +145 -124
  171. package/src/work/task-list.svelte.test.ts +5 -2
  172. package/src/work/time/time-tracker.svelte +35 -10
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { Pagination as PaginationPrimitive } from "bits-ui";
3
+ import { cn } from "../../lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ count = 0,
9
+ perPage = 10,
10
+ page = $bindable(1),
11
+ siblingCount = 1,
12
+ ...restProps
13
+ }: PaginationPrimitive.RootProps = $props();
14
+ </script>
15
+
16
+ <PaginationPrimitive.Root
17
+ bind:ref
18
+ bind:page
19
+ role="navigation"
20
+ aria-label="pagination"
21
+ data-slot="pagination"
22
+ {count}
23
+ {perPage}
24
+ {siblingCount}
25
+ class={cn("mx-auto flex w-full justify-center", className)}
26
+ {...restProps}
27
+ />
@@ -0,0 +1,4 @@
1
+ import { Pagination as PaginationPrimitive } from "bits-ui";
2
+ declare const $$Component: import("svelte").Component<PaginationPrimitive.RootProps, {}, "ref" | "page">;
3
+ type $$Component = ReturnType<typeof $$Component>;
4
+ export default $$Component;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { Toolbar } from "bits-ui";
2
3
  import type { DeckController } from "./deck-controller.svelte.js";
3
4
 
4
5
  interface Props {
@@ -26,44 +27,46 @@
26
27
  disabled = false,
27
28
  pressed?: boolean,
28
29
  )}
29
- <button
30
- type="button"
31
- class="deck__button"
32
- {onclick}
33
- {disabled}
34
- aria-label={label}
35
- aria-pressed={pressed}
36
- >
37
- <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
38
- <path
39
- d={icons[icon]}
40
- fill="none"
41
- stroke="currentColor"
42
- stroke-width="1.75"
43
- stroke-linecap="round"
44
- stroke-linejoin="round"
45
- />
46
- </svg>
47
- </button>
30
+ <Toolbar.Button {onclick} {disabled} aria-label={label} aria-pressed={pressed}>
31
+ {#snippet child({ props })}
32
+ <button {...props} class="deck__button">
33
+ <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
34
+ <path
35
+ d={icons[icon]}
36
+ fill="none"
37
+ stroke="currentColor"
38
+ stroke-width="1.75"
39
+ stroke-linecap="round"
40
+ stroke-linejoin="round"
41
+ />
42
+ </svg>
43
+ </button>
44
+ {/snippet}
45
+ </Toolbar.Button>
48
46
  {/snippet}
49
47
 
50
- <div class="deck__controls deck__chrome">
51
- {@render button("prev", deck.labels.previous, () => deck.prev(), deck.atStart)}
52
- <span class="deck__counter" aria-hidden="true">{deck.ordinal.n} / {deck.ordinal.of}</span>
53
- {@render button("next", deck.labels.next, () => deck.next(), deck.atEnd)}
54
- <span class="deck__divider" aria-hidden="true"></span>
55
- {@render button(
56
- "overview",
57
- deck.labels.overview,
58
- () => deck.toggleOverview(),
59
- false,
60
- deck.overview,
61
- )}
62
- {#if onPresenter}
63
- {@render button("presenter", deck.labels.presenter, onPresenter)}
64
- {/if}
65
- {@render button("fullscreen", deck.labels.fullscreen, onFullscreen)}
66
- </div>
48
+ <!-- The arrows are the toolbar's while a control has focus, so they move along it, not the deck. -->
49
+ <Toolbar.Root>
50
+ {#snippet child({ props })}
51
+ <div {...props} class="deck__controls deck__chrome">
52
+ {@render button("prev", deck.labels.previous, () => deck.prev(), deck.atStart)}
53
+ <span class="deck__counter" aria-hidden="true">{deck.ordinal.n} / {deck.ordinal.of}</span>
54
+ {@render button("next", deck.labels.next, () => deck.next(), deck.atEnd)}
55
+ <span class="deck__divider" aria-hidden="true"></span>
56
+ {@render button(
57
+ "overview",
58
+ deck.labels.overview,
59
+ () => deck.toggleOverview(),
60
+ false,
61
+ deck.overview,
62
+ )}
63
+ {#if onPresenter}
64
+ {@render button("presenter", deck.labels.presenter, onPresenter)}
65
+ {/if}
66
+ {@render button("fullscreen", deck.labels.fullscreen, onFullscreen)}
67
+ </div>
68
+ {/snippet}
69
+ </Toolbar.Root>
67
70
 
68
71
  <style>
69
72
  @layer theme, base, components, utilities;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import "./theme.css";
3
+ import { Progress } from "bits-ui";
3
4
  import { flushSync, onMount, setContext, untrack } from "svelte";
4
5
  import { attachBridge, attachChannel, attachUrlSync } from "./bridges.js";
5
6
  import Controls from "./Controls.svelte";
@@ -205,17 +206,19 @@
205
206
  </div>
206
207
 
207
208
  {#if progress}
208
- <div
209
- class="deck__progress deck__chrome"
210
- role="progressbar"
209
+ <Progress.Root
210
+ min={1}
211
+ max={deck.ordinal.of}
212
+ value={deck.ordinal.n}
211
213
  aria-label={deck.labels.progress}
212
- aria-valuemin="1"
213
- aria-valuemax={deck.ordinal.of}
214
- aria-valuenow={deck.ordinal.n}
215
214
  aria-valuetext={deck.labels.slide(deck.ordinal.n, deck.ordinal.of)}
216
215
  >
217
- <div class="deck__progress-fill" style:transform="scaleX({deck.progress})"></div>
218
- </div>
216
+ {#snippet child({ props })}
217
+ <div {...props} class="deck__progress deck__chrome">
218
+ <div class="deck__progress-fill" style:transform="scaleX({deck.progress})"></div>
219
+ </div>
220
+ {/snippet}
221
+ </Progress.Root>
219
222
  {/if}
220
223
 
221
224
  {#if controls}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import { Toggle } from "bits-ui";
3
4
  import { ArrowBendDownRight, Check, MapPin, PencilSimple, Trash } from "phosphor-svelte";
4
5
  import { cn } from "../lib/utils.js";
5
6
  import { focusAfterRender } from "../lib/focus.js";
@@ -338,14 +339,20 @@
338
339
  {labels.title}
339
340
  </h2>
340
341
  {#if resolved > 0}
341
- <Button
342
- variant="ghost"
343
- size="sm"
344
- aria-expanded={showResolved}
345
- onclick={() => (showResolved = !showResolved)}
346
- >
347
- {showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
348
- </Button>
342
+ <!-- A filter on the list, so it reports pressed, not expanded. The wash it had as an
343
+ expanded ghost button moves to aria-pressed. -->
344
+ <Toggle.Root bind:pressed={showResolved}>
345
+ {#snippet child({ props })}
346
+ <Button
347
+ {...props}
348
+ variant="ghost"
349
+ size="sm"
350
+ class="aria-pressed:bg-foreground/6 aria-pressed:text-foreground"
351
+ >
352
+ {showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
353
+ </Button>
354
+ {/snippet}
355
+ </Toggle.Root>
349
356
  {/if}
350
357
  </div>
351
358
 
@@ -159,7 +159,7 @@
159
159
  variant="text"
160
160
  onclick={startTitle}
161
161
  disabled={!onTitleChange}
162
- aria-label={labels.editTitle}
162
+ aria-label="{labels.editTitle}: {title}"
163
163
  class={cn(
164
164
  "group -mx-2 -my-1 px-2 py-1 text-2xl leading-tight font-semibold tracking-tight disabled:opacity-100",
165
165
  !onTitleChange && "hover:bg-transparent",
@@ -140,9 +140,8 @@
140
140
  </button>
141
141
  {:else if !row.own}
142
142
  <!-- A parent with no dates of its own: the span of what is below it. Not draggable. -->
143
- <div
144
- role="button"
145
- tabindex="0"
143
+ <button
144
+ type="button"
146
145
  aria-label="{title}, {describe(span)}"
147
146
  title="{title}, {describe(span)}"
148
147
  class={cn(
@@ -155,11 +154,9 @@
155
154
  style:width="{barWidth(span, layout)}px"
156
155
  style:height="{Math.max(layout.rowHeight - 24, 10)}px"
157
156
  onclick={() => onSelect?.(row.id)}
158
- onkeydown={(e) =>
159
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), onSelect?.(row.id))}
160
157
  onpointerenter={() => onHover(row.id, true)}
161
158
  onpointerleave={() => onHover(row.id, false)}
162
- ></div>
159
+ ></button>
163
160
  {:else}
164
161
  <GanttBar
165
162
  id={row.id}
@@ -28,6 +28,7 @@
28
28
  import InboxTraceView from "./inbox-trace.svelte";
29
29
  import { TagChip } from "../../components/chip/index.js";
30
30
  import { workLooks } from "../../components/work-parts/looks.js";
31
+ import { measureClamp } from "../../record/measure-clamp.js";
31
32
  import { FolderSimple } from "phosphor-svelte";
32
33
  import { Copied } from "../../components/copy/copied.svelte.js";
33
34
  import type { InboxDraftEdits, InboxItem, InboxVerdict } from "./inbox-types.js";
@@ -68,10 +69,7 @@
68
69
  // for a link the host already shows.
69
70
  let hasTrace = $derived(item.trace.evidence.length > 0 || !!why);
70
71
  let expanded = $state(false);
71
- let longCommand = $derived(
72
- !!facts.command &&
73
- (facts.command.text.length > 240 || facts.command.text.split("\n").length > 3),
74
- );
72
+ let commandCut = $state(false);
75
73
 
76
74
  let pickedDraft = $state<string>();
77
75
  let edits = $state<InboxDraftEdits>({});
@@ -210,13 +208,17 @@
210
208
  <!-- The padding sits on a wrapper: on the clamped box itself, the 4th line showed through it. -->
211
209
  <div class={cn(workLooks.inset, "w-full px-2.5 py-2")}>
212
210
  <pre
211
+ {@attach measureClamp(
212
+ () => expanded,
213
+ (cut) => (commandCut = cut),
214
+ )}
213
215
  class={cn(
214
216
  "text-foreground font-mono text-xs leading-5 break-words whitespace-pre-wrap",
215
217
  !expanded && "line-clamp-3",
216
218
  )}><span class="text-muted-foreground select-none"
217
219
  >{facts.command.tool} </span>{facts.command.text}</pre>
218
220
  </div>
219
- {#if longCommand}
221
+ {#if commandCut || expanded}
220
222
  <button
221
223
  type="button"
222
224
  class="text-muted-foreground hover:text-foreground text-xs underline-offset-2 hover:underline"
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import { Reasoning, ReasoningContent, ReasoningTrigger } from "../../ai/reasoning/index.js";
2
+ import { Brain } from "phosphor-svelte";
3
+ import * as Trace from "../../shadcn/trace/index.js";
3
4
  import type { InboxTrace } from "./inbox-types.js";
4
5
 
5
6
  interface Props {
@@ -18,13 +19,16 @@
18
19
  );
19
20
  </script>
20
21
 
21
- <!-- WHY the shared Reasoning and not a local disclosure: it is already the
22
- collapsed-by-default "how the agent got here" pattern, with the same chevron. -->
23
- <Reasoning>
24
- <ReasoningTrigger class="w-auto">
25
- <span>Trace{count > 0 ? ` · ${count} ${count === 1 ? "quote" : "quotes"}` : ""}</span>
26
- </ReasoningTrigger>
27
- <ReasoningContent>
22
+ <Trace.Collapsible class="text-sm">
23
+ <Trace.CollapsibleTrigger class="gap-2">
24
+ <Trace.Line>
25
+ <Trace.Icon><Brain aria-hidden="true" /></Trace.Icon>
26
+ <Trace.Title
27
+ >Trace{count > 0 ? ` · ${count} ${count === 1 ? "quote" : "quotes"}` : ""}</Trace.Title
28
+ >
29
+ </Trace.Line>
30
+ </Trace.CollapsibleTrigger>
31
+ <Trace.CollapsibleContent class="mt-3 mb-0 ml-0 border-l-2 pr-0 pl-3">
28
32
  <div class="flex flex-col gap-3">
29
33
  {#if label || trace.source.href}
30
34
  <p class="text-foreground">
@@ -54,5 +58,5 @@
54
58
  <p><span class="text-foreground font-medium">Why:</span> {reasoning}</p>
55
59
  {/if}
56
60
  </div>
57
- </ReasoningContent>
58
- </Reasoning>
61
+ </Trace.CollapsibleContent>
62
+ </Trace.Collapsible>
@@ -14,6 +14,7 @@
14
14
  type KanbanColumnView,
15
15
  } from "./kanban-board-state.svelte.js";
16
16
  import { Button } from "../shadcn/button/index.js";
17
+ import * as Collapsible from "../shadcn/collapsible/index.js";
17
18
  import { workLooks } from "../components/work-parts/looks.js";
18
19
  import type {
19
20
  KanbanCardContext,
@@ -278,33 +279,47 @@
278
279
  </div>
279
280
  {/if}
280
281
 
281
- {#if showTail}
282
- <!-- The tail sits shut in a 0fr row, so opening and closing animate its height. -->
283
- <div class="kanban-fold" data-open={open} inert={!open}>
284
- <div class="kanban-fold-inner" role="list">
285
- {#each tail as item, index (getId(item))}
286
- {@render card(column, item, head.length + index, list)}
287
- {/each}
288
- </div>
289
- </div>
290
- {/if}
282
+ <!-- The root renders no element, so the fold and its button stay items of the column's flex well. -->
283
+ <Collapsible.Root
284
+ bind:open={() => open, (next) => (next ? board.expand(column.id) : board.collapse(column.id))}
285
+ >
286
+ {#snippet child()}
287
+ {#if showTail}
288
+ <!-- The tail sits shut in a 0fr row, so opening and closing animate its height. -->
289
+ <Collapsible.Content forceMount>
290
+ {#snippet child({ props })}
291
+ <div {...props} class="kanban-fold" data-open={open} inert={!open}>
292
+ <div class="kanban-fold-inner" role="list">
293
+ {#each tail as item, index (getId(item))}
294
+ {@render card(column, item, head.length + index, list)}
295
+ {/each}
296
+ </div>
297
+ </div>
298
+ {/snippet}
299
+ </Collapsible.Content>
300
+ {/if}
291
301
 
292
- {#if dnd.showIndicatorEnd(column.id) && board.idAt(shown, shown.length - 1) !== dnd.draggedId}
293
- {@render dropSlot()}
294
- {/if}
302
+ {#if dnd.showIndicatorEnd(column.id) && board.idAt(shown, shown.length - 1) !== dnd.draggedId}
303
+ {@render dropSlot()}
304
+ {/if}
295
305
 
296
- {#if tail.length > 0 || open}
297
- <Button
298
- variant="ghost"
299
- size="sm"
300
- aria-expanded={open}
301
- onclick={() => (open ? board.collapse(column.id) : board.expand(column.id))}
302
- class="shrink-0 justify-start gap-1.5 px-2 text-xs font-normal text-muted-foreground"
303
- >
304
- <KanbanFoldChevron {open} class="size-2.5" />
305
- <span class="tabular-nums">{open ? lessLabel : moreLabel(tail.length)}</span>
306
- </Button>
307
- {/if}
306
+ {#if tail.length > 0 || open}
307
+ <Collapsible.Trigger>
308
+ {#snippet child({ props })}
309
+ <Button
310
+ {...props}
311
+ variant="ghost"
312
+ size="sm"
313
+ class="shrink-0 justify-start gap-1.5 px-2 text-xs font-normal text-muted-foreground"
314
+ >
315
+ <KanbanFoldChevron {open} class="size-2.5" />
316
+ <span class="tabular-nums">{open ? lessLabel : moreLabel(tail.length)}</span>
317
+ </Button>
318
+ {/snippet}
319
+ </Collapsible.Trigger>
320
+ {/if}
321
+ {/snippet}
322
+ </Collapsible.Root>
308
323
 
309
324
  {#if columnFooter}
310
325
  {@render columnFooter(column, context)}
@@ -3,6 +3,7 @@
3
3
  import { CaretDown } from "phosphor-svelte";
4
4
  import { cn } from "../lib/utils.js";
5
5
  import { Button } from "../shadcn/button/index.js";
6
+ import * as Collapsible from "../shadcn/collapsible/index.js";
6
7
  import { workLooks } from "../components/work-parts/looks.js";
7
8
  import type { KanbanRailState } from "./kanban-rail-state.svelte.js";
8
9
  import type { KanbanColumn as KanbanColumnDef } from "./kanban-types.js";
@@ -21,79 +22,80 @@
21
22
  let { columns, rail, countOf, hiddenLabel, showAllLabel, hiddenItem }: Props = $props();
22
23
  </script>
23
24
 
24
- <div class="kanban-rail flex w-40 shrink-0 flex-col gap-1">
25
- <Button
26
- variant="ghost"
27
- size="sm"
28
- aria-expanded={rail.railOpen}
29
- onclick={() => (rail.railOpen = !rail.railOpen)}
30
- class="justify-start px-2 text-xs font-normal text-muted-foreground"
31
- >
32
- <span
33
- class={cn(
34
- "size-2.5 transition-transform duration-fast ease-smooth-out motion-reduce:transition-none [&>svg]:size-full",
35
- !rail.railOpen && "-rotate-90",
36
- )}
37
- aria-hidden="true"><CaretDown aria-hidden="true" weight="bold" /></span
38
- >
39
- {hiddenLabel}
40
- </Button>
41
-
42
- {#if rail.railOpen}
43
- <div class="flex flex-col gap-0.5">
44
- {#each columns as column (column.id)}
45
- {@const count = countOf(column.id)}
46
- {@const over = rail.dragOverHidden === column.id}
47
- {@const hue = column.hue ?? "var(--color-primary)"}
48
- <div
49
- role="region"
50
- aria-label={column.ariaLabel ?? column.label}
25
+ <Collapsible.Root bind:open={rail.railOpen} class="kanban-rail flex w-40 shrink-0 flex-col gap-1">
26
+ <Collapsible.Trigger>
27
+ {#snippet child({ props })}
28
+ <Button
29
+ {...props}
30
+ variant="ghost"
31
+ size="sm"
32
+ class="justify-start px-2 text-xs font-normal text-muted-foreground"
33
+ >
34
+ <span
51
35
  class={cn(
52
- "kanban-rail-item transition-[background-color,outline-color] duration-quick ease-smooth-out",
53
- workLooks.row,
54
- rail.leaving.has(column.id) && "kanban-rail-item-leaving",
36
+ "size-2.5 transition-transform duration-fast ease-smooth-out motion-reduce:transition-none [&>svg]:size-full",
37
+ !rail.railOpen && "-rotate-90",
55
38
  )}
56
- style:background="color-mix(in oklch, {hue}
57
- {over ? 10 : 0}%, transparent)"
58
- style:outline="1px solid color-mix(in oklch, {hue}
59
- {over ? 28 : 0}%, transparent)"
60
- ondragover={(e) => rail.onHiddenDragOver(e, column.id)}
61
- ondragleave={rail.onHiddenDragLeave}
62
- ondrop={(e) => rail.onHiddenDrop(e, column.id)}
39
+ aria-hidden="true"><CaretDown aria-hidden="true" weight="bold" /></span
63
40
  >
64
- <Button
65
- variant="ghost"
66
- size="lg"
67
- onclick={() => rail.show(column.id)}
68
- class="w-full justify-start gap-2 text-xs text-muted-foreground"
69
- >
70
- {#if hiddenItem}
71
- {@render hiddenItem(column, { count, active: over })}
72
- {:else}
73
- <span class="truncate whitespace-nowrap">{column.label}</span>
74
- {#if count > 0}
75
- <span class="ms-auto text-xs text-muted-foreground/60 tabular-nums"
76
- >{count.toLocaleString()}</span
77
- >
78
- {/if}
79
- {/if}
80
- </Button>
81
- </div>
82
- {/each}
41
+ {hiddenLabel}
42
+ </Button>
43
+ {/snippet}
44
+ </Collapsible.Trigger>
83
45
 
84
- {#if columns.length > 1}
46
+ <Collapsible.Content class="flex flex-col gap-0.5">
47
+ {#each columns as column (column.id)}
48
+ {@const count = countOf(column.id)}
49
+ {@const over = rail.dragOverHidden === column.id}
50
+ {@const hue = column.hue ?? "var(--color-primary)"}
51
+ <div
52
+ role="region"
53
+ aria-label={column.ariaLabel ?? column.label}
54
+ class={cn(
55
+ "kanban-rail-item transition-[background-color,outline-color] duration-quick ease-smooth-out",
56
+ workLooks.row,
57
+ rail.leaving.has(column.id) && "kanban-rail-item-leaving",
58
+ )}
59
+ style:background="color-mix(in oklch, {hue}
60
+ {over ? 10 : 0}%, transparent)"
61
+ style:outline="1px solid color-mix(in oklch, {hue}
62
+ {over ? 28 : 0}%, transparent)"
63
+ ondragover={(e) => rail.onHiddenDragOver(e, column.id)}
64
+ ondragleave={rail.onHiddenDragLeave}
65
+ ondrop={(e) => rail.onHiddenDrop(e, column.id)}
66
+ >
85
67
  <Button
86
68
  variant="ghost"
87
- size="sm"
88
- onclick={rail.showAll}
89
- class="mt-1 justify-start text-xs font-normal text-muted-foreground"
69
+ size="lg"
70
+ onclick={() => rail.show(column.id)}
71
+ class="w-full justify-start gap-2 text-xs text-muted-foreground"
90
72
  >
91
- {showAllLabel}
73
+ {#if hiddenItem}
74
+ {@render hiddenItem(column, { count, active: over })}
75
+ {:else}
76
+ <span class="truncate whitespace-nowrap">{column.label}</span>
77
+ {#if count > 0}
78
+ <span class="ms-auto text-xs text-muted-foreground/60 tabular-nums"
79
+ >{count.toLocaleString()}</span
80
+ >
81
+ {/if}
82
+ {/if}
92
83
  </Button>
93
- {/if}
94
- </div>
95
- {/if}
96
- </div>
84
+ </div>
85
+ {/each}
86
+
87
+ {#if columns.length > 1}
88
+ <Button
89
+ variant="ghost"
90
+ size="sm"
91
+ onclick={rail.showAll}
92
+ class="mt-1 justify-start text-xs font-normal text-muted-foreground"
93
+ >
94
+ {showAllLabel}
95
+ </Button>
96
+ {/if}
97
+ </Collapsible.Content>
98
+ </Collapsible.Root>
97
99
 
98
100
  <style>
99
101
  /* Unlayered: the row's transition-[background-color,outline-color] utility would otherwise drop the fade out. */