zabi-components 6.0.0 → 7.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 +137 -1473
- package/dist/atoms/ActionPanel.svelte +116 -0
- package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
- package/dist/atoms/Badge.svelte +1 -1
- package/dist/atoms/Badge.svelte.d.ts +1 -1
- package/dist/atoms/Button.svelte +38 -21
- package/dist/atoms/Button.svelte.d.ts +6 -6
- package/dist/atoms/Card.svelte +12 -3
- package/dist/atoms/Card.svelte.d.ts +2 -2
- package/dist/atoms/CardHeader.svelte +8 -4
- package/dist/atoms/CardHeader.svelte.d.ts +4 -2
- package/dist/atoms/Checkbox.svelte +50 -76
- package/dist/atoms/Checkbox.svelte.d.ts +7 -2
- package/dist/atoms/CodeBlock.svelte +20 -34
- package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
- package/dist/atoms/ColorPicker.svelte +2 -6
- package/dist/atoms/Container.svelte +41 -0
- package/dist/atoms/Container.svelte.d.ts +12 -0
- package/dist/atoms/Divider.svelte +68 -0
- package/dist/atoms/Divider.svelte.d.ts +13 -0
- package/dist/atoms/FeatureCard.svelte +35 -50
- package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
- package/dist/atoms/IconButton.svelte +36 -29
- package/dist/atoms/IconButton.svelte.d.ts +7 -13
- package/dist/atoms/Input.svelte +58 -26
- package/dist/atoms/Input.svelte.d.ts +6 -1
- package/dist/atoms/List.svelte +2 -7
- package/dist/atoms/List.svelte.d.ts +1 -6
- package/dist/atoms/ListItem.svelte +54 -65
- package/dist/atoms/ListItem.svelte.d.ts +7 -13
- package/dist/atoms/ListItemLeading.svelte +19 -0
- package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
- package/dist/atoms/Progress.svelte +1 -1
- package/dist/atoms/Radio.svelte +51 -0
- package/dist/atoms/Radio.svelte.d.ts +14 -0
- package/dist/atoms/Select.svelte +23 -30
- package/dist/atoms/Select.svelte.d.ts +0 -10
- package/dist/atoms/SelectionControl.svelte +98 -0
- package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
- package/dist/atoms/Skeleton.svelte +39 -15
- package/dist/atoms/Skeleton.svelte.d.ts +7 -3
- package/dist/atoms/Table.svelte +27 -0
- package/dist/atoms/Table.svelte.d.ts +10 -0
- package/dist/atoms/Text.svelte +46 -0
- package/dist/atoms/Text.svelte.d.ts +13 -0
- package/dist/atoms/Textarea.svelte +74 -53
- package/dist/atoms/Textarea.svelte.d.ts +6 -2
- package/dist/atoms/ThemeToggle.svelte +35 -24
- package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
- package/dist/atoms/Toast.svelte +55 -31
- package/dist/atoms/Toast.svelte.d.ts +4 -1
- package/dist/atoms/Toggle.svelte +37 -22
- package/dist/atoms/Toggle.svelte.d.ts +2 -1
- package/dist/atoms/Tooltip.svelte +97 -14
- package/dist/atoms/Tooltip.svelte.d.ts +6 -5
- package/dist/atoms/index.d.ts +6 -0
- package/dist/atoms/index.js +6 -0
- package/dist/atoms/selection-control.styles.d.ts +25 -0
- package/dist/atoms/selection-control.styles.js +30 -0
- package/dist/components/atoms/index.d.ts +6 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +26 -0
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
- package/dist/components/index.d.ts +41 -5
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +10 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
- package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +39 -0
- package/dist/components/molecules/toast-store.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +2 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/page.types.d.ts +69 -0
- package/dist/components/types/page.types.d.ts.map +1 -0
- package/dist/components/types/page.types.js +1 -0
- package/dist/components/types/variants.d.ts +64 -0
- package/dist/components/types/variants.d.ts.map +1 -0
- package/dist/components/types/variants.js +44 -0
- package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
- package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +12 -0
- package/dist/components/util/focus-utils.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +17 -0
- package/dist/components/util/ssr-safe.d.ts.map +1 -0
- package/dist/index.d.ts +41 -5
- package/dist/index.js +44 -8
- package/dist/lib/layout-width-tokens.d.ts +17 -0
- package/dist/lib/layout-width-tokens.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/List.d.ts +3 -0
- package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/index.d.ts +4 -0
- package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
- package/dist/lib/showcase/components-catalog.d.ts.map +1 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +26 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -1
- package/dist/molecules/Alert.svelte +50 -23
- package/dist/molecules/Alert.svelte.d.ts +1 -1
- package/dist/molecules/ComponentDemo.svelte +2 -2
- package/dist/molecules/ContactForm.svelte +1 -1
- package/dist/molecules/Dropdown.svelte +99 -109
- package/dist/molecules/Dropdown.svelte.d.ts +12 -6
- package/dist/molecules/EmptyState.svelte +44 -0
- package/dist/molecules/EmptyState.svelte.d.ts +11 -0
- package/dist/molecules/FormField.svelte +89 -0
- package/dist/molecules/FormField.svelte.d.ts +23 -0
- package/dist/molecules/Header.svelte +41 -0
- package/dist/molecules/Header.svelte.d.ts +10 -0
- package/dist/molecules/Modal.svelte +37 -24
- package/dist/molecules/Modal.svelte.d.ts +4 -2
- package/dist/molecules/NavigationMenu.svelte +16 -0
- package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuContent.svelte +9 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuTrigger.svelte +9 -1
- package/dist/molecules/Page.svelte +16 -0
- package/dist/molecules/Page.svelte.d.ts +8 -0
- package/dist/molecules/PropsTable.svelte +61 -0
- package/dist/molecules/PropsTable.svelte.d.ts +8 -0
- package/dist/molecules/RadioGroup.svelte +197 -0
- package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
- package/dist/molecules/Section.svelte +2 -11
- package/dist/molecules/Section.svelte.d.ts +2 -11
- package/dist/molecules/SidebarBrandHeader.svelte +68 -0
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
- package/dist/molecules/SidebarFooter.svelte +119 -0
- package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
- package/dist/molecules/SidebarNavSection.svelte +49 -0
- package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
- package/dist/molecules/SlideUp.svelte +19 -14
- package/dist/molecules/SlideUp.svelte.d.ts +1 -1
- package/dist/molecules/Tabs.svelte +4 -9
- package/dist/molecules/Toaster.svelte +21 -0
- package/dist/molecules/Toaster.svelte.d.ts +6 -0
- package/dist/molecules/ToasterToast.svelte +217 -0
- package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
- package/dist/molecules/index.d.ts +10 -0
- package/dist/molecules/index.js +10 -0
- package/dist/molecules/navigation-menu-context.d.ts +11 -0
- package/dist/molecules/navigation-menu-context.js +18 -0
- package/dist/molecules/toast-store.d.ts +38 -0
- package/dist/molecules/toast-store.js +34 -0
- package/dist/organisms/SidebarAccountPanel.svelte +118 -0
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
- package/dist/organisms/SidebarNavigation.svelte +267 -237
- package/dist/organisms/SidebarNavigation.svelte.d.ts +21 -5
- package/dist/organisms/SidebarPanel.svelte +54 -22
- package/dist/organisms/SidebarPanel.svelte.d.ts +3 -0
- package/dist/organisms/{Navbar.svelte → TopNavbar.svelte} +22 -17
- package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
- package/dist/organisms/index.d.ts +2 -1
- package/dist/organisms/index.js +2 -1
- package/dist/routes/lib/focus-utils.d.ts +7 -18
- package/dist/routes/lib/focus-utils.ts +32 -34
- package/dist/routes/lib/ssr-safe.ts +5 -4
- package/dist/routes/lib/variant-utils.ts +48 -39
- package/dist/types/components.d.ts +187 -0
- package/dist/types/components.d.ts.map +1 -0
- package/dist/types/events.d.ts +200 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/index.d.ts +171 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/page.types.d.ts +2 -31
- package/dist/types/page.types.d.ts.map +1 -1
- package/dist/types/page.types.js +1 -0
- package/dist/types/page.types.ts +79 -0
- package/dist/types/variants.d.ts +3 -0
- package/dist/types/variants.d.ts.map +1 -0
- package/dist/types/variants.js +1 -0
- package/dist/types/variants.ts +5 -6
- package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
- package/dist/util/fixed-sidebar-flyout.js +148 -0
- package/dist/util/focus-utils.d.ts +11 -0
- package/dist/util/focus-utils.js +94 -0
- package/dist/util/ssr-safe.d.ts +16 -0
- package/dist/util/ssr-safe.js +48 -0
- package/dist/zabi-components-colors.css +128 -115
- package/dist/zabi-components-theme-dark-only.css +77 -72
- package/dist/zabi-components-theme-dark.css +77 -72
- package/dist/zabi-components-theme-only.css +51 -43
- package/dist/zabi-components-theme.css +51 -43
- package/dist/zabi-components.css +952 -249
- package/docs/lucide-icons.md +24 -0
- package/package.json +22 -9
- package/scripts/fix-lucide-svelte-icon-dts.js +35 -0
- package/dist/organisms/Navbar.svelte.d.ts +0 -41
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
RADIO_CHECKED_DOT_CLASSES,
|
|
4
|
+
RADIO_GROUP_CONTROL_SHELL,
|
|
5
|
+
RADIO_GROUP_OPTION_LABEL_ROW,
|
|
6
|
+
RADIO_GROUP_RING_OVERLAY,
|
|
7
|
+
} from "../atoms/selection-control.styles";
|
|
8
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
9
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
10
|
+
|
|
11
|
+
export type RadioGroupOption = {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
description?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
options: RadioGroupOption[];
|
|
20
|
+
/** Selected value; `bind:value`. Use `undefined` for no selection. */
|
|
21
|
+
value?: string | undefined;
|
|
22
|
+
/** Uncontrolled initial value. */
|
|
23
|
+
defaultValue?: string | undefined;
|
|
24
|
+
name?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
legend?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
options,
|
|
32
|
+
defaultValue,
|
|
33
|
+
value = $bindable(defaultValue),
|
|
34
|
+
name: nameProp,
|
|
35
|
+
disabled = false,
|
|
36
|
+
legend = "",
|
|
37
|
+
label = "",
|
|
38
|
+
...restProps
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const fallbackName = generateId("radiogroup");
|
|
42
|
+
const groupName = $derived(nameProp || fallbackName);
|
|
43
|
+
const legendText = $derived(legend || label);
|
|
44
|
+
|
|
45
|
+
const enabledOptions = $derived.by(() =>
|
|
46
|
+
options.filter((o: RadioGroupOption) => !disabled && !o.disabled),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const fallbackValue = $derived.by(() => enabledOptions[0]?.value ?? "");
|
|
50
|
+
|
|
51
|
+
/** False if controlled `value` is set but missing from `options` (stale); roving tabindex then follows first enabled instead. */
|
|
52
|
+
const valueMatchesOption = $derived(
|
|
53
|
+
value === undefined ||
|
|
54
|
+
value === "" ||
|
|
55
|
+
options.some((o: RadioGroupOption) => o.value === value),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
function isOptionDisabled(option: RadioGroupOption): boolean {
|
|
59
|
+
return disabled || !!option.disabled;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function tabIndexFor(option: RadioGroupOption): number {
|
|
63
|
+
if (isOptionDisabled(option)) return -1;
|
|
64
|
+
if (
|
|
65
|
+
value !== undefined &&
|
|
66
|
+
value !== "" &&
|
|
67
|
+
valueMatchesOption
|
|
68
|
+
) {
|
|
69
|
+
return option.value === value ? 0 : -1;
|
|
70
|
+
}
|
|
71
|
+
return option.value === fallbackValue ? 0 : -1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleChange(event: Event) {
|
|
75
|
+
if (disabled) return;
|
|
76
|
+
const target = event.target as HTMLInputElement;
|
|
77
|
+
value = target.value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const elementsByValue = new SvelteMap<string, HTMLInputElement>();
|
|
81
|
+
function registerRadio(valueKey: string) {
|
|
82
|
+
return (node: HTMLInputElement) => {
|
|
83
|
+
elementsByValue.set(valueKey, node);
|
|
84
|
+
return () => {
|
|
85
|
+
elementsByValue.delete(valueKey);
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function focusValue(nextValue: string) {
|
|
91
|
+
const el = elementsByValue.get(nextValue);
|
|
92
|
+
el?.focus();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function moveSelection(delta: number) {
|
|
96
|
+
const enabled = enabledOptions;
|
|
97
|
+
if (enabled.length === 0) return;
|
|
98
|
+
|
|
99
|
+
const rawIndex = enabled.findIndex((o) => o.value === value);
|
|
100
|
+
const currentIndex =
|
|
101
|
+
rawIndex === -1
|
|
102
|
+
? delta > 0
|
|
103
|
+
? -1
|
|
104
|
+
: enabled.length
|
|
105
|
+
: rawIndex;
|
|
106
|
+
const nextIndex =
|
|
107
|
+
(currentIndex + delta + enabled.length) % enabled.length;
|
|
108
|
+
const next = enabled[nextIndex];
|
|
109
|
+
if (!next) return;
|
|
110
|
+
value = next.value;
|
|
111
|
+
focusValue(next.value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
115
|
+
const key = event.key;
|
|
116
|
+
if (
|
|
117
|
+
key !== "ArrowRight" &&
|
|
118
|
+
key !== "ArrowDown" &&
|
|
119
|
+
key !== "ArrowLeft" &&
|
|
120
|
+
key !== "ArrowUp" &&
|
|
121
|
+
key !== "Home" &&
|
|
122
|
+
key !== "End"
|
|
123
|
+
) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const enabled = enabledOptions;
|
|
128
|
+
if (enabled.length === 0) return;
|
|
129
|
+
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
if (key === "ArrowRight" || key === "ArrowDown") {
|
|
132
|
+
moveSelection(1);
|
|
133
|
+
} else if (key === "ArrowLeft" || key === "ArrowUp") {
|
|
134
|
+
moveSelection(-1);
|
|
135
|
+
} else if (key === "Home") {
|
|
136
|
+
value = enabled[0].value;
|
|
137
|
+
focusValue(enabled[0].value);
|
|
138
|
+
} else if (key === "End") {
|
|
139
|
+
const last = enabled[enabled.length - 1];
|
|
140
|
+
value = last.value;
|
|
141
|
+
focusValue(last.value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const optionLabelWrapperClasses = RADIO_GROUP_OPTION_LABEL_ROW;
|
|
146
|
+
|
|
147
|
+
const radioControlShellClasses = RADIO_GROUP_CONTROL_SHELL;
|
|
148
|
+
|
|
149
|
+
const radioRingOverlayClasses = RADIO_GROUP_RING_OVERLAY;
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<fieldset
|
|
153
|
+
class="space-y-3"
|
|
154
|
+
aria-disabled={disabled ? "true" : undefined}
|
|
155
|
+
onkeydown={handleKeydown}
|
|
156
|
+
{...restProps}
|
|
157
|
+
>
|
|
158
|
+
{#if legendText}
|
|
159
|
+
<legend class="text-sm font-medium text-label mb-1">
|
|
160
|
+
{legendText}
|
|
161
|
+
</legend>
|
|
162
|
+
{/if}
|
|
163
|
+
|
|
164
|
+
<div class="space-y-3">
|
|
165
|
+
{#each options as option (option.value)}
|
|
166
|
+
{@const optionDisabled = isOptionDisabled(option)}
|
|
167
|
+
{@const isChecked = value === option.value}
|
|
168
|
+
<label class={optionLabelWrapperClasses}>
|
|
169
|
+
<span class={radioControlShellClasses}>
|
|
170
|
+
<input
|
|
171
|
+
type="radio"
|
|
172
|
+
name={groupName}
|
|
173
|
+
value={option.value}
|
|
174
|
+
checked={isChecked}
|
|
175
|
+
disabled={optionDisabled}
|
|
176
|
+
tabindex={tabIndexFor(option)}
|
|
177
|
+
class="sr-only"
|
|
178
|
+
onchange={handleChange}
|
|
179
|
+
{@attach registerRadio(option.value)}
|
|
180
|
+
/>
|
|
181
|
+
<span class={radioRingOverlayClasses}></span>
|
|
182
|
+
<span class={RADIO_CHECKED_DOT_CLASSES} aria-hidden="true"></span>
|
|
183
|
+
</span>
|
|
184
|
+
<span class="flex flex-col gap-1">
|
|
185
|
+
<span class="text-sm font-medium text-label">
|
|
186
|
+
{option.label}
|
|
187
|
+
</span>
|
|
188
|
+
{#if option.description}
|
|
189
|
+
<span class="text-sm text-description">
|
|
190
|
+
{option.description}
|
|
191
|
+
</span>
|
|
192
|
+
{/if}
|
|
193
|
+
</span>
|
|
194
|
+
</label>
|
|
195
|
+
{/each}
|
|
196
|
+
</div>
|
|
197
|
+
</fieldset>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type RadioGroupOption = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
};
|
|
7
|
+
interface Props {
|
|
8
|
+
options: RadioGroupOption[];
|
|
9
|
+
/** Selected value; `bind:value`. Use `undefined` for no selection. */
|
|
10
|
+
value?: string | undefined;
|
|
11
|
+
/** Uncontrolled initial value. */
|
|
12
|
+
defaultValue?: string | undefined;
|
|
13
|
+
name?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
legend?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
declare const RadioGroup: import("svelte").Component<Props, {}, "value">;
|
|
19
|
+
type RadioGroup = ReturnType<typeof RadioGroup>;
|
|
20
|
+
export default RadioGroup;
|
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import type { SizeVariant } from "
|
|
3
|
+
import type { SizeVariant } from "../types/variants.js";
|
|
4
4
|
import Heading from "../atoms/Heading.svelte";
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
|
-
/** Section title */
|
|
8
7
|
title?: string;
|
|
9
|
-
/** Section description/subtitle */
|
|
10
8
|
description?: string;
|
|
11
|
-
/**
|
|
9
|
+
/** Title heading level (1–6). */
|
|
12
10
|
headingLevel?: number;
|
|
13
|
-
/** Section size variant */
|
|
14
11
|
size?: SizeVariant;
|
|
15
|
-
/** Maximum width of the section container */
|
|
16
12
|
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
|
|
17
|
-
/** Background variant */
|
|
18
13
|
background?: "default" | "muted" | "accent" | "transparent";
|
|
19
|
-
/** Padding variant */
|
|
20
14
|
padding?: "none" | "sm" | "md" | "lg" | "xl";
|
|
21
|
-
/** Whether to center the content */
|
|
22
15
|
centered?: boolean;
|
|
23
|
-
/** Additional CSS classes */
|
|
24
16
|
className?: string;
|
|
25
|
-
/** Section content */
|
|
26
17
|
children?: Snippet;
|
|
27
18
|
}
|
|
28
19
|
|
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import type { SizeVariant } from "
|
|
2
|
+
import type { SizeVariant } from "../types/variants.js";
|
|
3
3
|
interface Props {
|
|
4
|
-
/** Section title */
|
|
5
4
|
title?: string;
|
|
6
|
-
/** Section description/subtitle */
|
|
7
5
|
description?: string;
|
|
8
|
-
/**
|
|
6
|
+
/** Title heading level (1–6). */
|
|
9
7
|
headingLevel?: number;
|
|
10
|
-
/** Section size variant */
|
|
11
8
|
size?: SizeVariant;
|
|
12
|
-
/** Maximum width of the section container */
|
|
13
9
|
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
|
|
14
|
-
/** Background variant */
|
|
15
10
|
background?: "default" | "muted" | "accent" | "transparent";
|
|
16
|
-
/** Padding variant */
|
|
17
11
|
padding?: "none" | "sm" | "md" | "lg" | "xl";
|
|
18
|
-
/** Whether to center the content */
|
|
19
12
|
centered?: boolean;
|
|
20
|
-
/** Additional CSS classes */
|
|
21
13
|
className?: string;
|
|
22
|
-
/** Section content */
|
|
23
14
|
children?: Snippet;
|
|
24
15
|
}
|
|
25
16
|
declare const Section: import("svelte").Component<Props, {}, "">;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
collapsed?: boolean;
|
|
4
|
+
brandName?: string;
|
|
5
|
+
logoSrc?: string;
|
|
6
|
+
logoAlt?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
collapsed = false,
|
|
12
|
+
brandName = "",
|
|
13
|
+
logoSrc = "",
|
|
14
|
+
logoAlt = "",
|
|
15
|
+
className = "",
|
|
16
|
+
}: Props = $props();
|
|
17
|
+
|
|
18
|
+
const trimmedName = $derived(brandName.trim());
|
|
19
|
+
const monogram = $derived(
|
|
20
|
+
trimmedName
|
|
21
|
+
? trimmedName
|
|
22
|
+
.split(/\s+/)
|
|
23
|
+
.map((w) => w[0])
|
|
24
|
+
.join("")
|
|
25
|
+
.slice(0, 2)
|
|
26
|
+
.toUpperCase()
|
|
27
|
+
: "",
|
|
28
|
+
);
|
|
29
|
+
const showLogo = $derived(Boolean(logoSrc));
|
|
30
|
+
const showWordmark = $derived(Boolean(trimmedName) && !collapsed);
|
|
31
|
+
const showMonogram = $derived(
|
|
32
|
+
collapsed && !showLogo && Boolean(monogram),
|
|
33
|
+
);
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
{#if showLogo || trimmedName}
|
|
37
|
+
<div
|
|
38
|
+
class={`flex w-full min-w-0 items-center gap-2.5 ${className}`.trim()}
|
|
39
|
+
>
|
|
40
|
+
{#if showLogo}
|
|
41
|
+
<span
|
|
42
|
+
class="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-background ring-1 ring-border"
|
|
43
|
+
>
|
|
44
|
+
<img
|
|
45
|
+
src={logoSrc}
|
|
46
|
+
alt={logoAlt || trimmedName || "Brand"}
|
|
47
|
+
class="size-full object-cover"
|
|
48
|
+
width="36"
|
|
49
|
+
height="36"
|
|
50
|
+
/>
|
|
51
|
+
</span>
|
|
52
|
+
{:else if showMonogram}
|
|
53
|
+
<span
|
|
54
|
+
class="flex size-9 shrink-0 items-center justify-center rounded-xl bg-action-primary-subtle text-xs font-bold text-headline ring-1 ring-border"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
>
|
|
57
|
+
{monogram}
|
|
58
|
+
</span>
|
|
59
|
+
{/if}
|
|
60
|
+
{#if showWordmark}
|
|
61
|
+
<span
|
|
62
|
+
class="truncate text-base font-semibold tracking-tight text-headline"
|
|
63
|
+
>
|
|
64
|
+
{trimmedName}
|
|
65
|
+
</span>
|
|
66
|
+
{/if}
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
collapsed?: boolean;
|
|
3
|
+
brandName?: string;
|
|
4
|
+
logoSrc?: string;
|
|
5
|
+
logoAlt?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const SidebarBrandHeader: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type SidebarBrandHeader = ReturnType<typeof SidebarBrandHeader>;
|
|
10
|
+
export default SidebarBrandHeader;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { fixedSidebarFlyout } from "../util/fixed-sidebar-flyout.js";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
showProfile?: boolean;
|
|
8
|
+
profileName?: string;
|
|
9
|
+
profileEmail?: string;
|
|
10
|
+
profileInitials?: string;
|
|
11
|
+
showLogout?: boolean;
|
|
12
|
+
logoutLabel?: string;
|
|
13
|
+
showThemeToggle?: boolean;
|
|
14
|
+
lightModeLabel?: string;
|
|
15
|
+
isLightMode?: boolean;
|
|
16
|
+
onLogout?: () => void;
|
|
17
|
+
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
profilePanelOpen?: boolean;
|
|
20
|
+
profilePanelControlsId?: string;
|
|
21
|
+
onProfileClick?: (event?: MouseEvent) => void;
|
|
22
|
+
profilePanel?: Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
collapsed = false,
|
|
27
|
+
showProfile = true,
|
|
28
|
+
profileName = "Zabi",
|
|
29
|
+
profileEmail = "hello@zabi.dev",
|
|
30
|
+
profileInitials = "ZA",
|
|
31
|
+
showLogout = true,
|
|
32
|
+
logoutLabel = "Logout",
|
|
33
|
+
showThemeToggle = true,
|
|
34
|
+
lightModeLabel = "Light mode",
|
|
35
|
+
isLightMode = $bindable(false),
|
|
36
|
+
onLogout,
|
|
37
|
+
onThemeToggle,
|
|
38
|
+
className = "",
|
|
39
|
+
profilePanelOpen = false,
|
|
40
|
+
profilePanelControlsId = "",
|
|
41
|
+
onProfileClick,
|
|
42
|
+
profilePanel,
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
const showFooter = $derived(showProfile || showLogout || showThemeToggle);
|
|
46
|
+
const showPanelLauncher = $derived(showProfile);
|
|
47
|
+
|
|
48
|
+
const avatarClasses =
|
|
49
|
+
"size-10 rounded-xl bg-action-primary text-action-primary flex items-center justify-center text-sm font-semibold shrink-0 ring-1 ring-border-focus";
|
|
50
|
+
|
|
51
|
+
function getTextToneClass(isMuted = false): string {
|
|
52
|
+
return isMuted ? "text-description" : "text-headline";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleProfileClick(event: MouseEvent): void {
|
|
56
|
+
onProfileClick?.(event);
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
{#if showFooter}
|
|
61
|
+
<div class="w-full shrink-0" data-sidebar-flyout-root>
|
|
62
|
+
<footer
|
|
63
|
+
class={`flex w-full shrink-0 flex-col gap-2.5 border-t border-border pt-3 pb-1 ${className}`.trim()}
|
|
64
|
+
aria-label="Account and settings"
|
|
65
|
+
>
|
|
66
|
+
{#if showPanelLauncher}
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
data-sidebar-flyout-anchor="profile"
|
|
70
|
+
class="w-full cursor-pointer rounded-xl px-2 py-2 outline-none transition-colors hover:bg-nav-menu-hover hover:text-nav-menu-item-hover hover:ring-1 hover:ring-border/60 active:bg-base-200 focus-ring focus-ring--nav"
|
|
71
|
+
aria-haspopup="dialog"
|
|
72
|
+
aria-expanded={profilePanelOpen}
|
|
73
|
+
aria-controls={profilePanelControlsId.trim()
|
|
74
|
+
? profilePanelControlsId.trim()
|
|
75
|
+
: undefined}
|
|
76
|
+
aria-label={collapsed
|
|
77
|
+
? `Open account panel`
|
|
78
|
+
: `Open account panel for ${profileName}`}
|
|
79
|
+
onclick={handleProfileClick}
|
|
80
|
+
>
|
|
81
|
+
<span class="flex w-full items-center gap-3 text-left">
|
|
82
|
+
<span class={avatarClasses} aria-hidden="true">
|
|
83
|
+
{profileInitials}
|
|
84
|
+
</span>
|
|
85
|
+
{#if !collapsed}
|
|
86
|
+
<span class="min-w-0">
|
|
87
|
+
<span
|
|
88
|
+
class="block truncate text-sm font-semibold {getTextToneClass()}"
|
|
89
|
+
>
|
|
90
|
+
{profileName}
|
|
91
|
+
</span>
|
|
92
|
+
<span
|
|
93
|
+
class="block truncate text-xs {getTextToneClass(true)}"
|
|
94
|
+
>
|
|
95
|
+
{profileEmail}
|
|
96
|
+
</span>
|
|
97
|
+
</span>
|
|
98
|
+
{/if}
|
|
99
|
+
</span>
|
|
100
|
+
</button>
|
|
101
|
+
{/if}
|
|
102
|
+
</footer>
|
|
103
|
+
|
|
104
|
+
{#if showPanelLauncher && profilePanelOpen && profilePanel}
|
|
105
|
+
<div
|
|
106
|
+
class="max-h-[min(32rem,calc(100dvh-6rem))] overflow-y-auto overscroll-contain outline-none"
|
|
107
|
+
role="presentation"
|
|
108
|
+
use:fixedSidebarFlyout={{
|
|
109
|
+
open: profilePanelOpen,
|
|
110
|
+
align: "bottom",
|
|
111
|
+
gap: 12,
|
|
112
|
+
anchorRole: "profile",
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{@render profilePanel()}
|
|
116
|
+
</div>
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
collapsed?: boolean;
|
|
4
|
+
showProfile?: boolean;
|
|
5
|
+
profileName?: string;
|
|
6
|
+
profileEmail?: string;
|
|
7
|
+
profileInitials?: string;
|
|
8
|
+
showLogout?: boolean;
|
|
9
|
+
logoutLabel?: string;
|
|
10
|
+
showThemeToggle?: boolean;
|
|
11
|
+
lightModeLabel?: string;
|
|
12
|
+
isLightMode?: boolean;
|
|
13
|
+
onLogout?: () => void;
|
|
14
|
+
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
profilePanelOpen?: boolean;
|
|
17
|
+
profilePanelControlsId?: string;
|
|
18
|
+
onProfileClick?: (event?: MouseEvent) => void;
|
|
19
|
+
profilePanel?: Snippet;
|
|
20
|
+
}
|
|
21
|
+
declare const SidebarFooter: import("svelte").Component<Props, {}, "isLightMode">;
|
|
22
|
+
type SidebarFooter = ReturnType<typeof SidebarFooter>;
|
|
23
|
+
export default SidebarFooter;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Disambiguates generated ids when section titles repeat. */
|
|
7
|
+
sectionKey?: string;
|
|
8
|
+
/** `aria-label` on the `<ul>` (required when title is hidden in collapsed mode). */
|
|
9
|
+
listAriaLabel: string;
|
|
10
|
+
collapsed?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
title = "",
|
|
17
|
+
sectionKey = "",
|
|
18
|
+
listAriaLabel,
|
|
19
|
+
collapsed = false,
|
|
20
|
+
className = "",
|
|
21
|
+
children,
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
const trimmedTitle = $derived(title.trim());
|
|
25
|
+
const headingId = $derived(
|
|
26
|
+
trimmedTitle
|
|
27
|
+
? `sidebar-section-${sectionKey || "group"}`
|
|
28
|
+
: undefined,
|
|
29
|
+
);
|
|
30
|
+
const showHeading = $derived(Boolean(trimmedTitle) && !collapsed);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class={`flex w-full flex-col gap-1.5 ${className}`.trim()}>
|
|
34
|
+
{#if showHeading && headingId}
|
|
35
|
+
<h2
|
|
36
|
+
class="px-0 text-[10px] font-semibold uppercase tracking-[0.12em] text-description"
|
|
37
|
+
id={headingId}
|
|
38
|
+
>
|
|
39
|
+
{trimmedTitle}
|
|
40
|
+
</h2>
|
|
41
|
+
{/if}
|
|
42
|
+
<ul
|
|
43
|
+
class="flex w-full flex-col gap-0.5 px-0"
|
|
44
|
+
aria-labelledby={showHeading && headingId ? headingId : undefined}
|
|
45
|
+
aria-label={showHeading ? undefined : listAriaLabel}
|
|
46
|
+
>
|
|
47
|
+
{@render children()}
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
title?: string;
|
|
4
|
+
/** Disambiguates generated ids when section titles repeat. */
|
|
5
|
+
sectionKey?: string;
|
|
6
|
+
/** `aria-label` on the `<ul>` (required when title is hidden in collapsed mode). */
|
|
7
|
+
listAriaLabel: string;
|
|
8
|
+
collapsed?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
}
|
|
12
|
+
declare const SidebarNavSection: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type SidebarNavSection = ReturnType<typeof SidebarNavSection>;
|
|
14
|
+
export default SidebarNavSection;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
returnFocus,
|
|
5
5
|
saveFocus,
|
|
6
6
|
trapFocus,
|
|
7
|
-
} from
|
|
7
|
+
} from '../util/focus-utils.js';
|
|
8
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
isOpen?: boolean;
|
|
@@ -14,12 +15,16 @@
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
let {
|
|
17
|
-
isOpen = false,
|
|
18
|
-
title =
|
|
18
|
+
isOpen = $bindable(false),
|
|
19
|
+
title = '',
|
|
20
|
+
onclick,
|
|
21
|
+
onkeydown,
|
|
19
22
|
children,
|
|
20
23
|
...restProps
|
|
21
24
|
} = $props<Props & { children?: any }>();
|
|
22
25
|
|
|
26
|
+
const slideTitleId = generateId('slideup-title');
|
|
27
|
+
|
|
23
28
|
let slideUpContainer = $state<HTMLDivElement>();
|
|
24
29
|
let cleanupFocusTrap: (() => void) | null = null;
|
|
25
30
|
|
|
@@ -31,7 +36,7 @@
|
|
|
31
36
|
}
|
|
32
37
|
returnFocus();
|
|
33
38
|
if (onclick && event) {
|
|
34
|
-
|
|
39
|
+
onclick(event);
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -46,6 +51,7 @@
|
|
|
46
51
|
} else if (!isOpen && cleanupFocusTrap) {
|
|
47
52
|
cleanupFocusTrap();
|
|
48
53
|
cleanupFocusTrap = null;
|
|
54
|
+
returnFocus();
|
|
49
55
|
}
|
|
50
56
|
});
|
|
51
57
|
|
|
@@ -57,12 +63,10 @@
|
|
|
57
63
|
|
|
58
64
|
function handleKeydown(event: Event) {
|
|
59
65
|
const keyboardEvent = event as KeyboardEvent;
|
|
60
|
-
if (keyboardEvent.key ===
|
|
66
|
+
if (keyboardEvent.key === 'Escape') {
|
|
61
67
|
closeSlideUp(event);
|
|
62
68
|
}
|
|
63
|
-
|
|
64
|
-
(onkeydown as (event: Event) => void)(event);
|
|
65
|
-
}
|
|
69
|
+
onkeydown?.(event);
|
|
66
70
|
}
|
|
67
71
|
</script>
|
|
68
72
|
|
|
@@ -73,25 +77,26 @@
|
|
|
73
77
|
onkeydown={handleKeydown}
|
|
74
78
|
role="dialog"
|
|
75
79
|
aria-modal="true"
|
|
76
|
-
aria-labelledby={title ?
|
|
80
|
+
aria-labelledby={title ? slideTitleId : undefined}
|
|
77
81
|
tabindex="-1"
|
|
82
|
+
{...restProps}
|
|
78
83
|
>
|
|
79
84
|
<div
|
|
80
85
|
bind:this={slideUpContainer}
|
|
81
86
|
class="fixed bottom-0 left-0 right-0 z-modal flex max-h-[90vh] cursor-default flex-col overflow-y-auto rounded-t-3xl bg-card shadow-xl animate-[slideUp_0.3s_ease-out]"
|
|
82
87
|
>
|
|
83
88
|
{#if title}
|
|
84
|
-
<div class="flex items-center justify-between px-6 pt-6
|
|
89
|
+
<div class="flex items-center justify-between px-6 pb-4 pt-6">
|
|
85
90
|
<h2
|
|
86
|
-
id=
|
|
87
|
-
class="text-2xl font-normal leading-8 text-headline
|
|
91
|
+
id={slideTitleId}
|
|
92
|
+
class="text-2xl font-normal leading-8 tracking-normal text-headline"
|
|
88
93
|
>
|
|
89
94
|
{title}
|
|
90
95
|
</h2>
|
|
91
96
|
<button
|
|
92
97
|
type="button"
|
|
93
98
|
onclick={closeSlideUp}
|
|
94
|
-
class="
|
|
99
|
+
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-2xl text-description transition-colors hover:bg-base-100 hover:text-headline"
|
|
95
100
|
aria-label="Close"
|
|
96
101
|
>
|
|
97
102
|
×
|
|
@@ -99,7 +104,7 @@
|
|
|
99
104
|
</div>
|
|
100
105
|
{/if}
|
|
101
106
|
|
|
102
|
-
<div class="px-6 pb-6
|
|
107
|
+
<div class="flex-1 px-6 pb-6">
|
|
103
108
|
{@render children?.()}
|
|
104
109
|
</div>
|
|
105
110
|
</div>
|
|
@@ -7,6 +7,6 @@ interface Props {
|
|
|
7
7
|
type $$ComponentProps = Props & {
|
|
8
8
|
children?: any;
|
|
9
9
|
};
|
|
10
|
-
declare const SlideUp: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
declare const SlideUp: import("svelte").Component<$$ComponentProps, {}, "isOpen">;
|
|
11
11
|
type SlideUp = ReturnType<typeof SlideUp>;
|
|
12
12
|
export default SlideUp;
|