@connve/ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +92 -2
  3. package/dist/AppShell.svelte +258 -0
  4. package/dist/AppShell.svelte.d.ts +29 -0
  5. package/dist/Badge.svelte +33 -0
  6. package/dist/Badge.svelte.d.ts +9 -0
  7. package/dist/ContextBridge.svelte +18 -0
  8. package/dist/ContextBridge.svelte.d.ts +8 -0
  9. package/dist/CopyButton.svelte +57 -0
  10. package/dist/CopyButton.svelte.d.ts +11 -0
  11. package/dist/EditButton.svelte +37 -0
  12. package/dist/EditButton.svelte.d.ts +9 -0
  13. package/dist/FolderTree.svelte +116 -0
  14. package/dist/FolderTree.svelte.d.ts +42 -0
  15. package/dist/IconButton.svelte +33 -0
  16. package/dist/IconButton.svelte.d.ts +16 -0
  17. package/dist/Inspector.svelte +115 -0
  18. package/dist/Inspector.svelte.d.ts +20 -0
  19. package/dist/InspectorSection.svelte +18 -0
  20. package/dist/InspectorSection.svelte.d.ts +9 -0
  21. package/dist/ListHeader.svelte +73 -0
  22. package/dist/ListHeader.svelte.d.ts +18 -0
  23. package/dist/Loading.svelte +25 -0
  24. package/dist/Loading.svelte.d.ts +9 -0
  25. package/dist/Logo.svelte +93 -0
  26. package/dist/Logo.svelte.d.ts +9 -0
  27. package/dist/NavList.svelte +74 -0
  28. package/dist/NavList.svelte.d.ts +24 -0
  29. package/dist/PaneResizer.svelte +36 -0
  30. package/dist/PaneResizer.svelte.d.ts +7 -0
  31. package/dist/Segmented.svelte +38 -0
  32. package/dist/Segmented.svelte.d.ts +33 -0
  33. package/dist/SidePane.svelte +148 -0
  34. package/dist/SidePane.svelte.d.ts +17 -0
  35. package/dist/StateMessage.svelte +110 -0
  36. package/dist/StateMessage.svelte.d.ts +15 -0
  37. package/dist/StatusDot.svelte +32 -0
  38. package/dist/StatusDot.svelte.d.ts +10 -0
  39. package/dist/ThemeToggle.svelte +37 -0
  40. package/dist/ThemeToggle.svelte.d.ts +3 -0
  41. package/dist/focus.d.ts +2 -0
  42. package/dist/focus.js +39 -0
  43. package/dist/icons.d.ts +19 -0
  44. package/dist/icons.js +90 -0
  45. package/dist/index.d.ts +24 -0
  46. package/dist/index.js +23 -0
  47. package/dist/labels.svelte.d.ts +28 -0
  48. package/dist/labels.svelte.js +27 -0
  49. package/dist/mobile.d.ts +5 -0
  50. package/dist/mobile.js +7 -0
  51. package/dist/paneSize.svelte.d.ts +26 -0
  52. package/dist/paneSize.svelte.js +112 -0
  53. package/dist/shell.svelte.d.ts +18 -0
  54. package/dist/shell.svelte.js +20 -0
  55. package/dist/theme.css +169 -0
  56. package/dist/theme.svelte.d.ts +13 -0
  57. package/dist/theme.svelte.js +46 -0
  58. package/dist/tooltip.d.ts +3 -0
  59. package/dist/tooltip.js +7 -0
  60. package/dist/tree.d.ts +17 -0
  61. package/dist/tree.js +44 -0
  62. package/package.json +82 -5
@@ -0,0 +1,148 @@
1
+ <script lang="ts">
2
+ import Icon from '@iconify/svelte';
3
+ import * as icons from './icons.js';
4
+ import { getAllContexts, tick, type Snippet } from 'svelte';
5
+ import { MediaQuery } from 'svelte/reactivity';
6
+ import ContextBridge from './ContextBridge.svelte';
7
+ import PaneResizer from './PaneResizer.svelte';
8
+ import { keepFocusWithin } from './focus.js';
9
+ import { MOBILE_QUERY, PANE_OVER_PAGE } from './mobile.js';
10
+ import { sidePaneWidth as pane } from './paneSize.svelte.js';
11
+ import { getShell } from './shell.svelte.js';
12
+
13
+ interface Props {
14
+ open: boolean;
15
+ /** Names the pane above its content, e.g. "Folders". */
16
+ title: string;
17
+ collapseLabel: string;
18
+ expandLabel: string;
19
+ ontoggle: () => void;
20
+ /** Closes the pane from the backdrop or Escape on a phone. */
21
+ onclose: () => void;
22
+ children: Snippet;
23
+ /** Actions kept on the narrow rail while the pane is collapsed. */
24
+ rail?: Snippet;
25
+ }
26
+
27
+ let { open, title, collapseLabel, expandLabel, ontoggle, onclose, children, rail }: Props =
28
+ $props();
29
+
30
+ const shell = getShell('SidePane');
31
+ // The shell renders the pane, so its content gets this page's contexts passed along.
32
+ const contexts = getAllContexts();
33
+ const titleId = $props.id();
34
+
35
+ /** Below this window width an open inspector folds the pane. */
36
+ const roomy = new MediaQuery('min-width: 1280px', true);
37
+ const phone = new MediaQuery(MOBILE_QUERY);
38
+
39
+ const anyInspector = $derived(shell.inspectors > 0);
40
+ // Unfolded by hand while an inspector is open; folds again when the first opens or the last closes.
41
+ let unfolded = $derived.by(() => {
42
+ void anyInspector;
43
+ return false;
44
+ });
45
+ let folded = $derived(open && !unfolded && anyInspector && !roomy.current);
46
+ let shown = $derived(open && !folded);
47
+ /** On a phone the open pane covers the page, so it acts as a dialog. */
48
+ let covers = $derived(shown && phone.current);
49
+ let panel = $state<HTMLElement | null>(null);
50
+
51
+ function toggle() {
52
+ if (folded) unfolded = true;
53
+ else ontoggle();
54
+ }
55
+
56
+ $effect(() => {
57
+ shell.pane = aside;
58
+ return () => {
59
+ if (shell.pane === aside) shell.pane = null;
60
+ };
61
+ });
62
+
63
+ $effect(() => {
64
+ shell.expand = shown ? null : { label: expandLabel, onclick: toggle };
65
+ return () => {
66
+ if (shell.expand?.onclick === toggle) shell.expand = null;
67
+ };
68
+ });
69
+
70
+ // Covering the page, the pane takes focus, and gives it to the top bar's expand button when it closes.
71
+ $effect(() => {
72
+ if (!covers || !panel) return;
73
+ const el = panel;
74
+ el.focus();
75
+ return () => {
76
+ const active = document.activeElement;
77
+ if (active && active !== document.body && !el.contains(active)) return;
78
+ tick().then(() => shell.expandButton?.focus());
79
+ };
80
+ });
81
+
82
+ function onkeydown(event: KeyboardEvent) {
83
+ if (!covers || !panel) return;
84
+ if (event.key === 'Escape' && !event.defaultPrevented) onclose();
85
+ else keepFocusWithin(event, panel);
86
+ }
87
+ </script>
88
+
89
+ {#if shown}
90
+ <div class="fixed inset-0 z-20 bg-black/30 md:hidden" aria-hidden="true" onclick={onclose}></div>
91
+ {/if}
92
+
93
+ {#snippet aside()}
94
+ <ContextBridge {contexts}>
95
+ <aside
96
+ bind:this={panel}
97
+ tabindex="-1"
98
+ role={covers ? 'dialog' : undefined}
99
+ aria-modal={covers ? 'true' : undefined}
100
+ aria-labelledby={shown ? titleId : undefined}
101
+ aria-label={shown ? undefined : title}
102
+ {onkeydown}
103
+ class={[
104
+ 'relative flex h-full shrink-0 flex-col bg-base-100 outline-none',
105
+ !pane.dragging && 'transition-[width] duration-200 ease-out motion-reduce:transition-none',
106
+ shown ? ['border-r border-base-200', PANE_OVER_PAGE] : 'w-14 max-md:hidden'
107
+ ]}
108
+ style:width={shown ? `min(${pane.size}px, 35vw)` : undefined}
109
+ >
110
+ {#if shown && !phone.current}
111
+ <PaneResizer {pane} />
112
+ {/if}
113
+ <div
114
+ class={[
115
+ 'flex h-16 shrink-0 items-center',
116
+ shown ? 'justify-between pr-3 pl-6' : 'justify-center'
117
+ ]}
118
+ >
119
+ {#if shown}
120
+ <h2 id={titleId} class="truncate text-[0.9375rem] font-semibold">{title}</h2>
121
+ {/if}
122
+ <div class="tooltip tooltip-right" data-tip={shown ? collapseLabel : expandLabel}>
123
+ <button
124
+ type="button"
125
+ aria-label={shown ? collapseLabel : expandLabel}
126
+ aria-expanded={shown}
127
+ class="flex h-8 w-8 items-center justify-center rounded-md text-base-content/60 transition-colors hover:bg-base-200 hover:text-base-content"
128
+ onclick={toggle}
129
+ >
130
+ <Icon
131
+ icon={shown ? icons.layoutSidebarLeftCollapse : icons.layoutSidebarLeftExpand}
132
+ class="h-5 w-5"
133
+ />
134
+ </button>
135
+ </div>
136
+ </div>
137
+ {#if shown}
138
+ <div class="flex min-h-0 flex-1 flex-col">
139
+ {@render children()}
140
+ </div>
141
+ {:else if rail}
142
+ <div class="flex flex-col items-center gap-1">
143
+ {@render rail()}
144
+ </div>
145
+ {/if}
146
+ </aside>
147
+ </ContextBridge>
148
+ {/snippet}
@@ -0,0 +1,17 @@
1
+ import { type Snippet } from 'svelte';
2
+ interface Props {
3
+ open: boolean;
4
+ /** Names the pane above its content, e.g. "Folders". */
5
+ title: string;
6
+ collapseLabel: string;
7
+ expandLabel: string;
8
+ ontoggle: () => void;
9
+ /** Closes the pane from the backdrop or Escape on a phone. */
10
+ onclose: () => void;
11
+ children: Snippet;
12
+ /** Actions kept on the narrow rail while the pane is collapsed. */
13
+ rail?: Snippet;
14
+ }
15
+ declare const SidePane: import("svelte").Component<Props, {}, "">;
16
+ type SidePane = ReturnType<typeof SidePane>;
17
+ export default SidePane;
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ // Full-panel empty/error state with a brand glow. `notice` for "nothing
3
+ // here yet", `oops` for a failed load.
4
+ import Icon, { type IconifyIcon } from '@iconify/svelte';
5
+ import * as icons from './icons.js';
6
+ import type { Snippet } from 'svelte';
7
+ import type { HTMLAttributes } from 'svelte/elements';
8
+ import { labels } from './labels.svelte.js';
9
+
10
+ interface Props extends HTMLAttributes<HTMLDivElement> {
11
+ tone?: 'notice' | 'oops';
12
+ /** `labels.stateNotice` or `labels.stateOops` when unset. */
13
+ title?: string;
14
+ message: string;
15
+ icon?: string | IconifyIcon;
16
+ /** Actions under the message, e.g. a retry button. */
17
+ children?: Snippet;
18
+ }
19
+
20
+ let {
21
+ tone = 'notice',
22
+ title,
23
+ message,
24
+ icon,
25
+ class: className,
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let resolvedTitle = $derived(title ?? (tone === 'oops' ? labels.stateOops : labels.stateNotice));
31
+ let resolvedIcon = $derived(icon ?? (tone === 'oops' ? icons.moodSad : icons.sparkles));
32
+ </script>
33
+
34
+ <!-- A failed load is announced as it appears. -->
35
+ <div
36
+ role={tone === 'oops' ? 'alert' : undefined}
37
+ {...rest}
38
+ class={[
39
+ 'state-message relative flex min-h-full flex-1 flex-col items-center justify-center gap-3 px-6 py-16 text-center',
40
+ className
41
+ ]}
42
+ >
43
+ <div class="state-glow pointer-events-none absolute inset-0" aria-hidden="true">
44
+ <div class="state-blob state-blob-1"></div>
45
+ <div class="state-blob state-blob-2"></div>
46
+ <div class="state-blob state-blob-3"></div>
47
+ </div>
48
+
49
+ <span class="state-icon relative flex h-12 w-12 items-center justify-center rounded-full">
50
+ <Icon icon={resolvedIcon} class="h-6 w-6" />
51
+ </span>
52
+
53
+ <div class="relative">
54
+ <h2 class="state-title text-lg font-semibold">{resolvedTitle}</h2>
55
+ <p class="mt-1 max-w-md text-sm opacity-70">{message}</p>
56
+ </div>
57
+
58
+ {#if children}
59
+ <div class="relative mt-2">{@render children()}</div>
60
+ {/if}
61
+ </div>
62
+
63
+ <style>
64
+ /* Brand glow — matches the agents hero. The theme sets its strength. */
65
+ .state-glow {
66
+ filter: blur(80px);
67
+ opacity: var(--state-glow-opacity, 0.22);
68
+ }
69
+ /* The blobs sit around the centre of the message, so the glow is behind it
70
+ whatever the width of the page or the panes beside it. */
71
+ .state-blob {
72
+ position: absolute;
73
+ top: 50%;
74
+ left: 50%;
75
+ border-radius: 9999px;
76
+ mix-blend-mode: var(--state-glow-blend, multiply);
77
+ }
78
+ .state-blob-1 {
79
+ width: min(30vw, 26rem);
80
+ height: min(30vw, 26rem);
81
+ transform: translate(-80%, -70%);
82
+ background: radial-gradient(circle, #00e168 0%, transparent 70%);
83
+ }
84
+ .state-blob-2 {
85
+ width: min(28vw, 24rem);
86
+ height: min(28vw, 24rem);
87
+ transform: translate(-20%, -55%);
88
+ background: radial-gradient(circle, #00b4a6 0%, transparent 70%);
89
+ }
90
+ .state-blob-3 {
91
+ width: min(26vw, 22rem);
92
+ height: min(26vw, 22rem);
93
+ transform: translate(-50%, -20%);
94
+ background: radial-gradient(circle, #006b55 0%, transparent 75%);
95
+ }
96
+
97
+ .state-icon {
98
+ background: color-mix(in oklab, var(--color-primary) 12%, transparent);
99
+ color: var(--color-primary);
100
+ }
101
+
102
+ /* Accent-gradient title, matching connve.com's shimmering headline; the theme sets
103
+ stops that read on its base. */
104
+ .state-title {
105
+ background: var(--state-title);
106
+ background-clip: text;
107
+ -webkit-background-clip: text;
108
+ color: transparent;
109
+ }
110
+ </style>
@@ -0,0 +1,15 @@
1
+ import { type IconifyIcon } from '@iconify/svelte';
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ interface Props extends HTMLAttributes<HTMLDivElement> {
5
+ tone?: 'notice' | 'oops';
6
+ /** `labels.stateNotice` or `labels.stateOops` when unset. */
7
+ title?: string;
8
+ message: string;
9
+ icon?: string | IconifyIcon;
10
+ /** Actions under the message, e.g. a retry button. */
11
+ children?: Snippet;
12
+ }
13
+ declare const StateMessage: import("svelte").Component<Props, {}, "">;
14
+ type StateMessage = ReturnType<typeof StateMessage>;
15
+ export default StateMessage;
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ type Variant = 'neutral' | 'success' | 'warning' | 'error';
5
+
6
+ interface Props extends HTMLAttributes<HTMLSpanElement> {
7
+ variant?: Variant;
8
+ /** The status in words; the dot only repeats it. */
9
+ label: string;
10
+ }
11
+
12
+ let { variant = 'neutral', label, class: className, ...rest }: Props = $props();
13
+
14
+ const DOT: Record<Variant, string> = {
15
+ neutral: 'bg-base-content/30',
16
+ success: 'bg-primary',
17
+ warning: 'bg-warning',
18
+ error: 'bg-error'
19
+ };
20
+ </script>
21
+
22
+ <span
23
+ {...rest}
24
+ class={[
25
+ 'inline-flex items-center gap-1.5 whitespace-nowrap',
26
+ variant === 'error' && 'text-error',
27
+ className
28
+ ]}
29
+ >
30
+ <span class={['h-2 w-2 shrink-0 rounded-full', DOT[variant]]} aria-hidden="true"></span>
31
+ {label}
32
+ </span>
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type Variant = 'neutral' | 'success' | 'warning' | 'error';
3
+ interface Props extends HTMLAttributes<HTMLSpanElement> {
4
+ variant?: Variant;
5
+ /** The status in words; the dot only repeats it. */
6
+ label: string;
7
+ }
8
+ declare const StatusDot: import("svelte").Component<Props, {}, "">;
9
+ type StatusDot = ReturnType<typeof StatusDot>;
10
+ export default StatusDot;
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import Icon, { type IconifyIcon } from '@iconify/svelte';
3
+ import * as icons from './icons.js';
4
+ import { labels } from './labels.svelte.js';
5
+ import { theme, type ThemePref } from './theme.svelte.js';
6
+
7
+ let options = $derived<{ pref: ThemePref; icon: IconifyIcon; label: string }[]>([
8
+ { pref: 'system', icon: icons.deviceDesktop, label: labels.themeSystem },
9
+ { pref: 'light', icon: icons.sun, label: labels.themeLight },
10
+ { pref: 'dark', icon: icons.moon, label: labels.themeDark }
11
+ ]);
12
+ </script>
13
+
14
+ <div
15
+ class="flex items-center gap-0.5 rounded-full border border-base-300 bg-base-200/50 p-0.5"
16
+ role="group"
17
+ aria-label={labels.theme}
18
+ >
19
+ {#each options as option (option.pref)}
20
+ <div class="tooltip tooltip-bottom" data-tip={option.label}>
21
+ <button
22
+ type="button"
23
+ class={[
24
+ 'flex h-7 w-7 items-center justify-center rounded-full transition-colors',
25
+ theme.pref === option.pref
26
+ ? 'bg-base-100 text-primary shadow-sm'
27
+ : 'text-base-content/60 hover:text-base-content'
28
+ ]}
29
+ aria-label={labels.themeOption(option.label)}
30
+ aria-pressed={theme.pref === option.pref}
31
+ onclick={() => theme.set(option.pref)}
32
+ >
33
+ <Icon icon={option.icon} class="h-4 w-4" />
34
+ </button>
35
+ </div>
36
+ {/each}
37
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const ThemeToggle: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
3
+ export default ThemeToggle;
@@ -0,0 +1,2 @@
1
+ /** Keeps Tab and Shift+Tab inside `container`, for a panel that covers the page; the container itself may hold focus. */
2
+ export declare function keepFocusWithin(event: KeyboardEvent, container: HTMLElement): void;
package/dist/focus.js ADDED
@@ -0,0 +1,39 @@
1
+ const FOCUSABLE = [
2
+ 'a[href]',
3
+ 'area[href]',
4
+ 'button:not([disabled])',
5
+ 'input:not([disabled]):not([type="hidden"])',
6
+ 'select:not([disabled])',
7
+ 'textarea:not([disabled])',
8
+ 'summary',
9
+ 'iframe',
10
+ 'audio[controls]',
11
+ 'video[controls]',
12
+ '[contenteditable]:not([contenteditable="false"])',
13
+ '[tabindex]:not([tabindex="-1"])'
14
+ ].join(',');
15
+ /** The elements Tab reaches inside `container`, in order; hidden and inert ones are skipped. */
16
+ function tabbable(container) {
17
+ return [...container.querySelectorAll(FOCUSABLE)].filter((el) => !el.closest('[inert]') && el.checkVisibility({ visibilityProperty: true }));
18
+ }
19
+ /** Keeps Tab and Shift+Tab inside `container`, for a panel that covers the page; the container itself may hold focus. */
20
+ export function keepFocusWithin(event, container) {
21
+ if (event.key !== 'Tab')
22
+ return;
23
+ const focusable = tabbable(container);
24
+ const first = focusable[0];
25
+ const last = focusable[focusable.length - 1];
26
+ if (!first) {
27
+ event.preventDefault();
28
+ return;
29
+ }
30
+ const atStart = document.activeElement === first || document.activeElement === container;
31
+ if (event.shiftKey && atStart) {
32
+ event.preventDefault();
33
+ last.focus();
34
+ }
35
+ else if (!event.shiftKey && document.activeElement === last) {
36
+ event.preventDefault();
37
+ first.focus();
38
+ }
39
+ }
@@ -0,0 +1,19 @@
1
+ import type { IconifyIcon } from '@iconify/svelte';
2
+ export declare const check: IconifyIcon;
3
+ export declare const chevronLeft: IconifyIcon;
4
+ export declare const chevronRight: IconifyIcon;
5
+ export declare const copy: IconifyIcon;
6
+ export declare const deviceDesktop: IconifyIcon;
7
+ export declare const folder: IconifyIcon;
8
+ export declare const folderOpen: IconifyIcon;
9
+ export declare const gitPullRequest: IconifyIcon;
10
+ export declare const layoutSidebarLeftCollapse: IconifyIcon;
11
+ export declare const layoutSidebarLeftExpand: IconifyIcon;
12
+ export declare const menu2: IconifyIcon;
13
+ export declare const moodSad: IconifyIcon;
14
+ export declare const moon: IconifyIcon;
15
+ export declare const pencil: IconifyIcon;
16
+ export declare const plus: IconifyIcon;
17
+ export declare const sparkles: IconifyIcon;
18
+ export declare const sun: IconifyIcon;
19
+ export declare const x: IconifyIcon;
package/dist/icons.js ADDED
@@ -0,0 +1,90 @@
1
+ export const check = {
2
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m5 12l5 5L20 7"/>',
3
+ width: 24,
4
+ height: 24
5
+ };
6
+ export const chevronLeft = {
7
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 6l-6 6l6 6"/>',
8
+ width: 24,
9
+ height: 24
10
+ };
11
+ export const chevronRight = {
12
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m9 6l6 6l-6 6"/>',
13
+ width: 24,
14
+ height: 24
15
+ };
16
+ export const copy = {
17
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z"/><path d="M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1"/></g>',
18
+ width: 24,
19
+ height: 24
20
+ };
21
+ export const deviceDesktop = {
22
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm4 15h10m-8-4v4m6-4v4"/>',
23
+ width: 24,
24
+ height: 24
25
+ };
26
+ export const folder = {
27
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/>',
28
+ width: 24,
29
+ height: 24
30
+ };
31
+ export const folderOpen = {
32
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m5 19l2.757-7.351A1 1 0 0 1 8.693 11H21a1 1 0 0 1 .986 1.164l-.996 5.211A2 2 0 0 1 19.026 19za2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4l3 3h7a2 2 0 0 1 2 2v2"/>',
33
+ width: 24,
34
+ height: 24
35
+ };
36
+ export const gitPullRequest = {
37
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M4 18a2 2 0 1 0 4 0a2 2 0 1 0-4 0M4 6a2 2 0 1 0 4 0a2 2 0 1 0-4 0m12 12a2 2 0 1 0 4 0a2 2 0 1 0-4 0M6 8v8"/><path d="M11 6h5a2 2 0 0 1 2 2v8"/><path d="m14 9l-3-3l3-3"/></g>',
38
+ width: 24,
39
+ height: 24
40
+ };
41
+ export const layoutSidebarLeftCollapse = {
42
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm5-2v16"/><path d="m15 10l-2 2l2 2"/></g>',
43
+ width: 24,
44
+ height: 24
45
+ };
46
+ export const layoutSidebarLeftExpand = {
47
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm5-2v16"/><path d="m14 10l2 2l-2 2"/></g>',
48
+ width: 24,
49
+ height: 24
50
+ };
51
+ export const menu2 = {
52
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>',
53
+ width: 24,
54
+ height: 24
55
+ };
56
+ export const moodSad = {
57
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m6-2h.01M15 10h.01"/><path d="M9.5 15.25a3.5 3.5 0 0 1 5 0"/></g>',
58
+ width: 24,
59
+ height: 24
60
+ };
61
+ export const moon = {
62
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z"/>',
63
+ width: 24,
64
+ height: 24
65
+ };
66
+ export const pencil = {
67
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 20h4L18.5 9.5a2.828 2.828 0 1 0-4-4L4 16zm9.5-13.5l4 4"/>',
68
+ width: 24,
69
+ height: 24
70
+ };
71
+ export const plus = {
72
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m-7-7h14"/>',
73
+ width: 24,
74
+ height: 24
75
+ };
76
+ export const sparkles = {
77
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 18a2 2 0 0 1 2 2a2 2 0 0 1 2-2a2 2 0 0 1-2-2a2 2 0 0 1-2 2m0-12a2 2 0 0 1 2 2a2 2 0 0 1 2-2a2 2 0 0 1-2-2a2 2 0 0 1-2 2M9 18a6 6 0 0 1 6-6a6 6 0 0 1-6-6a6 6 0 0 1-6 6a6 6 0 0 1 6 6"/>',
78
+ width: 24,
79
+ height: 24
80
+ };
81
+ export const sun = {
82
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0-8 0m-5 0h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7l-.7.7m0 11.4l.7.7m-12.1-.7l-.7.7"/>',
83
+ width: 24,
84
+ height: 24
85
+ };
86
+ export const x = {
87
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12"/>',
88
+ width: 24,
89
+ height: 24
90
+ };
@@ -0,0 +1,24 @@
1
+ export { default as AppShell, type NavItem } from './AppShell.svelte';
2
+ export { default as Badge } from './Badge.svelte';
3
+ export { default as CopyButton } from './CopyButton.svelte';
4
+ export { default as EditButton } from './EditButton.svelte';
5
+ export { default as FolderTree } from './FolderTree.svelte';
6
+ export { default as IconButton } from './IconButton.svelte';
7
+ export { default as Inspector } from './Inspector.svelte';
8
+ export { default as InspectorSection } from './InspectorSection.svelte';
9
+ export { default as ListHeader } from './ListHeader.svelte';
10
+ export { default as Loading } from './Loading.svelte';
11
+ export { default as Logo } from './Logo.svelte';
12
+ export { default as NavList, type NavGroup, type NavLink } from './NavList.svelte';
13
+ export { default as PaneResizer } from './PaneResizer.svelte';
14
+ export { default as Segmented } from './Segmented.svelte';
15
+ export { default as SidePane } from './SidePane.svelte';
16
+ export { default as StateMessage } from './StateMessage.svelte';
17
+ export { default as StatusDot } from './StatusDot.svelte';
18
+ export { default as ThemeToggle } from './ThemeToggle.svelte';
19
+ export { labels, type Labels } from './labels.svelte.js';
20
+ export { isMobile, MOBILE_QUERY, PANE_OVER_PAGE } from './mobile.js';
21
+ export { PaneSize, inspectorWidth, sidePaneWidth, type PaneBounds } from './paneSize.svelte.js';
22
+ export { theme, type ThemePref } from './theme.svelte.js';
23
+ export type { TooltipSide } from './tooltip.js';
24
+ export { buildTree, type TreeNode } from './tree.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ export { default as AppShell } from './AppShell.svelte';
2
+ export { default as Badge } from './Badge.svelte';
3
+ export { default as CopyButton } from './CopyButton.svelte';
4
+ export { default as EditButton } from './EditButton.svelte';
5
+ export { default as FolderTree } from './FolderTree.svelte';
6
+ export { default as IconButton } from './IconButton.svelte';
7
+ export { default as Inspector } from './Inspector.svelte';
8
+ export { default as InspectorSection } from './InspectorSection.svelte';
9
+ export { default as ListHeader } from './ListHeader.svelte';
10
+ export { default as Loading } from './Loading.svelte';
11
+ export { default as Logo } from './Logo.svelte';
12
+ export { default as NavList } from './NavList.svelte';
13
+ export { default as PaneResizer } from './PaneResizer.svelte';
14
+ export { default as Segmented } from './Segmented.svelte';
15
+ export { default as SidePane } from './SidePane.svelte';
16
+ export { default as StateMessage } from './StateMessage.svelte';
17
+ export { default as StatusDot } from './StatusDot.svelte';
18
+ export { default as ThemeToggle } from './ThemeToggle.svelte';
19
+ export { labels } from './labels.svelte.js';
20
+ export { isMobile, MOBILE_QUERY, PANE_OVER_PAGE } from './mobile.js';
21
+ export { PaneSize, inspectorWidth, sidePaneWidth } from './paneSize.svelte.js';
22
+ export { theme } from './theme.svelte.js';
23
+ export { buildTree } from './tree.js';
@@ -0,0 +1,28 @@
1
+ /** Texts the components show; an app replaces them with its translations at startup. */
2
+ export declare const labels: {
3
+ back: string;
4
+ close: string;
5
+ copy: string;
6
+ copied: string;
7
+ loading: string;
8
+ resize: string;
9
+ new: string;
10
+ edit: string;
11
+ editReadOnly: string;
12
+ menu: string;
13
+ /** Names the main navigation; screen readers add "navigation". */
14
+ navigation: string;
15
+ navExpand: string;
16
+ navCollapse: string;
17
+ /** Names a navigation item that shows a count, when it has no `badgeLabel`. */
18
+ navBadge: (label: string, count: number) => string;
19
+ breadcrumb: string;
20
+ theme: string;
21
+ themeSystem: string;
22
+ themeLight: string;
23
+ themeDark: string;
24
+ themeOption: (theme: string) => string;
25
+ stateNotice: string;
26
+ stateOops: string;
27
+ };
28
+ export type Labels = typeof labels;
@@ -0,0 +1,27 @@
1
+ /** Texts the components show; an app replaces them with its translations at startup. */
2
+ export const labels = $state({
3
+ back: 'Back',
4
+ close: 'Close',
5
+ copy: 'Copy',
6
+ copied: 'Copied',
7
+ loading: 'Loading',
8
+ resize: 'Resize the side panel with the arrow keys; Home or double-click resets it',
9
+ new: 'New',
10
+ edit: 'Edit',
11
+ editReadOnly: 'Read-only, managed in the repository',
12
+ menu: 'Menu',
13
+ /** Names the main navigation; screen readers add "navigation". */
14
+ navigation: 'Main',
15
+ navExpand: 'Expand sidebar',
16
+ navCollapse: 'Collapse sidebar',
17
+ /** Names a navigation item that shows a count, when it has no `badgeLabel`. */
18
+ navBadge: (label, count) => `${label}, ${count}`,
19
+ breadcrumb: 'Breadcrumb',
20
+ theme: 'Color theme',
21
+ themeSystem: 'System',
22
+ themeLight: 'Light',
23
+ themeDark: 'Dark',
24
+ themeOption: (theme) => `${theme} theme`,
25
+ stateNotice: 'Nothing here yet',
26
+ stateOops: 'Oops, something went wrong'
27
+ });
@@ -0,0 +1,5 @@
1
+ /** Below Tailwind's `md` breakpoint, where side panes open over the page instead of beside it and start closed. */
2
+ export declare const MOBILE_QUERY = "(max-width: 767px)";
3
+ export declare function isMobile(): boolean;
4
+ /** Classes that lay an open side pane over the page on mobile. */
5
+ export declare const PANE_OVER_PAGE = "max-md:absolute max-md:inset-y-0 max-md:left-0 max-md:z-30 max-md:w-72! max-md:max-w-[85vw] max-md:shadow-xl";
package/dist/mobile.js ADDED
@@ -0,0 +1,7 @@
1
+ /** Below Tailwind's `md` breakpoint, where side panes open over the page instead of beside it and start closed. */
2
+ export const MOBILE_QUERY = '(max-width: 767px)';
3
+ export function isMobile() {
4
+ return typeof window !== 'undefined' && window.matchMedia(MOBILE_QUERY).matches;
5
+ }
6
+ /** Classes that lay an open side pane over the page on mobile. */
7
+ export const PANE_OVER_PAGE = 'max-md:absolute max-md:inset-y-0 max-md:left-0 max-md:z-30 max-md:w-72! max-md:max-w-[85vw] max-md:shadow-xl';