@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.
- package/README.md +2 -0
- package/dist/charts/dashboard/data-table.js +1 -2
- package/dist/components/list-page/filter-section.js +1 -1
- package/dist/components/list-page/list-table.js +5 -6
- package/dist/components/list-page/queue-rail.js +2 -1
- package/dist/components/picker/trigger.js +3 -3
- package/dist/components/segmented/segmented.js +5 -5
- package/dist/components/work-parts/looks.js +1 -1
- package/dist/shadcn/dialog/index.js +3 -2
- package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
- package/dist/shadcn/sheet/index.js +3 -2
- package/dist/shadcn/skeleton/index.js +3 -2
- package/dist/status/index.js +1 -0
- package/dist/work/kanban-drag.js +13 -7
- package/package.json +1 -1
- package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
- package/src/ai/loading-state/PixelLoader.svelte +1 -1
- package/src/animations/dropdown.css +19 -10
- package/src/animations/modal.css +7 -2
- package/src/charts/charts.css +3 -5
- package/src/charts/dashboard/dashboard-section.svelte +1 -1
- package/src/charts/dashboard/data-table.ts +1 -2
- package/src/charts/dashboard/stat-tile.svelte +1 -1
- package/src/charts/number-chart.css +8 -7
- package/src/charts/pop-value.svelte +68 -0
- package/src/charts/primitives/chart-tooltip.svelte +1 -1
- package/src/charts/reference-line.css +1 -1
- package/src/charts/stat-card.css +4 -4
- package/src/components/copy/copy-button.svelte +11 -10
- package/src/components/data-table/row-arrow.svelte +1 -1
- package/src/components/data-table/row-context-menu.svelte +1 -1
- package/src/components/data-table/sortable-head.svelte +2 -2
- package/src/components/data-table/table-skeleton.svelte +5 -5
- package/src/components/docs/doc-page.svelte +1 -3
- package/src/components/docs/on-this-page.svelte +4 -4
- package/src/components/field-row/field-row.svelte +1 -1
- package/src/components/list-page/detail-empty.svelte +3 -8
- package/src/components/list-page/detail-header.svelte +1 -1
- package/src/components/list-page/detail-row.svelte +2 -1
- package/src/components/list-page/empty-state.svelte +15 -7
- package/src/components/list-page/filter-item.svelte +2 -2
- package/src/components/list-page/filter-popover.svelte +1 -1
- package/src/components/list-page/filter-section.ts +1 -4
- package/src/components/list-page/grouped-list.svelte +37 -8
- package/src/components/list-page/list-page-shell.svelte +1 -1
- package/src/components/list-page/list-table.ts +6 -6
- package/src/components/list-page/master-detail.svelte +1 -1
- package/src/components/list-page/queue-rail.ts +2 -2
- package/src/components/list-page/rail-empty.svelte +1 -1
- package/src/components/list-page/rail-row.svelte +5 -5
- package/src/components/open-in/open-in.svelte +2 -1
- package/src/components/picker/trigger.ts +6 -5
- package/src/components/section-card/section-card.svelte +1 -1
- package/src/components/section-header/section-header.svelte +4 -2
- package/src/components/section-header/section-label.svelte +2 -2
- package/src/components/segmented/animated-tabs.svelte +1 -1
- package/src/components/segmented/section-runs.svelte +1 -1
- package/src/components/segmented/segment.svelte +4 -6
- package/src/components/segmented/segmented-track.svelte +18 -52
- package/src/components/segmented/segmented.ts +5 -5
- package/src/components/table-view/table-view.svelte +43 -24
- package/src/components/tags-input/tags-input.svelte +17 -31
- package/src/components/work-parts/looks.ts +1 -1
- package/src/editor/content.css +3 -3
- package/src/editor/editor.svelte +1 -1
- package/src/inbox/thread-inbox.svelte +2 -9
- package/src/panel/side-panel.svelte +9 -4
- package/src/record/info-list.svelte +7 -8
- package/src/record/project-list.svelte +1 -1
- package/src/record/record-demo.svelte +2 -4
- package/src/record/record-shell.svelte +1 -1
- package/src/record/record-stats.svelte +9 -3
- package/src/record/steps-rail.svelte +7 -11
- package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
- package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
- package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
- package/src/shadcn/badge/badge.svelte +3 -1
- package/src/shadcn/bubble/bubble-content.svelte +1 -1
- package/src/shadcn/bubble/bubble.svelte +1 -1
- package/src/shadcn/button/button.svelte +11 -5
- package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
- package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
- package/src/shadcn/calendar/calendar.svelte +1 -1
- package/src/shadcn/card/card.svelte +2 -1
- package/src/shadcn/checkbox/checkbox.svelte +1 -1
- package/src/shadcn/code-block/code-block-actions.svelte +1 -1
- package/src/shadcn/command/command-dialog.svelte +2 -2
- package/src/shadcn/command/command-group.svelte +1 -1
- package/src/shadcn/command/command-input.svelte +1 -1
- package/src/shadcn/command/command-item.svelte +1 -1
- package/src/shadcn/dialog/dialog-body.svelte +22 -0
- package/src/shadcn/dialog/dialog-content.svelte +19 -4
- package/src/shadcn/dialog/dialog-footer.svelte +19 -1
- package/src/shadcn/dialog/dialog-header.svelte +6 -1
- package/src/shadcn/dialog/dialog-title.svelte +2 -1
- package/src/shadcn/dialog/index.ts +3 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
- package/src/shadcn/field/field-separator.svelte +1 -1
- package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
- package/src/shadcn/input/input.svelte +2 -2
- package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
- package/src/shadcn/popover/popover-content.svelte +1 -1
- package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
- package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
- package/src/shadcn/select/select-content.svelte +6 -2
- package/src/shadcn/select/select-item.svelte +1 -1
- package/src/shadcn/select/select-trigger.svelte +1 -1
- package/src/shadcn/sheet/index.ts +3 -0
- package/src/shadcn/sheet/sheet-body.svelte +22 -0
- package/src/shadcn/sheet/sheet-content.svelte +27 -5
- package/src/shadcn/sheet/sheet-footer.svelte +14 -2
- package/src/shadcn/sheet/sheet-header.svelte +1 -1
- package/src/shadcn/sheet/sheet-title.svelte +1 -1
- package/src/shadcn/sheet/sheet.css +11 -5
- package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
- package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
- package/src/shadcn/sidebar/sidebar.svelte +1 -1
- package/src/shadcn/skeleton/index.ts +3 -0
- package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
- package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
- package/src/shadcn/skeleton/skeleton.svelte +5 -1
- package/src/shadcn/slider/slider.svelte +1 -1
- package/src/shadcn/switch/switch.svelte +2 -2
- package/src/shadcn/table/table-row.svelte +1 -1
- package/src/shadcn/tabs/tabs-list.svelte +1 -1
- package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
- package/src/shadcn/textarea/textarea.svelte +1 -1
- package/src/shadcn/toggle/toggle.svelte +2 -2
- package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
- package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
- package/src/shadcn/trace/trace.svelte +1 -1
- package/src/status/README.md +1 -1
- package/src/status/index.ts +1 -0
- package/src/status/status-badge.svelte +21 -7
- package/src/status/status-dot.svelte +6 -11
- package/src/status/status-pill.svelte +50 -0
- package/src/thread/thread.svelte +54 -136
- package/src/tokens/craft.css +88 -30
- package/src/tokens/palette.css +16 -9
- package/src/work/demo.svelte +48 -42
- package/src/work/detail/detail-demo.svelte +33 -24
- package/src/work/detail/task-chip.svelte +4 -4
- package/src/work/detail/task-detail.svelte +41 -108
- package/src/work/detail/task-property.svelte +7 -13
- package/src/work/detail/task-thread.svelte +48 -119
- package/src/work/docs/doc-editor-page.svelte +5 -4
- package/src/work/docs/doc-menu.svelte +9 -4
- package/src/work/docs/docs-demo.svelte +4 -4
- package/src/work/docs/edited-chip.svelte +11 -4
- package/src/work/gantt/gantt-bar.svelte +18 -14
- package/src/work/gantt/gantt-grid.svelte +7 -7
- package/src/work/gantt/gantt-group-row.svelte +7 -10
- package/src/work/gantt/gantt-header.svelte +3 -1
- package/src/work/gantt/gantt-task-row.svelte +19 -9
- package/src/work/gantt/gantt.css +4 -5
- package/src/work/inbox/inbox-card.svelte +32 -10
- package/src/work/inbox/inbox-draft.svelte +2 -2
- package/src/work/kanban-column.svelte +7 -15
- package/src/work/kanban-drag.ts +13 -8
- package/src/work/task-gantt.svelte +2 -1
- package/src/work/task-list.svelte +54 -53
- package/src/work/time/time-chip.svelte +16 -19
- package/src/work/time/time-dot.svelte +2 -1
- package/src/work/time/time-ring.svelte +2 -3
- 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
|
|
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-
|
|
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
|
-
|
|
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:
|
|
7
|
-
headRow: (className) => cn("hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
15
|
-
"hover:bg-
|
|
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
|
|
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`:
|
|
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
|
|
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:
|
|
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)-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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 };
|
package/dist/status/index.js
CHANGED
|
@@ -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";
|
package/dist/work/kanban-drag.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
const FRAME_PAD =
|
|
1
|
+
const FRAME_PAD = 24;
|
|
2
2
|
/**
|
|
3
|
-
* Native drag shows a flat, faded snapshot. This hands the browser a
|
|
4
|
-
*
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|
-
|
|
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
|
|
package/src/animations/modal.css
CHANGED
|
@@ -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-
|
|
73
|
-
|
|
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
|
}
|
package/src/charts/charts.css
CHANGED
|
@@ -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:
|
|
9
|
-
border-radius:
|
|
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-
|
|
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
|
|
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-
|
|
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:
|
|
11
|
-
border-radius:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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}
|
package/src/charts/stat-card.css
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
flex-direction: column;
|
|
7
7
|
gap: 0.375rem;
|
|
8
8
|
padding: 1.25rem 1.5rem;
|
|
9
|
-
border:
|
|
10
|
-
border-radius:
|
|
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:
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
53
|
+
<CaretUpDown class="size-3.5 text-muted-foreground" />
|
|
54
54
|
{/if}
|
|
55
55
|
</span>
|
|
56
56
|
</th>
|