@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
package/README.md CHANGED
@@ -23,6 +23,8 @@ only a convenience.
23
23
 
24
24
  See [COMPONENTS.md](./COMPONENTS.md) for the full list, one line each.
25
25
 
26
+ See [DESIGN.md](./DESIGN.md) for the design rules: what tokens, atoms, molecules and organisms may and may not do.
27
+
26
28
  ## Setup
27
29
 
28
30
  Import the style layer once, after Tailwind:
@@ -2,8 +2,7 @@
2
2
  * The one skin for a dashboard data table. Classes, not a component: two
3
3
  * dashboards share nothing but the look, and each drew its own slightly different one.
4
4
  */
5
- // Heads aren't uppercase. `SectionLabel` already is, and a nine-column head in
6
- // the same style fights the panel title and reads worse where density matters.
5
+ // Heads are sentence case, like every label (DESIGN.md, Patterns: labels).
7
6
  export const dataTableClass = "w-full text-sm";
8
7
  export const dataTheadClass = "bg-muted/40 text-xs text-muted-foreground";
9
8
  export const dataThClass = "px-4 py-2.5 text-left font-medium";
@@ -3,5 +3,5 @@ import { cn } from "../../lib/utils.js";
3
3
  // sections can't drift in size or weight.
4
4
  // `first:` drops the leading header's top padding, since the popover pads itself.
5
5
  export function filterSectionClass(className) {
6
- return cn("px-2 pt-2 pb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground first:pt-1", className);
6
+ return cn("px-2 pt-2 pb-1 text-2xs font-medium text-muted-foreground first:pt-1", className);
7
7
  }
@@ -1,10 +1,9 @@
1
1
  import { cn } from "../../lib/utils.js";
2
- /**
3
- * The list-page table rhythm, Attio's: one 40px row, a hairline under each, a flat hover.
4
- */
2
+ /** The list-page table rhythm: 32px rows (DESIGN.md rule 19), hairlines, a flat hover. */
3
+ // The header is a muted band tinted off the foreground, so it shows on card and canvas.
5
4
  export const listTable = {
6
- /** The header row: 36px, a hairline under it. On the cells: `thead` zeroes its row's border. */
7
- headRow: (className) => cn("hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-9 [&>th]:border-b [&>th]:border-border", className),
5
+ /** The header row: 32px, muted, a hairline under it. On the cells: `thead` zeroes its row's border. */
6
+ headRow: (className) => cn("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", className),
8
7
  /** A body row. The shared row lifts on hover; a flush list row does not. */
9
- row: (className) => cn("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", className)
8
+ row: (className) => cn("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", className)
10
9
  };
@@ -3,7 +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 = "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";
7
8
  /**
8
9
  * The rail column track, passed to `MasterDetail`'s `railWidth`. One page ran
9
10
  * 20px wider, so the detail pane jumped between pages.
@@ -11,8 +11,8 @@ import { tv } from "tailwind-variants";
11
11
  // box shows only under the pointer and on focus.
12
12
  export const pickerTriggerClass = tv({
13
13
  base: [
14
- "t-press inline-flex min-w-0 items-center gap-1.5 rounded-md text-[13px] font-medium whitespace-nowrap select-none",
15
- "hover:bg-muted data-[state=open]:bg-muted",
14
+ "t-press inline-flex min-w-0 items-center gap-1.5 rounded-lg text-(length:--text-control) font-medium whitespace-nowrap select-none",
15
+ "transition-colors duration-quick ease-smooth-out hover:bg-foreground/6 data-[state=open]:bg-foreground/6 motion-reduce:transition-none",
16
16
  "disabled:pointer-events-none disabled:opacity-50",
17
17
  "[&_svg]:pointer-events-none [&_svg]:shrink-0"
18
18
  ],
@@ -33,7 +33,7 @@ export const pickerTriggerClass = tv({
33
33
  },
34
34
  /** No value yet: the label is a prompt, so it recedes until hovered. */
35
35
  empty: {
36
- true: "text-muted-foreground/70 hover:text-muted-foreground",
36
+ true: "text-muted-foreground hover:text-foreground",
37
37
  false: ""
38
38
  },
39
39
  /** A row that carries no action still needs the column's box. */
@@ -13,10 +13,10 @@ export const segmentedTrackClass = tv({
13
13
  // WHY no size variant: `sm` drew 24px, and every tab control must share one height.
14
14
  base: "h-7 any-pointer-coarse:h-11 shrink-0 items-stretch",
15
15
  variants: {
16
- // `pill`: a bordered well with a travelling highlight. `underline`: bare
16
+ // `pill`: grey track, sliding white thumb. `underline`: bare
17
17
  // labels over one hairline, the active one marked by a rule that travels.
18
18
  variant: {
19
- 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",
19
+ pill: "overflow-hidden rounded-lg bg-foreground/6 p-0.5",
20
20
  underline: "gap-5 border-b border-border"
21
21
  },
22
22
  // Fill the row rather than sit at its natural width. For a narrow rail.
@@ -36,14 +36,14 @@ export const segmentedTriggerClass = tv({
36
36
  "text-muted-foreground hover:text-foreground",
37
37
  "disabled:pointer-events-none disabled:opacity-50",
38
38
  "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
39
- "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none",
39
+ "focus-visible:shadow-(--ring-focus-inset) focus-visible:outline-none",
40
40
  "h-full text-xs"
41
41
  ],
42
42
  variants: {
43
- // The pill sits concentric in the track's 2px inset; the underline has no well, so
43
+ // The pill sits concentric in the track's 2px inset , so 8px; the underline has no well, so
44
44
  // no padding, and its rule is as wide as the label.
45
45
  variant: {
46
- pill: "rounded-[calc(var(--radius-lg)-3px)] px-2.5 any-pointer-coarse:px-3.5",
46
+ pill: "rounded-[calc(var(--radius-lg)-2px)] px-2.5 any-pointer-coarse:px-3.5",
47
47
  underline: "rounded-sm px-0"
48
48
  },
49
49
  // WHY `min-w-fit flex-auto`, not equal fractions: fractions cut "Approved" to
@@ -30,7 +30,7 @@ export const ganttLooks = {
30
30
  bar: "rounded-lg",
31
31
  mark: "rounded-sm",
32
32
  /** The readout that follows a dragged bar. */
33
- tip: "rounded-sm bg-foreground px-2 py-1 text-[11px] font-medium whitespace-nowrap text-background tabular-nums shadow-raised",
33
+ tip: "rounded-sm bg-foreground px-2 py-1 text-2xs font-medium whitespace-nowrap text-background tabular-nums shadow-raised",
34
34
  /** A title that didn't fit its bar, set beside it. */
35
35
  outsideLabel: "rounded-sm bg-card/90 px-1 text-xs text-muted-foreground",
36
36
  track: "rounded-full",
@@ -8,6 +8,7 @@ import Content from "../../../src/shadcn/dialog/dialog-content.svelte";
8
8
  import Description from "../../../src/shadcn/dialog/dialog-description.svelte";
9
9
  import Trigger from "../../../src/shadcn/dialog/dialog-trigger.svelte";
10
10
  import Close from "../../../src/shadcn/dialog/dialog-close.svelte";
11
- export { Root, Title, Portal, Footer, Header, Trigger, Overlay, Content, Description, Close,
11
+ import Body from "../../../src/shadcn/dialog/dialog-body.svelte";
12
+ export { Root, Title, Portal, Footer, Header, Trigger, Overlay, Content, Description, Close, Body,
12
13
  //
13
- Root as Dialog, Title as DialogTitle, Portal as DialogPortal, Footer as DialogFooter, Header as DialogHeader, Trigger as DialogTrigger, Overlay as DialogOverlay, Content as DialogContent, Description as DialogDescription, Close as DialogClose };
14
+ Root as Dialog, Title as DialogTitle, Portal as DialogPortal, Footer as DialogFooter, Header as DialogHeader, Trigger as DialogTrigger, Overlay as DialogOverlay, Content as DialogContent, Description as DialogDescription, Close as DialogClose, Body as DialogBody };
@@ -18,7 +18,7 @@ import { tv } from "tailwind-variants";
18
18
  * A row has to look the same either way, so it answers to both.
19
19
  */
20
20
  export const menuItemClass = tv({
21
- base: "relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-disabled:pointer-events-none data-disabled:opacity-50 disabled:pointer-events-none disabled:opacity-50",
21
+ base: "relative flex cursor-default items-center gap-1.5 rounded-lg px-1.5 py-1 text-sm outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-disabled:pointer-events-none data-disabled:opacity-50 disabled:pointer-events-none disabled:opacity-50",
22
22
  variants: {
23
23
  variant: {
24
24
  default: "hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground",
@@ -8,6 +8,7 @@ import Header from "../../../src/shadcn/sheet/sheet-header.svelte";
8
8
  import Footer from "../../../src/shadcn/sheet/sheet-footer.svelte";
9
9
  import Title from "../../../src/shadcn/sheet/sheet-title.svelte";
10
10
  import Description from "../../../src/shadcn/sheet/sheet-description.svelte";
11
- export { Root, Close, Trigger, Portal, Overlay, Content, Header, Footer, Title, Description,
11
+ import Body from "../../../src/shadcn/sheet/sheet-body.svelte";
12
+ export { Root, Close, Trigger, Portal, Overlay, Content, Header, Footer, Title, Description, Body,
12
13
  //
13
- Root as Sheet, Close as SheetClose, Trigger as SheetTrigger, Portal as SheetPortal, Overlay as SheetOverlay, Content as SheetContent, Header as SheetHeader, Footer as SheetFooter, Title as SheetTitle, Description as SheetDescription };
14
+ Root as Sheet, Close as SheetClose, Trigger as SheetTrigger, Portal as SheetPortal, Overlay as SheetOverlay, Content as SheetContent, Header as SheetHeader, Footer as SheetFooter, Title as SheetTitle, Description as SheetDescription, Body as SheetBody };
@@ -1,4 +1,5 @@
1
1
  import Root from "../../../src/shadcn/skeleton/skeleton.svelte";
2
- export { Root,
2
+ import Delay from "../../../src/shadcn/skeleton/skeleton-delay.svelte";
3
+ export { Root, Delay,
3
4
  //
4
- Root as Skeleton };
5
+ Root as Skeleton, Delay as SkeletonDelay };
@@ -2,3 +2,4 @@ export { default as StatusBadge } from "../../src/status/status-badge.svelte";
2
2
  export { default as StatusDot } from "../../src/status/status-dot.svelte";
3
3
  export { default as StatusIcon } from "../../src/status/status-icon.svelte";
4
4
  export { defineStatuses } from "./define-statuses.js";
5
+ export { default as StatusPill, PILL_TINT } from "../../src/status/status-pill.svelte";
@@ -1,13 +1,15 @@
1
- const FRAME_PAD = 16;
1
+ const FRAME_PAD = 24;
2
2
  /**
3
- * Native drag shows a flat, faded snapshot. This hands the browser a tilted
4
- * copy with a lifted shadow instead. Call it from `ondragstart`.
3
+ * Native drag shows a flat, faded snapshot. This hands the browser a lifted copy instead: the
4
+ * raised elevation step and a 2% grow, so the held card reads as picked up. The browser moves the
5
+ * image with the pointer itself, so there's no transition while it moves. Call it from
6
+ * `ondragstart`. `tilt` rotates the copy in degrees, 0 by default.
5
7
  */
6
- export function liftDragImage(e, tilt = -3) {
8
+ export function liftDragImage(e, tilt = 0) {
7
9
  const source = e.currentTarget;
8
10
  if (!source || !e.dataTransfer) return;
9
11
  const rect = source.getBoundingClientRect();
10
- // The padded frame leaves room for the tilt and shadow, or the snapshot crops them.
12
+ // The padded frame leaves room for the shadow and the grow, or the snapshot crops them.
11
13
  const frame = document.createElement("div");
12
14
  Object.assign(frame.style, {
13
15
  position: "fixed",
@@ -17,12 +19,16 @@ export function liftDragImage(e, tilt = -3) {
17
19
  pointerEvents: "none"
18
20
  });
19
21
  const ghost = source.cloneNode(true);
22
+ // The copy's first child is the card the consumer drew, so the shadow sits on its corners.
23
+ const card = ghost.firstElementChild ?? ghost;
20
24
  Object.assign(ghost.style, {
21
25
  width: `${rect.width}px`,
22
26
  opacity: "1",
23
- rotate: `${tilt}deg`,
24
- filter: "drop-shadow(0 2px 4px rgb(35 35 35 / 0.08)) drop-shadow(0 10px 18px rgb(35 35 35 / 0.14))"
27
+ rotate: tilt ? `${tilt}deg` : "",
28
+ // 2 minus the tooltip's shrink step is a 1.02 grow, kept on the motion scale.
29
+ scale: "calc(2 - var(--scale-small))"
25
30
  });
31
+ card.style.boxShadow = "var(--elev-raised)";
26
32
  frame.appendChild(ghost);
27
33
  document.body.appendChild(frame);
28
34
  e.dataTransfer.setDragImage(frame, e.clientX - rect.left + FRAME_PAD, e.clientY - rect.top + FRAME_PAD);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyramzi/ui",
3
- "version": "2.19.2",
3
+ "version": "2.21.0",
4
4
  "description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
5
5
  "homepage": "https://ui.heyramzi.com",
6
6
  "license": "MIT",
@@ -15,10 +15,7 @@
15
15
  </script>
16
16
 
17
17
  <p
18
- class={cn(
19
- "text-[11px] font-medium uppercase tracking-[0.16em] text-[color:var(--muted-foreground,inherit)]",
20
- className,
21
- )}
18
+ class={cn("text-2xs font-medium text-[color:var(--muted-foreground,inherit)]", className)}
22
19
  {...rest}
23
20
  >
24
21
  {@render children?.()}
@@ -69,7 +69,7 @@
69
69
  <span class="shimmer text-sm font-medium">{label}</span>
70
70
 
71
71
  {#if elapsed}
72
- <span class="font-mono text-[11px] tabular-nums text-[color:var(--muted-foreground,#71717a)]">
72
+ <span class="font-mono text-2xs tabular-nums text-[color:var(--muted-foreground,#71717a)]">
73
73
  {clock}
74
74
  </span>
75
75
  {/if}
@@ -68,36 +68,45 @@
68
68
  transform var(--dropdown-close-dur) var(--dropdown-ease),
69
69
  opacity var(--dropdown-close-dur) var(--dropdown-ease);
70
70
  }
71
+ /* WHY the floating var first: bits-ui computes the exact point on the trigger after
72
+ a flip or a shift, so a menu nudged off the viewport edge still grows from its
73
+ trigger. The side and align corners stay as the fallback for a non-bits surface. */
71
74
  .t-dropdown[data-side="bottom"][data-align="start"] {
72
- transform-origin: top left;
75
+ transform-origin: var(--bits-floating-transform-origin, top left);
73
76
  }
74
77
  .t-dropdown[data-side="bottom"][data-align="center"] {
75
- transform-origin: top center;
78
+ transform-origin: var(--bits-floating-transform-origin, top center);
76
79
  }
77
80
  .t-dropdown[data-side="bottom"][data-align="end"] {
78
- transform-origin: top right;
81
+ transform-origin: var(--bits-floating-transform-origin, top right);
79
82
  }
80
83
  .t-dropdown[data-side="top"][data-align="start"] {
81
- transform-origin: bottom left;
84
+ transform-origin: var(--bits-floating-transform-origin, bottom left);
82
85
  }
83
86
  .t-dropdown[data-side="top"][data-align="center"] {
84
- transform-origin: bottom center;
87
+ transform-origin: var(--bits-floating-transform-origin, bottom center);
85
88
  }
86
89
  .t-dropdown[data-side="top"][data-align="end"] {
87
- transform-origin: bottom right;
90
+ transform-origin: var(--bits-floating-transform-origin, bottom right);
88
91
  }
89
92
  .t-dropdown[data-side="left"],
90
93
  .t-dropdown[data-side="inline-end"] {
91
- transform-origin: right center;
94
+ transform-origin: var(--bits-floating-transform-origin, right center);
92
95
  }
93
96
  .t-dropdown[data-side="right"],
94
97
  .t-dropdown[data-side="inline-start"] {
95
- transform-origin: left center;
98
+ transform-origin: var(--bits-floating-transform-origin, left center);
96
99
  }
97
100
 
101
+ /* Reduced motion keeps the fade and drops the scale (DESIGN.md rule 13). Same
102
+ specificity as the state rules above and later, so it wins without !important. */
98
103
  @media (prefers-reduced-motion: reduce) {
99
- .t-dropdown {
100
- transition: none !important;
104
+ .t-dropdown,
105
+ .t-dropdown.is-open,
106
+ .t-dropdown.is-closing,
107
+ .t-dropdown[data-state] {
108
+ transform: none;
109
+ transition-property: opacity;
101
110
  }
102
111
  }
103
112
 
@@ -67,9 +67,14 @@
67
67
  transition: opacity var(--modal-close-dur) var(--modal-ease);
68
68
  }
69
69
 
70
+ /* Reduced motion keeps the fade and drops the scale (DESIGN.md rule 13). The
71
+ backdrop only ever fades, so it keeps its transition. */
70
72
  @media (prefers-reduced-motion: reduce) {
71
73
  .t-modal,
72
- .t-modal-backdrop {
73
- transition: none !important;
74
+ .t-modal.is-open,
75
+ .t-modal.is-closing,
76
+ .t-modal[data-state] {
77
+ transform: none;
78
+ transition-property: opacity;
74
79
  }
75
80
  }
@@ -5,14 +5,12 @@
5
5
  .lc-tooltip-container[data-variant="default"] {
6
6
  color: var(--popover-foreground, var(--foreground));
7
7
  background-color: var(--popover, #fff);
8
- border: 1px solid var(--border, transparent);
9
- border-radius: 0.5rem;
8
+ border: 0;
9
+ border-radius: var(--radius-lg);
10
10
  padding: 0.625rem 0.75rem;
11
11
  min-width: 8.75rem;
12
12
  backdrop-filter: none;
13
- box-shadow:
14
- 0 1px 2px -1px oklch(0 0 0 / 0.2),
15
- 0 10px 28px -10px oklch(0 0 0 / 0.32);
13
+ box-shadow: var(--elev-hairline), var(--elev-raised);
16
14
  /* WHY: opacity only; the container's transform is inline positioning, so
17
15
  animating transform here would fight it and make the tooltip jump. */
18
16
  transform-origin: top left;
@@ -42,7 +42,7 @@
42
42
  {#if href}
43
43
  <a
44
44
  {href}
45
- class="group inline-flex items-center gap-1 rounded-md text-xs font-medium text-muted-foreground transition-colors duration-[var(--duration-quick)] hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
45
+ class="group inline-flex items-center gap-1 rounded-md text-xs font-medium text-muted-foreground transition-colors duration-[var(--duration-quick)] hover:text-foreground focus-visible:shadow-(--ring-focus) focus-visible:outline-none"
46
46
  >
47
47
  {openLabel}
48
48
  <ArrowRight
@@ -3,8 +3,7 @@
3
3
  * dashboards share nothing but the look, and each drew its own slightly different one.
4
4
  */
5
5
 
6
- // Heads aren't uppercase. `SectionLabel` already is, and a nine-column head in
7
- // the same style fights the panel title and reads worse where density matters.
6
+ // Heads are sentence case, like every label (DESIGN.md, Patterns: labels).
8
7
  export const dataTableClass = "w-full text-sm";
9
8
 
10
9
  export const dataTheadClass = "bg-muted/40 text-xs text-muted-foreground";
@@ -119,7 +119,7 @@
119
119
  data-series={hasSeries ? "" : undefined}
120
120
  class={cn(
121
121
  "hr-stat-tile group/tile relative block rounded-xl",
122
- href && "t-press focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none",
122
+ href && "t-press focus-visible:shadow-(--ring-focus) focus-visible:outline-none",
123
123
  className,
124
124
  )}
125
125
  >
@@ -7,15 +7,15 @@
7
7
  gap: 0.375rem;
8
8
  min-width: 0;
9
9
  padding: 1rem 1.25rem;
10
- border: 1px solid var(--border, oklch(0.92 0.004 286));
11
- border-radius: 0.75rem;
10
+ border: 0;
11
+ border-radius: var(--radius-xl);
12
12
  background: var(--card, white);
13
13
  box-shadow: var(--chart-card-shadow);
14
- transition: border-color var(--chart-motion, 250ms) var(--chart-ease-out, ease);
14
+ transition: box-shadow var(--chart-motion) var(--chart-ease-out);
15
15
  }
16
16
  @media (hover: hover) and (pointer: fine) {
17
17
  .hr-number-chart:hover {
18
- border-color: color-mix(in srgb, var(--ring, var(--chart-1)) 28%, var(--border, transparent));
18
+ box-shadow: var(--chart-card-shadow-hover);
19
19
  }
20
20
  }
21
21
  /* A band is one panel with hairlines through it, so a figure inside it must bring no
@@ -28,7 +28,7 @@
28
28
  }
29
29
  @media (hover: hover) and (pointer: fine) {
30
30
  .hr-number-chart[data-bare]:hover {
31
- border-color: transparent;
31
+ box-shadow: none;
32
32
  }
33
33
  }
34
34
 
@@ -64,12 +64,13 @@
64
64
  }
65
65
 
66
66
  /* Baseline-aligned and top-anchored, so values sit on one line even when only some
67
- figures carry a caption. */
67
+ figures carry a caption. A delta that doesn't fit drops under the value, never onto it. */
68
68
  .hr-number-row {
69
69
  display: flex;
70
+ flex-wrap: wrap;
70
71
  align-items: baseline;
71
72
  justify-content: space-between;
72
- gap: 0.5rem;
73
+ gap: 0.125rem 0.5rem;
73
74
  }
74
75
 
75
76
  /* WHY the heading family and not the body one: a figure is structure, not
@@ -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>