@classic-homes/theme-svelte 1.35.0 → 1.37.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/dist/lib/components/AppLauncher.svelte +9 -63
- package/dist/lib/components/AppLauncherGrid.svelte +91 -0
- package/dist/lib/components/AppLauncherGrid.svelte.d.ts +24 -0
- package/dist/lib/components/Dialog.svelte +8 -0
- package/dist/lib/components/Dialog.svelte.d.ts +6 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { Snippet } from 'svelte';
|
|
22
22
|
import { cn } from '../utils.js';
|
|
23
23
|
import Icon from './Icon.svelte';
|
|
24
|
+
import AppLauncherGrid from './AppLauncherGrid.svelte';
|
|
24
25
|
import { buttonVariants } from './button-variants.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -69,29 +70,6 @@
|
|
|
69
70
|
open = next;
|
|
70
71
|
onOpenChange?.(next);
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
-
/** Arrow keys walk the grid by row and column; Home/End go to the ends. */
|
|
74
|
-
function handleKeydown(event: KeyboardEvent) {
|
|
75
|
-
const tiles = Array.from(
|
|
76
|
-
(event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>('[data-app-launcher-tile]')
|
|
77
|
-
);
|
|
78
|
-
const current = tiles.indexOf(document.activeElement as HTMLElement);
|
|
79
|
-
if (current === -1) return;
|
|
80
|
-
const last = tiles.length - 1;
|
|
81
|
-
const step: Record<string, number> = {
|
|
82
|
-
ArrowRight: 1,
|
|
83
|
-
ArrowLeft: -1,
|
|
84
|
-
ArrowDown: columns,
|
|
85
|
-
ArrowUp: -columns,
|
|
86
|
-
};
|
|
87
|
-
let next: number;
|
|
88
|
-
if (event.key === 'Home') next = 0;
|
|
89
|
-
else if (event.key === 'End') next = last;
|
|
90
|
-
else if (event.key in step) next = current + step[event.key];
|
|
91
|
-
else return;
|
|
92
|
-
event.preventDefault();
|
|
93
|
-
tiles[Math.min(Math.max(next, 0), last)].focus();
|
|
94
|
-
}
|
|
95
73
|
</script>
|
|
96
74
|
|
|
97
75
|
<Popover.Root bind:open onOpenChange={handleOpenChange}>
|
|
@@ -111,46 +89,14 @@
|
|
|
111
89
|
className
|
|
112
90
|
)}
|
|
113
91
|
>
|
|
114
|
-
<
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
onkeydown={handleKeydown}
|
|
123
|
-
>
|
|
124
|
-
{#each apps as app (app.id)}
|
|
125
|
-
<li>
|
|
126
|
-
<a
|
|
127
|
-
href={app.href}
|
|
128
|
-
target={app.external ? '_blank' : undefined}
|
|
129
|
-
rel={app.external ? 'noopener noreferrer' : undefined}
|
|
130
|
-
title={app.description}
|
|
131
|
-
data-app-launcher-tile={app.id}
|
|
132
|
-
class="flex h-full flex-col items-center gap-1.5 rounded-md px-1 py-3 text-center text-xs font-medium text-foreground hover:bg-muted-hover focus-visible:bg-muted-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
133
|
-
onclick={() => handleOpenChange(false)}
|
|
134
|
-
>
|
|
135
|
-
<span
|
|
136
|
-
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary"
|
|
137
|
-
aria-hidden="true"
|
|
138
|
-
>
|
|
139
|
-
{#if icon}
|
|
140
|
-
{@render icon(app)}
|
|
141
|
-
{:else}
|
|
142
|
-
<Icon name={app.icon ?? 'external-link'} size={22} />
|
|
143
|
-
{/if}
|
|
144
|
-
</span>
|
|
145
|
-
<span class="line-clamp-2 break-words">{app.label}</span>
|
|
146
|
-
{#if app.external}
|
|
147
|
-
<span class="sr-only">(opens in a new tab)</span>
|
|
148
|
-
{/if}
|
|
149
|
-
</a>
|
|
150
|
-
</li>
|
|
151
|
-
{/each}
|
|
152
|
-
</ul>
|
|
153
|
-
</nav>
|
|
92
|
+
<AppLauncherGrid
|
|
93
|
+
{apps}
|
|
94
|
+
label={title ?? triggerLabel}
|
|
95
|
+
{title}
|
|
96
|
+
{columns}
|
|
97
|
+
{icon}
|
|
98
|
+
onSelect={() => handleOpenChange(false)}
|
|
99
|
+
/>
|
|
154
100
|
</Popover.Content>
|
|
155
101
|
</Popover.Portal>
|
|
156
102
|
</Popover.Root>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { AppLauncherItem } from './AppLauncher.svelte';
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AppLauncherGrid - AppLauncher's tile grid on its own, for showing the apps
|
|
8
|
+
* somewhere other than the header popover (a phone's bottom Sheet, a home
|
|
9
|
+
* page). Tiles are links; arrow keys walk the grid, Home/End go to the ends.
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
apps: AppLauncherItem[];
|
|
13
|
+
/** Accessible name of the grid's `nav` */
|
|
14
|
+
label: string;
|
|
15
|
+
/** Visible heading above the tiles; none when unset */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Tiles per row (default: 3) */
|
|
18
|
+
columns?: number;
|
|
19
|
+
/** Called when a tile is picked, e.g. to close the surface around it */
|
|
20
|
+
onSelect?: (app: AppLauncherItem) => void;
|
|
21
|
+
/** Draws a tile's icon instead of `item.icon`, e.g. an app's own mark */
|
|
22
|
+
icon?: Snippet<[AppLauncherItem]>;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let { apps, label, title, columns = 3, onSelect, icon, class: className }: Props = $props();
|
|
27
|
+
|
|
28
|
+
/** Arrow keys walk the grid by row and column; Home/End go to the ends. */
|
|
29
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
30
|
+
const tiles = Array.from(
|
|
31
|
+
(event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>('[data-app-launcher-tile]')
|
|
32
|
+
);
|
|
33
|
+
const current = tiles.indexOf(document.activeElement as HTMLElement);
|
|
34
|
+
if (current === -1) return;
|
|
35
|
+
const last = tiles.length - 1;
|
|
36
|
+
const step: Record<string, number> = {
|
|
37
|
+
ArrowRight: 1,
|
|
38
|
+
ArrowLeft: -1,
|
|
39
|
+
ArrowDown: columns,
|
|
40
|
+
ArrowUp: -columns,
|
|
41
|
+
};
|
|
42
|
+
let next: number;
|
|
43
|
+
if (event.key === 'Home') next = 0;
|
|
44
|
+
else if (event.key === 'End') next = last;
|
|
45
|
+
else if (event.key in step) next = current + step[event.key];
|
|
46
|
+
else return;
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
tiles[Math.min(Math.max(next, 0), last)].focus();
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<nav aria-label={label} class={className}>
|
|
53
|
+
{#if title}
|
|
54
|
+
<p class="px-2 pb-2 pt-1 text-sm font-semibold">{title}</p>
|
|
55
|
+
{/if}
|
|
56
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
57
|
+
<ul
|
|
58
|
+
class="grid gap-1"
|
|
59
|
+
style:grid-template-columns="repeat({columns}, minmax(0, 1fr))"
|
|
60
|
+
onkeydown={handleKeydown}
|
|
61
|
+
>
|
|
62
|
+
{#each apps as app (app.id)}
|
|
63
|
+
<li>
|
|
64
|
+
<a
|
|
65
|
+
href={app.href}
|
|
66
|
+
target={app.external ? '_blank' : undefined}
|
|
67
|
+
rel={app.external ? 'noopener noreferrer' : undefined}
|
|
68
|
+
title={app.description}
|
|
69
|
+
data-app-launcher-tile={app.id}
|
|
70
|
+
class="flex h-full flex-col items-center gap-1.5 rounded-md px-1 py-3 text-center text-xs font-medium text-foreground hover:bg-muted-hover focus-visible:bg-muted-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
71
|
+
onclick={() => onSelect?.(app)}
|
|
72
|
+
>
|
|
73
|
+
<span
|
|
74
|
+
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
>
|
|
77
|
+
{#if icon}
|
|
78
|
+
{@render icon(app)}
|
|
79
|
+
{:else}
|
|
80
|
+
<Icon name={app.icon ?? 'external-link'} size={22} />
|
|
81
|
+
{/if}
|
|
82
|
+
</span>
|
|
83
|
+
<span class="line-clamp-2 break-words">{app.label}</span>
|
|
84
|
+
{#if app.external}
|
|
85
|
+
<span class="sr-only">(opens in a new tab)</span>
|
|
86
|
+
{/if}
|
|
87
|
+
</a>
|
|
88
|
+
</li>
|
|
89
|
+
{/each}
|
|
90
|
+
</ul>
|
|
91
|
+
</nav>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AppLauncherItem } from './AppLauncher.svelte';
|
|
3
|
+
/**
|
|
4
|
+
* AppLauncherGrid - AppLauncher's tile grid on its own, for showing the apps
|
|
5
|
+
* somewhere other than the header popover (a phone's bottom Sheet, a home
|
|
6
|
+
* page). Tiles are links; arrow keys walk the grid, Home/End go to the ends.
|
|
7
|
+
*/
|
|
8
|
+
interface Props {
|
|
9
|
+
apps: AppLauncherItem[];
|
|
10
|
+
/** Accessible name of the grid's `nav` */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Visible heading above the tiles; none when unset */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Tiles per row (default: 3) */
|
|
15
|
+
columns?: number;
|
|
16
|
+
/** Called when a tile is picked, e.g. to close the surface around it */
|
|
17
|
+
onSelect?: (app: AppLauncherItem) => void;
|
|
18
|
+
/** Draws a tile's icon instead of `item.icon`, e.g. an app's own mark */
|
|
19
|
+
icon?: Snippet<[AppLauncherItem]>;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const AppLauncherGrid: import("svelte").Component<Props, {}, "">;
|
|
23
|
+
type AppLauncherGrid = ReturnType<typeof AppLauncherGrid>;
|
|
24
|
+
export default AppLauncherGrid;
|
|
@@ -41,6 +41,12 @@
|
|
|
41
41
|
* keeps focus where the caller put it (e.g. a field it just revealed).
|
|
42
42
|
*/
|
|
43
43
|
onCloseAutoFocus?: (event: Event) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Called as the dialog opens, before focus moves to its first focusable
|
|
46
|
+
* element (often the close button); `preventDefault()` and focus a field
|
|
47
|
+
* yourself to start somewhere else, e.g. a form's first input.
|
|
48
|
+
*/
|
|
49
|
+
onOpenAutoFocus?: (event: Event) => void;
|
|
44
50
|
/**
|
|
45
51
|
* Unsaved work inside. When true, an overlay click, Escape or the close
|
|
46
52
|
* button asks "Discard unsaved changes?" instead of closing. Left unset,
|
|
@@ -77,6 +83,7 @@
|
|
|
77
83
|
headerActions,
|
|
78
84
|
showClose = true,
|
|
79
85
|
onCloseAutoFocus,
|
|
86
|
+
onOpenAutoFocus,
|
|
80
87
|
dirty,
|
|
81
88
|
}: Props = $props();
|
|
82
89
|
|
|
@@ -123,6 +130,7 @@
|
|
|
123
130
|
<DialogPrimitive.Content
|
|
124
131
|
bind:ref={contentEl}
|
|
125
132
|
{onCloseAutoFocus}
|
|
133
|
+
{onOpenAutoFocus}
|
|
126
134
|
onInteractOutside={guard.dismiss}
|
|
127
135
|
onEscapeKeydown={guard.escape}
|
|
128
136
|
oninput={guard.track}
|
|
@@ -35,6 +35,12 @@ interface Props {
|
|
|
35
35
|
* keeps focus where the caller put it (e.g. a field it just revealed).
|
|
36
36
|
*/
|
|
37
37
|
onCloseAutoFocus?: (event: Event) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Called as the dialog opens, before focus moves to its first focusable
|
|
40
|
+
* element (often the close button); `preventDefault()` and focus a field
|
|
41
|
+
* yourself to start somewhere else, e.g. a form's first input.
|
|
42
|
+
*/
|
|
43
|
+
onOpenAutoFocus?: (event: Event) => void;
|
|
38
44
|
/**
|
|
39
45
|
* Unsaved work inside. When true, an overlay click, Escape or the close
|
|
40
46
|
* button asks "Discard unsaved changes?" instead of closing. Left unset,
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export { default as AlertDialog } from './components/AlertDialog.svelte';
|
|
|
42
42
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
43
43
|
export { default as DropdownMenu } from './components/DropdownMenu.svelte';
|
|
44
44
|
export { default as AppLauncher, type AppLauncherItem } from './components/AppLauncher.svelte';
|
|
45
|
+
export { default as AppLauncherGrid } from './components/AppLauncherGrid.svelte';
|
|
45
46
|
export { default as DataPanel } from './components/DataPanel.svelte';
|
|
46
47
|
export { default as DataPanelHeader } from './components/DataPanelHeader.svelte';
|
|
47
48
|
export { default as DataPanelContent } from './components/DataPanelContent.svelte';
|
package/dist/lib/index.js
CHANGED
|
@@ -50,6 +50,7 @@ export { default as AlertDialog } from './components/AlertDialog.svelte';
|
|
|
50
50
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
51
51
|
export { default as DropdownMenu } from './components/DropdownMenu.svelte';
|
|
52
52
|
export { default as AppLauncher } from './components/AppLauncher.svelte';
|
|
53
|
+
export { default as AppLauncherGrid } from './components/AppLauncherGrid.svelte';
|
|
53
54
|
// DataPanel Components
|
|
54
55
|
export { default as DataPanel } from './components/DataPanel.svelte';
|
|
55
56
|
export { default as DataPanelHeader } from './components/DataPanelHeader.svelte';
|