@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
@@ -29,7 +29,7 @@
29
29
  <ScrollAreaPrimitive.Viewport
30
30
  bind:ref={viewportRef}
31
31
  data-slot="scroll-area-viewport"
32
- class="cn-scroll-area-viewport focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
32
+ class="cn-scroll-area-viewport size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:shadow-(--ring-focus) focus-visible:outline-1"
33
33
  >
34
34
  {@render children?.()}
35
35
  </ScrollAreaPrimitive.Viewport>
@@ -11,6 +11,8 @@
11
11
  ref = $bindable(null),
12
12
  class: className,
13
13
  sideOffset = 4,
14
+ // 8px between the list and the viewport edge, as the dropdown and popover keep.
15
+ collisionPadding = 8,
14
16
  portalProps,
15
17
  children,
16
18
  preventScroll = true,
@@ -24,10 +26,12 @@
24
26
  <SelectPrimitive.Content
25
27
  bind:ref
26
28
  {sideOffset}
29
+ {collisionPadding}
27
30
  {preventScroll}
28
31
  data-slot="select-content"
29
32
  class={cn(
30
- "t-dropdown bg-popover text-popover-foreground ring-foreground/10 min-w-36 rounded-xl shadow-raised ring-1 relative isolate z-50 max-h-[var(--bits-floating-available-height,100svh)] max-w-[calc(100vw-1rem)] overflow-x-hidden overflow-y-auto overscroll-contain",
33
+ // 14px around 10px rows inside a 4px inset, so the corners run concentric.
34
+ "t-dropdown bg-popover text-popover-foreground min-w-36 rounded-xl shadow-edge-raised relative isolate z-50 max-h-[var(--bits-floating-available-height,100svh)] max-w-[calc(100vw-1rem)] overflow-x-hidden overflow-y-auto overscroll-contain",
31
35
  className,
32
36
  )}
33
37
  {...restProps}
@@ -35,7 +39,7 @@
35
39
  <SelectScrollUpButton />
36
40
  <SelectPrimitive.Viewport
37
41
  class={cn(
38
- "h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1",
42
+ "h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1",
39
43
  )}
40
44
  >
41
45
  {@render children?.()}
@@ -18,7 +18,7 @@
18
18
  {value}
19
19
  data-slot="select-item"
20
20
  class={cn(
21
- "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 focus:bg-accent data-highlighted:bg-accent data-highlighted:text-accent-foreground focus:text-accent-foreground relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
21
+ "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 focus:bg-accent data-highlighted:bg-accent data-highlighted:text-accent-foreground focus:text-accent-foreground relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
22
22
  className,
23
23
  )}
24
24
  {...restProps}
@@ -19,7 +19,7 @@
19
19
  data-slot="select-trigger"
20
20
  data-size={size}
21
21
  class={cn(
22
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-1 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:text-[0.8rem] relative data-[size=sm]:after:absolute data-[size=sm]:after:top-1/2 data-[size=sm]:after:left-0 data-[size=sm]:after:h-[max(100%,2rem)] data-[size=sm]:after:w-full data-[size=sm]:after:-translate-y-1/2 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
22
+ "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 enabled:hover:border-foreground/20 focus-visible:border-ring focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-1 pr-2 pl-2.5 text-sm shadow-field transition-[color,background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:text-[0.8rem] relative data-[size=sm]:after:absolute data-[size=sm]:after:top-1/2 data-[size=sm]:after:left-0 data-[size=sm]:after:h-[max(100%,2rem)] data-[size=sm]:after:w-full data-[size=sm]:after:-translate-y-1/2 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
23
23
  className,
24
24
  )}
25
25
  {...restProps}
@@ -8,6 +8,7 @@ import Header from "./sheet-header.svelte";
8
8
  import Footer from "./sheet-footer.svelte";
9
9
  import Title from "./sheet-title.svelte";
10
10
  import Description from "./sheet-description.svelte";
11
+ import Body from "./sheet-body.svelte";
11
12
 
12
13
  export {
13
14
  Root,
@@ -20,6 +21,7 @@ export {
20
21
  Footer,
21
22
  Title,
22
23
  Description,
24
+ Body,
23
25
  //
24
26
  Root as Sheet,
25
27
  Close as SheetClose,
@@ -31,4 +33,5 @@ export {
31
33
  Footer as SheetFooter,
32
34
  Title as SheetTitle,
33
35
  Description as SheetDescription,
36
+ Body as SheetBody,
34
37
  };
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "../../lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <!-- The scrolling middle of a sheet, padded like the header and footer so the three
14
+ share one left edge. It takes the height the header and footer leave. -->
15
+ <div
16
+ bind:this={ref}
17
+ data-slot="sheet-body"
18
+ class={cn("flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto overscroll-contain px-4", className)}
19
+ {...restProps}
20
+ >
21
+ {@render children?.()}
22
+ </div>
@@ -19,6 +19,7 @@
19
19
  class: className,
20
20
  side = "right",
21
21
  showCloseButton = true,
22
+ floating,
22
23
  portalProps,
23
24
  children,
24
25
  ...restProps
@@ -26,20 +27,38 @@
26
27
  portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SheetPortal>>;
27
28
  side?: Side;
28
29
  showCloseButton?: boolean;
30
+ /**
31
+ * From `sm` up the sheet floats 8px off the viewport edge at the overlay radius, like
32
+ * SidePanel. On by default for left and right sheets; `floating={false}` keeps one flush.
33
+ * Under `sm` it stays full bleed, since a phone needs the width.
34
+ */
35
+ floating?: boolean;
29
36
  children: Snippet;
30
37
  } = $props();
31
38
 
39
+ // Side sheets float unless a caller opts out; a bottom sheet stays docked.
40
+ const floats = $derived(floating ?? side !== "bottom");
41
+
32
42
  // Plain side utilities, not `data-[side=x]:` variants, because the variant outranks a
33
43
  // caller's `sm:max-w-xl` and tailwind-merge can't dedupe across modifier sets.
34
44
  const SIDE_CLASS: Record<Side, string> = {
35
45
  // h-dvh, not h-full: on iOS Safari a fixed panel sizes against the layout
36
46
  // viewport, which is taller than the visible area, so h-full hides the footer.
37
- right: "inset-y-0 right-0 h-dvh w-3/4 border-l sm:max-w-sm",
38
- left: "inset-y-0 left-0 h-dvh w-3/4 border-r sm:max-w-sm",
47
+ // No side border: `shadow-edge-overlay` draws the hairline and the depth.
48
+ right: "inset-y-0 right-0 h-dvh w-3/4 sm:max-w-sm",
49
+ left: "inset-y-0 left-0 h-dvh w-3/4 sm:max-w-sm",
39
50
  // Full width, capped at 80vh so the overlay stays visible. The safe-area
40
51
  // padding keeps the last row clear of the home indicator.
41
52
  bottom:
42
- "inset-x-0 bottom-0 h-auto max-h-[80vh] w-full rounded-t-lg border-t pb-[env(safe-area-inset-bottom)]",
53
+ "inset-x-0 bottom-0 h-auto max-h-[80vh] w-full rounded-t-2xl pb-[env(safe-area-inset-bottom)]",
54
+ };
55
+
56
+ // Floating: an 8px inset (`2`) on the sheet's own sides, the height cut by both gutters,
57
+ // and the overlay radius on every corner (DESIGN.md rule 2).
58
+ const FLOATING_CLASS: Record<Side, string> = {
59
+ right: "sm:inset-y-2 sm:right-2 sm:h-[calc(100dvh-1rem)] sm:rounded-2xl",
60
+ left: "sm:inset-y-2 sm:left-2 sm:h-[calc(100dvh-1rem)] sm:rounded-2xl",
61
+ bottom: "sm:inset-x-2 sm:bottom-2 sm:w-[calc(100%-1rem)] sm:rounded-2xl",
43
62
  };
44
63
  </script>
45
64
 
@@ -49,9 +68,12 @@
49
68
  bind:ref
50
69
  data-slot="sheet-content"
51
70
  data-side={side}
71
+ data-floating={floats || undefined}
72
+ data-close={showCloseButton || undefined}
52
73
  class={cn(
53
- "t-sheet bg-background fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-overlay",
74
+ "t-sheet bg-popover text-popover-foreground fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-edge-overlay",
54
75
  SIDE_CLASS[side],
76
+ floats && FLOATING_CLASS[side],
55
77
  className,
56
78
  )}
57
79
  {...restProps}
@@ -60,7 +82,7 @@
60
82
  {#if showCloseButton}
61
83
  <SheetPrimitive.Close data-slot="sheet-close">
62
84
  {#snippet child({ props })}
63
- <Button variant="ghost" class="absolute top-3 right-3" size="sm" icon {...props}>
85
+ <Button variant="ghost" class="absolute top-3 right-2" size="sm" icon {...props}>
64
86
  <X size={16} />
65
87
  <span class="sr-only">Close</span>
66
88
  </Button>
@@ -6,14 +6,26 @@
6
6
  ref = $bindable(null),
7
7
  class: className,
8
8
  children,
9
+ band = false,
9
10
  ...restProps
10
- }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
12
+ /**
13
+ * A hairline on top and a quiet surface edge to edge, following the sheet's bottom
14
+ * corners when it floats. A bare `data-band` attribute does the same.
15
+ */
16
+ band?: boolean;
17
+ } = $props();
11
18
  </script>
12
19
 
13
20
  <div
14
21
  bind:this={ref}
15
22
  data-slot="sheet-footer"
16
- class={cn("gap-2 p-4 mt-auto flex flex-col", className)}
23
+ data-band={band || undefined}
24
+ class={cn(
25
+ "mt-auto flex flex-col gap-2 p-4",
26
+ "data-band:border-border data-band:bg-muted/50 data-band:border-t data-band:rounded-b-[inherit] data-band:flex-col-reverse data-band:sm:flex-row data-band:sm:justify-end",
27
+ className,
28
+ )}
17
29
  {...restProps}
18
30
  >
19
31
  {@render children?.()}
@@ -13,7 +13,7 @@
13
13
  <div
14
14
  bind:this={ref}
15
15
  data-slot="sheet-header"
16
- class={cn("gap-0.5 p-4 flex flex-col", className)}
16
+ class={cn("flex flex-col gap-1 p-4 in-data-close:pe-12", className)}
17
17
  {...restProps}
18
18
  >
19
19
  {@render children?.()}
@@ -12,6 +12,6 @@
12
12
  <SheetPrimitive.Title
13
13
  bind:ref
14
14
  data-slot="sheet-title"
15
- class={cn("text-foreground text-base font-medium", className)}
15
+ class={cn("text-foreground text-base leading-6 font-semibold tracking-tight", className)}
16
16
  {...restProps}
17
17
  />
@@ -1,10 +1,12 @@
1
1
  /* The sheet slide: transitions.dev panel reveal, owned by the sheet. */
2
2
 
3
+ /* The same 300ms open and 250ms close as before, named. DESIGN.md hasn't decided
4
+ whether the sheet takes Vaul's 500ms drawer timing, so the values hold. */
3
5
  :root {
4
- --sheet-open-dur: 300ms;
5
- --sheet-close-dur: 250ms;
6
+ --sheet-open-dur: var(--duration-slow);
7
+ --sheet-close-dur: var(--duration-fast);
6
8
  --sheet-translate: 100%;
7
- --sheet-ease: cubic-bezier(0.22, 1, 0.36, 1);
9
+ --sheet-ease: var(--ease-smooth-out);
8
10
  }
9
11
 
10
12
  .t-sheet {
@@ -41,8 +43,12 @@
41
43
  opacity var(--sheet-open-dur) var(--sheet-ease);
42
44
  }
43
45
 
46
+ /* Reduced motion keeps the fade and drops the slide (DESIGN.md rule 13). */
44
47
  @media (prefers-reduced-motion: reduce) {
45
- .t-sheet {
46
- transition: none !important;
48
+ .t-sheet,
49
+ .t-sheet[data-side],
50
+ .t-sheet[data-state] {
51
+ transform: none;
52
+ transition-property: opacity;
47
53
  }
48
54
  }
@@ -15,7 +15,7 @@
15
15
 
16
16
  const mergedProps = $derived({
17
17
  class: cn(
18
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:ring-2 [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 lg:after:hidden [&>svg]:shrink-0",
18
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:shadow-(--ring-focus) [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 lg:after:hidden [&>svg]:shrink-0",
19
19
  className,
20
20
  ),
21
21
  "data-slot": "sidebar-group-action",
@@ -15,7 +15,7 @@
15
15
 
16
16
  const mergedProps = $derived({
17
17
  class: cn(
18
- "text-sidebar-foreground/90 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 group-data-[collapsible=icon]:pointer-events-none focus-visible:ring-2 [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
18
+ "text-sidebar-foreground/90 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 group-data-[collapsible=icon]:pointer-events-none focus-visible:shadow-(--ring-focus) [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
19
19
  className,
20
20
  ),
21
21
  "data-slot": "sidebar-group-label",
@@ -14,7 +14,7 @@
14
14
  bind:this={ref}
15
15
  data-slot="sidebar-inset"
16
16
  class={cn(
17
- "bg-background transition-[margin] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] motion-reduce:transition-none lg:peer-data-[variant=inset]:m-2 lg:peer-data-[variant=inset]:ml-0 lg:peer-data-[variant=inset]:rounded-xl lg:peer-data-[variant=inset]:shadow-sm lg:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 relative flex min-h-0 w-full flex-1 flex-col",
17
+ "bg-background transition-[margin] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] motion-reduce:transition-none lg:peer-data-[variant=inset]:m-2 lg:peer-data-[variant=inset]:ml-0 lg:peer-data-[variant=inset]:rounded-3xl lg:peer-data-[variant=inset]:bg-card lg:peer-data-[variant=inset]:shadow-card lg:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 relative flex min-h-0 w-full flex-1 flex-col",
18
18
  className,
19
19
  )}
20
20
  {...restProps}
@@ -17,7 +17,7 @@
17
17
 
18
18
  const mergedProps = $derived({
19
19
  class: cn(
20
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-md p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:ring-2 [&>svg]:size-4 flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 lg:after:hidden [&>svg]:shrink-0",
20
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-foreground/4 hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-md p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:shadow-(--ring-focus) [&>svg]:size-4 flex items-center justify-center outline-hidden transition-[background-color,color,transform] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 lg:after:hidden [&>svg]:shrink-0",
21
21
  showOnHover &&
22
22
  "peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-open:opacity-100 lg:opacity-0",
23
23
  className,
@@ -2,10 +2,10 @@
2
2
  import { tv, type VariantProps } from "tailwind-variants";
3
3
 
4
4
  export const sidebarMenuButtonVariants = tv({
5
- base: "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding,background-color,color] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
5
+ base: "ring-sidebar-ring hover:bg-sidebar-foreground/4 hover:text-sidebar-accent-foreground active:bg-sidebar-foreground/8 active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding,background-color,color] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:shadow-(--ring-focus) data-active:font-medium peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
6
6
  variants: {
7
7
  variant: {
8
- default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
8
+ default: "hover:bg-sidebar-foreground/4 hover:text-sidebar-accent-foreground",
9
9
  outline:
10
10
  "bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
11
11
  },
@@ -19,7 +19,7 @@
19
19
 
20
20
  const mergedProps = $derived({
21
21
  class: cn(
22
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 any-pointer-coarse:h-11 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
22
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-foreground/4 hover:text-sidebar-accent-foreground active:bg-sidebar-foreground/8 active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 transition-[background-color,color] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] any-pointer-coarse:h-11 focus-visible:shadow-(--ring-focus) data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
23
23
  className,
24
24
  ),
25
25
  "data-slot": "sidebar-menu-sub-button",
@@ -14,7 +14,7 @@
14
14
  bind:this={ref}
15
15
  data-slot="sidebar-menu"
16
16
  data-sidebar="menu"
17
- class={cn("gap-0 flex w-full min-w-0 flex-col", className)}
17
+ class={cn("gap-0.5 flex w-full min-w-0 flex-col", className)}
18
18
  {...restProps}
19
19
  >
20
20
  {@render children?.()}
@@ -100,7 +100,7 @@
100
100
  <div
101
101
  data-sidebar="sidebar"
102
102
  data-slot="sidebar-inner"
103
- class="bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col"
103
+ class="bg-sidebar group-data-[variant=floating]:rounded-xl group-data-[variant=floating]:shadow-card flex size-full flex-col"
104
104
  >
105
105
  {@render children?.()}
106
106
  </div>
@@ -1,7 +1,10 @@
1
1
  import Root from "./skeleton.svelte";
2
+ import Delay from "./skeleton-delay.svelte";
2
3
 
3
4
  export {
4
5
  Root,
6
+ Delay,
5
7
  //
6
8
  Root as Skeleton,
9
+ Delay as SkeletonDelay,
7
10
  };
@@ -0,0 +1,63 @@
1
+ <script lang="ts" module>
2
+ /** DESIGN.md rule 20: a skeleton waits this long, so a fast answer never shows one. */
3
+ export const SHOW_DELAY_MS = 150;
4
+ /** DESIGN.md rule 20: once shown, a skeleton stays this long, so it never blinks. */
5
+ export const MIN_VISIBLE_MS = 300;
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ import { untrack, type Snippet } from "svelte";
10
+
11
+ interface Props {
12
+ /** The data is on its way. */
13
+ loading: boolean;
14
+ /** What shows while it loads: one or more `Skeleton`s shaped like the content. */
15
+ skeleton: Snippet;
16
+ /** The content, drawn once loading ends and the skeleton has had its minimum time. */
17
+ children: Snippet;
18
+ /** Ms to wait before the skeleton shows. */
19
+ showDelay?: number;
20
+ /** Ms the skeleton stays once it shows. */
21
+ minVisible?: number;
22
+ }
23
+
24
+ let {
25
+ loading,
26
+ skeleton,
27
+ children,
28
+ showDelay = SHOW_DELAY_MS,
29
+ minVisible = MIN_VISIBLE_MS,
30
+ }: Props = $props();
31
+
32
+ // `wait` draws nothing: the first 150ms of a load, which a fast answer never leaves.
33
+ let phase = $state<"wait" | "skeleton" | "content">(untrack(() => (loading ? "wait" : "content")));
34
+ let shownAt = 0;
35
+
36
+ $effect(() => {
37
+ const isLoading = loading;
38
+ let timer: ReturnType<typeof setTimeout> | undefined;
39
+ untrack(() => {
40
+ if (isLoading) {
41
+ if (phase === "skeleton") return;
42
+ phase = "wait";
43
+ timer = setTimeout(() => {
44
+ phase = "skeleton";
45
+ shownAt = performance.now();
46
+ }, showDelay);
47
+ } else if (phase === "skeleton") {
48
+ const left = minVisible - (performance.now() - shownAt);
49
+ if (left > 0) timer = setTimeout(() => (phase = "content"), left);
50
+ else phase = "content";
51
+ } else {
52
+ phase = "content";
53
+ }
54
+ });
55
+ return () => clearTimeout(timer);
56
+ });
57
+ </script>
58
+
59
+ {#if phase === "skeleton"}
60
+ {@render skeleton()}
61
+ {:else if phase === "content"}
62
+ {@render children()}
63
+ {/if}
@@ -0,0 +1,57 @@
1
+ import { createRawSnippet, flushSync, mount, unmount } from "svelte";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
3
+ import SkeletonDelay from "./skeleton-delay.svelte";
4
+
5
+ const skeleton = createRawSnippet(() => ({ render: () => "<i>skeleton</i>" }));
6
+ const children = createRawSnippet(() => ({ render: () => "<b>content</b>" }));
7
+
8
+ function render(loading: boolean) {
9
+ const props = $state({ loading, skeleton, children });
10
+ const target = document.createElement("div");
11
+ const app = mount(SkeletonDelay, { target, props });
12
+ flushSync();
13
+ return {
14
+ shows: () => target.textContent,
15
+ set: (next: boolean) => {
16
+ props.loading = next;
17
+ flushSync();
18
+ },
19
+ wait: (ms: number) => {
20
+ vi.advanceTimersByTime(ms);
21
+ flushSync();
22
+ },
23
+ done: () => void unmount(app),
24
+ };
25
+ }
26
+
27
+ describe("SkeletonDelay", () => {
28
+ beforeEach(() => vi.useFakeTimers());
29
+ afterEach(() => vi.useRealTimers());
30
+
31
+ it("shows nothing for a fast answer, then the content", () => {
32
+ const view = render(true);
33
+ expect(view.shows()).toBe("");
34
+ view.wait(100);
35
+ view.set(false);
36
+ expect(view.shows()).toBe("content");
37
+ view.done();
38
+ });
39
+
40
+ it("shows the skeleton after 150ms and holds it 300ms", () => {
41
+ const view = render(true);
42
+ view.wait(150);
43
+ expect(view.shows()).toBe("skeleton");
44
+ view.wait(50);
45
+ view.set(false);
46
+ expect(view.shows()).toBe("skeleton");
47
+ view.wait(250);
48
+ expect(view.shows()).toBe("content");
49
+ view.done();
50
+ });
51
+
52
+ it("draws the content at once when nothing is loading", () => {
53
+ const view = render(false);
54
+ expect(view.shows()).toBe("content");
55
+ view.done();
56
+ });
57
+ });
@@ -12,6 +12,10 @@
12
12
  <div
13
13
  bind:this={ref}
14
14
  data-slot="skeleton"
15
- class={cn("bg-muted rounded-md animate-pulse", className)}
15
+ class={cn(
16
+ // A foreground wash on light, because `bg-muted` (L 0.94) vanished on the 0.945 canvas.
17
+ "bg-foreground/6 dark:bg-muted rounded-sm animate-pulse motion-reduce:animate-none",
18
+ className,
19
+ )}
16
20
  {...restProps}
17
21
  ></div>
@@ -45,7 +45,7 @@
45
45
  data-slot="slider-thumb"
46
46
  aria-label={thumbLabel}
47
47
  aria-valuetext={valueText}
48
- class="bg-background focus-visible:ring-ring/50 block size-5 cursor-grab rounded-full border-2 border-current shadow-[var(--elev-btn)] transition-[transform,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none hover:scale-110 focus-visible:ring-3 active:cursor-grabbing data-active:scale-110 data-active:shadow-[var(--elev-raised)] pointer-coarse:size-6 after:absolute after:top-1/2 after:left-1/2 after:size-8 after:-translate-x-1/2 after:-translate-y-1/2 any-pointer-coarse:after:size-11"
48
+ class="bg-card block size-5 cursor-grab rounded-full border-2 border-current shadow-[var(--elev-btn)] transition-[transform,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none hover:scale-110 focus-visible:shadow-(--ring-focus) active:cursor-grabbing data-active:scale-110 data-active:shadow-[var(--elev-raised)] pointer-coarse:size-6 after:absolute after:top-1/2 after:left-1/2 after:size-8 after:-translate-x-1/2 after:-translate-y-1/2 any-pointer-coarse:after:size-11"
49
49
  />
50
50
  {/each}
51
51
  {/snippet}
@@ -19,13 +19,13 @@
19
19
  data-slot="switch"
20
20
  data-size={size}
21
21
  class={cn(
22
- "data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-[18px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-[background-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none after:absolute after:-inset-x-3 after:top-1/2 after:h-[max(100%,2rem)] after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] data-disabled:cursor-not-allowed data-disabled:opacity-50",
22
+ "data-checked:bg-primary data-unchecked:bg-input focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent aria-invalid:ring-3 data-[size=default]:h-[18px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-[background-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none after:absolute after:-inset-x-3 after:top-1/2 after:h-[max(100%,2rem)] after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] data-disabled:cursor-not-allowed data-disabled:opacity-50",
23
23
  className,
24
24
  )}
25
25
  {...restProps}
26
26
  >
27
27
  <SwitchPrimitive.Thumb
28
28
  data-slot="switch-thumb"
29
- class="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform duration-[var(--duration-medium)] ease-[var(--ease-smooth-out)] rtl:data-[state=checked]:translate-x-[calc(-100%)]"
29
+ class="bg-card dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform duration-[var(--duration-medium)] ease-[var(--ease-smooth-out)] rtl:data-[state=checked]:translate-x-[calc(-100%)]"
30
30
  />
31
31
  </SwitchPrimitive.Root>
@@ -14,7 +14,7 @@
14
14
  bind:this={ref}
15
15
  data-slot="table-row"
16
16
  class={cn(
17
- "data-[state=selected]:bg-muted transition-[background-color,transform,box-shadow] duration-[var(--duration-quick)] ease-out hover:bg-muted/20 hover:-translate-y-px hover:shadow-[0_1px_3px_rgba(0,0,0,0.04)] motion-reduce:hover:translate-y-0 motion-reduce:hover:shadow-none",
17
+ "data-[state=selected]:bg-muted transition-[background-color] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] hover:bg-muted/20",
18
18
  className,
19
19
  )}
20
20
  {...restProps}
@@ -29,7 +29,7 @@
29
29
  shows the trigger's own active background and never an empty pill. -->
30
30
  <span
31
31
  aria-hidden="true"
32
- class="bg-background dark:border-input dark:bg-input/30 pointer-events-none absolute inset-y-[3px] left-0 z-0 w-0 rounded-md border border-transparent opacity-0 shadow-sm transition-[transform,width,opacity] duration-[var(--duration-fast,250ms)] ease-[var(--ease-smooth-out,cubic-bezier(0.22,1,0.36,1))] will-change-[transform,width] motion-reduce:transition-opacity"
32
+ class="bg-card dark:bg-input/30 pointer-events-none absolute inset-y-[3px] left-0 z-0 w-0 rounded-[calc(var(--radius-lg)-3px)] border border-transparent opacity-0 shadow-btn transition-[transform,width,opacity] duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] will-change-transform motion-reduce:transition-opacity"
33
33
  {@attach slidingPill}
34
34
  ></span>
35
35
  {@render children?.()}
@@ -13,7 +13,8 @@
13
13
  bind:ref
14
14
  data-slot="tabs-trigger"
15
15
  class={cn(
16
- "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground group-data-[pill=on]/tabs-list:data-[state=active]:border-transparent group-data-[pill=on]/tabs-list:data-[state=active]:bg-transparent group-data-[pill=on]/tabs-list:data-[state=active]:shadow-none dark:group-data-[pill=on]/tabs-list:data-[state=active]:bg-transparent relative z-[1] inline-flex h-full flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 text-sm group-data-[size=sm]/tabs-list:text-[0.8rem] font-medium whitespace-nowrap after:absolute after:top-1/2 after:left-0 after:h-[max(100%,2rem)] after:w-full after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
16
+ // Muted at rest, foreground on hover and when active, so each state reads stronger than rest.
17
+ "data-[state=active]:bg-card data-[state=active]:text-foreground hover:text-foreground focus-visible:shadow-(--ring-focus) focus-visible:data-[state=active]:shadow-(--ring-focus) dark:data-[state=active]:bg-input/30 text-muted-foreground group-data-[pill=on]/tabs-list:data-[state=active]:border-transparent group-data-[pill=on]/tabs-list:data-[state=active]:bg-transparent group-data-[pill=on]/tabs-list:data-[state=active]:shadow-none dark:group-data-[pill=on]/tabs-list:data-[state=active]:bg-transparent relative z-[1] inline-flex h-full flex-1 items-center justify-center gap-1.5 rounded-[calc(var(--radius-lg)-3px)] border border-transparent px-2 text-sm group-data-[size=sm]/tabs-list:text-[0.8rem] font-medium whitespace-nowrap after:absolute after:top-1/2 after:left-0 after:h-[max(100%,2rem)] after:w-full after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] transition-[color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-btn [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
17
18
  className,
18
19
  )}
19
20
  {...restProps}
@@ -15,7 +15,7 @@
15
15
  bind:this={ref}
16
16
  data-slot={dataSlot}
17
17
  class={cn(
18
- "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border bg-transparent px-2.5 py-2 text-base shadow-field transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
18
+ "border-input dark:bg-input/30 enabled:hover:border-foreground/20 focus-visible:border-ring focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border bg-transparent px-2.5 py-2 text-base shadow-field transition-[color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
19
19
  className,
20
20
  )}
21
21
  bind:value
@@ -2,12 +2,12 @@
2
2
  import { type VariantProps, tv } from "tailwind-variants";
3
3
 
4
4
  export const toggleVariants = tv({
5
- base: "relative after:absolute after:top-1/2 after:left-1/2 after:h-[max(100%,2rem)] after:w-[max(100%,2rem)] after:-translate-x-1/2 after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] any-pointer-coarse:after:w-[max(100%,2.75rem)] hover:bg-muted hover:text-muted-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
5
+ base: "relative after:absolute after:top-1/2 after:left-1/2 after:h-[max(100%,2rem)] after:w-[max(100%,2rem)] after:-translate-x-1/2 after:-translate-y-1/2 any-pointer-coarse:after:h-[max(100%,2.75rem)] any-pointer-coarse:after:w-[max(100%,2.75rem)] hover:bg-muted hover:text-muted-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[background-color,color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
6
6
  variants: {
7
7
  variant: {
8
8
  default: "bg-transparent",
9
9
  outline:
10
- "border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-xs",
10
+ "border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-btn",
11
11
  },
12
12
  size: {
13
13
  // Same steps as Button: sm 28, default 32, lg 36. 40 went with the scale.
@@ -59,7 +59,7 @@ get along, so we shut typescript up by casting `value` to `never`.
59
59
  data-spacing={spacing}
60
60
  style={`--gap: ${spacing}`}
61
61
  class={cn(
62
- "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
62
+ "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-btn",
63
63
  className,
64
64
  )}
65
65
  {...restProps}
@@ -27,7 +27,7 @@
27
27
  {sideOffset}
28
28
  {side}
29
29
  class={cn(
30
- "t-dropdown z-50 w-fit max-w-xs rounded-lg border border-border bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-raised select-none outline-none",
30
+ "t-dropdown z-50 w-fit max-w-xs rounded-lg bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-edge-raised select-none outline-none",
31
31
  "has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm",
32
32
  className,
33
33
  )}