@heyramzi/ui 2.20.0 → 2.21.1
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/alert/alert-variants.js +9 -0
- package/dist/shadcn/alert/index.js +1 -1
- package/dist/shadcn/badge/badge-variants.js +17 -0
- package/dist/shadcn/badge/index.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/shadcn/toggle/index.js +1 -1
- package/dist/shadcn/toggle/toggle-variants.js +20 -0
- package/dist/status/index.js +2 -0
- package/dist/status/pill-tint.js +13 -0
- package/dist/work/kanban-drag.js +13 -7
- package/package.json +23 -23
- 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-rail.svelte +5 -1
- 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/alert-variants.ts +17 -0
- package/src/shadcn/alert/alert.svelte +2 -16
- package/src/shadcn/alert/index.ts +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-variants.ts +23 -0
- package/src/shadcn/badge/badge.svelte +2 -20
- package/src/shadcn/badge/index.ts +1 -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/index.ts +1 -1
- package/src/shadcn/toggle/toggle-variants.ts +26 -0
- package/src/shadcn/toggle/toggle.svelte +7 -25
- 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 +2 -0
- package/src/status/pill-tint.ts +16 -0
- package/src/status/status-badge.svelte +21 -7
- package/src/status/status-dot.svelte +6 -11
- package/src/status/status-pill.svelte +40 -0
- package/src/svelte-reexports.test.ts +43 -0
- package/src/thread/thread.svelte +54 -136
- package/src/tokens/craft.css +55 -36
- 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
|
@@ -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(
|
|
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-
|
|
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:
|
|
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-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type VariantProps, tv } from "tailwind-variants";
|
|
2
|
+
|
|
3
|
+
export const toggleVariants = tv({
|
|
4
|
+
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",
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
default: "bg-transparent",
|
|
8
|
+
outline:
|
|
9
|
+
"border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-btn",
|
|
10
|
+
},
|
|
11
|
+
size: {
|
|
12
|
+
// Same steps as Button: sm 28, default 32, lg 36. 40 went with the scale.
|
|
13
|
+
default: "h-8 min-w-8 px-2",
|
|
14
|
+
sm: "h-7 min-w-7 px-1.5",
|
|
15
|
+
lg: "h-9 min-w-9 px-2.5",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "default",
|
|
20
|
+
size: "default",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
|
|
25
|
+
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
|
|
26
|
+
export type ToggleVariants = VariantProps<typeof toggleVariants>;
|
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
toggleVariants,
|
|
4
|
+
type ToggleSize,
|
|
5
|
+
type ToggleVariant,
|
|
6
|
+
type ToggleVariants,
|
|
7
|
+
} from "./toggle-variants.js";
|
|
3
8
|
|
|
4
|
-
export
|
|
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",
|
|
6
|
-
variants: {
|
|
7
|
-
variant: {
|
|
8
|
-
default: "bg-transparent",
|
|
9
|
-
outline:
|
|
10
|
-
"border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-xs",
|
|
11
|
-
},
|
|
12
|
-
size: {
|
|
13
|
-
// Same steps as Button: sm 28, default 32, lg 36. 40 went with the scale.
|
|
14
|
-
default: "h-8 min-w-8 px-2",
|
|
15
|
-
sm: "h-7 min-w-7 px-1.5",
|
|
16
|
-
lg: "h-9 min-w-9 px-2.5",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
defaultVariants: {
|
|
20
|
-
variant: "default",
|
|
21
|
-
size: "default",
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
|
|
26
|
-
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
|
|
27
|
-
export type ToggleVariants = VariantProps<typeof toggleVariants>;
|
|
9
|
+
export { toggleVariants, type ToggleSize, type ToggleVariant, type ToggleVariants };
|
|
28
10
|
</script>
|
|
29
11
|
|
|
30
12
|
<script lang="ts">
|
|
@@ -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-
|
|
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
|
|
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
|
)}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
bind:ref
|
|
18
18
|
data-slot="trace-collapsible-trigger"
|
|
19
19
|
class={cn(
|
|
20
|
-
"gap-1 rounded-md focus-visible:ring-
|
|
20
|
+
"gap-1 rounded-md focus-visible:shadow-(--ring-focus) group/trace-collapsible-trigger flex w-fit max-w-full min-w-0 cursor-pointer items-center text-left text-muted-foreground transition-colors outline-none hover:text-foreground has-[[data-status=error]]:text-destructive",
|
|
21
21
|
className
|
|
22
22
|
)}
|
|
23
23
|
{...restProps}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
bind:this={ref}
|
|
15
15
|
data-slot="trace"
|
|
16
16
|
class={cn(
|
|
17
|
-
"gap-2 *:has-[>[data-slot=trace-line]]:rounded-md *:has-[>[data-slot=trace-line]]:focus-visible:ring-
|
|
17
|
+
"gap-2 *:has-[>[data-slot=trace-line]]:rounded-md *:has-[>[data-slot=trace-line]]:focus-visible:shadow-(--ring-focus) flex w-full min-w-0 flex-col *:has-[>[data-slot=trace-line]]:flex *:has-[>[data-slot=trace-line]]:w-fit *:has-[>[data-slot=trace-line]]:max-w-full *:has-[>[data-slot=trace-line]]:min-w-0 *:has-[>[data-slot=trace-line]]:text-left *:has-[>[data-slot=trace-line]]:outline-none",
|
|
18
18
|
className
|
|
19
19
|
)}
|
|
20
20
|
{...restProps}
|
package/src/status/README.md
CHANGED
|
@@ -15,4 +15,4 @@ Status badge, dot and icon from one map. `defineStatuses` builds the map, and th
|
|
|
15
15
|
<StatusBadge {statuses} status="sent" />
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Exports: `StatusBadge`, `StatusDot`, `StatusIcon`, `defineStatuses` and the types `StatusDef`, `StatusDotVariant`, `StatusId`, `StatusMap`, `Statuses`. The old subpaths `tasks/status`, `tasks/status-dot` and `tasks/statuses` resolve to the same files.
|
|
18
|
+
Exports: `StatusBadge`, `StatusPill` (the tinted pill StatusBadge draws, for an organism that has its own icon and label), `StatusDot`, `StatusIcon`, `defineStatuses` and the types `StatusDef`, `StatusDotVariant`, `StatusId`, `StatusMap`, `Statuses`. The old subpaths `tasks/status`, `tasks/status-dot` and `tasks/statuses` resolve to the same files.
|
package/src/status/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Tone } from "../lib/tone.js";
|
|
2
|
+
|
|
3
|
+
// A quiet tint and no border: the icon and the text carry the tone, the fill only groups
|
|
4
|
+
// them. Neutral reads off the foreground, not `bg-muted`, because muted sits 0.005 L from
|
|
5
|
+
// the canvas and vanishes there (tokens board, "what fights the new scale").
|
|
6
|
+
// It lives in a .ts module, not status-pill.svelte's module script, so a plain tsc consumer
|
|
7
|
+
// can import it: tsc's `*.svelte` shim only knows a default export.
|
|
8
|
+
export const PILL_TINT: Record<Tone, string> = {
|
|
9
|
+
neutral: "bg-foreground/6 text-muted-foreground",
|
|
10
|
+
accent:
|
|
11
|
+
"bg-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_12%,transparent)] text-[color:var(--tone-accent,var(--color-primary))]",
|
|
12
|
+
info: "bg-info/12 text-info-800 dark:text-info-400",
|
|
13
|
+
success: "bg-success/12 text-success-800 dark:text-success-400",
|
|
14
|
+
warning: "bg-warning/14 text-warning-800 dark:text-warning-400",
|
|
15
|
+
destructive: "bg-destructive/10 text-destructive-ink",
|
|
16
|
+
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
-
import
|
|
5
|
+
import StatusPill from "./status-pill.svelte";
|
|
6
|
+
import StatusIcon from "./status-icon.svelte";
|
|
6
7
|
import { Spinner } from "../shadcn/spinner/index.js";
|
|
7
8
|
import { TONE_COLOR, type Tone } from "../lib/tone.js";
|
|
8
9
|
import type { Statuses } from "./status-types.js";
|
|
@@ -44,22 +45,35 @@
|
|
|
44
45
|
let resolvedLabel = $derived(
|
|
45
46
|
label ?? (statuses && status ? statuses.label(status) : (status ?? "")),
|
|
46
47
|
);
|
|
48
|
+
// Every registry status: StatusIcon draws the variant's shape when the map has none (DESIGN.md, Patterns).
|
|
49
|
+
let hasIcon = $derived(Boolean(statuses && status && statuses.has(status)));
|
|
47
50
|
</script>
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
<!-- The pill draws the tint; this picks what goes before the label. A registry icon wins,
|
|
53
|
+
so the state reads without its colour. -->
|
|
54
|
+
{#snippet lead()}
|
|
55
|
+
{#if busy}
|
|
56
|
+
<Spinner class="size-3" aria-hidden="true" />
|
|
57
|
+
{:else if hasIcon && statuses && status}
|
|
58
|
+
<StatusIcon {statuses} {status} size={12} animate={false} />
|
|
59
|
+
{:else if dot}
|
|
51
60
|
<span
|
|
52
61
|
class="size-1.5 shrink-0 rounded-full"
|
|
53
62
|
style:background-color={TONE_COLOR[resolvedTone]}
|
|
54
63
|
aria-hidden="true"
|
|
55
64
|
></span>
|
|
56
65
|
{/if}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
{/snippet}
|
|
67
|
+
|
|
68
|
+
<StatusPill
|
|
69
|
+
tone={resolvedTone}
|
|
70
|
+
icon={busy || hasIcon || dot ? lead : undefined}
|
|
71
|
+
class={cn(capitalize && "capitalize", className)}
|
|
72
|
+
{...restProps}
|
|
73
|
+
>
|
|
60
74
|
{#if children}
|
|
61
75
|
{@render children()}
|
|
62
76
|
{:else}
|
|
63
77
|
{resolvedLabel}
|
|
64
78
|
{/if}
|
|
65
|
-
</
|
|
79
|
+
</StatusPill>
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
|
|
152
152
|
.sd-fill {
|
|
153
153
|
transform-origin: 7px 7px;
|
|
154
|
-
animation: sd-fill-in var(--duration-slow
|
|
154
|
+
animation: sd-fill-in var(--duration-slow) var(--ease-smooth-out) forwards;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
@keyframes sd-fill-in {
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
|
|
168
168
|
.sd-pop {
|
|
169
169
|
transform-origin: 7px 7px;
|
|
170
|
-
animation: sd-pop-in var(--duration-medium
|
|
170
|
+
animation: sd-pop-in var(--duration-medium) var(--ease-smooth-out) forwards;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
@keyframes sd-pop-in {
|
|
@@ -175,10 +175,6 @@
|
|
|
175
175
|
transform: scale(0.4);
|
|
176
176
|
opacity: 0;
|
|
177
177
|
}
|
|
178
|
-
60% {
|
|
179
|
-
transform: scale(1.08);
|
|
180
|
-
opacity: 1;
|
|
181
|
-
}
|
|
182
178
|
to {
|
|
183
179
|
transform: scale(1);
|
|
184
180
|
opacity: 1;
|
|
@@ -188,19 +184,18 @@
|
|
|
188
184
|
.sd-check {
|
|
189
185
|
stroke-dasharray: 12;
|
|
190
186
|
stroke-dashoffset: 12;
|
|
191
|
-
animation: sd-trace var(--duration-medium
|
|
192
|
-
var(--duration-quick, 120ms) forwards;
|
|
187
|
+
animation: sd-trace var(--duration-medium) var(--ease-smooth-out) var(--duration-quick) forwards;
|
|
193
188
|
}
|
|
194
189
|
|
|
195
190
|
.sd-cross {
|
|
196
191
|
stroke-dasharray: 6;
|
|
197
192
|
stroke-dashoffset: 6;
|
|
198
|
-
animation: sd-trace calc(var(--duration-fast
|
|
199
|
-
|
|
193
|
+
animation: sd-trace calc(var(--duration-fast) / 2) var(--ease-smooth-out) var(--duration-quick)
|
|
194
|
+
forwards;
|
|
200
195
|
}
|
|
201
196
|
|
|
202
197
|
.sd-cross-second {
|
|
203
|
-
animation-delay: calc(var(--duration-quick
|
|
198
|
+
animation-delay: calc(var(--duration-quick) + var(--duration-fast) / 2);
|
|
204
199
|
}
|
|
205
200
|
|
|
206
201
|
@keyframes sd-trace {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Tone } from "../lib/tone.js";
|
|
3
|
+
import { PILL_TINT } from "./pill-tint.js";
|
|
4
|
+
|
|
5
|
+
export { PILL_TINT };
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import type { Snippet } from "svelte";
|
|
10
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
11
|
+
import { cn } from "../lib/utils.js";
|
|
12
|
+
|
|
13
|
+
interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "class" | "children"> {
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
/** What sits before the label: a Phosphor icon, a `StatusIcon`, a dot or a spinner. */
|
|
16
|
+
icon?: Snippet;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { tone = "neutral", icon, children, class: className, ...restProps }: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- A pill means status or a count (DESIGN.md rule 15), so this is the one rounded-full chip. -->
|
|
25
|
+
<span
|
|
26
|
+
data-slot="status-pill"
|
|
27
|
+
data-tone={tone}
|
|
28
|
+
class={cn(
|
|
29
|
+
"inline-flex h-5 max-w-full shrink-0 items-center gap-1 overflow-hidden rounded-full px-2 text-xs font-medium whitespace-nowrap",
|
|
30
|
+
"transition-[background-color,color] duration-quick ease-smooth-out motion-reduce:transition-none",
|
|
31
|
+
"[&>svg]:size-3 [&>svg]:shrink-0",
|
|
32
|
+
icon && "pl-1.5",
|
|
33
|
+
PILL_TINT[tone],
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...restProps}
|
|
37
|
+
>
|
|
38
|
+
{@render icon?.()}
|
|
39
|
+
<span class="min-w-0 truncate">{@render children?.()}</span>
|
|
40
|
+
</span>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vite-plus/test";
|
|
4
|
+
|
|
5
|
+
// A plain tsc consumer (upsys app-cli) reads this package's src through a `*.svelte` shim that
|
|
6
|
+
// only knows a default export. A .ts file that re-exports a named value from a .svelte module
|
|
7
|
+
// fails there, as PILL_TINT did in 2.21.0. Named values live in a .ts module the component imports.
|
|
8
|
+
const root = import.meta.dirname;
|
|
9
|
+
|
|
10
|
+
function tsFiles(dir: string): string[] {
|
|
11
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
12
|
+
const path = join(dir, entry.name);
|
|
13
|
+
if (entry.isDirectory()) return tsFiles(path);
|
|
14
|
+
return entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") ? [path] : [];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const reexport =
|
|
19
|
+
/export\s+(type\s+)?(\*|\{[^}]*\})\s*(?:as\s+\w+\s*)?from\s*["']([^"']+\.svelte)["']/g;
|
|
20
|
+
|
|
21
|
+
describe("re-exports from .svelte modules", () => {
|
|
22
|
+
it("carry only the default export or types", () => {
|
|
23
|
+
const offenders: string[] = [];
|
|
24
|
+
for (const file of tsFiles(root)) {
|
|
25
|
+
for (const [, typeOnly, list, from] of readFileSync(file, "utf8").matchAll(reexport)) {
|
|
26
|
+
if (typeOnly) continue;
|
|
27
|
+
const names =
|
|
28
|
+
list === "*"
|
|
29
|
+
? ["*"]
|
|
30
|
+
: list
|
|
31
|
+
.slice(1, -1)
|
|
32
|
+
.split(",")
|
|
33
|
+
.map((n) => n.trim());
|
|
34
|
+
const values = names.filter(
|
|
35
|
+
(n) => n && n !== "default" && !/^(type\s|default\s+as\s)/.test(n),
|
|
36
|
+
);
|
|
37
|
+
if (values.length)
|
|
38
|
+
offenders.push(`${relative(root, file)}: ${values.join(", ")} from ${from}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
expect(offenders).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|