@noahbaumgartner/svelte-ui 0.0.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 +65 -0
- package/dist/components/alert-dialog/AlertDialog.svelte +161 -0
- package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +20 -0
- package/dist/components/avatar/Avatar.svelte +85 -0
- package/dist/components/avatar/Avatar.svelte.d.ts +12 -0
- package/dist/components/badge/Badge.svelte +49 -0
- package/dist/components/badge/Badge.svelte.d.ts +12 -0
- package/dist/components/breadcrumb/Breadcrumb.svelte +37 -0
- package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +32 -0
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +10 -0
- package/dist/components/breadcrumb/BreadcrumbItem.svelte +71 -0
- package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +12 -0
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +18 -0
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +18 -0
- package/dist/components/button/Button.svelte +247 -0
- package/dist/components/button/Button.svelte.d.ts +24 -0
- package/dist/components/button-group/ButtonGroup.svelte +89 -0
- package/dist/components/button-group/ButtonGroup.svelte.d.ts +12 -0
- package/dist/components/calendar/Calendar.svelte +401 -0
- package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
- package/dist/components/calendar/dates.d.ts +12 -0
- package/dist/components/calendar/dates.js +48 -0
- package/dist/components/card/Card.svelte +55 -0
- package/dist/components/card/Card.svelte.d.ts +10 -0
- package/dist/components/card/CardAction.svelte +24 -0
- package/dist/components/card/CardAction.svelte.d.ts +9 -0
- package/dist/components/card/CardContent.svelte +20 -0
- package/dist/components/card/CardContent.svelte.d.ts +8 -0
- package/dist/components/card/CardDescription.svelte +23 -0
- package/dist/components/card/CardDescription.svelte.d.ts +8 -0
- package/dist/components/card/CardFooter.svelte +28 -0
- package/dist/components/card/CardFooter.svelte.d.ts +9 -0
- package/dist/components/card/CardHeader.svelte +29 -0
- package/dist/components/card/CardHeader.svelte.d.ts +9 -0
- package/dist/components/card/CardTitle.svelte +23 -0
- package/dist/components/card/CardTitle.svelte.d.ts +8 -0
- package/dist/components/checkbox/Checkbox.svelte +93 -0
- package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -0
- package/dist/components/color-picker/ColorPicker.svelte +233 -0
- package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -0
- package/dist/components/context-menu/ContextMenu.svelte +201 -0
- package/dist/components/context-menu/ContextMenu.svelte.d.ts +14 -0
- package/dist/components/context-menu/ContextMenuItem.svelte +105 -0
- package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +14 -0
- package/dist/components/context-menu/context.d.ts +14 -0
- package/dist/components/context-menu/context.js +2 -0
- package/dist/components/dialog/Dialog.svelte +225 -0
- package/dist/components/dialog/Dialog.svelte.d.ts +22 -0
- package/dist/components/field/Field.svelte +78 -0
- package/dist/components/field/Field.svelte.d.ts +11 -0
- package/dist/components/field/FieldContent.svelte +25 -0
- package/dist/components/field/FieldContent.svelte.d.ts +9 -0
- package/dist/components/field/FieldDescription.svelte +34 -0
- package/dist/components/field/FieldDescription.svelte.d.ts +9 -0
- package/dist/components/field/FieldError.svelte +45 -0
- package/dist/components/field/FieldError.svelte.d.ts +11 -0
- package/dist/components/field/FieldGroup.svelte +30 -0
- package/dist/components/field/FieldGroup.svelte.d.ts +9 -0
- package/dist/components/field/FieldLegend.svelte +34 -0
- package/dist/components/field/FieldLegend.svelte.d.ts +10 -0
- package/dist/components/field/FieldSeparator.svelte +31 -0
- package/dist/components/field/FieldSeparator.svelte.d.ts +9 -0
- package/dist/components/field/FieldSet.svelte +33 -0
- package/dist/components/field/FieldSet.svelte.d.ts +9 -0
- package/dist/components/input/Input.svelte +459 -0
- package/dist/components/input/Input.svelte.d.ts +10 -0
- package/dist/components/input-group/InputGroup.svelte +88 -0
- package/dist/components/input-group/InputGroup.svelte.d.ts +9 -0
- package/dist/components/input-group/InputGroupAddon.svelte +88 -0
- package/dist/components/input-group/InputGroupAddon.svelte.d.ts +11 -0
- package/dist/components/kbd/Kbd.svelte +46 -0
- package/dist/components/kbd/Kbd.svelte.d.ts +8 -0
- package/dist/components/kbd/KbdGroup.svelte +27 -0
- package/dist/components/kbd/KbdGroup.svelte.d.ts +8 -0
- package/dist/components/label/Label.svelte +48 -0
- package/dist/components/label/Label.svelte.d.ts +9 -0
- package/dist/components/link/Link.svelte +40 -0
- package/dist/components/link/Link.svelte.d.ts +10 -0
- package/dist/components/navbar/Navbar.svelte +266 -0
- package/dist/components/navbar/Navbar.svelte.d.ts +26 -0
- package/dist/components/navigation-menu/NavigationMenu.svelte +95 -0
- package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +12 -0
- package/dist/components/navigation-menu/NavigationMenuItem.svelte +257 -0
- package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +15 -0
- package/dist/components/navigation-menu/NavigationMenuLink.svelte +91 -0
- package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +16 -0
- package/dist/components/navigation-menu/context.d.ts +15 -0
- package/dist/components/navigation-menu/context.js +2 -0
- package/dist/components/popover/Popover.svelte +295 -0
- package/dist/components/popover/Popover.svelte.d.ts +34 -0
- package/dist/components/radio-group/RadioGroup.svelte +86 -0
- package/dist/components/radio-group/RadioGroup.svelte.d.ts +16 -0
- package/dist/components/radio-group/RadioGroupItem.svelte +95 -0
- package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +7 -0
- package/dist/components/radio-group/context.d.ts +9 -0
- package/dist/components/radio-group/context.js +2 -0
- package/dist/components/separator/Separator.svelte +41 -0
- package/dist/components/separator/Separator.svelte.d.ts +10 -0
- package/dist/components/sidebar/Sidebar.svelte +180 -0
- package/dist/components/sidebar/Sidebar.svelte.d.ts +14 -0
- package/dist/components/sidebar/SidebarContent.svelte +30 -0
- package/dist/components/sidebar/SidebarContent.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarFooter.svelte +23 -0
- package/dist/components/sidebar/SidebarFooter.svelte.d.ts +8 -0
- package/dist/components/sidebar/SidebarGroup.svelte +55 -0
- package/dist/components/sidebar/SidebarGroup.svelte.d.ts +11 -0
- package/dist/components/sidebar/SidebarHeader.svelte +23 -0
- package/dist/components/sidebar/SidebarHeader.svelte.d.ts +8 -0
- package/dist/components/sidebar/SidebarInset.svelte +25 -0
- package/dist/components/sidebar/SidebarInset.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarMenu.svelte +27 -0
- package/dist/components/sidebar/SidebarMenu.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarMenuItem.svelte +221 -0
- package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +23 -0
- package/dist/components/sidebar/SidebarProvider.svelte +62 -0
- package/dist/components/sidebar/SidebarProvider.svelte.d.ts +11 -0
- package/dist/components/sidebar/SidebarSeparator.svelte +5 -0
- package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +18 -0
- package/dist/components/sidebar/SidebarTrigger.svelte +24 -0
- package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +7 -0
- package/dist/components/sidebar/context.d.ts +15 -0
- package/dist/components/sidebar/context.js +2 -0
- package/dist/components/skeleton/Skeleton.svelte +30 -0
- package/dist/components/skeleton/Skeleton.svelte.d.ts +5 -0
- package/dist/components/spinner/Spinner.svelte +72 -0
- package/dist/components/spinner/Spinner.svelte.d.ts +9 -0
- package/dist/components/switch/Switch.svelte +88 -0
- package/dist/components/switch/Switch.svelte.d.ts +5 -0
- package/dist/components/table/Table.svelte +276 -0
- package/dist/components/table/Table.svelte.d.ts +60 -0
- package/dist/components/textarea/Textarea.svelte +57 -0
- package/dist/components/textarea/Textarea.svelte.d.ts +4 -0
- package/dist/components/time-picker/TimePicker.svelte +162 -0
- package/dist/components/time-picker/TimePicker.svelte.d.ts +11 -0
- package/dist/components/toaster/Toaster.svelte +151 -0
- package/dist/components/toaster/Toaster.svelte.d.ts +7 -0
- package/dist/components/toaster/toast.svelte.d.ts +28 -0
- package/dist/components/toaster/toast.svelte.js +22 -0
- package/dist/components/toggle/Toggle.svelte +31 -0
- package/dist/components/toggle/Toggle.svelte.d.ts +8 -0
- package/dist/components/tooltip/Tooltip.svelte +224 -0
- package/dist/components/tooltip/Tooltip.svelte.d.ts +27 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +66 -0
- package/dist/styles/tokens.css +50 -0
- package/package.json +88 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
6
|
+
/** The page content next to the sidebar. */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<main {...rest} class={['sidebar-inset', className]}>
|
|
14
|
+
{@render children()}
|
|
15
|
+
</main>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.sidebar-inset {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
flex: 1;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
background-color: var(--color-bg);
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
4
|
+
/** The page content next to the sidebar. */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const SidebarInset: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type SidebarInset = ReturnType<typeof SidebarInset>;
|
|
9
|
+
export default SidebarInset;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLUListElement>, 'children'> & {
|
|
6
|
+
/** `SidebarMenuItem`s. */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<ul {...rest} class={['sidebar-menu', className]}>
|
|
14
|
+
{@render children()}
|
|
15
|
+
</ul>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.sidebar-menu {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 2px;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
list-style: none;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLUListElement>, 'children'> & {
|
|
4
|
+
/** `SidebarMenuItem`s. */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const SidebarMenu: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type SidebarMenu = ReturnType<typeof SidebarMenu>;
|
|
9
|
+
export default SidebarMenu;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { slide } from 'svelte/transition';
|
|
4
|
+
import type { LucideIcon } from '@lucide/svelte';
|
|
5
|
+
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
6
|
+
import Ellipsis from '@lucide/svelte/icons/ellipsis';
|
|
7
|
+
import Button from '../button/Button.svelte';
|
|
8
|
+
import ContextMenu from '../context-menu/ContextMenu.svelte';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
/** Links the item. Ignored when the item has `items`. */
|
|
12
|
+
href?: string;
|
|
13
|
+
/** Any Lucide icon (import from `svelte-ui/icons`). Stays visible when the sidebar is collapsed to icons. */
|
|
14
|
+
icon?: LucideIcon;
|
|
15
|
+
/** Marks the item as the current page. */
|
|
16
|
+
active?: boolean;
|
|
17
|
+
/** Short count or status at the end of the item, e.g. unread messages. */
|
|
18
|
+
badge?: string | number;
|
|
19
|
+
/** Nested `SidebarMenuItem`s, shown in a submenu the item toggles. */
|
|
20
|
+
items?: Snippet;
|
|
21
|
+
/** Whether the submenu is open. Bindable. */
|
|
22
|
+
expanded?: boolean;
|
|
23
|
+
/** `ContextMenuItem`s for a menu revealed on hover. */
|
|
24
|
+
menu?: Snippet;
|
|
25
|
+
onclick?: () => void;
|
|
26
|
+
children: Snippet;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
href,
|
|
31
|
+
icon: Icon,
|
|
32
|
+
active = false,
|
|
33
|
+
badge,
|
|
34
|
+
items,
|
|
35
|
+
expanded = $bindable(false),
|
|
36
|
+
menu,
|
|
37
|
+
onclick,
|
|
38
|
+
children
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const id = $props.id();
|
|
42
|
+
let classes = $derived(['sidebar-menu-button', { 'sidebar-menu-button--active': active }]);
|
|
43
|
+
|
|
44
|
+
function toggle() {
|
|
45
|
+
expanded = !expanded;
|
|
46
|
+
onclick?.();
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#snippet content()}
|
|
51
|
+
{#if Icon}<Icon class="sidebar-menu-item-icon" aria-hidden="true" />{/if}
|
|
52
|
+
<span class="sidebar-menu-item-label">{@render children()}</span>
|
|
53
|
+
{#if badge !== undefined}<span class="sidebar-menu-item-badge">{badge}</span>{/if}
|
|
54
|
+
{#if items}<ChevronRight class="sidebar-menu-item-chevron" aria-hidden="true" />{/if}
|
|
55
|
+
{/snippet}
|
|
56
|
+
|
|
57
|
+
<li class={['sidebar-menu-item', { 'sidebar-menu-item--has-menu': menu }]}>
|
|
58
|
+
{#if items}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
aria-expanded={expanded}
|
|
62
|
+
aria-controls={expanded ? `${id}-items` : undefined}
|
|
63
|
+
class={classes}
|
|
64
|
+
onclick={toggle}
|
|
65
|
+
>
|
|
66
|
+
{@render content()}
|
|
67
|
+
</button>
|
|
68
|
+
{:else if href}
|
|
69
|
+
<!-- href comes from the consumer, who resolves it; this library has no routes -->
|
|
70
|
+
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
|
71
|
+
<a {href} aria-current={active ? 'page' : undefined} class={classes} {onclick}>
|
|
72
|
+
{@render content()}
|
|
73
|
+
</a>
|
|
74
|
+
{:else}
|
|
75
|
+
<button type="button" aria-current={active ? 'page' : undefined} class={classes} {onclick}>
|
|
76
|
+
{@render content()}
|
|
77
|
+
</button>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
{#if menu}
|
|
81
|
+
<div class="sidebar-menu-item-menu">
|
|
82
|
+
<ContextMenu align="end">
|
|
83
|
+
{#snippet trigger(props)}
|
|
84
|
+
<Button {...props} variant="ghost" size="sm" icon={Ellipsis} label="More" />
|
|
85
|
+
{/snippet}
|
|
86
|
+
{@render menu()}
|
|
87
|
+
</ContextMenu>
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
90
|
+
|
|
91
|
+
{#if items && expanded}
|
|
92
|
+
<ul id="{id}-items" class="sidebar-menu-sub" transition:slide={{ duration: 200 }}>
|
|
93
|
+
{@render items()}
|
|
94
|
+
</ul>
|
|
95
|
+
{/if}
|
|
96
|
+
</li>
|
|
97
|
+
|
|
98
|
+
<style>
|
|
99
|
+
.sidebar-menu-item {
|
|
100
|
+
position: relative;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sidebar-menu-button {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 8px;
|
|
108
|
+
width: 100%;
|
|
109
|
+
height: 32px;
|
|
110
|
+
padding: 0 8px;
|
|
111
|
+
box-sizing: border-box;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
background-color: transparent;
|
|
114
|
+
border: none;
|
|
115
|
+
border-radius: 8px;
|
|
116
|
+
font: inherit;
|
|
117
|
+
font-size: 13px;
|
|
118
|
+
color: var(--color-text-secondary);
|
|
119
|
+
text-align: left;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
outline: none;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
transition:
|
|
124
|
+
background-color 150ms ease,
|
|
125
|
+
color 150ms ease,
|
|
126
|
+
width 200ms ease;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sidebar-menu-button:hover {
|
|
130
|
+
background-color: var(--color-surface);
|
|
131
|
+
color: var(--color-text);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.sidebar-menu-button:focus-visible {
|
|
135
|
+
outline: 2px solid var(--color-accent);
|
|
136
|
+
outline-offset: -2px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.sidebar-menu-button--active {
|
|
140
|
+
background-color: var(--color-surface);
|
|
141
|
+
color: var(--color-text);
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.sidebar-menu-item--has-menu > .sidebar-menu-button {
|
|
146
|
+
padding-right: 36px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.sidebar-menu-item :global(.sidebar-menu-item-icon) {
|
|
150
|
+
width: 16px;
|
|
151
|
+
height: 16px;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.sidebar-menu-item-label {
|
|
156
|
+
flex: 1;
|
|
157
|
+
min-width: 0;
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
text-overflow: ellipsis;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.sidebar-menu-item-badge {
|
|
164
|
+
font-size: 12px;
|
|
165
|
+
font-variant-numeric: tabular-nums;
|
|
166
|
+
color: var(--color-text-muted);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.sidebar-menu-item :global(.sidebar-menu-item-chevron) {
|
|
170
|
+
width: 14px;
|
|
171
|
+
height: 14px;
|
|
172
|
+
flex-shrink: 0;
|
|
173
|
+
color: var(--color-text-muted);
|
|
174
|
+
transition: rotate 200ms ease;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.sidebar-menu-button[aria-expanded='true'] :global(.sidebar-menu-item-chevron) {
|
|
178
|
+
rotate: 90deg;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Menu trigger, shown while the item is hovered or the menu has focus */
|
|
182
|
+
.sidebar-menu-item-menu {
|
|
183
|
+
position: absolute;
|
|
184
|
+
top: 2px;
|
|
185
|
+
right: 2px;
|
|
186
|
+
opacity: 0;
|
|
187
|
+
transition: opacity 150ms ease;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.sidebar-menu-item:hover > .sidebar-menu-item-menu,
|
|
191
|
+
.sidebar-menu-item-menu:focus-within {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@media (hover: none) {
|
|
196
|
+
.sidebar-menu-item-menu {
|
|
197
|
+
opacity: 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.sidebar-menu-sub {
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
gap: 2px;
|
|
205
|
+
margin: 2px 0 2px 15px;
|
|
206
|
+
padding: 0 0 0 8px;
|
|
207
|
+
list-style: none;
|
|
208
|
+
border-left: 1px solid var(--color-border);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* Collapsed to icons: a square button with the label clipped (it stays the accessible name) */
|
|
212
|
+
:global(.sidebar--collapsed-icon) .sidebar-menu-item .sidebar-menu-button {
|
|
213
|
+
width: 32px;
|
|
214
|
+
padding: 0 8px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
:global(.sidebar--collapsed-icon) .sidebar-menu-item-menu,
|
|
218
|
+
:global(.sidebar--collapsed-icon) .sidebar-menu-sub {
|
|
219
|
+
display: none;
|
|
220
|
+
}
|
|
221
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { LucideIcon } from '@lucide/svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
/** Links the item. Ignored when the item has `items`. */
|
|
5
|
+
href?: string;
|
|
6
|
+
/** Any Lucide icon (import from `svelte-ui/icons`). Stays visible when the sidebar is collapsed to icons. */
|
|
7
|
+
icon?: LucideIcon;
|
|
8
|
+
/** Marks the item as the current page. */
|
|
9
|
+
active?: boolean;
|
|
10
|
+
/** Short count or status at the end of the item, e.g. unread messages. */
|
|
11
|
+
badge?: string | number;
|
|
12
|
+
/** Nested `SidebarMenuItem`s, shown in a submenu the item toggles. */
|
|
13
|
+
items?: Snippet;
|
|
14
|
+
/** Whether the submenu is open. Bindable. */
|
|
15
|
+
expanded?: boolean;
|
|
16
|
+
/** `ContextMenuItem`s for a menu revealed on hover. */
|
|
17
|
+
menu?: Snippet;
|
|
18
|
+
onclick?: () => void;
|
|
19
|
+
children: Snippet;
|
|
20
|
+
};
|
|
21
|
+
declare const SidebarMenuItem: import("svelte").Component<Props, {}, "expanded">;
|
|
22
|
+
type SidebarMenuItem = ReturnType<typeof SidebarMenuItem>;
|
|
23
|
+
export default SidebarMenuItem;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { setSidebarContext } from './context.js';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
7
|
+
/** Whether the sidebar is expanded on desktop. Bindable. */
|
|
8
|
+
open?: boolean;
|
|
9
|
+
/** A `Sidebar` and a `SidebarInset`. Size them with the `--sidebar-width`, `--sidebar-width-icon` and `--sidebar-height` custom properties. */
|
|
10
|
+
children: Snippet;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let { open = $bindable(true), class: className, children, ...rest }: Props = $props();
|
|
14
|
+
|
|
15
|
+
const id = $props.id();
|
|
16
|
+
let mobile = $state(false);
|
|
17
|
+
let openMobile = $state(false);
|
|
18
|
+
|
|
19
|
+
setSidebarContext({
|
|
20
|
+
id: `${id}-sidebar`,
|
|
21
|
+
get open() {
|
|
22
|
+
return open;
|
|
23
|
+
},
|
|
24
|
+
get mobile() {
|
|
25
|
+
return mobile;
|
|
26
|
+
},
|
|
27
|
+
get openMobile() {
|
|
28
|
+
return openMobile;
|
|
29
|
+
},
|
|
30
|
+
toggle: () => {
|
|
31
|
+
if (mobile) openMobile = !openMobile;
|
|
32
|
+
else open = !open;
|
|
33
|
+
},
|
|
34
|
+
closeMobile: () => (openMobile = false)
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
const query = window.matchMedia('(max-width: 767px)');
|
|
39
|
+
const update = () => {
|
|
40
|
+
mobile = query.matches;
|
|
41
|
+
openMobile = false;
|
|
42
|
+
};
|
|
43
|
+
update();
|
|
44
|
+
query.addEventListener('change', update);
|
|
45
|
+
return () => query.removeEventListener('change', update);
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div {...rest} class={['sidebar-provider', className]}>
|
|
50
|
+
{@render children()}
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<style>
|
|
54
|
+
.sidebar-provider {
|
|
55
|
+
--sidebar-width: 256px;
|
|
56
|
+
--sidebar-width-icon: 48px;
|
|
57
|
+
--sidebar-height: 100svh;
|
|
58
|
+
display: flex;
|
|
59
|
+
width: 100%;
|
|
60
|
+
min-height: var(--sidebar-height);
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
/** Whether the sidebar is expanded on desktop. Bindable. */
|
|
5
|
+
open?: boolean;
|
|
6
|
+
/** A `Sidebar` and a `SidebarInset`. Size them with the `--sidebar-width`, `--sidebar-width-icon` and `--sidebar-height` custom properties. */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
};
|
|
9
|
+
declare const SidebarProvider: import("svelte").Component<Props, {}, "open">;
|
|
10
|
+
type SidebarProvider = ReturnType<typeof SidebarProvider>;
|
|
11
|
+
export default SidebarProvider;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const SidebarSeparator: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type SidebarSeparator = InstanceType<typeof SidebarSeparator>;
|
|
18
|
+
export default SidebarSeparator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import PanelLeft from '@lucide/svelte/icons/panel-left';
|
|
3
|
+
import Button from '../button/Button.svelte';
|
|
4
|
+
import { getSidebarContext } from './context.js';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
/** Accessible name of the button. */
|
|
8
|
+
label?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { label = 'Toggle sidebar' }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const sidebar = getSidebarContext();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<Button
|
|
17
|
+
variant="ghost"
|
|
18
|
+
size="sm"
|
|
19
|
+
icon={PanelLeft}
|
|
20
|
+
{label}
|
|
21
|
+
aria-controls={sidebar.id}
|
|
22
|
+
aria-expanded={sidebar.mobile ? sidebar.openMobile : sidebar.open}
|
|
23
|
+
onclick={sidebar.toggle}
|
|
24
|
+
/>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SidebarContext = {
|
|
2
|
+
/** Id of the sidebar element. */
|
|
3
|
+
readonly id: string;
|
|
4
|
+
/** Whether the sidebar is expanded on desktop. */
|
|
5
|
+
readonly open: boolean;
|
|
6
|
+
/** Below 768px the sidebar turns into a drawer. */
|
|
7
|
+
readonly mobile: boolean;
|
|
8
|
+
/** Whether the drawer is shown on mobile. */
|
|
9
|
+
readonly openMobile: boolean;
|
|
10
|
+
/** Collapses or expands the sidebar, or opens or closes the drawer on mobile. */
|
|
11
|
+
toggle: () => void;
|
|
12
|
+
/** Closes the drawer on mobile, e.g. after navigating. */
|
|
13
|
+
closeMobile: () => void;
|
|
14
|
+
};
|
|
15
|
+
export declare const getSidebarContext: () => SidebarContext, setSidebarContext: (context: SidebarContext) => SidebarContext;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'>;
|
|
5
|
+
|
|
6
|
+
let { class: className, ...rest }: Props = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div {...rest} aria-hidden="true" class={['skeleton', className]}></div>
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.skeleton {
|
|
13
|
+
flex-shrink: 0;
|
|
14
|
+
border-radius: 6px;
|
|
15
|
+
background-color: var(--color-surface);
|
|
16
|
+
animation: skeleton-pulse 2s ease-in-out infinite;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@keyframes skeleton-pulse {
|
|
20
|
+
50% {
|
|
21
|
+
opacity: 0.5;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (prefers-reduced-motion: reduce) {
|
|
26
|
+
.skeleton {
|
|
27
|
+
animation: none;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
/** Accessible name. Ignored when the spinner is `aria-hidden`, e.g. inside a loading Button. */
|
|
7
|
+
label?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { size = 'md', label = 'Loading', class: className, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<span {...rest} role="status" aria-label={label} class={['spinner', `spinner--${size}`, className]}
|
|
14
|
+
></span>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
@property --spinner-angle {
|
|
18
|
+
syntax: '<angle>';
|
|
19
|
+
initial-value: 0deg;
|
|
20
|
+
inherits: false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.spinner {
|
|
24
|
+
--spinner-size: 16px;
|
|
25
|
+
position: relative;
|
|
26
|
+
display: inline-block;
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
width: var(--spinner-size);
|
|
29
|
+
height: var(--spinner-size);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.spinner--sm {
|
|
33
|
+
--spinner-size: 14px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.spinner--lg {
|
|
37
|
+
--spinner-size: 20px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.spinner::before,
|
|
41
|
+
.spinner::after {
|
|
42
|
+
content: '';
|
|
43
|
+
position: absolute;
|
|
44
|
+
inset: 0;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
border: calc(var(--spinner-size) / 8) solid currentColor;
|
|
47
|
+
border-radius: 30%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Track */
|
|
51
|
+
.spinner::before {
|
|
52
|
+
opacity: 0.2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Handle: a conic mask whose head is opaque and whose tail fades out */
|
|
56
|
+
.spinner::after {
|
|
57
|
+
mask: conic-gradient(from var(--spinner-angle), transparent 0% 60%, #000 100%);
|
|
58
|
+
animation: spinner-rotate 1s linear infinite;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes spinner-rotate {
|
|
62
|
+
to {
|
|
63
|
+
--spinner-angle: 360deg;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@media (prefers-reduced-motion: reduce) {
|
|
68
|
+
.spinner::after {
|
|
69
|
+
animation-duration: 3s;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
|
|
3
|
+
size?: 'sm' | 'md' | 'lg';
|
|
4
|
+
/** Accessible name. Ignored when the spinner is `aria-hidden`, e.g. inside a loading Button. */
|
|
5
|
+
label?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const Spinner: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Spinner = ReturnType<typeof Spinner>;
|
|
9
|
+
export default Spinner;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
type Props = Omit<HTMLInputAttributes, 'type' | 'role'>;
|
|
5
|
+
|
|
6
|
+
let { checked = $bindable(false), class: className, ...rest }: Props = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<span class={['switch', className]}>
|
|
10
|
+
<input {...rest} type="checkbox" role="switch" bind:checked />
|
|
11
|
+
<span class="switch-thumb" aria-hidden="true"></span>
|
|
12
|
+
</span>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
.switch {
|
|
16
|
+
position: relative;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
width: 36px;
|
|
20
|
+
height: 22px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
input {
|
|
24
|
+
appearance: none;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
margin: 0;
|
|
29
|
+
background-color: var(--color-surface);
|
|
30
|
+
border: 1px solid var(--color-border);
|
|
31
|
+
border-radius: 9px;
|
|
32
|
+
outline: none;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
transition:
|
|
35
|
+
background-color 200ms ease,
|
|
36
|
+
border-color 200ms ease;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input:checked {
|
|
40
|
+
background-color: var(--color-accent);
|
|
41
|
+
border-color: var(--color-accent);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
input:focus-visible {
|
|
45
|
+
outline: 2px solid var(--color-accent);
|
|
46
|
+
outline-offset: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
input[aria-invalid='true'] {
|
|
50
|
+
border-color: var(--color-destructive);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
input:disabled {
|
|
54
|
+
cursor: not-allowed;
|
|
55
|
+
opacity: 0.5;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Radius is the track's 9px minus the 4px inset, so the corners stay concentric */
|
|
59
|
+
.switch-thumb {
|
|
60
|
+
position: absolute;
|
|
61
|
+
top: 4px;
|
|
62
|
+
left: 4px;
|
|
63
|
+
width: 14px;
|
|
64
|
+
height: 14px;
|
|
65
|
+
background-color: var(--color-base);
|
|
66
|
+
border-radius: 5px;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
transition:
|
|
69
|
+
transform 200ms ease,
|
|
70
|
+
background-color 200ms ease;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
input:checked + .switch-thumb {
|
|
74
|
+
background-color: var(--color-accent-foreground);
|
|
75
|
+
transform: translateX(14px);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
input:disabled + .switch-thumb {
|
|
79
|
+
opacity: 0.5;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (prefers-reduced-motion: reduce) {
|
|
83
|
+
input,
|
|
84
|
+
.switch-thumb {
|
|
85
|
+
transition: none;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</style>
|