@heyramzi/ui 2.19.2 → 2.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +2 -0
  2. package/dist/charts/dashboard/data-table.js +1 -2
  3. package/dist/components/list-page/filter-section.js +1 -1
  4. package/dist/components/list-page/list-table.js +5 -6
  5. package/dist/components/list-page/queue-rail.js +2 -1
  6. package/dist/components/picker/trigger.js +3 -3
  7. package/dist/components/segmented/segmented.js +5 -5
  8. package/dist/components/work-parts/looks.js +1 -1
  9. package/dist/shadcn/dialog/index.js +3 -2
  10. package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
  11. package/dist/shadcn/sheet/index.js +3 -2
  12. package/dist/shadcn/skeleton/index.js +3 -2
  13. package/dist/status/index.js +1 -0
  14. package/dist/work/kanban-drag.js +13 -7
  15. package/package.json +1 -1
  16. package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
  17. package/src/ai/loading-state/PixelLoader.svelte +1 -1
  18. package/src/animations/dropdown.css +19 -10
  19. package/src/animations/modal.css +7 -2
  20. package/src/charts/charts.css +3 -5
  21. package/src/charts/dashboard/dashboard-section.svelte +1 -1
  22. package/src/charts/dashboard/data-table.ts +1 -2
  23. package/src/charts/dashboard/stat-tile.svelte +1 -1
  24. package/src/charts/number-chart.css +8 -7
  25. package/src/charts/pop-value.svelte +68 -0
  26. package/src/charts/primitives/chart-tooltip.svelte +1 -1
  27. package/src/charts/reference-line.css +1 -1
  28. package/src/charts/stat-card.css +4 -4
  29. package/src/components/copy/copy-button.svelte +11 -10
  30. package/src/components/data-table/row-arrow.svelte +1 -1
  31. package/src/components/data-table/row-context-menu.svelte +1 -1
  32. package/src/components/data-table/sortable-head.svelte +2 -2
  33. package/src/components/data-table/table-skeleton.svelte +5 -5
  34. package/src/components/docs/doc-page.svelte +1 -3
  35. package/src/components/docs/on-this-page.svelte +4 -4
  36. package/src/components/field-row/field-row.svelte +1 -1
  37. package/src/components/list-page/detail-empty.svelte +3 -8
  38. package/src/components/list-page/detail-header.svelte +1 -1
  39. package/src/components/list-page/detail-row.svelte +2 -1
  40. package/src/components/list-page/empty-state.svelte +15 -7
  41. package/src/components/list-page/filter-item.svelte +2 -2
  42. package/src/components/list-page/filter-popover.svelte +1 -1
  43. package/src/components/list-page/filter-section.ts +1 -4
  44. package/src/components/list-page/grouped-list.svelte +37 -8
  45. package/src/components/list-page/list-page-shell.svelte +1 -1
  46. package/src/components/list-page/list-table.ts +6 -6
  47. package/src/components/list-page/master-detail.svelte +1 -1
  48. package/src/components/list-page/queue-rail.ts +2 -2
  49. package/src/components/list-page/rail-empty.svelte +1 -1
  50. package/src/components/list-page/rail-row.svelte +5 -5
  51. package/src/components/open-in/open-in.svelte +2 -1
  52. package/src/components/picker/trigger.ts +6 -5
  53. package/src/components/section-card/section-card.svelte +1 -1
  54. package/src/components/section-header/section-header.svelte +4 -2
  55. package/src/components/section-header/section-label.svelte +2 -2
  56. package/src/components/segmented/animated-tabs.svelte +1 -1
  57. package/src/components/segmented/section-runs.svelte +1 -1
  58. package/src/components/segmented/segment.svelte +4 -6
  59. package/src/components/segmented/segmented-track.svelte +18 -52
  60. package/src/components/segmented/segmented.ts +5 -5
  61. package/src/components/table-view/table-view.svelte +43 -24
  62. package/src/components/tags-input/tags-input.svelte +17 -31
  63. package/src/components/work-parts/looks.ts +1 -1
  64. package/src/editor/content.css +3 -3
  65. package/src/editor/editor.svelte +1 -1
  66. package/src/inbox/thread-inbox.svelte +2 -9
  67. package/src/panel/side-panel.svelte +9 -4
  68. package/src/record/info-list.svelte +7 -8
  69. package/src/record/project-list.svelte +1 -1
  70. package/src/record/record-demo.svelte +2 -4
  71. package/src/record/record-shell.svelte +1 -1
  72. package/src/record/record-stats.svelte +9 -3
  73. package/src/record/steps-rail.svelte +7 -11
  74. package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
  75. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
  76. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
  77. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
  78. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
  79. package/src/shadcn/badge/badge.svelte +3 -1
  80. package/src/shadcn/bubble/bubble-content.svelte +1 -1
  81. package/src/shadcn/bubble/bubble.svelte +1 -1
  82. package/src/shadcn/button/button.svelte +11 -5
  83. package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
  84. package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
  85. package/src/shadcn/calendar/calendar.svelte +1 -1
  86. package/src/shadcn/card/card.svelte +2 -1
  87. package/src/shadcn/checkbox/checkbox.svelte +1 -1
  88. package/src/shadcn/code-block/code-block-actions.svelte +1 -1
  89. package/src/shadcn/command/command-dialog.svelte +2 -2
  90. package/src/shadcn/command/command-group.svelte +1 -1
  91. package/src/shadcn/command/command-input.svelte +1 -1
  92. package/src/shadcn/command/command-item.svelte +1 -1
  93. package/src/shadcn/dialog/dialog-body.svelte +22 -0
  94. package/src/shadcn/dialog/dialog-content.svelte +19 -4
  95. package/src/shadcn/dialog/dialog-footer.svelte +19 -1
  96. package/src/shadcn/dialog/dialog-header.svelte +6 -1
  97. package/src/shadcn/dialog/dialog-title.svelte +2 -1
  98. package/src/shadcn/dialog/index.ts +3 -0
  99. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
  100. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  101. package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
  102. package/src/shadcn/field/field-separator.svelte +1 -1
  103. package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
  104. package/src/shadcn/input/input.svelte +2 -2
  105. package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
  106. package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
  107. package/src/shadcn/popover/popover-content.svelte +1 -1
  108. package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
  109. package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
  110. package/src/shadcn/select/select-content.svelte +6 -2
  111. package/src/shadcn/select/select-item.svelte +1 -1
  112. package/src/shadcn/select/select-trigger.svelte +1 -1
  113. package/src/shadcn/sheet/index.ts +3 -0
  114. package/src/shadcn/sheet/sheet-body.svelte +22 -0
  115. package/src/shadcn/sheet/sheet-content.svelte +27 -5
  116. package/src/shadcn/sheet/sheet-footer.svelte +14 -2
  117. package/src/shadcn/sheet/sheet-header.svelte +1 -1
  118. package/src/shadcn/sheet/sheet-title.svelte +1 -1
  119. package/src/shadcn/sheet/sheet.css +11 -5
  120. package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
  121. package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
  122. package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
  123. package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
  124. package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
  125. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
  126. package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
  127. package/src/shadcn/sidebar/sidebar.svelte +1 -1
  128. package/src/shadcn/skeleton/index.ts +3 -0
  129. package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
  130. package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
  131. package/src/shadcn/skeleton/skeleton.svelte +5 -1
  132. package/src/shadcn/slider/slider.svelte +1 -1
  133. package/src/shadcn/switch/switch.svelte +2 -2
  134. package/src/shadcn/table/table-row.svelte +1 -1
  135. package/src/shadcn/tabs/tabs-list.svelte +1 -1
  136. package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
  137. package/src/shadcn/textarea/textarea.svelte +1 -1
  138. package/src/shadcn/toggle/toggle.svelte +2 -2
  139. package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
  140. package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
  141. package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
  142. package/src/shadcn/trace/trace.svelte +1 -1
  143. package/src/status/README.md +1 -1
  144. package/src/status/index.ts +1 -0
  145. package/src/status/status-badge.svelte +21 -7
  146. package/src/status/status-dot.svelte +6 -11
  147. package/src/status/status-pill.svelte +50 -0
  148. package/src/thread/thread.svelte +54 -136
  149. package/src/tokens/craft.css +88 -30
  150. package/src/tokens/palette.css +16 -9
  151. package/src/work/demo.svelte +48 -42
  152. package/src/work/detail/detail-demo.svelte +33 -24
  153. package/src/work/detail/task-chip.svelte +4 -4
  154. package/src/work/detail/task-detail.svelte +41 -108
  155. package/src/work/detail/task-property.svelte +7 -13
  156. package/src/work/detail/task-thread.svelte +48 -119
  157. package/src/work/docs/doc-editor-page.svelte +5 -4
  158. package/src/work/docs/doc-menu.svelte +9 -4
  159. package/src/work/docs/docs-demo.svelte +4 -4
  160. package/src/work/docs/edited-chip.svelte +11 -4
  161. package/src/work/gantt/gantt-bar.svelte +18 -14
  162. package/src/work/gantt/gantt-grid.svelte +7 -7
  163. package/src/work/gantt/gantt-group-row.svelte +7 -10
  164. package/src/work/gantt/gantt-header.svelte +3 -1
  165. package/src/work/gantt/gantt-task-row.svelte +19 -9
  166. package/src/work/gantt/gantt.css +4 -5
  167. package/src/work/inbox/inbox-card.svelte +32 -10
  168. package/src/work/inbox/inbox-draft.svelte +2 -2
  169. package/src/work/kanban-column.svelte +7 -15
  170. package/src/work/kanban-drag.ts +13 -8
  171. package/src/work/task-gantt.svelte +2 -1
  172. package/src/work/task-list.svelte +54 -53
  173. package/src/work/time/time-chip.svelte +16 -19
  174. package/src/work/time/time-dot.svelte +2 -1
  175. package/src/work/time/time-ring.svelte +2 -3
  176. package/src/work/time/time-tracker.svelte +72 -166
@@ -16,7 +16,7 @@
16
16
  interface Props {
17
17
  /** Fill the row rather than sit at its natural width. */
18
18
  fill?: boolean;
19
- /** `pill`, a bordered well with a travelling highlight, or `underline`, a travelling rule. */
19
+ /** `pill`, a grey track with a white thumb that slides, or `underline`, a travelling rule. */
20
20
  variant?: SegmentedVariant;
21
21
  class?: string;
22
22
  children: Snippet;
@@ -64,45 +64,6 @@
64
64
  <!-- STYLES -->
65
65
  <!-- =========================== -->
66
66
  <style>
67
- /* Inset separators keep the strip one bordered object. */
68
- .track :global(.ui-segment)::before {
69
- content: "";
70
- position: absolute;
71
- top: 22%;
72
- bottom: 22%;
73
- left: 0;
74
- width: 1px;
75
- background-color: var(--color-border);
76
- transition: opacity var(--duration-fast) var(--ease-smooth-out);
77
- pointer-events: none;
78
- }
79
-
80
- .track :global(.pill + .ui-segment)::before,
81
- .track :global([data-slot="tabs-list"] > .ui-segment:first-child)::before {
82
- display: none;
83
- }
84
-
85
- /* The highlight and hover wash hide the seams on either side. */
86
- .track :global(.ui-segment[data-state="active"])::before,
87
- .track :global(.ui-segment[aria-current="page"])::before,
88
- .track :global(.ui-segment[aria-pressed="true"])::before,
89
- .track :global(.ui-segment[data-state="active"] + .ui-segment)::before,
90
- .track :global(.ui-segment[aria-current="page"] + .ui-segment)::before,
91
- .track :global(.ui-segment[aria-pressed="true"] + .ui-segment)::before,
92
- .track
93
- :global(
94
- [data-slot="tabs-list"]:has(> .ui-segment:last-child[data-state="active"]) + .ui-segment
95
- )::before {
96
- opacity: 0;
97
- }
98
-
99
- @media (hover: hover) and (pointer: fine) {
100
- .track :global(.ui-segment:hover)::before,
101
- .track :global(.ui-segment:hover + .ui-segment)::before {
102
- opacity: 0;
103
- }
104
- }
105
-
106
67
  /* The label's colour lands with the pill, on the same fast step. */
107
68
  .track :global(.ui-segment) {
108
69
  transition:
@@ -122,7 +83,7 @@
122
83
  [data-state="open"]
123
84
  )
124
85
  ) {
125
- background-color: color-mix(in oklch, var(--color-muted) 55%, transparent);
86
+ background-color: color-mix(in oklch, var(--color-foreground) 5%, transparent);
126
87
  }
127
88
  }
128
89
 
@@ -134,17 +95,18 @@
134
95
  z-index: 0;
135
96
  width: 0;
136
97
  opacity: 0;
137
- /* Concentric with the border and the 2px inset. */
138
- border-radius: calc(var(--radius-lg) - 3px);
139
- background-color: var(--color-muted);
140
- box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-foreground) 6%, transparent);
98
+ /* The white thumb on the grey track. Concentric: the track's control radius less its
99
+ 2px inset. The resting button shadow lifts it; no border, so no seam to fight. */
100
+ border-radius: calc(var(--radius-lg) - 2px);
101
+ background-color: var(--color-card);
102
+ box-shadow: var(--elev-btn), var(--elev-hairline);
141
103
  transform: translateX(0);
142
104
  /* transitions-dev `16-tabs-sliding`: the fast step, 250ms. */
143
105
  transition:
144
106
  transform var(--duration-fast) var(--ease-smooth-out),
145
107
  width var(--duration-fast) var(--ease-smooth-out),
146
108
  opacity var(--duration-quick) var(--ease-smooth-out);
147
- will-change: transform, width;
109
+ will-change: transform;
148
110
  pointer-events: none;
149
111
  }
150
112
 
@@ -152,16 +114,20 @@
152
114
  so a server-rendered strip is right before any JS arrives. */
153
115
  .track:not([data-pill="on"])
154
116
  :global(:is([data-state="active"], [aria-current="page"], [aria-pressed="true"])) {
155
- background-color: var(--color-muted);
156
- box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-foreground) 6%, transparent);
117
+ background-color: var(--color-card);
118
+ box-shadow: var(--elev-btn), var(--elev-hairline);
157
119
  }
158
120
 
159
- /* The underline: no seams, no wash, and the pill becomes a 2px rule on the
160
- hairline. `--segmented-rule` lets a brand colour it from a class. */
161
- .track[data-variant="underline"] :global(.ui-segment)::before {
162
- display: none;
121
+ /* On dark the card is the darker surface, so the thumb lifts toward the text instead. */
122
+ :global(.dark) .pill,
123
+ :global(.dark)
124
+ .track:not([data-pill="on"])
125
+ :global(:is([data-state="active"], [aria-current="page"], [aria-pressed="true"])) {
126
+ background-color: color-mix(in oklch, var(--color-foreground) 14%, var(--color-card));
163
127
  }
164
128
 
129
+ /* The underline: no wash, and the pill becomes a 2px rule on the
130
+ hairline. `--segmented-rule` lets a brand colour it from a class. */
165
131
  @media (hover: hover) {
166
132
  .track[data-variant="underline"]
167
133
  :global(
@@ -17,10 +17,10 @@ export const segmentedTrackClass = tv({
17
17
  // WHY no size variant: `sm` drew 24px, and every tab control must share one height.
18
18
  base: "h-7 any-pointer-coarse:h-11 shrink-0 items-stretch",
19
19
  variants: {
20
- // `pill`: a bordered well with a travelling highlight. `underline`: bare
20
+ // `pill`: grey track, sliding white thumb. `underline`: bare
21
21
  // labels over one hairline, the active one marked by a rule that travels.
22
22
  variant: {
23
- pill: "overflow-hidden rounded-lg border border-border p-0.5 any-pointer-coarse:border-0 any-pointer-coarse:px-[3px] any-pointer-coarse:py-0",
23
+ pill: "overflow-hidden rounded-lg bg-foreground/6 p-0.5",
24
24
  underline: "gap-5 border-b border-border",
25
25
  },
26
26
  // Fill the row rather than sit at its natural width. For a narrow rail.
@@ -46,16 +46,16 @@ export const segmentedTriggerClass = tv({
46
46
  "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
47
47
  // WHY an inset focus ring: the track clips to its rounded corners, so an
48
48
  // outside ring was cut in half on the end segments.
49
- "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none",
49
+ "focus-visible:shadow-(--ring-focus-inset) focus-visible:outline-none",
50
50
  // WHY no height on the trigger: an icon makes a segment taller than a word.
51
51
  // `h-full` against the track's `items-stretch` keeps every segment the same box.
52
52
  "h-full text-xs",
53
53
  ],
54
54
  variants: {
55
- // The pill sits concentric in the track's 2px inset; the underline has no well, so
55
+ // The pill sits concentric in the track's 2px inset , so 8px; the underline has no well, so
56
56
  // no padding, and its rule is as wide as the label.
57
57
  variant: {
58
- pill: "rounded-[calc(var(--radius-lg)-3px)] px-2.5 any-pointer-coarse:px-3.5",
58
+ pill: "rounded-[calc(var(--radius-lg)-2px)] px-2.5 any-pointer-coarse:px-3.5",
59
59
  underline: "rounded-sm px-0",
60
60
  },
61
61
  // WHY `min-w-fit flex-auto`, not equal fractions: fractions cut "Approved" to
@@ -34,10 +34,12 @@
34
34
  /** Draws a cell its own way. Print `text` for the plain case. */
35
35
  cell?: Snippet<[TableCell<Row>]>;
36
36
  rowClass?: (row: Row) => string | undefined;
37
- /** Below this width the table scrolls sideways inside its frame. */
37
+ /**
38
+ * Below this width the table scrolls sideways inside its frame. Defaults to the columns'
39
+ * own widths (8rem a text column, 5rem a number), so a phone scrolls rather than splits words.
40
+ * Pass "0" for a table that should squeeze.
41
+ */
38
42
  minWidth?: string;
39
- /** Ignored: the panel or card under a table is its frame, so it draws none of its own. */
40
- bare?: boolean;
41
43
  class?: string;
42
44
  /** The sorted column and its direction. Bind it; columns with `sortable` set it. */
43
45
  sort?: SortState<Row>;
@@ -91,7 +93,7 @@
91
93
  footer,
92
94
  cell,
93
95
  rowClass,
94
- minWidth = "0",
96
+ minWidth,
95
97
  class: className,
96
98
  sort = $bindable(null),
97
99
  rowHref,
@@ -120,6 +122,17 @@
120
122
  // DERIVED
121
123
  // =========================
122
124
  let span = $derived(columns.length + (selectable ? 1 : 0));
125
+ // WHY a floor from the columns: with none, a 390px panel squeezed a text column until it broke
126
+ // "Handover" mid-word. A column hidden on small screens doesn't count, since it's gone there.
127
+ let floor = $derived.by(() => {
128
+ const parts = columns
129
+ .filter((c) => !c.hideBelow)
130
+ .map((c) =>
131
+ c.width && /^[\d.]+(rem|px)$/.test(c.width) ? c.width : c.numeric ? "5rem" : "8rem",
132
+ );
133
+ if (selectable) parts.push("2.5rem");
134
+ return parts.length ? `calc(${parts.join(" + ")})` : "0";
135
+ });
123
136
  let selectedCount = $derived(rows.filter((row) => selected.has(rowKey(row))).length);
124
137
  let allSelected = $derived(rows.length > 0 && selectedCount === rows.length);
125
138
  let someSelected = $derived(selectedCount > 0 && !allSelected);
@@ -230,12 +243,15 @@
230
243
  {#each columns as column (column.key)}
231
244
  <Table.Cell
232
245
  class={cn(
233
- "align-top",
234
- column.numeric ? "text-end tabular-nums" : "whitespace-normal [overflow-wrap:anywhere]",
246
+ "h-8 py-1.5 align-top any-pointer-coarse:h-11",
247
+ // WHY break-words, not overflow-wrap anywhere: anywhere shrinks a cell's min-content to one
248
+ // letter, so the table split words before it scrolled. break-words only splits a word
249
+ // longer than the whole column, a URL.
250
+ column.numeric ? "text-end tabular-nums" : "whitespace-normal break-words",
235
251
  column.muted && "text-muted-foreground",
236
252
  column.interactive && "relative z-10",
237
253
  column.hideBelow && HIDE[column.hideBelow],
238
- column.sticky && "bg-background sticky left-0 z-10",
254
+ column.sticky && "bg-card sticky left-0 z-10",
239
255
  column.class,
240
256
  )}
241
257
  >
@@ -280,9 +296,12 @@
280
296
  onclick={onRowClick ? (event: MouseEvent) => rowClick(event, row) : undefined}
281
297
  onkeydown={onRowClick ? (event: KeyboardEvent) => rowKeydown(event, row) : undefined}
282
298
  class={cn(
283
- href && "relative t-press active:bg-muted/50",
299
+ // Rows, not a card per row (DESIGN.md rule 19): a hairline under each and a flat
300
+ // hover tint. The atom's row lifts; this one does not.
301
+ "border-b border-border/70 last:border-b-0 hover:translate-y-0 hover:bg-foreground/3 hover:shadow-none",
302
+ href && "relative t-press active:bg-foreground/6",
284
303
  onRowClick &&
285
- "t-press active:bg-muted/50 focus-visible:ring-ring cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-inset",
304
+ "t-press active:bg-foreground/6 cursor-pointer outline-none focus-visible:shadow-(--ring-focus-inset)",
286
305
  rowClass?.(row),
287
306
  )}
288
307
  >
@@ -303,13 +322,13 @@
303
322
  {href}
304
323
  aria-label={linkName(row)}
305
324
  onclick={onRowLink ? (event: MouseEvent) => linkClick(event, row) : undefined}
306
- class="absolute inset-0 z-0 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
325
+ class="absolute inset-0 z-0 outline-none focus-visible:shadow-(--ring-focus-inset)"
307
326
  ></a>
308
327
  </td>
309
328
  {/if}
310
329
  </Table.Row>
311
330
  {#if subRow && expanded?.(row)}
312
- <Table.Row class="bg-muted/20 hover:bg-muted/20 hover:translate-y-0 hover:shadow-none">
331
+ <Table.Row class="bg-foreground/2 hover:bg-foreground/2 hover:translate-y-0 hover:shadow-none">
313
332
  <Table.Cell colspan={span + (rowHref ? 1 : 0)} class="whitespace-normal">
314
333
  {@render subRow(row)}
315
334
  </Table.Cell>
@@ -317,16 +336,16 @@
317
336
  {/if}
318
337
  {/snippet}
319
338
 
320
- <!-- WHY no frame: the surface under a table is its frame, the white panel or a section card. -->
339
+ <!-- WHY no frame: the surface under a table is its frame, the white panel or a section card (DESIGN.md, Patterns: tables). -->
321
340
  <div class={cn("min-w-0", className)}>
322
341
  <Table.Root
323
342
  aria-label={label}
324
343
  aria-busy={loading || undefined}
325
- style={`min-width: ${minWidth}`}
344
+ style={`min-width: ${minWidth ?? floor}`}
326
345
  class="min-w-0"
327
346
  >
328
- <Table.Header>
329
- <Table.Row>
347
+ <Table.Header class="bg-foreground/3">
348
+ <Table.Row class="hover:translate-y-0 hover:bg-transparent hover:shadow-none">
330
349
  {#if selectable}
331
350
  <Table.Head class="w-10">
332
351
  <Checkbox
@@ -343,9 +362,9 @@
343
362
  class={cn(
344
363
  "whitespace-normal",
345
364
  column.numeric && "text-end",
346
- column.sortable && "h-10 p-0",
365
+ column.sortable && "h-8 p-0",
347
366
  column.hideBelow && HIDE[column.hideBelow],
348
- column.sticky && "bg-background sticky left-0 z-20",
367
+ column.sticky && "bg-card sticky left-0 z-20",
349
368
  column.headClass ?? column.class,
350
369
  )}
351
370
  style={column.width ? `width: ${column.width}` : undefined}
@@ -356,7 +375,7 @@
356
375
  <button
357
376
  type="button"
358
377
  class={cn(
359
- "t-press hover:bg-muted/30 focus-visible:ring-ring flex h-10 w-full items-center gap-1 px-2 text-xs font-medium outline-none focus-visible:ring-2 focus-visible:ring-inset",
378
+ "t-press transition-colors duration-quick ease-smooth-out hover:bg-foreground/4 flex h-8 w-full items-center gap-1 px-2 text-xs font-medium outline-none focus-visible:shadow-(--ring-focus-inset)",
360
379
  column.numeric ? "justify-end" : "justify-start",
361
380
  )}
362
381
  onclick={() => (sort = toggleSort(sort, column.key as keyof Row & string))}
@@ -367,7 +386,7 @@
367
386
  {:else if dir === "desc"}
368
387
  <CaretDown class="text-foreground size-3.5" weight="bold" aria-hidden="true" />
369
388
  {:else}
370
- <CaretUpDown class="text-muted-foreground/75 size-3.5" aria-hidden="true" />
389
+ <CaretUpDown class="text-muted-foreground size-3.5" aria-hidden="true" />
371
390
  {/if}
372
391
  </button>
373
392
  {:else if head}
@@ -392,23 +411,23 @@
392
411
  <Table.Body>
393
412
  {#if entry.key !== null}
394
413
  <Table.Row
395
- class="bg-muted/40 hover:bg-muted/40 hover:translate-y-0 hover:shadow-none"
414
+ class="bg-foreground/4 hover:translate-y-0 hover:bg-foreground/4 hover:shadow-none"
396
415
  >
397
416
  <Table.Head
398
417
  colspan={span + (rowHref ? 1 : 0)}
399
418
  scope="colgroup"
400
- class="text-foreground h-10 p-0"
419
+ class="text-foreground h-8 p-0"
401
420
  >
402
421
  {#if collapsible}
403
422
  <button
404
423
  type="button"
405
424
  aria-expanded={!folded}
406
- class="t-press focus-visible:ring-ring flex h-10 w-full items-center gap-2 px-2 text-start outline-none focus-visible:ring-2 focus-visible:ring-inset"
425
+ class="t-press flex h-8 w-full items-center gap-2 px-2 text-start outline-none focus-visible:shadow-(--ring-focus-inset)"
407
426
  onclick={() => toggleGroup(tableGroup.key)}
408
427
  >
409
428
  <CaretDown
410
429
  class={cn(
411
- "size-3.5 shrink-0 transition-transform duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] motion-reduce:transition-none",
430
+ "size-3.5 shrink-0 transition-transform duration-fast ease-smooth-out motion-reduce:transition-none",
412
431
  folded && "-rotate-90",
413
432
  )}
414
433
  aria-hidden="true"
@@ -416,7 +435,7 @@
416
435
  {#if groupHeader}{@render groupHeader(tableGroup)}{:else}{tableGroup.key}{/if}
417
436
  </button>
418
437
  {:else}
419
- <span class="flex h-10 items-center px-2">
438
+ <span class="flex h-8 items-center px-2">
420
439
  {#if groupHeader}{@render groupHeader(tableGroup)}{:else}{tableGroup.key}{/if}
421
440
  </span>
422
441
  {/if}
@@ -2,8 +2,9 @@
2
2
  // =========================
3
3
  // IMPORTS
4
4
  // =========================
5
- import { Plus, X } from "phosphor-svelte";
5
+ import { Plus } from "phosphor-svelte";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import TagChip from "../chip/tag-chip.svelte";
7
8
  import {
8
9
  addTag,
9
10
  canCreate,
@@ -105,9 +106,7 @@
105
106
  ...(creatable ? [{ kind: "create" as const, name: query.trim() }] : []),
106
107
  ],
107
108
  );
108
- const nothing = $derived(
109
- !free && editing && !full && query.trim() !== "" && rows.length === 0,
110
- );
109
+ const nothing = $derived(!free && editing && !full && query.trim() !== "" && rows.length === 0);
111
110
  const open = $derived(editing && !full && (rows.length > 0 || nothing));
112
111
 
113
112
  // =========================
@@ -129,8 +128,7 @@
129
128
  queueMicrotask(() => input?.focus());
130
129
  }
131
130
 
132
- // Leaving the field keeps what was typed only when it is an existing label said another way.
133
- // Anything else is dropped unless the field is free: free text is never saved by walking away.
131
+ // Blur keeps typed text only when it's an existing label spelt another way, or the field is free.
134
132
  function stop() {
135
133
  const typed = query;
136
134
  query = "";
@@ -219,31 +217,19 @@
219
217
 
220
218
  <!-- =========================== -->
221
219
  <!-- MARKUP -->
222
- <!-- WHY: the edit frame is --radius-lg with 4px of padding, so the chip inside is --radius-xs
223
- (8 - 4), and a read-state chip on its own takes --radius-sm. -->
220
+ <!-- WHY: the chips are TagChip at --radius-sm (6px); the edit frame is --radius-lg (10px), which
221
+ is 6 plus its 4px of padding, so the curves stay concentric. -->
224
222
  <!-- WHY text-base on the input: below 16px iOS zooms the page when it gets focus. -->
225
223
  <!-- =========================== -->
226
224
  {#snippet chip(tag: string, removable: boolean)}
227
- <span
228
- class={cn(
229
- "inline-flex h-5 max-w-full items-center gap-0.5 bg-muted px-1.5 text-xs font-medium text-foreground",
230
- removable ? "rounded-(--radius-xs) pr-0.5" : "rounded-(--radius-sm)",
231
- )}
225
+ <TagChip
226
+ title={tag}
227
+ removeLabel={text.remove(tag)}
228
+ removeFocusable={false}
229
+ onremove={removable ? () => commit(removeTag(tags, tag)) : undefined}
232
230
  >
233
- <span class="truncate" title={tag}>{tag}</span>
234
- {#if removable}
235
- <button
236
- type="button"
237
- tabindex="-1"
238
- class="relative grid size-4 shrink-0 place-items-center rounded-(--radius-xs) text-muted-foreground transition-colors hover:bg-foreground/10 hover:text-foreground before:absolute before:-inset-1.5 before:content-['']"
239
- aria-label={text.remove(tag)}
240
- onmousedown={(e) => e.preventDefault()}
241
- onclick={() => commit(removeTag(tags, tag))}
242
- >
243
- <X class="size-3" weight="bold" aria-hidden="true" />
244
- </button>
245
- {/if}
246
- </span>
231
+ {tag}
232
+ </TagChip>
247
233
  {/snippet}
248
234
 
249
235
  <div
@@ -254,7 +240,7 @@
254
240
  >
255
241
  {#if editing}
256
242
  <div
257
- class="-ml-1.5 flex min-h-7 flex-wrap items-center gap-1 rounded-lg bg-background p-1 ring-1 ring-ring/60 focus-within:ring-2 focus-within:ring-ring"
243
+ class="-ml-1.5 flex min-h-7 flex-wrap items-center gap-1 rounded-lg bg-card p-1 ring-1 ring-ring/60 focus-within:ring-2 focus-within:ring-ring"
258
244
  >
259
245
  {#each tags as tag (tag.toLowerCase())}
260
246
  {@render chip(tag, true)}
@@ -288,7 +274,7 @@
288
274
  id={listId}
289
275
  role="listbox"
290
276
  aria-label={label}
291
- class="absolute top-full left-0 z-50 mt-1 max-h-60 w-full min-w-48 overflow-y-auto rounded-xl border border-border bg-popover p-1 text-popover-foreground shadow-(--elev-overlay)"
277
+ class="absolute top-full left-0 z-50 mt-1 max-h-60 w-full min-w-48 overflow-y-auto rounded-xl bg-popover p-1 text-popover-foreground shadow-edge-raised"
292
278
  >
293
279
  {#each rows as row, index (row.kind + row.name)}
294
280
  <!-- svelte-ignore a11y_click_events_have_key_events -->
@@ -298,7 +284,7 @@
298
284
  aria-selected={index === active}
299
285
  class={cn(
300
286
  "flex min-h-8 cursor-default items-center gap-2 rounded-lg px-2 text-sm any-pointer-coarse:min-h-11",
301
- index === active && "bg-accent text-accent-foreground",
287
+ index === active && "bg-foreground/6 text-foreground",
302
288
  )}
303
289
  onmousedown={(e) => e.preventDefault()}
304
290
  onmousemove={() => (active = index)}
@@ -321,7 +307,7 @@
321
307
  <button
322
308
  type="button"
323
309
  class={cn(
324
- "-ml-1.5 flex min-h-7 w-full flex-wrap items-center gap-1 rounded-lg px-1.5 py-1 text-left transition-colors outline-none hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none",
310
+ "-ml-1.5 flex min-h-7 w-full flex-wrap items-center gap-1 rounded-lg px-1.5 py-1 text-left transition-colors outline-none duration-quick ease-smooth-out hover:bg-foreground/4 focus-visible:shadow-(--ring-focus) disabled:pointer-events-none motion-reduce:transition-none",
325
311
  )}
326
312
  aria-label={label}
327
313
  {disabled}
@@ -31,7 +31,7 @@ export const ganttLooks = {
31
31
  bar: "rounded-lg",
32
32
  mark: "rounded-sm",
33
33
  /** The readout that follows a dragged bar. */
34
- tip: "rounded-sm bg-foreground px-2 py-1 text-[11px] font-medium whitespace-nowrap text-background tabular-nums shadow-raised",
34
+ tip: "rounded-sm bg-foreground px-2 py-1 text-2xs font-medium whitespace-nowrap text-background tabular-nums shadow-raised",
35
35
  /** A title that didn't fit its bar, set beside it. */
36
36
  outsideLabel: "rounded-sm bg-card/90 px-1 text-xs text-muted-foreground",
37
37
  track: "rounded-full",
@@ -3,8 +3,8 @@
3
3
  consuming Svelte components. */
4
4
 
5
5
  .tiptap-content {
6
- line-height: 1.75;
7
- font-size: 1rem;
6
+ line-height: var(--content-leading, 1.75);
7
+ font-size: var(--content-size, 1rem);
8
8
  color: var(--color-foreground);
9
9
  }
10
10
 
@@ -63,7 +63,7 @@
63
63
  }
64
64
 
65
65
  .tiptap-content p {
66
- margin-bottom: 1rem;
66
+ margin-bottom: var(--content-gap, 1rem);
67
67
  text-wrap: pretty;
68
68
  overflow-wrap: break-word;
69
69
  }
@@ -379,7 +379,7 @@
379
379
  .editor-wrapper {
380
380
  position: relative;
381
381
  border-radius: var(--radius-xl);
382
- background: var(--color-background);
382
+ background: var(--color-card);
383
383
  border: 1px solid var(--color-border);
384
384
  transition:
385
385
  border-color var(--duration-quick) var(--ease-smooth-out),
@@ -29,7 +29,7 @@
29
29
  import { Button } from "../shadcn/button/index.js";
30
30
  import { Kbd, KbdGroup } from "../shadcn/kbd/index.js";
31
31
  import { cn } from "../lib/utils.js";
32
- import { TONE_CLASS } from "../lib/tone.js";
32
+ import StatusPill from "../status/status-pill.svelte";
33
33
  import {
34
34
  Thread,
35
35
  type ThreadComment,
@@ -414,14 +414,7 @@
414
414
  {#if thread.subject.snoozedUntil}
415
415
  <BellSlash class="size-3.5 text-muted-foreground" aria-label={labels.snoozed} />
416
416
  {:else if thread.pending.length > 0}
417
- <span
418
- class={cn(
419
- "inline-flex h-5 items-center rounded-sm border px-1.5 font-medium",
420
- TONE_CLASS.warning,
421
- )}
422
- >
423
- {labels.needsYou}
424
- </span>
417
+ <StatusPill tone="warning">{labels.needsYou}</StatusPill>
425
418
  {:else if thread.unread > 0}
426
419
  <Circle
427
420
  weight="fill"
@@ -14,6 +14,7 @@
14
14
  import type { Tone } from "../lib/tone.js";
15
15
  import { cn } from "../lib/utils.js";
16
16
  import StatusBadge from "../status/status-badge.svelte";
17
+ import PopValue from "../charts/pop-value.svelte";
17
18
 
18
19
  // =========================
19
20
  // PROPS
@@ -113,7 +114,8 @@
113
114
  side="right"
114
115
  bind:ref={panel}
115
116
  tabindex={-1}
116
- class={cn("flex w-full flex-col gap-0 p-0", WIDTH[width], bare && "overflow-hidden")}
117
+ floating
118
+ class={cn("flex w-full flex-col gap-0 overflow-hidden p-0", WIDTH[width])}
117
119
  {onOpenAutoFocus}
118
120
  >
119
121
  {#if bare}
@@ -165,11 +167,14 @@
165
167
  <div class="min-w-0">
166
168
  <dt class="text-xs text-muted-foreground">{stat.label}</dt>
167
169
  <dd
168
- class={cn("text-sm font-medium tabular-nums", STAT_TONE[stat.tone ?? "default"])}
170
+ class={cn(
171
+ "text-lg leading-tight font-semibold tabular-nums",
172
+ STAT_TONE[stat.tone ?? "default"],
173
+ )}
169
174
  >
170
- {stat.value}
175
+ <PopValue value={stat.value} />
171
176
  {#if stat.sub}
172
- <span class="text-xs font-normal text-muted-foreground">{stat.sub}</span>
177
+ <span class="ms-1 text-xs font-normal text-muted-foreground">{stat.sub}</span>
173
178
  {/if}
174
179
  </dd>
175
180
  </div>
@@ -171,15 +171,16 @@
171
171
  {/each}
172
172
  </span>
173
173
  {:else if row.tone && row.value}
174
- <StatusBadge tone={row.tone} capitalize={false}>{row.value}</StatusBadge>
174
+ <StatusBadge tone={row.tone} capitalize={false} class="w-fit max-w-full"
175
+ >{row.value}</StatusBadge
176
+ >
175
177
  {:else if row.href && row.value}
176
178
  <a
177
179
  href={row.href}
178
180
  title={row.value}
179
181
  class={cn(
180
182
  "min-w-0 truncate text-sm font-medium underline-offset-2 hover:underline",
181
- clamp &&
182
- "rounded-xs focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
183
+ clamp && "rounded-xs focus-visible:shadow-(--ring-focus) focus-visible:outline-none",
183
184
  )}>{row.value}</a
184
185
  >
185
186
  {:else if row.value}
@@ -299,12 +300,10 @@
299
300
  {:else if shown.length > 0}
300
301
  <div class={cn("flex flex-col gap-2", className)}>
301
302
  {#if title}
302
- <h2 class="text-xs font-medium tracking-wide text-muted-foreground uppercase">{title}</h2>
303
+ <h2 class="text-xs font-medium text-muted-foreground">{title}</h2>
303
304
  {/if}
304
305
  <!-- Labels size to their text, capped at 45%; the dt's min-w-20 is the 5rem floor. min() can't take max-content, so a min() template is dropped whole. -->
305
- <dl
306
- class="grid grid-cols-[fit-content(45%)_minmax(0,1fr)] items-start gap-x-4 gap-y-2.5"
307
- >
306
+ <dl class="grid grid-cols-[fit-content(45%)_minmax(0,1fr)] items-start gap-x-4 gap-y-2.5">
308
307
  {#each shown as row, index (row.divider ? `divider-${index}` : keyOf(row))}
309
308
  {#if row.divider}
310
309
  <div class="col-span-2 -my-0.5 border-t border-border/50"></div>
@@ -355,7 +354,7 @@
355
354
  <Select.Trigger
356
355
  size="sm"
357
356
  aria-label={t.edit(row.label)}
358
- class="-ml-1.5 min-h-7 h-auto w-fit max-w-full gap-1.5 border-transparent bg-transparent px-1.5 shadow-none transition-colors hover:bg-muted/60 focus-visible:ring-3 data-[state=open]:bg-muted/60"
357
+ class="-ml-1.5 min-h-7 h-auto w-fit max-w-full gap-1.5 border-transparent bg-transparent px-1.5 shadow-none transition-colors hover:bg-muted/60 focus-visible:shadow-(--ring-focus) data-[state=open]:bg-muted/60"
359
358
  >
360
359
  <span data-slot="select-value" class="flex min-w-0 items-center gap-1.5">
361
360
  {@render display(row)}
@@ -122,7 +122,7 @@
122
122
  aria-current={active ? "page" : undefined}
123
123
  class={cn(
124
124
  "flex flex-col gap-1.5 rounded-lg px-2 py-2 transition-colors any-pointer-coarse:py-3",
125
- "outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
125
+ "outline-none focus-visible:shadow-(--ring-focus)",
126
126
  active ? "bg-muted" : "hover:bg-muted/60",
127
127
  )}
128
128
  >
@@ -183,7 +183,7 @@
183
183
  </div>
184
184
 
185
185
  <div class={narrow ? "mx-auto w-full max-w-[390px]" : "w-full"}>
186
- <div class="overflow-hidden rounded-xl border border-border">
186
+ <div class="overflow-hidden rounded-xl shadow-card">
187
187
  {#if fixture === "project"}
188
188
  <RecordShell
189
189
  bind:tab
@@ -320,9 +320,7 @@
320
320
  {#snippet rail()}
321
321
  <div class="flex flex-col gap-6">
322
322
  <section class="flex flex-col gap-2">
323
- <h2 class="px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
324
- Projects
325
- </h2>
323
+ <h2 class="px-2 text-xs font-medium text-muted-foreground">Projects</h2>
326
324
  <ProjectList projects={summaries} {stages} today={DEMO_TODAY} {locale} />
327
325
  </section>
328
326
  <FieldSection
@@ -62,7 +62,7 @@
62
62
  <!-- =========================== -->
63
63
  <!-- WHY a container query: the record sits in a page with its own sidebar, so the
64
64
  viewport says nothing about how much room it has. -->
65
- <div class={cn("@container flex min-w-0 flex-col bg-background", className)}>
65
+ <div class={cn("@container flex min-w-0 flex-col bg-card", className)}>
66
66
  {@render header()}
67
67
  {@render banner?.()}
68
68
 
@@ -3,6 +3,7 @@
3
3
  // IMPORTS
4
4
  // =========================
5
5
  import { cn } from "../lib/utils.js";
6
+ import PopValue from "../charts/pop-value.svelte";
6
7
  import type { PanelStat } from "../panel/side-panel-types.js";
7
8
  import { gutterClass } from "./gutter.js";
8
9
 
@@ -36,10 +37,15 @@
36
37
  {#each stats as stat (stat.label)}
37
38
  <div class="min-w-0">
38
39
  <dt class="text-xs text-muted-foreground">{stat.label}</dt>
39
- <dd class={cn("text-sm font-medium tabular-nums", TONE[stat.tone ?? "default"])}>
40
- {stat.value}
40
+ <dd
41
+ class={cn(
42
+ "text-lg leading-tight font-semibold tabular-nums",
43
+ TONE[stat.tone ?? "default"],
44
+ )}
45
+ >
46
+ <PopValue value={stat.value} />
41
47
  {#if stat.sub}
42
- <span class="text-xs font-normal text-muted-foreground">{stat.sub}</span>
48
+ <span class="ms-1 text-xs font-normal text-muted-foreground">{stat.sub}</span>
43
49
  {/if}
44
50
  </dd>
45
51
  </div>