@heyramzi/ui 2.20.0 → 2.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +2 -0
  2. package/dist/charts/dashboard/data-table.js +1 -2
  3. package/dist/components/list-page/filter-section.js +1 -1
  4. package/dist/components/list-page/list-table.js +5 -6
  5. package/dist/components/list-page/queue-rail.js +2 -1
  6. package/dist/components/picker/trigger.js +3 -3
  7. package/dist/components/segmented/segmented.js +5 -5
  8. package/dist/components/work-parts/looks.js +1 -1
  9. package/dist/shadcn/alert/alert-variants.js +9 -0
  10. package/dist/shadcn/alert/index.js +1 -1
  11. package/dist/shadcn/badge/badge-variants.js +17 -0
  12. package/dist/shadcn/badge/index.js +1 -1
  13. package/dist/shadcn/dialog/index.js +3 -2
  14. package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
  15. package/dist/shadcn/sheet/index.js +3 -2
  16. package/dist/shadcn/skeleton/index.js +3 -2
  17. package/dist/shadcn/toggle/index.js +1 -1
  18. package/dist/shadcn/toggle/toggle-variants.js +20 -0
  19. package/dist/status/index.js +2 -0
  20. package/dist/status/pill-tint.js +13 -0
  21. package/dist/work/kanban-drag.js +13 -7
  22. package/package.json +23 -23
  23. package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
  24. package/src/ai/loading-state/PixelLoader.svelte +1 -1
  25. package/src/animations/dropdown.css +19 -10
  26. package/src/animations/modal.css +7 -2
  27. package/src/charts/charts.css +3 -5
  28. package/src/charts/dashboard/dashboard-section.svelte +1 -1
  29. package/src/charts/dashboard/data-table.ts +1 -2
  30. package/src/charts/dashboard/stat-tile.svelte +1 -1
  31. package/src/charts/number-chart.css +8 -7
  32. package/src/charts/pop-value.svelte +68 -0
  33. package/src/charts/primitives/chart-tooltip.svelte +1 -1
  34. package/src/charts/reference-line.css +1 -1
  35. package/src/charts/stat-card.css +4 -4
  36. package/src/components/copy/copy-button.svelte +11 -10
  37. package/src/components/data-table/row-arrow.svelte +1 -1
  38. package/src/components/data-table/row-context-menu.svelte +1 -1
  39. package/src/components/data-table/sortable-head.svelte +2 -2
  40. package/src/components/data-table/table-skeleton.svelte +5 -5
  41. package/src/components/docs/doc-page.svelte +1 -3
  42. package/src/components/docs/on-this-page.svelte +4 -4
  43. package/src/components/field-row/field-row.svelte +1 -1
  44. package/src/components/list-page/detail-empty.svelte +3 -8
  45. package/src/components/list-page/detail-header.svelte +1 -1
  46. package/src/components/list-page/detail-row.svelte +2 -1
  47. package/src/components/list-page/empty-state.svelte +15 -7
  48. package/src/components/list-page/filter-item.svelte +2 -2
  49. package/src/components/list-page/filter-popover.svelte +1 -1
  50. package/src/components/list-page/filter-section.ts +1 -4
  51. package/src/components/list-page/grouped-list.svelte +37 -8
  52. package/src/components/list-page/list-page-shell.svelte +1 -1
  53. package/src/components/list-page/list-table.ts +6 -6
  54. package/src/components/list-page/master-detail.svelte +1 -1
  55. package/src/components/list-page/queue-rail.ts +2 -2
  56. package/src/components/list-page/rail-empty.svelte +1 -1
  57. package/src/components/list-page/rail-row.svelte +5 -5
  58. package/src/components/open-in/open-in.svelte +2 -1
  59. package/src/components/picker/trigger.ts +6 -5
  60. package/src/components/section-card/section-card.svelte +1 -1
  61. package/src/components/section-header/section-header.svelte +4 -2
  62. package/src/components/section-header/section-label.svelte +2 -2
  63. package/src/components/segmented/animated-tabs.svelte +1 -1
  64. package/src/components/segmented/section-runs.svelte +1 -1
  65. package/src/components/segmented/segment.svelte +4 -6
  66. package/src/components/segmented/segmented-track.svelte +18 -52
  67. package/src/components/segmented/segmented.ts +5 -5
  68. package/src/components/table-view/table-view.svelte +43 -24
  69. package/src/components/tags-input/tags-input.svelte +17 -31
  70. package/src/components/work-parts/looks.ts +1 -1
  71. package/src/editor/content.css +3 -3
  72. package/src/editor/editor.svelte +1 -1
  73. package/src/inbox/thread-inbox.svelte +2 -9
  74. package/src/panel/side-panel.svelte +9 -4
  75. package/src/record/info-list.svelte +7 -8
  76. package/src/record/project-list.svelte +1 -1
  77. package/src/record/record-demo.svelte +2 -4
  78. package/src/record/record-rail.svelte +5 -1
  79. package/src/record/record-shell.svelte +1 -1
  80. package/src/record/record-stats.svelte +9 -3
  81. package/src/record/steps-rail.svelte +7 -11
  82. package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
  83. package/src/shadcn/alert/alert-variants.ts +17 -0
  84. package/src/shadcn/alert/alert.svelte +2 -16
  85. package/src/shadcn/alert/index.ts +1 -1
  86. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
  87. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
  88. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
  89. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
  90. package/src/shadcn/badge/badge-variants.ts +23 -0
  91. package/src/shadcn/badge/badge.svelte +2 -20
  92. package/src/shadcn/badge/index.ts +1 -1
  93. package/src/shadcn/bubble/bubble-content.svelte +1 -1
  94. package/src/shadcn/bubble/bubble.svelte +1 -1
  95. package/src/shadcn/button/button.svelte +11 -5
  96. package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
  97. package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
  98. package/src/shadcn/calendar/calendar.svelte +1 -1
  99. package/src/shadcn/card/card.svelte +2 -1
  100. package/src/shadcn/checkbox/checkbox.svelte +1 -1
  101. package/src/shadcn/code-block/code-block-actions.svelte +1 -1
  102. package/src/shadcn/command/command-dialog.svelte +2 -2
  103. package/src/shadcn/command/command-group.svelte +1 -1
  104. package/src/shadcn/command/command-input.svelte +1 -1
  105. package/src/shadcn/command/command-item.svelte +1 -1
  106. package/src/shadcn/dialog/dialog-body.svelte +22 -0
  107. package/src/shadcn/dialog/dialog-content.svelte +19 -4
  108. package/src/shadcn/dialog/dialog-footer.svelte +19 -1
  109. package/src/shadcn/dialog/dialog-header.svelte +6 -1
  110. package/src/shadcn/dialog/dialog-title.svelte +2 -1
  111. package/src/shadcn/dialog/index.ts +3 -0
  112. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
  113. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  114. package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
  115. package/src/shadcn/field/field-separator.svelte +1 -1
  116. package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
  117. package/src/shadcn/input/input.svelte +2 -2
  118. package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
  119. package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
  120. package/src/shadcn/popover/popover-content.svelte +1 -1
  121. package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
  122. package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
  123. package/src/shadcn/select/select-content.svelte +6 -2
  124. package/src/shadcn/select/select-item.svelte +1 -1
  125. package/src/shadcn/select/select-trigger.svelte +1 -1
  126. package/src/shadcn/sheet/index.ts +3 -0
  127. package/src/shadcn/sheet/sheet-body.svelte +22 -0
  128. package/src/shadcn/sheet/sheet-content.svelte +27 -5
  129. package/src/shadcn/sheet/sheet-footer.svelte +14 -2
  130. package/src/shadcn/sheet/sheet-header.svelte +1 -1
  131. package/src/shadcn/sheet/sheet-title.svelte +1 -1
  132. package/src/shadcn/sheet/sheet.css +11 -5
  133. package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
  134. package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
  135. package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
  136. package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
  137. package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
  138. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
  139. package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
  140. package/src/shadcn/sidebar/sidebar.svelte +1 -1
  141. package/src/shadcn/skeleton/index.ts +3 -0
  142. package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
  143. package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
  144. package/src/shadcn/skeleton/skeleton.svelte +5 -1
  145. package/src/shadcn/slider/slider.svelte +1 -1
  146. package/src/shadcn/switch/switch.svelte +2 -2
  147. package/src/shadcn/table/table-row.svelte +1 -1
  148. package/src/shadcn/tabs/tabs-list.svelte +1 -1
  149. package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
  150. package/src/shadcn/textarea/textarea.svelte +1 -1
  151. package/src/shadcn/toggle/index.ts +1 -1
  152. package/src/shadcn/toggle/toggle-variants.ts +26 -0
  153. package/src/shadcn/toggle/toggle.svelte +7 -25
  154. package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
  155. package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
  156. package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
  157. package/src/shadcn/trace/trace.svelte +1 -1
  158. package/src/status/README.md +1 -1
  159. package/src/status/index.ts +2 -0
  160. package/src/status/pill-tint.ts +16 -0
  161. package/src/status/status-badge.svelte +21 -7
  162. package/src/status/status-dot.svelte +6 -11
  163. package/src/status/status-pill.svelte +40 -0
  164. package/src/svelte-reexports.test.ts +43 -0
  165. package/src/thread/thread.svelte +54 -136
  166. package/src/tokens/craft.css +55 -36
  167. package/src/tokens/palette.css +16 -9
  168. package/src/work/demo.svelte +48 -42
  169. package/src/work/detail/detail-demo.svelte +33 -24
  170. package/src/work/detail/task-chip.svelte +4 -4
  171. package/src/work/detail/task-detail.svelte +41 -108
  172. package/src/work/detail/task-property.svelte +7 -13
  173. package/src/work/detail/task-thread.svelte +48 -119
  174. package/src/work/docs/doc-editor-page.svelte +5 -4
  175. package/src/work/docs/doc-menu.svelte +9 -4
  176. package/src/work/docs/docs-demo.svelte +4 -4
  177. package/src/work/docs/edited-chip.svelte +11 -4
  178. package/src/work/gantt/gantt-bar.svelte +18 -14
  179. package/src/work/gantt/gantt-grid.svelte +7 -7
  180. package/src/work/gantt/gantt-group-row.svelte +7 -10
  181. package/src/work/gantt/gantt-header.svelte +3 -1
  182. package/src/work/gantt/gantt-task-row.svelte +19 -9
  183. package/src/work/gantt/gantt.css +4 -5
  184. package/src/work/inbox/inbox-card.svelte +32 -10
  185. package/src/work/inbox/inbox-draft.svelte +2 -2
  186. package/src/work/kanban-column.svelte +7 -15
  187. package/src/work/kanban-drag.ts +13 -8
  188. package/src/work/task-gantt.svelte +2 -1
  189. package/src/work/task-list.svelte +54 -53
  190. package/src/work/time/time-chip.svelte +16 -19
  191. package/src/work/time/time-dot.svelte +2 -1
  192. package/src/work/time/time-ring.svelte +2 -3
  193. package/src/work/time/time-tracker.svelte +72 -166
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "../animations/digit-pop-in.css";
6
+ import { cn } from "../lib/utils.js";
7
+
8
+ // =========================
9
+ // PROPS
10
+ // =========================
11
+ interface Props {
12
+ /** The formatted value. Characters that change between renders pop in; the rest stay still. */
13
+ value: string;
14
+ class?: string;
15
+ }
16
+ let { value, class: className }: Props = $props();
17
+
18
+ // =========================
19
+ // STATE
20
+ // =========================
21
+ let group = $state<HTMLElement | null>(null);
22
+ // Plain, not $state: it only remembers the last value painted, so writing it never re-renders.
23
+ let previous: string | null = null;
24
+
25
+ // =========================
26
+ // DERIVED
27
+ // =========================
28
+ // Only the characters that differ from the last value animate, so 1,204 to 1,205 moves one
29
+ // digit, not four. The stagger caps at 2 steps to keep the whole pop under the 300ms ceiling
30
+ // plus two stagger beats.
31
+ let chars = $derived.by(() => {
32
+ const before = previous;
33
+ let changed = 0;
34
+ return [...value].map((char, index) => {
35
+ const moved = before !== null && before[index] !== char;
36
+ return { char, moved, stagger: moved ? Math.min(changed++, 2) : 0 };
37
+ });
38
+ });
39
+
40
+ // =========================
41
+ // EFFECTS
42
+ // =========================
43
+ // transitions-dev number pop-in: drop the class, force a reflow, add it back, or a second
44
+ // change never replays. The first paint doesn't animate: nothing changed yet.
45
+ $effect(() => {
46
+ const next = value;
47
+ if (!group) return;
48
+ if (previous !== null && previous !== next) {
49
+ group.classList.remove("is-animating");
50
+ void group.offsetWidth;
51
+ group.classList.add("is-animating");
52
+ }
53
+ previous = next;
54
+ });
55
+ </script>
56
+
57
+ <span bind:this={group} class={cn("t-digit-group whitespace-pre tabular-nums", className)}>
58
+ <span class="sr-only">{value}</span>
59
+ <span aria-hidden="true" class="contents">
60
+ {#each chars as part, index (`${index}:${part.char}`)}
61
+ {#if part.moved}
62
+ <span class="t-digit" data-stagger={part.stagger}>{part.char}</span>
63
+ {:else}
64
+ <span>{part.char}</span>
65
+ {/if}
66
+ {/each}
67
+ </span>
68
+ </span>
@@ -92,7 +92,7 @@
92
92
  <TooltipPrimitive.Root variant="none">
93
93
  <div
94
94
  class={cn(
95
- "border-border/50 bg-background grid min-w-[9rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
95
+ "bg-card grid min-w-[9rem] items-start gap-1.5 rounded-lg px-2.5 py-1.5 text-xs shadow-edge-raised",
96
96
  className,
97
97
  )}
98
98
  {...restProps}
@@ -12,7 +12,7 @@
12
12
  fill: var(--hr-reference-color);
13
13
  font-size: 0.75rem;
14
14
  font-weight: 500;
15
- stroke: var(--background);
15
+ stroke: var(--card);
16
16
  stroke-width: 3;
17
17
  paint-order: stroke;
18
18
  }
@@ -6,18 +6,18 @@
6
6
  flex-direction: column;
7
7
  gap: 0.375rem;
8
8
  padding: 1.25rem 1.5rem;
9
- border: 1px solid var(--border, oklch(0.92 0.004 286));
10
- border-radius: 1rem;
9
+ border: 0;
10
+ border-radius: var(--radius-xl);
11
11
  background: var(--card, white);
12
12
  box-shadow: var(--chart-card-shadow);
13
13
  min-width: 0;
14
14
  /* WHY: clips the edge-to-edge chart to the card's rounded corners. */
15
15
  overflow: hidden;
16
- transition: border-color var(--chart-motion, 250ms) var(--chart-ease-out, ease);
16
+ transition: box-shadow var(--chart-motion) var(--chart-ease-out);
17
17
  }
18
18
  @media (hover: hover) and (pointer: fine) {
19
19
  .hr-stat-card:hover {
20
- border-color: color-mix(in srgb, var(--ring, var(--chart-1)) 28%, var(--border, transparent));
20
+ box-shadow: var(--chart-card-shadow-hover);
21
21
  }
22
22
  }
23
23
  /* WHY: a bare figure, not a card. Dropping the chrome avoids a card-in-card
@@ -105,35 +105,36 @@
105
105
  title={label}
106
106
  aria-label={announce === "name" && copied ? copiedLabel : label}
107
107
  class={cn(
108
- "group/copy t-press shrink-0 outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
108
+ "group/copy t-press shrink-0 outline-none focus-visible:shadow-(--ring-focus)",
109
109
  text === undefined
110
110
  ? size === "sm"
111
- ? "grid size-5 place-items-center rounded-md hover:bg-accent"
111
+ ? "grid size-5 place-items-center rounded-sm transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 motion-reduce:transition-none"
112
112
  : buttonVariants({
113
113
  variant: variant === "outline" ? "outline" : "ghost",
114
114
  size: size === "md" ? "sm" : "default",
115
115
  icon: true,
116
116
  })
117
117
  : inline
118
- ? "inline-flex cursor-copy items-baseline gap-1.5 transition-colors hover:text-foreground"
119
- : "flex items-center gap-1 rounded px-1.5 py-0.5 font-mono text-xs tabular-nums text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground",
118
+ ? "inline-flex cursor-copy items-baseline gap-1.5 transition-colors duration-quick ease-smooth-out hover:text-foreground motion-reduce:transition-none"
119
+ : "flex items-center gap-1 rounded-sm px-1.5 py-0.5 font-mono text-xs tabular-nums text-muted-foreground transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 hover:text-foreground motion-reduce:transition-none",
120
120
  className,
121
121
  )}
122
122
  >
123
123
  <!-- One slot, two glyphs, per the transitions-dev icon-swap: the check
124
- arrives on the bounce, the copy glyph leaves without one. -->
124
+ grows in from half size, the copy glyph only fades. Reduced motion keeps the fade. -->
125
125
  <span
126
126
  class={cn(
127
127
  "relative grid place-items-center",
128
128
  text === undefined && size !== "sm" ? "size-4" : "size-3",
129
- inline && "order-last self-center opacity-60 transition-opacity group-hover/copy:opacity-100",
129
+ inline &&
130
+ "order-last self-center opacity-60 transition-opacity duration-quick ease-smooth-out group-hover/copy:opacity-100",
130
131
  )}
131
132
  >
132
133
  <Check
133
134
  class={cn(
134
- "col-start-1 row-start-1 size-full transition-[opacity,transform] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)]",
135
+ "col-start-1 row-start-1 size-full transition-[opacity,transform] duration-fast ease-smooth-out motion-reduce:transition-opacity",
135
136
  inline ? "text-current" : "text-success-800 dark:text-success-400",
136
- copied ? "scale-100 opacity-100" : "scale-50 opacity-0",
137
+ copied ? "scale-100 opacity-100" : "scale-50 opacity-0 motion-reduce:scale-100",
137
138
  )}
138
139
  weight="bold"
139
140
  aria-hidden="true"
@@ -141,7 +142,7 @@
141
142
  {#if agent}
142
143
  <StarFour
143
144
  class={cn(
144
- "col-start-1 row-start-1 size-full text-brand/70 transition-[opacity,color] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-hover/copy:text-brand",
145
+ "col-start-1 row-start-1 size-full text-brand/70 transition-[opacity,color] duration-fast ease-smooth-out motion-reduce:transition-opacity group-hover/copy:text-brand",
145
146
  copied ? "opacity-0" : "opacity-100",
146
147
  )}
147
148
  weight="bold"
@@ -150,7 +151,7 @@
150
151
  {:else}
151
152
  <Copy
152
153
  class={cn(
153
- "col-start-1 row-start-1 size-full transition-[opacity,color] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)]",
154
+ "col-start-1 row-start-1 size-full transition-[opacity,color] duration-fast ease-smooth-out motion-reduce:transition-opacity",
154
155
  inline ? "text-current" : "text-muted-foreground group-hover/copy:text-foreground",
155
156
  copied ? "opacity-0" : "opacity-100",
156
157
  )}
@@ -23,7 +23,7 @@
23
23
  <!-- =========================== -->
24
24
  <CaretRight
25
25
  class={cn(
26
- "size-3.5 text-muted-foreground opacity-0 transition-opacity group-hover/row:opacity-100",
26
+ "size-3.5 text-muted-foreground opacity-0 transition-opacity duration-quick ease-smooth-out group-hover/row:opacity-100",
27
27
  className,
28
28
  )}
29
29
  weight="bold"
@@ -92,7 +92,7 @@
92
92
  <HoldToConfirm
93
93
  duration={800}
94
94
  onConfirm={onDelete}
95
- class="relative flex w-full cursor-default items-center justify-start gap-2 rounded-md px-1.5 py-1 text-sm text-destructive outline-hidden"
95
+ class="relative flex w-full cursor-default items-center justify-start gap-2 rounded-lg px-1.5 py-1 text-sm text-destructive outline-hidden"
96
96
  >
97
97
  {#snippet idle()}
98
98
  <Trash size={14} />
@@ -35,7 +35,7 @@
35
35
  data-slot="table-head"
36
36
  class={cn(
37
37
  "text-muted-foreground h-8 px-2 text-left align-middle text-xs font-medium whitespace-nowrap select-none",
38
- "cursor-pointer hover:bg-muted/30 transition-colors",
38
+ "cursor-pointer transition-colors duration-quick ease-smooth-out hover:bg-foreground/4 motion-reduce:transition-none",
39
39
  className,
40
40
  )}
41
41
  tabindex="0"
@@ -50,7 +50,7 @@
50
50
  {:else if direction === "desc"}
51
51
  <CaretDown class="size-3.5 text-foreground" weight="bold" />
52
52
  {:else}
53
- <CaretUpDown class="size-3.5 text-muted-foreground/75" />
53
+ <CaretUpDown class="size-3.5 text-muted-foreground" />
54
54
  {/if}
55
55
  </span>
56
56
  </th>
@@ -42,13 +42,13 @@
42
42
  {#each { length: rows } as _, rowIdx (rowIdx)}
43
43
  <tr
44
44
  data-slot="table-row"
45
- class="border-b border-border/50 transition-opacity"
46
- style="animation: skeleton-fade-in var(--duration-slow) var(--ease-out) calc(var(--duration-stagger) * {rowIdx}) both"
45
+ class="border-b border-border/70 transition-opacity"
46
+ style="animation: skeleton-fade-in var(--duration-slow) var(--ease-smooth-out) calc(var(--duration-stagger) * {rowIdx}) both"
47
47
  >
48
48
  {#each cols as col, colIdx (colIdx)}
49
49
  <td
50
50
  data-slot="table-cell"
51
- class="h-11 px-2 {col.align === 'center'
51
+ class="h-8 px-2 any-pointer-coarse:h-11 {col.align === 'center'
52
52
  ? 'text-center'
53
53
  : col.align === 'right'
54
54
  ? 'text-right'
@@ -62,10 +62,10 @@
62
62
  : ''}"
63
63
  >
64
64
  {#if col.circle}
65
- <Skeleton class="size-6 shrink-0 rounded-md" />
65
+ <Skeleton class="size-5 shrink-0 rounded-full" />
66
66
  {/if}
67
67
  <Skeleton
68
- class="h-3.5 rounded {col.width ?? WIDTHS[(rowIdx + colIdx) % WIDTHS.length]}"
68
+ class="h-3 rounded-sm {col.width ?? WIDTHS[(rowIdx + colIdx) % WIDTHS.length]}"
69
69
  />
70
70
  </div>
71
71
  </td>
@@ -89,9 +89,7 @@
89
89
  >
90
90
  {#if hasRail}
91
91
  <!-- Below the rail's width the contents sit on a strip that stays put. -->
92
- <div
93
- class="sticky top-0 z-20 border-b border-border bg-background/95 backdrop-blur @3xl/page:hidden"
94
- >
92
+ <div class="sticky top-0 z-20 border-b border-border bg-card/95 backdrop-blur @3xl/page:hidden">
95
93
  <OnThisPage
96
94
  variant="bar"
97
95
  items={headings}
@@ -149,7 +149,7 @@
149
149
  type="button"
150
150
  tabindex="-1"
151
151
  onclick={() => pick(item.id)}
152
- class="block w-full truncate rounded-md py-1 pr-3 text-left text-[13px] leading-5 transition-colors duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] hover:bg-foreground/6 {item.level ===
152
+ class="block w-full truncate rounded-md py-1 pr-3 text-left text-(length:--text-control) leading-5 transition-colors duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] hover:bg-foreground/6 {item.level ===
153
153
  3
154
154
  ? 'pl-5'
155
155
  : 'pl-2'} {current
@@ -164,7 +164,7 @@
164
164
  {:else if variant === "rail"}
165
165
  <nav aria-label={label} class="flex max-h-[calc(100svh-8rem)] flex-col gap-3">
166
166
  <!-- Set like the map's own headings on the left, so the two rails read as one. -->
167
- <p class="font-heading text-xs font-semibold uppercase tracking-wider text-muted-foreground">
167
+ <p class="font-heading text-xs font-semibold text-muted-foreground">
168
168
  {label}
169
169
  </p>
170
170
  <div
@@ -190,7 +190,7 @@
190
190
  data-current={current}
191
191
  aria-current={current ? "location" : undefined}
192
192
  onclick={() => pick(item.id)}
193
- class="otp-row block w-full py-1.5 pl-3 pr-1 text-left text-[13px] leading-snug transition-colors duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] any-pointer-coarse:py-2.5 {current
193
+ class="otp-row block w-full py-1.5 pl-3 pr-1 text-left text-(length:--text-control) leading-snug transition-colors duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] any-pointer-coarse:py-2.5 {current
194
194
  ? 'font-medium text-foreground'
195
195
  : 'text-muted-foreground hover:text-foreground'}"
196
196
  >
@@ -232,7 +232,7 @@
232
232
  {#if open}
233
233
  <nav
234
234
  aria-label={label}
235
- class="thin-scrollbar absolute inset-x-2 top-full z-20 mt-1 max-h-[60svh] overflow-y-auto rounded-xl border border-border bg-popover p-1.5 shadow-lg"
235
+ class="thin-scrollbar absolute inset-x-2 top-full z-20 mt-1 max-h-[60svh] overflow-y-auto rounded-2xl bg-popover p-1.5 shadow-edge-raised"
236
236
  >
237
237
  <ul class="flex flex-col">
238
238
  {#each items as item (item.id)}
@@ -52,7 +52,7 @@
52
52
  {#if action}
53
53
  <!-- A disabled action reads as a muted button, not a half-faded one. -->
54
54
  <div
55
- class="shrink-0 [&_button:disabled]:bg-muted [&_button:disabled]:text-muted-foreground [&_button:disabled]:opacity-100 [&_button:disabled]:shadow-none"
55
+ class="shrink-0 [&_button:disabled]:bg-foreground/6 [&_button:disabled]:text-muted-foreground [&_button:disabled]:opacity-100 [&_button:disabled]:shadow-none"
56
56
  >
57
57
  {@render action()}
58
58
  </div>
@@ -10,8 +10,8 @@
10
10
  // TYPES
11
11
  // =========================
12
12
  interface Props {
13
- // Shown when nothing is picked or the board is empty. Both mean "content
14
- // goes here", so both get a dashed `border-2` edge.
13
+ // Shown when nothing is picked or the board is empty. It sits on the white
14
+ // panel with no frame of its own: a dashed edge drew a box round nothing.
15
15
  icon?: PhosphorIcon;
16
16
  iconWeight?: IconWeight;
17
17
  title: string;
@@ -40,11 +40,6 @@
40
40
  <!-- =========================== -->
41
41
  <!-- MARKUP -->
42
42
  <!-- =========================== -->
43
- <div
44
- class={cn(
45
- "flex h-full items-center justify-center rounded-xl border-2 border-dashed border-border",
46
- className,
47
- )}
48
- >
43
+ <div class={cn("flex h-full items-center justify-center rounded-xl bg-card", className)}>
49
44
  <EmptyState {icon} {iconWeight} {title} {description} {actionLabel} {actionHref} {onAction} />
50
45
  </div>
@@ -61,7 +61,7 @@
61
61
  <!-- =========================== -->
62
62
  <!-- MARKUP -->
63
63
  <!-- =========================== -->
64
- <section class={cn("overflow-hidden rounded-xl border border-border bg-card", className)}>
64
+ <section class={cn("overflow-hidden rounded-xl bg-card shadow-card", className)}>
65
65
  <div class="flex flex-wrap items-start justify-between gap-x-4 gap-y-3 p-4">
66
66
  {#if leading}
67
67
  <div class="shrink-0">{@render leading()}</div>
@@ -27,7 +27,8 @@
27
27
  <!-- =========================== -->
28
28
  <div
29
29
  class={cn(
30
- "border-t border-border px-4 py-3 text-sm leading-relaxed",
30
+ // WHY first:border-t-0: the panel header already draws the line above the first row.
31
+ "border-t border-border px-4 py-3 text-sm leading-relaxed first:border-t-0",
31
32
  muted && "text-xs text-muted-foreground",
32
33
  className,
33
34
  )}
@@ -36,16 +36,18 @@
36
36
  <!-- =========================== -->
37
37
  <!-- MARKUP -->
38
38
  <!-- =========================== -->
39
- <!-- The four parts arrive in reading order. The stagger is what says the page
40
- finished loading and is simply empty. -->
39
+ <!-- transitions-dev texts reveal: the parts arrive in reading order, so the page reads as
40
+ loaded and empty. Reduced motion keeps the fade. -->
41
41
  <Empty.Root class="t-empty py-16">
42
42
  <Empty.Header class="max-w-md">
43
43
  {#if Icon}
44
- <Empty.Media class="mb-4 size-16 rounded-2xl bg-brand/10 text-brand">
45
- <Icon weight={iconWeight} class="size-8" />
44
+ <!-- A white tile, not a brand tint: the accent marks the one action (DESIGN.md
45
+ rule 1), and the button below is it. -->
46
+ <Empty.Media class="mb-3 size-12 rounded-xl bg-card text-foreground shadow-card">
47
+ <Icon weight={iconWeight} class="size-6" />
46
48
  </Empty.Media>
47
49
  {/if}
48
- <Empty.Title class="font-heading text-lg font-semibold [overflow-wrap:anywhere]"
50
+ <Empty.Title class="font-heading text-base font-semibold text-balance [overflow-wrap:anywhere]"
49
51
  >{title}</Empty.Title
50
52
  >
51
53
  {#if description}
@@ -84,10 +86,16 @@
84
86
  }
85
87
  }
86
88
 
89
+ @keyframes -global-t-empty-fade {
90
+ from {
91
+ opacity: 0;
92
+ }
93
+ }
94
+
87
95
  @media (prefers-reduced-motion: reduce) {
88
96
  :global(.t-empty) > :global(*) {
89
- animation-duration: 1ms;
90
- animation-delay: 0ms;
97
+ animation-name: t-empty-fade;
98
+ animation-delay: 0s;
91
99
  }
92
100
  }
93
101
  </style>
@@ -28,13 +28,13 @@
28
28
  <!-- =========================== -->
29
29
  <button
30
30
  type="button"
31
- class="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm transition-colors hover:bg-muted"
31
+ class="flex min-h-8 items-center gap-2 rounded-lg px-2 text-sm transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 motion-reduce:transition-none any-pointer-coarse:min-h-11"
32
32
  {onclick}
33
33
  >
34
34
  {#if variant === "radio"}
35
35
  <span
36
36
  class={cn(
37
- "grid size-3.5 shrink-0 place-items-center rounded-full border transition-colors",
37
+ "grid size-3.5 shrink-0 place-items-center rounded-full border transition-colors duration-quick ease-smooth-out motion-reduce:transition-none",
38
38
  checked ? "border-primary bg-primary text-primary-foreground" : "border-border",
39
39
  )}
40
40
  >
@@ -71,7 +71,7 @@
71
71
  {/if}
72
72
  {#if hasCount}
73
73
  <span
74
- class="ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-brand/15 px-1 text-[10px] tabular-nums font-semibold text-brand dark:text-brand-light"
74
+ class="ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-brand/12 px-1 text-xs leading-none font-semibold text-brand tabular-nums dark:text-brand-light"
75
75
  >
76
76
  {count.toLocaleString()}
77
77
  </span>
@@ -5,8 +5,5 @@ import { cn } from "../../lib/utils.js";
5
5
 
6
6
  // `first:` drops the leading header's top padding, since the popover pads itself.
7
7
  export function filterSectionClass(className?: string): string {
8
- return cn(
9
- "px-2 pt-2 pb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground first:pt-1",
10
- className,
11
- );
8
+ return cn("px-2 pt-2 pb-1 text-2xs font-medium text-muted-foreground first:pt-1", className);
12
9
  }
@@ -4,7 +4,7 @@
4
4
  // =========================
5
5
  import { untrack, type Snippet } from "svelte";
6
6
  import { SvelteSet } from "svelte/reactivity";
7
- import { CaretDown } from "phosphor-svelte";
7
+ import { CaretDown, Plus } from "phosphor-svelte";
8
8
  import { cn } from "../../lib/utils.js";
9
9
 
10
10
  // =========================
@@ -22,6 +22,12 @@
22
22
  one doesn't collapse the group. Shown on hover of the header row. */
23
23
  actions?: Snippet<[TGroup]>;
24
24
  rows: Snippet<[TGroup]>;
25
+ /** A count beside the header, in a small pill. Leave it out when the header draws its own. */
26
+ getCount?: (group: TGroup) => number;
27
+ /** Shows an add button at the end of the header row. */
28
+ onAdd?: (group: TGroup) => void;
29
+ /** The add button's accessible name. */
30
+ addLabel?: string;
25
31
  class?: string;
26
32
  onGroupDragOver?: (e: DragEvent, group: TGroup) => void;
27
33
  onGroupDragLeave?: (e: DragEvent) => void;
@@ -39,6 +45,9 @@
39
45
  header,
40
46
  actions,
41
47
  rows,
48
+ getCount,
49
+ onAdd,
50
+ addLabel = "Add",
42
51
  class: className,
43
52
  onGroupDragOver,
44
53
  onGroupDragLeave,
@@ -70,38 +79,58 @@
70
79
  {#each groups as group, index (getKey(group))}
71
80
  {@const key = getKey(group)}
72
81
  {@const isCollapsed = collapsed.has(key)}
82
+ <!-- Groups part by space, not a rule: the header band already marks where one starts
83
+ (DESIGN.md rule 19). -->
73
84
  <div
74
- class={cn(index > 0 && "mt-3 border-t border-border/50 pt-3")}
85
+ class={cn(index > 0 && "mt-4")}
75
86
  role={onGroupDragOver || onGroupDrop ? "group" : undefined}
76
87
  ondragover={onGroupDragOver ? (e) => onGroupDragOver(e, group) : undefined}
77
88
  ondragleave={onGroupDragLeave}
78
89
  ondrop={onGroupDrop ? (e) => onGroupDrop(e, group) : undefined}
79
90
  >
91
+ <!-- A quiet band, 32px like a row, so the header reads as the group's lid
92
+ without a border. The tint is on the foreground: bg-muted vanishes on the canvas. -->
80
93
  <div
81
- class="group/header flex items-center gap-1 rounded-lg transition-colors hover:bg-muted/60"
94
+ class="group/header hover-row flex min-h-8 items-center gap-1 rounded-lg bg-foreground/4 transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 motion-reduce:transition-none any-pointer-coarse:min-h-11"
82
95
  >
83
96
  <button
84
97
  type="button"
85
- class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2 text-left"
98
+ class="flex min-w-0 flex-1 cursor-pointer items-center gap-2 self-stretch rounded-lg px-2.5 text-left outline-none focus-visible:shadow-[var(--ring-focus)]"
86
99
  onclick={() => toggle(key)}
87
100
  aria-expanded={!isCollapsed}
88
101
  >
102
+ <!-- The accordion chevron: it turns on the fast step and holds still under
103
+ reduced motion, where the open state is aria-expanded and the rows. -->
89
104
  <CaretDown
90
105
  class={cn(
91
- "size-3 shrink-0 text-muted-foreground/60 transition-transform duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)]",
106
+ "size-3 shrink-0 text-muted-foreground transition-transform duration-fast ease-smooth-out motion-reduce:transition-none",
92
107
  isCollapsed && "-rotate-90",
93
108
  )}
94
109
  weight="bold"
95
110
  />
96
111
  {@render header(group, { collapsed: isCollapsed })}
112
+ {#if getCount}
113
+ <span
114
+ class="rounded-full bg-foreground/6 px-1.5 text-xs leading-5 font-medium text-muted-foreground tabular-nums"
115
+ >{getCount(group)}</span
116
+ >
117
+ {/if}
97
118
  </button>
98
119
  {#if actions}
99
- <div
100
- class="flex shrink-0 items-center gap-1 pe-2 opacity-0 transition-opacity group-hover/header:opacity-100 focus-within:opacity-100 any-pointer-coarse:opacity-100"
101
- >
120
+ <div class="row-action flex shrink-0 items-center gap-1 pe-1">
102
121
  {@render actions(group)}
103
122
  </div>
104
123
  {/if}
124
+ {#if onAdd}
125
+ <button
126
+ type="button"
127
+ class="t-press me-1 grid size-6 shrink-0 place-items-center rounded-sm text-muted-foreground transition-colors duration-quick ease-smooth-out outline-none hover:bg-foreground/6 hover:text-foreground focus-visible:shadow-[var(--ring-focus)] motion-reduce:transition-none any-pointer-coarse:size-11"
128
+ aria-label={addLabel}
129
+ onclick={() => onAdd(group)}
130
+ >
131
+ <Plus class="size-3.5" weight="bold" aria-hidden="true" />
132
+ </button>
133
+ {/if}
105
134
  </div>
106
135
 
107
136
  {#if !isCollapsed}
@@ -125,7 +125,7 @@
125
125
  <!-- Sticky, because the section tabs live here. `-mx-4 px-4` covers the
126
126
  gutter, `-mt-4 py-4` pulls the top padding inside the sticky box. -->
127
127
  <div
128
- class="@container/toolbar sticky top-0 z-20 -mx-4 -mt-4 bg-background/95 px-4 py-4 backdrop-blur supports-[backdrop-filter]:bg-background/70"
128
+ class="@container/toolbar sticky top-0 z-20 -mx-4 -mt-4 bg-card/95 px-4 py-4 backdrop-blur supports-[backdrop-filter]:bg-card/70"
129
129
  >
130
130
  <div class={cn("flex flex-wrap items-center gap-2", toolbarClass)}>
131
131
  <!-- The section tabs lead: which page comes before which slice of it.
@@ -1,19 +1,19 @@
1
1
  import { cn } from "../../lib/utils.js";
2
2
 
3
- /**
4
- * The list-page table rhythm, Attio's: one 40px row, a hairline under each, a flat hover.
5
- */
3
+ /** The list-page table rhythm: 32px rows (DESIGN.md rule 19), hairlines, a flat hover. */
4
+
5
+ // The header is a muted band tinted off the foreground, so it shows on card and canvas.
6
6
  export const listTable = {
7
- /** The header row: 36px, a hairline under it. On the cells: `thead` zeroes its row's border. */
7
+ /** The header row: 32px, muted, a hairline under it. On the cells: `thead` zeroes its row's border. */
8
8
  headRow: (className?: string) =>
9
9
  cn(
10
- "hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-9 [&>th]:border-b [&>th]:border-border",
10
+ "hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-8 [&>th]:border-b [&>th]:border-border [&>th]:bg-foreground/3 [&>th]:text-xs [&>th]:font-medium [&>th]:text-muted-foreground",
11
11
  className,
12
12
  ),
13
13
  /** A body row. The shared row lifts on hover; a flush list row does not. */
14
14
  row: (className?: string) =>
15
15
  cn(
16
- "border-b border-border/50 last:border-b-0 hover:translate-y-0 hover:bg-muted/40 hover:shadow-none [&>td]:h-10 [&>td]:py-0",
16
+ "border-b border-border/50 last:border-b-0 hover:translate-y-0 hover:bg-foreground/3 hover:shadow-none [&>td]:h-8 [&>td]:py-0 [&>td]:tabular-nums any-pointer-coarse:[&>td]:h-11",
17
17
  className,
18
18
  ),
19
19
  };
@@ -201,7 +201,7 @@
201
201
  @media (prefers-reduced-motion: reduce) {
202
202
  .md-cols > .md-rail,
203
203
  .md-cols > .md-detail {
204
- animation-duration: 1ms;
204
+ animation: none;
205
205
  }
206
206
  }
207
207
  </style>
@@ -3,8 +3,8 @@
3
3
  * A class, not a wrapper: the shell's `<aside>` already owns the scroll height.
4
4
  */
5
5
  // Copies of this string drifted to `rounded-lg`, the control radius, not the container one.
6
- export const queueRailClass =
7
- "flex flex-col overflow-hidden rounded-xl border border-border bg-card";
6
+ // No `border`: the card shadow draws its own hairline, and both drew a 2px edge.
7
+ export const queueRailClass = "flex flex-col overflow-hidden rounded-xl bg-card shadow-card";
8
8
 
9
9
  /**
10
10
  * The rail column track, passed to `MasterDetail`'s `railWidth`. One page ran
@@ -33,7 +33,7 @@
33
33
  {#if Icon}
34
34
  <Icon
35
35
  weight={iconWeight}
36
- class={cn("size-6", iconClass ?? "text-muted-foreground/40")}
36
+ class={cn("size-6", iconClass ?? "text-muted-foreground")}
37
37
  aria-hidden="true"
38
38
  />
39
39
  {/if}
@@ -91,10 +91,10 @@
91
91
  <div
92
92
  bind:this={row}
93
93
  class={cn(
94
- // `group/row` too: `CopyHandoff` waits for `group-hover/row`, and a bare
95
- // `group` left it invisible on every rail.
96
- "group group/row relative border-t border-border first:border-t-0",
97
- selected ? "bg-accent" : "hover:bg-muted/50",
94
+ // `hover-row`: a caller's `row-action` (CopyHandoff) reveals on it. DESIGN.md Patterns.
95
+ "group group/row hover-row relative border-t border-border first:border-t-0",
96
+ "transition-colors duration-quick ease-smooth-out motion-reduce:transition-none",
97
+ selected ? "bg-accent" : "hover:bg-foreground/4",
98
98
  className,
99
99
  )}
100
100
  >
@@ -102,7 +102,7 @@
102
102
  type="button"
103
103
  class={cn(
104
104
  "t-press flex w-full items-start gap-3 px-4 py-3 text-left any-pointer-coarse:py-3.5",
105
- "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset",
105
+ "focus-visible:outline-none focus-visible:shadow-(--ring-focus-inset)",
106
106
  // Room for the actions only where they always show; a hover-only reserve
107
107
  // cost every title 96px. The overlay below sits on its own surface instead.
108
108
  actions && "any-pointer-coarse:pr-24",