@heyramzi/ui 2.20.0 → 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 +55 -36
  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
@@ -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",
@@ -72,6 +72,7 @@
72
72
  call site that forgot would open the row as well. -->
73
73
  <!-- svelte-ignore a11y_no_static_element_interactions -->
74
74
  <div
75
+ role="presentation"
75
76
  class="inline-flex shrink-0"
76
77
  onclick={(e) => e.stopPropagation()}
77
78
  onkeydown={(e) => e.stopPropagation()}
@@ -89,7 +90,7 @@
89
90
  ? "inline-flex shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
90
91
  : cn(
91
92
  "inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
92
- size === "sm" ? "px-1.5 py-0.5 text-[11px]" : "px-2 py-1 text-xs",
93
+ size === "sm" ? "px-1.5 py-0.5 text-2xs" : "px-2 py-1 text-xs",
93
94
  ),
94
95
  variant === "icon" && (size === "sm" ? "size-5" : "size-6"),
95
96
  className,
@@ -14,10 +14,11 @@ import { tv } from "tailwind-variants";
14
14
  // box shows only under the pointer and on focus.
15
15
  export const pickerTriggerClass = tv({
16
16
  base: [
17
- // 13px at weight 500 is Linear's own property row, measured 31 Aug 2026 on a
18
- // 16px root. 14px reads looser than the reference, and it shows in the sidebar.
19
- "t-press inline-flex min-w-0 items-center gap-1.5 rounded-md text-[13px] font-medium whitespace-nowrap select-none",
20
- "hover:bg-muted data-[state=open]:bg-muted",
17
+ // `length:` stops tailwind-merge reading `text-control` as a colour (craft.css, Type).
18
+ "t-press inline-flex min-w-0 items-center gap-1.5 rounded-lg text-(length:--text-control) font-medium whitespace-nowrap select-none",
19
+ // The hover box is a foreground tint: property rows sit on the canvas too, where bg-muted
20
+ // is 0.005 L away and vanished. Radius is the control step, 10px.
21
+ "transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 data-[state=open]:bg-foreground/6 motion-reduce:transition-none",
21
22
  "disabled:pointer-events-none disabled:opacity-50",
22
23
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
23
24
  ],
@@ -34,7 +35,7 @@ export const pickerTriggerClass = tv({
34
35
  */
35
36
  block: { true: "flex w-full", false: "w-fit" },
36
37
  /** No value yet: the label is a prompt, so it recedes until hovered. */
37
- empty: { true: "text-muted-foreground/70 hover:text-muted-foreground", false: "" },
38
+ empty: { true: "text-muted-foreground hover:text-foreground", false: "" },
38
39
  /** A row that carries no action still needs the column's box. */
39
40
  inert: { true: "hover:bg-transparent cursor-default", false: "" },
40
41
  },
@@ -32,7 +32,7 @@
32
32
  <!-- =========================== -->
33
33
  <Card.Root class={cn("gap-0 py-0", className)}>
34
34
  <header class="flex min-h-14 items-center justify-between gap-3 border-b px-4 py-3 sm:px-6">
35
- <Card.Title as="h3" class="min-w-0 text-base font-medium [overflow-wrap:anywhere]"
35
+ <Card.Title as="h3" class="min-w-0 text-base font-medium text-balance [overflow-wrap:anywhere]"
36
36
  >{title}</Card.Title
37
37
  >
38
38
  {#if actions}
@@ -37,7 +37,7 @@
37
37
  <SectionLabel class="min-w-0 [overflow-wrap:anywhere]">{title}</SectionLabel>
38
38
  {#if count !== undefined}
39
39
  <span
40
- class="shrink-0 rounded-md bg-muted px-1.5 py-0.5 text-xs leading-none font-medium tabular-nums text-muted-foreground"
40
+ class="shrink-0 rounded-full bg-foreground/6 px-1.5 py-0.5 text-xs leading-none font-medium tabular-nums text-muted-foreground"
41
41
  aria-label={`${count.toLocaleString()} ${countLabel}`}
42
42
  >
43
43
  {count.toLocaleString()}
@@ -45,7 +45,9 @@
45
45
  {/if}
46
46
  </svelte:element>
47
47
  {#if description}
48
- <p class="text-sm text-muted-foreground [overflow-wrap:anywhere]">{description}</p>
48
+ <p class="text-sm text-pretty text-muted-foreground [overflow-wrap:anywhere]">
49
+ {description}
50
+ </p>
49
51
  {/if}
50
52
  </div>
51
53
  {#if actions}
@@ -28,12 +28,12 @@
28
28
  <!-- =========================== -->
29
29
  <span
30
30
  class={cn(
31
- "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground",
31
+ "flex items-center gap-2 text-xs font-semibold text-balance text-muted-foreground",
32
32
  className,
33
33
  )}
34
34
  >
35
35
  {#if Icon}
36
- <Icon class="size-4 text-brand" aria-hidden="true" />
36
+ <Icon class="size-4" weight="bold" aria-hidden="true" />
37
37
  {/if}
38
38
  {@render children()}
39
39
  </span>
@@ -157,7 +157,7 @@
157
157
  <span
158
158
  role="presentation"
159
159
  aria-hidden="true"
160
- class="flex shrink-0 items-center px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase"
160
+ class="flex shrink-0 items-center px-2 text-xs font-medium text-muted-foreground"
161
161
  >{tab.group.label}</span
162
162
  >
163
163
  {/if}
@@ -65,7 +65,7 @@
65
65
  {#each strip.runs as run, index (index)}
66
66
  <div class={cn("flex shrink-0 items-center gap-2", index === firstTitled && "ml-auto")}>
67
67
  {#if run.label}
68
- <span class="text-[10px] font-medium tracking-wider text-muted-foreground uppercase">
68
+ <span class="text-xs font-medium text-muted-foreground">
69
69
  {run.label}
70
70
  </span>
71
71
  {/if}
@@ -64,7 +64,7 @@
64
64
  src={image}
65
65
  {label}
66
66
  class={cn(
67
- "opacity-75 transition-opacity duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-hover/segment:opacity-100",
67
+ "opacity-75 transition-opacity duration-fast ease-smooth-out motion-reduce:transition-none group-hover/segment:opacity-100",
68
68
  on && "opacity-100",
69
69
  auto && "group-data-[state=active]/segment:opacity-100",
70
70
  )}
@@ -76,9 +76,7 @@
76
76
  {label}
77
77
 
78
78
  {#if badge}
79
- <span class="text-primary text-[10px] font-semibold tracking-wide whitespace-nowrap uppercase"
80
- >{badge}</span
81
- >
79
+ <span class="text-primary text-xs font-semibold whitespace-nowrap">{badge}</span>
82
80
  {/if}
83
81
 
84
82
  {#if count != null}
@@ -88,8 +86,8 @@
88
86
 
89
87
  // WHY min-width equals height with 4px padding: `min-w-5 px-1.5` made a digit
90
88
  // 20 by 18, so `rounded-full` drew an ellipse.
91
- "inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] leading-none font-medium tabular-nums",
92
- "bg-muted-foreground/15 text-muted-foreground",
89
+ "inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-xs leading-none font-medium tabular-nums",
90
+ "bg-foreground/8 text-muted-foreground",
93
91
  on && "bg-brand/14 text-brand",
94
92
  auto &&
95
93
  "group-data-[state=active]/segment:bg-brand/14 group-data-[state=active]/segment:text-brand",