@heyramzi/ui 3.1.0 → 3.3.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/dist/components/list-page/toolbar-density.js +20 -1
- package/dist/lib/sliding-pill.js +2 -2
- package/dist/record/measure-clamp.js +23 -0
- package/dist/shadcn/button-group/index.js +6 -0
- package/dist/shadcn/checkbox/checkbox-box.js +3 -0
- package/dist/shadcn/drawer/index.js +14 -0
- package/dist/shadcn/input-group/index.js +9 -0
- package/dist/shadcn/item/index.js +13 -0
- package/dist/shadcn/native-select/index.js +6 -0
- package/dist/shadcn/pagination/index.js +12 -0
- package/package.json +7 -2
- package/scripts/sync-shadcn.mjs +0 -6
- package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
- package/src/canvas/pen/PenOverlay.svelte +1 -30
- package/src/canvas/screen/PartView.svelte +13 -8
- package/src/canvas/screen/ScreenBody.svelte +18 -11
- package/src/canvas/ui/BarTooltip.svelte +46 -0
- package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
- package/src/canvas/ui/CanvasToolbar.svelte +170 -156
- package/src/canvas/ui/StylePanel.svelte +179 -102
- package/src/canvas/ui/canvas-ui.css +12 -76
- package/src/charts/dashboard/data-table.svelte +80 -80
- package/src/charts/dashboard/hint.svelte +34 -0
- package/src/charts/dashboard/hint.svelte.d.ts +10 -0
- package/src/charts/dashboard/rating-chip.svelte +4 -3
- package/src/charts/legend.css +0 -8
- package/src/charts/legend.svelte +26 -30
- package/src/components/copy/copy-button.svelte +10 -2
- package/src/components/copy/copy-button.test.ts +7 -0
- package/src/components/data-table/table-skeleton.svelte +15 -7
- package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
- package/src/components/list-page/filter-item.svelte +20 -7
- package/src/components/list-page/filter-popover.svelte +57 -17
- package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
- package/src/components/list-page/grouped-list.svelte +41 -23
- package/src/components/list-page/list-page-shell.svelte +155 -69
- package/src/components/list-page/list-page-shell.svelte.d.ts +6 -1
- package/src/components/list-page/toolbar-density.ts +24 -1
- package/src/components/picker/picker.svelte +8 -3
- package/src/components/picker/picker.svelte.d.ts +2 -1
- package/src/components/segmented/segmented-filter.svelte +19 -10
- package/src/components/segmented/segmented-track.svelte +11 -3
- package/src/components/table-view/table-view.svelte +22 -6
- package/src/components/tags-input/tags-input.svelte +94 -77
- package/src/components/toolbar/toolbar.svelte +2 -1
- package/src/components/toolbar/toolbar.test.ts +5 -4
- package/src/components/work-parts/group-head.svelte +28 -9
- package/src/components/work-parts/group-head.svelte.d.ts +5 -2
- package/src/editor/editor-toolbar.svelte +223 -193
- package/src/editor/editor.svelte +56 -38
- package/src/editor/image-toolbar.svelte +99 -37
- package/src/editor/image-toolbar.svelte.d.ts +4 -5
- package/src/editor/url-dialog.svelte +96 -0
- package/src/editor/url-dialog.svelte.d.ts +15 -0
- package/src/effects/theme-toggle/ThemeToggle.css +8 -8
- package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
- package/src/inbox/thread-inbox.svelte +9 -1
- package/src/lib/live-region.svelte +12 -4
- package/src/lib/live-region.svelte.d.ts +5 -1
- package/src/lib/sliding-pill.ts +3 -2
- package/src/record/info-list.svelte +33 -59
- package/src/record/measure-clamp.ts +31 -0
- package/src/record/record-shell.svelte +38 -39
- package/src/record/record-timeline.svelte +10 -5
- package/src/record/steps-rail.svelte +50 -36
- package/src/record/where-we-are.svelte +5 -19
- package/src/shadcn/button-group/button-group-separator.svelte +23 -0
- package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
- package/src/shadcn/button-group/button-group-text.svelte +28 -0
- package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
- package/src/shadcn/button-group/button-group.svelte +46 -0
- package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
- package/src/shadcn/button-group/index.ts +15 -0
- package/src/shadcn/checkbox/checkbox-box.ts +4 -0
- package/src/shadcn/checkbox/checkbox.svelte +2 -4
- package/src/shadcn/drawer/drawer-close.svelte +7 -0
- package/src/shadcn/drawer/drawer-close.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-content.svelte +36 -0
- package/src/shadcn/drawer/drawer-content.svelte.d.ts +10 -0
- package/src/shadcn/drawer/drawer-description.svelte +17 -0
- package/src/shadcn/drawer/drawer-description.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-footer.svelte +20 -0
- package/src/shadcn/drawer/drawer-footer.svelte.d.ts +5 -0
- package/src/shadcn/drawer/drawer-header.svelte +23 -0
- package/src/shadcn/drawer/drawer-header.svelte.d.ts +5 -0
- package/src/shadcn/drawer/drawer-nested.svelte +12 -0
- package/src/shadcn/drawer/drawer-nested.svelte.d.ts +3 -0
- package/src/shadcn/drawer/drawer-overlay.svelte +20 -0
- package/src/shadcn/drawer/drawer-overlay.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-portal.svelte +7 -0
- package/src/shadcn/drawer/drawer-portal.svelte.d.ts +3 -0
- package/src/shadcn/drawer/drawer-title.svelte +17 -0
- package/src/shadcn/drawer/drawer-title.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-trigger.svelte +7 -0
- package/src/shadcn/drawer/drawer-trigger.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer.svelte +12 -0
- package/src/shadcn/drawer/drawer.svelte.d.ts +3 -0
- package/src/shadcn/drawer/index.ts +38 -0
- package/src/shadcn/input-group/index.ts +22 -0
- package/src/shadcn/input-group/input-group-addon.svelte +51 -0
- package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-button.svelte +49 -0
- package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-input.svelte +20 -0
- package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
- package/src/shadcn/input-group/input-group-text.svelte +19 -0
- package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
- package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
- package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
- package/src/shadcn/input-group/input-group.svelte +24 -0
- package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
- package/src/shadcn/item/index.ts +34 -0
- package/src/shadcn/item/item-actions.svelte +20 -0
- package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
- package/src/shadcn/item/item-content.svelte +23 -0
- package/src/shadcn/item/item-content.svelte.d.ts +5 -0
- package/src/shadcn/item/item-description.svelte +23 -0
- package/src/shadcn/item/item-description.svelte.d.ts +5 -0
- package/src/shadcn/item/item-footer.svelte +20 -0
- package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
- package/src/shadcn/item/item-group.svelte +21 -0
- package/src/shadcn/item/item-group.svelte.d.ts +5 -0
- package/src/shadcn/item/item-header.svelte +20 -0
- package/src/shadcn/item/item-header.svelte.d.ts +5 -0
- package/src/shadcn/item/item-media.svelte +42 -0
- package/src/shadcn/item/item-media.svelte.d.ts +17 -0
- package/src/shadcn/item/item-separator.svelte +19 -0
- package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
- package/src/shadcn/item/item-title.svelte +20 -0
- package/src/shadcn/item/item-title.svelte.d.ts +5 -0
- package/src/shadcn/item/item.svelte +61 -0
- package/src/shadcn/item/item.svelte.d.ts +28 -0
- package/src/shadcn/native-select/index.ts +13 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select-option.svelte +20 -0
- package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select.svelte +45 -0
- package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
- package/src/shadcn/pagination/index.ts +31 -0
- package/src/shadcn/pagination/pagination-content.svelte +20 -0
- package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-item.svelte +14 -0
- package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-link.svelte +41 -0
- package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
- package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-next.svelte +26 -0
- package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-previous.svelte +30 -0
- package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination.svelte +27 -0
- package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
- package/src/slides/Controls.svelte +39 -36
- package/src/slides/Deck.svelte +11 -8
- package/src/thread/thread.svelte +15 -8
- package/src/work/detail/task-detail.svelte +1 -1
- package/src/work/gantt/gantt-task-row.svelte +3 -6
- package/src/work/inbox/inbox-card.svelte +7 -5
- package/src/work/inbox/inbox-trace.svelte +14 -10
- package/src/work/kanban-board.svelte +40 -25
- package/src/work/kanban-hidden-rail.svelte +67 -65
- package/src/work/task-list.svelte +145 -124
- package/src/work/task-list.svelte.test.ts +5 -2
- package/src/work/time/time-tracker.svelte +35 -10
|
@@ -2,61 +2,124 @@
|
|
|
2
2
|
// =========================
|
|
3
3
|
// IMPORTS
|
|
4
4
|
// =========================
|
|
5
|
+
import { Toolbar } from "bits-ui";
|
|
6
|
+
import * as Popover from "../shadcn/popover/index.js";
|
|
7
|
+
import * as Tooltip from "../shadcn/tooltip/index.js";
|
|
5
8
|
import { Trash, TextAlignLeft, TextAlignCenter, TextAlignRight } from "phosphor-svelte";
|
|
6
9
|
|
|
7
10
|
// =========================
|
|
8
11
|
// TYPES
|
|
9
12
|
// =========================
|
|
10
13
|
interface Props {
|
|
11
|
-
|
|
14
|
+
/** The image the toolbar floats over. */
|
|
15
|
+
anchor: HTMLElement | null;
|
|
16
|
+
open: boolean;
|
|
12
17
|
onAlign: (align: "left" | "center" | "right") => void;
|
|
13
18
|
onDelete: () => void;
|
|
14
19
|
}
|
|
15
20
|
|
|
21
|
+
type Tool = {
|
|
22
|
+
label: string;
|
|
23
|
+
icon: typeof Trash;
|
|
24
|
+
destructive?: boolean;
|
|
25
|
+
run: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
16
28
|
// =========================
|
|
17
29
|
// PROPS
|
|
18
30
|
// =========================
|
|
19
|
-
let {
|
|
31
|
+
let { anchor, open = $bindable(), onAlign, onDelete }: Props = $props();
|
|
32
|
+
|
|
33
|
+
// =========================
|
|
34
|
+
// CONSTANTS
|
|
35
|
+
// =========================
|
|
36
|
+
const ALIGN: Tool[] = [
|
|
37
|
+
{ label: "Align left", icon: TextAlignLeft, run: () => onAlign("left") },
|
|
38
|
+
{ label: "Align center", icon: TextAlignCenter, run: () => onAlign("center") },
|
|
39
|
+
{ label: "Align right", icon: TextAlignRight, run: () => onAlign("right") },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// =========================
|
|
43
|
+
// STATE
|
|
44
|
+
// =========================
|
|
45
|
+
let content = $state<HTMLElement | null>(null);
|
|
46
|
+
|
|
47
|
+
// =========================
|
|
48
|
+
// FUNCTIONS
|
|
49
|
+
// =========================
|
|
50
|
+
/** Esc from inside the toolbar goes back to the text, which still holds the image selected. */
|
|
51
|
+
function returnToText(e: Event) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
const active = document.activeElement;
|
|
54
|
+
if (!active || active === document.body || content?.contains(active)) {
|
|
55
|
+
anchor?.closest<HTMLElement>("[contenteditable='true']")?.focus();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
20
58
|
</script>
|
|
21
59
|
|
|
22
60
|
<!-- =========================== -->
|
|
23
61
|
<!-- MARKUP -->
|
|
24
62
|
<!-- =========================== -->
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class="image-toolbar-btn destructive"
|
|
54
|
-
title="Delete image"
|
|
55
|
-
onclick={onDelete}
|
|
63
|
+
{#snippet tool(t: Tool)}
|
|
64
|
+
<Tooltip.Root>
|
|
65
|
+
<Tooltip.Trigger aria-label={t.label} onclick={t.run}>
|
|
66
|
+
{#snippet child({ props: trigger })}
|
|
67
|
+
<Toolbar.Button {...trigger}>
|
|
68
|
+
{#snippet child({ props })}
|
|
69
|
+
<button {...props} class="image-toolbar-btn" class:destructive={t.destructive}>
|
|
70
|
+
<t.icon aria-hidden="true" size={14} />
|
|
71
|
+
</button>
|
|
72
|
+
{/snippet}
|
|
73
|
+
</Toolbar.Button>
|
|
74
|
+
{/snippet}
|
|
75
|
+
</Tooltip.Trigger>
|
|
76
|
+
<Tooltip.Content>{t.label}</Tooltip.Content>
|
|
77
|
+
</Tooltip.Root>
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
80
|
+
<!-- Not portaled, so Tab from the text reaches it next. Focus stays in the text when it opens. -->
|
|
81
|
+
<Popover.Root bind:open>
|
|
82
|
+
<Popover.Content
|
|
83
|
+
bind:ref={content}
|
|
84
|
+
customAnchor={anchor}
|
|
85
|
+
side="top"
|
|
86
|
+
sideOffset={4}
|
|
87
|
+
trapFocus={false}
|
|
88
|
+
portalProps={{ disabled: true }}
|
|
89
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
90
|
+
onCloseAutoFocus={returnToText}
|
|
56
91
|
>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
{#snippet child({ props, wrapperProps })}
|
|
93
|
+
<div {...wrapperProps}>
|
|
94
|
+
<!-- Esc closes the toolbar itself here; left to Bits, the first one only shuts the tooltip. -->
|
|
95
|
+
<Toolbar.Root
|
|
96
|
+
{...props}
|
|
97
|
+
aria-label="Image"
|
|
98
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
99
|
+
if (e.key === "Escape") open = false;
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{#snippet child({ props: root })}
|
|
103
|
+
<Tooltip.Provider>
|
|
104
|
+
<div {...root} class="image-toolbar">
|
|
105
|
+
{#each ALIGN as t (t.label)}
|
|
106
|
+
{@render tool(t)}
|
|
107
|
+
{/each}
|
|
108
|
+
<div class="image-toolbar-divider"></div>
|
|
109
|
+
{@render tool({
|
|
110
|
+
label: "Delete image",
|
|
111
|
+
icon: Trash,
|
|
112
|
+
destructive: true,
|
|
113
|
+
run: onDelete,
|
|
114
|
+
})}
|
|
115
|
+
</div>
|
|
116
|
+
</Tooltip.Provider>
|
|
117
|
+
{/snippet}
|
|
118
|
+
</Toolbar.Root>
|
|
119
|
+
</div>
|
|
120
|
+
{/snippet}
|
|
121
|
+
</Popover.Content>
|
|
122
|
+
</Popover.Root>
|
|
60
123
|
|
|
61
124
|
<!-- =========================== -->
|
|
62
125
|
<!-- STYLE -->
|
|
@@ -67,7 +130,6 @@
|
|
|
67
130
|
@layer components {
|
|
68
131
|
/* ---- IMAGE TOOLBAR ---- */
|
|
69
132
|
.image-toolbar {
|
|
70
|
-
position: absolute;
|
|
71
133
|
z-index: 40;
|
|
72
134
|
display: flex;
|
|
73
135
|
align-items: center;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
interface Props {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
2
|
+
/** The image the toolbar floats over. */
|
|
3
|
+
anchor: HTMLElement | null;
|
|
4
|
+
open: boolean;
|
|
6
5
|
onAlign: (align: "left" | "center" | "right") => void;
|
|
7
6
|
onDelete: () => void;
|
|
8
7
|
}
|
|
9
|
-
declare const $$Component: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
declare const $$Component: import("svelte").Component<Props, {}, "open">;
|
|
10
9
|
type $$Component = ReturnType<typeof $$Component>;
|
|
11
10
|
export default $$Component;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { Button } from "../shadcn/button/index.js";
|
|
6
|
+
import * as Dialog from "../shadcn/dialog/index.js";
|
|
7
|
+
import { SimpleField } from "../shadcn/field/index.js";
|
|
8
|
+
import { Input } from "../shadcn/input/index.js";
|
|
9
|
+
|
|
10
|
+
// =========================
|
|
11
|
+
// TYPES
|
|
12
|
+
// =========================
|
|
13
|
+
interface Props {
|
|
14
|
+
open: boolean;
|
|
15
|
+
heading: string;
|
|
16
|
+
label: string;
|
|
17
|
+
placeholder: string;
|
|
18
|
+
/** Adds an optional second field under the link, with this label. */
|
|
19
|
+
titleLabel?: string;
|
|
20
|
+
/** Inserts from the fields, then answers the error to show, or null once it's done. */
|
|
21
|
+
onSubmit: (url: string, title: string) => string | null;
|
|
22
|
+
/** Focus goes back to the text, whichever way the dialog closed. */
|
|
23
|
+
onClosed: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// =========================
|
|
27
|
+
// PROPS
|
|
28
|
+
// =========================
|
|
29
|
+
let {
|
|
30
|
+
open = $bindable(),
|
|
31
|
+
heading,
|
|
32
|
+
label,
|
|
33
|
+
placeholder,
|
|
34
|
+
titleLabel,
|
|
35
|
+
onSubmit,
|
|
36
|
+
onClosed,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
// =========================
|
|
40
|
+
// STATE
|
|
41
|
+
// =========================
|
|
42
|
+
const uid = $props.id();
|
|
43
|
+
let url = $state("");
|
|
44
|
+
let title = $state("");
|
|
45
|
+
let error = $state<string | null>(null);
|
|
46
|
+
|
|
47
|
+
// =========================
|
|
48
|
+
// FUNCTIONS
|
|
49
|
+
// =========================
|
|
50
|
+
function submit(e: SubmitEvent) {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
error = onSubmit(url.trim(), title.trim());
|
|
53
|
+
if (!error) open = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function reset(e: Event) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
url = "";
|
|
59
|
+
title = "";
|
|
60
|
+
error = null;
|
|
61
|
+
onClosed();
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<!-- =========================== -->
|
|
66
|
+
<!-- MARKUP -->
|
|
67
|
+
<!-- =========================== -->
|
|
68
|
+
<Dialog.Root bind:open>
|
|
69
|
+
<Dialog.Content onCloseAutoFocus={reset}>
|
|
70
|
+
<form class="contents" novalidate onsubmit={submit}>
|
|
71
|
+
<Dialog.Header>
|
|
72
|
+
<Dialog.Title>{heading}</Dialog.Title>
|
|
73
|
+
</Dialog.Header>
|
|
74
|
+
<Dialog.Body>
|
|
75
|
+
<SimpleField {label} for="{uid}-url" error={error ?? undefined}>
|
|
76
|
+
<Input
|
|
77
|
+
id="{uid}-url"
|
|
78
|
+
type="url"
|
|
79
|
+
bind:value={url}
|
|
80
|
+
{placeholder}
|
|
81
|
+
oninput={() => (error = null)}
|
|
82
|
+
/>
|
|
83
|
+
</SimpleField>
|
|
84
|
+
{#if titleLabel}
|
|
85
|
+
<SimpleField label={titleLabel} for="{uid}-title">
|
|
86
|
+
<Input id="{uid}-title" bind:value={title} />
|
|
87
|
+
</SimpleField>
|
|
88
|
+
{/if}
|
|
89
|
+
</Dialog.Body>
|
|
90
|
+
<Dialog.Footer>
|
|
91
|
+
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
92
|
+
<Button type="submit">Insert</Button>
|
|
93
|
+
</Dialog.Footer>
|
|
94
|
+
</form>
|
|
95
|
+
</Dialog.Content>
|
|
96
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
open: boolean;
|
|
3
|
+
heading: string;
|
|
4
|
+
label: string;
|
|
5
|
+
placeholder: string;
|
|
6
|
+
/** Adds an optional second field under the link, with this label. */
|
|
7
|
+
titleLabel?: string;
|
|
8
|
+
/** Inserts from the fields, then answers the error to show, or null once it's done. */
|
|
9
|
+
onSubmit: (url: string, title: string) => string | null;
|
|
10
|
+
/** Focus goes back to the text, whichever way the dialog closed. */
|
|
11
|
+
onClosed: () => void;
|
|
12
|
+
}
|
|
13
|
+
declare const $$Component: import("svelte").Component<Props, {}, "open">;
|
|
14
|
+
type $$Component = ReturnType<typeof $$Component>;
|
|
15
|
+
export default $$Component;
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
transition: opacity 500ms;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
.theme-toggle-slide[data-
|
|
89
|
+
.theme-toggle-slide[data-mode="dark"] {
|
|
90
90
|
--tt-glow-x: 75%;
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -110,17 +110,17 @@
|
|
|
110
110
|
color: var(--theme-toggle-moon, var(--color-muted-foreground));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
.theme-toggle-slide[data-
|
|
114
|
-
.theme-toggle-slide[data-
|
|
113
|
+
.theme-toggle-slide[data-mode="light"] .theme-toggle-slide__icon--sun,
|
|
114
|
+
.theme-toggle-slide[data-mode="dark"] .theme-toggle-slide__icon--moon {
|
|
115
115
|
opacity: 0.7;
|
|
116
116
|
transform: scale(1);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
.theme-toggle-slide[data-
|
|
119
|
+
.theme-toggle-slide[data-mode="light"] .theme-toggle-slide__icon--sun {
|
|
120
120
|
animation: theme-toggle-spin 8s linear infinite;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
.theme-toggle-slide[data-
|
|
123
|
+
.theme-toggle-slide[data-mode="dark"] .theme-toggle-slide__icon--moon {
|
|
124
124
|
animation: theme-toggle-wiggle 2s ease-in-out infinite;
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
background-color 500ms;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
.theme-toggle-slide[data-
|
|
146
|
+
.theme-toggle-slide[data-mode="dark"] .theme-toggle-slide__thumb {
|
|
147
147
|
--tt-thumb-color: var(--theme-toggle-thumb-dark, var(--color-primary));
|
|
148
148
|
transform: translateX(var(--tt-thumb));
|
|
149
149
|
}
|
|
@@ -174,12 +174,12 @@
|
|
|
174
174
|
transform: scale(0.9) rotate(-90deg);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
.theme-toggle-slide[data-
|
|
177
|
+
.theme-toggle-slide[data-mode="dark"] .theme-toggle-slide__mark--sun {
|
|
178
178
|
opacity: 0;
|
|
179
179
|
transform: scale(0.9) rotate(90deg);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
.theme-toggle-slide[data-
|
|
182
|
+
.theme-toggle-slide[data-mode="dark"] .theme-toggle-slide__mark--moon {
|
|
183
183
|
opacity: 1;
|
|
184
184
|
transform: scale(1) rotate(0);
|
|
185
185
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// =========================
|
|
5
5
|
import "./ThemeToggle.css";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { Switch } from "bits-ui";
|
|
7
8
|
import { Monitor, Moon, Sun } from "phosphor-svelte";
|
|
8
9
|
import { revealTheme } from "../../lib/theme-transition.js";
|
|
9
10
|
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
let text = $derived(
|
|
56
57
|
label ?? (mode ? `Theme: ${mode}` : isDark ? "Switch to light mode" : "Switch to dark mode"),
|
|
57
58
|
);
|
|
59
|
+
// A switch reads its own on or off, so its name stays put instead of naming the next press.
|
|
60
|
+
let slideText = $derived(label ?? "Dark mode");
|
|
58
61
|
|
|
59
62
|
// =========================
|
|
60
63
|
// FUNCTIONS
|
|
@@ -63,6 +66,14 @@
|
|
|
63
66
|
revealTheme(event, () => onToggle(event), { skip: skipTransition });
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
// bits-ui flips a switch on keydown with no click, and `onToggle` wants a MouseEvent, so the
|
|
70
|
+
// key becomes the click a plain button would have fired. preventDefault stops bits-ui's own.
|
|
71
|
+
function clickOnKey(event: KeyboardEvent & { currentTarget: HTMLButtonElement }) {
|
|
72
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
event.currentTarget.click();
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
function iconClass(show: boolean, tilt: "rotate-90" | "-rotate-90") {
|
|
67
78
|
return `absolute size-4 transition-[opacity,scale,rotate] duration-300 ${
|
|
68
79
|
show ? "scale-100 rotate-0 opacity-100" : `scale-90 ${tilt} opacity-0`
|
|
@@ -74,12 +85,15 @@
|
|
|
74
85
|
<!-- MARKUP -->
|
|
75
86
|
<!-- =========================== -->
|
|
76
87
|
{#if style === "slide"}
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
<!-- WHY a no-op setter: the theme is the caller's, so `onclick` hands it the MouseEvent and
|
|
89
|
+
the switch only mirrors `active`. -->
|
|
90
|
+
<Switch.Root
|
|
91
|
+
bind:checked={() => active === "dark", () => {}}
|
|
79
92
|
onclick={handleClick}
|
|
80
|
-
|
|
93
|
+
onkeydown={clickOnKey}
|
|
94
|
+
aria-label={slideText}
|
|
81
95
|
{title}
|
|
82
|
-
data-
|
|
96
|
+
data-mode={active === "dark" ? "dark" : "light"}
|
|
83
97
|
class={cn("theme-toggle-slide", size === "default" && "theme-toggle-slide--lg", className)}
|
|
84
98
|
>
|
|
85
99
|
<span class="theme-toggle-slide__glow" aria-hidden="true"></span>
|
|
@@ -104,8 +118,8 @@
|
|
|
104
118
|
class="theme-toggle-slide__mark theme-toggle-slide__mark--moon"
|
|
105
119
|
/>
|
|
106
120
|
</span>
|
|
107
|
-
<span class="sr-only">{
|
|
108
|
-
</
|
|
121
|
+
<span class="sr-only">{slideText}</span>
|
|
122
|
+
</Switch.Root>
|
|
109
123
|
{:else}
|
|
110
124
|
<button
|
|
111
125
|
onclick={handleClick}
|
|
@@ -139,6 +139,8 @@
|
|
|
139
139
|
let labels = $derived(resolveInboxLabels(labelOverrides));
|
|
140
140
|
let filter = $state<InboxFilter>({ ...NO_FILTER });
|
|
141
141
|
let snoozing = $state(false);
|
|
142
|
+
const uid = $props.id();
|
|
143
|
+
const snoozeChoicesId = `${uid}-snooze`;
|
|
142
144
|
let pane = $state<"rail" | "detail">("rail");
|
|
143
145
|
let root = $state<HTMLDivElement>();
|
|
144
146
|
let cards = $state<Record<string, ReturnType<typeof InboxCard> | null>>({});
|
|
@@ -472,6 +474,7 @@
|
|
|
472
474
|
variant="ghost"
|
|
473
475
|
onclick={snoozeKey}
|
|
474
476
|
aria-expanded={snoozing}
|
|
477
|
+
aria-controls={snoozeChoicesId}
|
|
475
478
|
aria-keyshortcuts="h"
|
|
476
479
|
>
|
|
477
480
|
<BellSlash aria-hidden="true" />{labels.snooze}
|
|
@@ -496,7 +499,12 @@
|
|
|
496
499
|
</DetailHeader>
|
|
497
500
|
|
|
498
501
|
{#if snoozing}
|
|
499
|
-
<div
|
|
502
|
+
<div
|
|
503
|
+
id={snoozeChoicesId}
|
|
504
|
+
class="flex flex-wrap gap-2"
|
|
505
|
+
role="group"
|
|
506
|
+
aria-label={labels.snooze}
|
|
507
|
+
>
|
|
500
508
|
{#each snoozeChoices(new Date()) as choice, index (choice.id)}
|
|
501
509
|
<Button size="sm" variant="outline" onclick={() => snooze(choice.until)}>
|
|
502
510
|
<Kbd>{index + 1}</Kbd>{snoozeLabel(choice.id)}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { tick } from "svelte";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Drives the region from state: whatever it holds is what gets read. Leave it out to use `announce`. */
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { message }: Props = $props();
|
|
10
|
+
|
|
11
|
+
let spoken = $state("");
|
|
5
12
|
|
|
6
13
|
/** Reads text out politely. Clearing first makes the same words read again. */
|
|
7
14
|
export async function announce(text: string): Promise<void> {
|
|
8
|
-
|
|
15
|
+
spoken = "";
|
|
9
16
|
await tick();
|
|
10
|
-
|
|
17
|
+
spoken = text;
|
|
11
18
|
}
|
|
12
19
|
</script>
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
<!-- A span, so it can sit inside a button. sr-only takes it out of the flow either way. -->
|
|
22
|
+
<span class="sr-only" role="status" aria-live="polite">{message ?? spoken}</span>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Drives the region from state: whatever it holds is what gets read. Leave it out to use `announce`. */
|
|
3
|
+
message?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const $$Component: import("svelte").Component<Props, {
|
|
2
6
|
announce: (text: string) => Promise<void>;
|
|
3
7
|
}, "">;
|
|
4
8
|
type $$Component = ReturnType<typeof $$Component>;
|
package/src/lib/sliding-pill.ts
CHANGED
|
@@ -6,11 +6,12 @@ import type { Attachment } from "svelte/attachments";
|
|
|
6
6
|
// `parentElement`, so any strip that renders one span gets the motion.
|
|
7
7
|
|
|
8
8
|
// WHY measure: the active item's width is its label. The selector covers the
|
|
9
|
-
//
|
|
9
|
+
// ways a strip marks it: a tab, a nav link, a pressed button, a bits-ui toggle group item.
|
|
10
10
|
|
|
11
11
|
// Per transitions.dev `tabs-sliding`: first paint and resizes write the
|
|
12
12
|
// position with the transition off, so the pill snaps in instead of flying.
|
|
13
|
-
const ACTIVE =
|
|
13
|
+
const ACTIVE =
|
|
14
|
+
'[data-state="active"],[aria-current="page"],[aria-pressed="true"],[data-state="on"]';
|
|
14
15
|
|
|
15
16
|
export const slidingPill: Attachment<HTMLElement> = (pill) => {
|
|
16
17
|
const list = pill.parentElement;
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
// IMPORTS
|
|
4
4
|
// =========================
|
|
5
5
|
import type { Snippet } from "svelte";
|
|
6
|
-
import {
|
|
6
|
+
import { CaretUpDown } from "phosphor-svelte";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
|
-
import { Button } from "../shadcn/button/index.js";
|
|
8
|
+
import { Button, buttonVariants } from "../shadcn/button/index.js";
|
|
9
9
|
import { Input } from "../shadcn/input/index.js";
|
|
10
10
|
import { Textarea } from "../shadcn/textarea/index.js";
|
|
11
11
|
import { Spinner } from "../shadcn/spinner/index.js";
|
|
12
12
|
import * as Select from "../shadcn/select/index.js";
|
|
13
|
-
import
|
|
14
|
-
import * as Command from "../shadcn/command/index.js";
|
|
13
|
+
import { Picker } from "../components/picker/index.js";
|
|
15
14
|
import { TagsInput } from "../components/tags-input/index.js";
|
|
16
15
|
import StatusBadge from "../status/status-badge.svelte";
|
|
17
16
|
import { resolveLabels, type InfoRow, type RecordLabels } from "./types.js";
|
|
@@ -58,7 +57,6 @@
|
|
|
58
57
|
let draft = $state("");
|
|
59
58
|
let saving = $state<string | null>(null);
|
|
60
59
|
let failed = $state<{ key: string; text: string } | null>(null);
|
|
61
|
-
let pickerOpen = $state<string | null>(null);
|
|
62
60
|
|
|
63
61
|
// =========================
|
|
64
62
|
// DERIVED
|
|
@@ -130,7 +128,6 @@
|
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
function pick(row: InfoRow, value: string) {
|
|
133
|
-
pickerOpen = null;
|
|
134
131
|
const current = typeof row.editValue === "string" ? row.editValue : null;
|
|
135
132
|
if (value !== current) void commit(row, value, false);
|
|
136
133
|
}
|
|
@@ -197,58 +194,34 @@
|
|
|
197
194
|
{/snippet}
|
|
198
195
|
|
|
199
196
|
{#snippet list(row: InfoRow, key: string, multi: boolean)}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
? row.editValue
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<Command.Input placeholder={t.search} />
|
|
229
|
-
<Command.List class="max-h-72 overflow-y-auto">
|
|
230
|
-
<Command.Empty>{t.noMatch}</Command.Empty>
|
|
231
|
-
<Command.Group>
|
|
232
|
-
{#each options as option (option.value)}
|
|
233
|
-
<Command.Item
|
|
234
|
-
value={option.label}
|
|
235
|
-
onSelect={() => (multi ? toggle(row, option.value) : pick(row, option.value))}
|
|
236
|
-
>
|
|
237
|
-
<Check
|
|
238
|
-
aria-hidden="true"
|
|
239
|
-
class={cn(
|
|
240
|
-
"size-3.5",
|
|
241
|
-
current.includes(option.value) ? "opacity-100" : "opacity-0",
|
|
242
|
-
)}
|
|
243
|
-
/>
|
|
244
|
-
{option.label}
|
|
245
|
-
</Command.Item>
|
|
246
|
-
{/each}
|
|
247
|
-
</Command.Group>
|
|
248
|
-
</Command.List>
|
|
249
|
-
</Command.Root>
|
|
250
|
-
</Popover.Content>
|
|
251
|
-
</Popover.Root>
|
|
197
|
+
<Picker
|
|
198
|
+
options={row.options ?? []}
|
|
199
|
+
value={multi
|
|
200
|
+
? Array.isArray(row.editValue)
|
|
201
|
+
? row.editValue
|
|
202
|
+
: []
|
|
203
|
+
: typeof row.editValue === "string"
|
|
204
|
+
? row.editValue
|
|
205
|
+
: null}
|
|
206
|
+
onSelect={(value) => (multi ? toggle(row, value) : pick(row, value))}
|
|
207
|
+
triggerClass={cn(buttonVariants({ variant: "text", size: "sm" }), OPENER)}
|
|
208
|
+
searchable
|
|
209
|
+
searchPlaceholder={t.search}
|
|
210
|
+
emptyLabel={t.noMatch}
|
|
211
|
+
width="lg"
|
|
212
|
+
disabled={saving === key}
|
|
213
|
+
>
|
|
214
|
+
{#snippet trigger()}
|
|
215
|
+
<!-- Named by its text: the field, then the value it shows (WCAG 2.5.3, label in name). -->
|
|
216
|
+
<span class="sr-only">{t.edit(row.label)}</span>
|
|
217
|
+
{@render display(row)}
|
|
218
|
+
{#if saving === key}
|
|
219
|
+
<Spinner class="size-3.5 text-muted-foreground" />
|
|
220
|
+
{:else if !multi}
|
|
221
|
+
<CaretUpDown aria-hidden="true" class="size-3.5 shrink-0 opacity-60" />
|
|
222
|
+
{/if}
|
|
223
|
+
{/snippet}
|
|
224
|
+
</Picker>
|
|
252
225
|
{/snippet}
|
|
253
226
|
|
|
254
227
|
{#if layout === "row" && (shown.length > 0 || trailing)}
|
|
@@ -412,9 +385,10 @@
|
|
|
412
385
|
variant="text"
|
|
413
386
|
size="sm"
|
|
414
387
|
class={cn(OPENER, isEmpty(row) && "text-muted-foreground")}
|
|
415
|
-
aria-label={t.edit(row.label)}
|
|
416
388
|
onclick={() => begin(row)}
|
|
417
389
|
>
|
|
390
|
+
<!-- Named by its text: the field, then the value it shows (WCAG 2.5.3, label in name). -->
|
|
391
|
+
<span class="sr-only">{t.edit(row.label)}</span>
|
|
418
392
|
{@render display(row)}
|
|
419
393
|
</Button>
|
|
420
394
|
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { untrack } from "svelte";
|
|
2
|
+
import type { Attachment } from "svelte/attachments";
|
|
3
|
+
|
|
4
|
+
/** Reports whether a line-clamped box cuts its text, so "More" shows only then. */
|
|
5
|
+
|
|
6
|
+
// WHY measure: a note that fits two lines at 1280px overflows at 390px.
|
|
7
|
+
|
|
8
|
+
// WHY untracked: an open box keeps its last answer, so a fold never remounts the toggle under focus.
|
|
9
|
+
export function measureClamp(
|
|
10
|
+
expanded: () => boolean,
|
|
11
|
+
onCut: (cut: boolean) => void,
|
|
12
|
+
): Attachment<HTMLElement> {
|
|
13
|
+
return (el) => {
|
|
14
|
+
const measure = () =>
|
|
15
|
+
untrack(() => {
|
|
16
|
+
if (!expanded()) onCut(el.scrollHeight > el.clientHeight + 1);
|
|
17
|
+
});
|
|
18
|
+
measure();
|
|
19
|
+
const observer = new ResizeObserver(measure);
|
|
20
|
+
observer.observe(el);
|
|
21
|
+
// A web font landing rewraps the text in a box that keeps its height, and the observer stays quiet.
|
|
22
|
+
let live = true;
|
|
23
|
+
void document.fonts?.ready.then(() => live && measure());
|
|
24
|
+
document.fonts?.addEventListener("loadingdone", measure);
|
|
25
|
+
return () => {
|
|
26
|
+
live = false;
|
|
27
|
+
observer.disconnect();
|
|
28
|
+
document.fonts?.removeEventListener("loadingdone", measure);
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|