zabi-components 7.0.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -3
- package/THEMING.md +255 -15
- package/dist/atoms/ActionPanel.svelte +12 -7
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Badge.svelte +93 -56
- package/dist/atoms/Badge.svelte.d.ts +15 -3
- package/dist/atoms/Button.svelte +49 -49
- package/dist/atoms/Button.svelte.d.ts +3 -0
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +10 -2
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +4 -0
- package/dist/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/atoms/CodeBlock.svelte +15 -7
- package/dist/atoms/CodeBlock.svelte.d.ts +9 -3
- package/dist/atoms/ColorPicker.svelte +19 -9
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -1
- package/dist/atoms/Container.svelte +2 -3
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- package/dist/atoms/Heading.svelte +56 -27
- package/dist/atoms/Heading.svelte.d.ts +17 -4
- package/dist/atoms/IconButton.svelte +30 -37
- package/dist/atoms/Input.svelte +26 -34
- package/dist/atoms/List.svelte +10 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +2 -2
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +4 -1
- package/dist/atoms/Progress.svelte.d.ts +2 -0
- package/dist/atoms/Radio.svelte +4 -0
- package/dist/atoms/Radio.svelte.d.ts +2 -0
- package/dist/atoms/Select.svelte +65 -67
- package/dist/atoms/Select.svelte.d.ts +4 -0
- package/dist/atoms/SelectionControl.svelte +5 -1
- package/dist/atoms/SelectionControl.svelte.d.ts +2 -0
- package/dist/atoms/Skeleton.svelte +5 -3
- package/dist/atoms/Table.svelte +2 -1
- package/dist/atoms/Text.svelte +34 -5
- package/dist/atoms/Text.svelte.d.ts +7 -1
- package/dist/atoms/Textarea.svelte +7 -6
- package/dist/atoms/ThemeToggle.svelte +17 -14
- package/dist/atoms/ThemeToggle.svelte.d.ts +2 -0
- package/dist/atoms/Toast.svelte +10 -8
- package/dist/atoms/Toggle.svelte +21 -13
- package/dist/atoms/Toggle.svelte.d.ts +7 -0
- package/dist/atoms/Tooltip.svelte +80 -22
- package/dist/atoms/Tooltip.svelte.d.ts +15 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +1 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/util/cn.d.ts +8 -0
- package/dist/components/util/cn.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -6
- package/dist/molecules/Alert.svelte +43 -64
- package/dist/molecules/Alert.svelte.d.ts +7 -1
- package/dist/molecules/ComponentDemo.svelte +10 -2
- package/dist/molecules/ComponentDemo.svelte.d.ts +2 -0
- package/dist/molecules/ContactForm.svelte +12 -2
- package/dist/molecules/ContactForm.svelte.d.ts +2 -0
- package/dist/molecules/Dropdown.svelte +40 -11
- package/dist/molecules/Dropdown.svelte.d.ts +4 -0
- package/dist/molecules/EmptyState.svelte +2 -1
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +2 -1
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +54 -15
- package/dist/molecules/ImageUpload.svelte.d.ts +16 -5
- package/dist/molecules/Modal.svelte +110 -31
- package/dist/molecules/Modal.svelte.d.ts +9 -6
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +36 -38
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -2
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +10 -2
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +8 -2
- package/dist/molecules/Page.svelte.d.ts +2 -0
- package/dist/molecules/PropsTable.svelte +6 -3
- package/dist/molecules/PropsTable.svelte.d.ts +2 -0
- package/dist/molecules/RadioGroup.svelte +5 -1
- package/dist/molecules/RadioGroup.svelte.d.ts +2 -0
- package/dist/molecules/Section.svelte +12 -4
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SidebarBrandHeader.svelte +13 -4
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +2 -0
- package/dist/molecules/SidebarFooter.svelte +12 -4
- package/dist/molecules/SidebarFooter.svelte.d.ts +2 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +85 -21
- package/dist/molecules/SlideUp.svelte.d.ts +5 -4
- package/dist/molecules/Tabs.svelte +25 -19
- package/dist/molecules/Tabs.svelte.d.ts +8 -4
- package/dist/molecules/Toaster.svelte +3 -2
- package/dist/molecules/ToasterToast.svelte +41 -10
- package/dist/molecules/ToasterToast.svelte.d.ts +2 -0
- package/dist/organisms/SidebarAccountPanel.svelte +4 -0
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarNavigation.svelte +102 -59
- package/dist/organisms/SidebarNavigation.svelte.d.ts +2 -0
- package/dist/organisms/SidebarPanel.svelte +19 -11
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +112 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +52 -0
- package/dist/organisms/TopNavbar.svelte +88 -17
- package/dist/organisms/TopNavbar.svelte.d.ts +2 -0
- package/dist/organisms/index.d.ts +1 -0
- package/dist/organisms/index.js +1 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +3 -0
- package/dist/util/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -0
- package/dist/util/ssr-safe.d.ts +4 -4
- package/dist/zabi-components-colors.css +2 -2
- package/dist/zabi-components-theme-dark-only.css +139 -92
- package/dist/zabi-components-theme-dark.css +139 -92
- package/dist/zabi-components-theme-only.css +268 -122
- package/dist/zabi-components-theme.css +268 -122
- package/dist/zabi-components.css +831 -883
- package/docs/lucide-icons.md +3 -1
- package/package.json +19 -24
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
import { cn } from "../util/cn.js";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
children?: Snippet;
|
|
7
|
+
class?: string;
|
|
8
|
+
/** @deprecated use `class`. */
|
|
6
9
|
className?: string;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
let { children, className = "" }: Props = $props();
|
|
12
|
+
let { children, class: classAttr = "", className: legacyClass = "" }: Props = $props();
|
|
13
|
+
|
|
14
|
+
/** `class` is the public prop; `className` is a deprecated alias. */
|
|
15
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
10
16
|
</script>
|
|
11
17
|
|
|
12
|
-
<div class="mx-auto w-full space-y-10
|
|
18
|
+
<div class={cn("mx-auto w-full space-y-10", className)}>
|
|
13
19
|
{#if children}
|
|
14
20
|
{@render children()}
|
|
15
21
|
{/if}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ComponentProp } from "../types/page.types";
|
|
3
3
|
import Table from "../atoms/Table.svelte";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
7
|
+
/** Extra classes for the host element. */
|
|
8
|
+
class?: string;
|
|
6
9
|
props: ComponentProp[];
|
|
7
10
|
caption?: string;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
let { props, caption = "Props / API" }: Props = $props();
|
|
13
|
+
let { class: className = "", props, caption = "Props / API" }: Props = $props();
|
|
11
14
|
</script>
|
|
12
15
|
|
|
13
16
|
{#if props.length === 0}
|
|
14
|
-
<div class="rounded-
|
|
17
|
+
<div class={cn("rounded-control border border-border bg-base-50 px-4 py-4 text-sm text-description", className)}>
|
|
15
18
|
No documented props.
|
|
16
19
|
</div>
|
|
17
20
|
{:else}
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
</thead>
|
|
38
41
|
<tbody>
|
|
39
42
|
{#each props as prop}
|
|
40
|
-
<tr class="hover:bg-
|
|
43
|
+
<tr class="hover:bg-surface-hover">
|
|
41
44
|
<td class="border-b border-border px-4 py-3 font-mono text-body">
|
|
42
45
|
{prop.name}
|
|
43
46
|
</td>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
} from "../atoms/selection-control.styles";
|
|
8
8
|
import { generateId } from "../util/ssr-safe.js";
|
|
9
9
|
import { SvelteMap } from "svelte/reactivity";
|
|
10
|
+
import { cn } from "../util/cn.js";
|
|
10
11
|
|
|
11
12
|
export type RadioGroupOption = {
|
|
12
13
|
value: string;
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
interface Props {
|
|
20
|
+
/** Extra classes for the host element. */
|
|
21
|
+
class?: string;
|
|
19
22
|
options: RadioGroupOption[];
|
|
20
23
|
/** Selected value; `bind:value`. Use `undefined` for no selection. */
|
|
21
24
|
value?: string | undefined;
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
let {
|
|
34
|
+
class: className = "",
|
|
31
35
|
options,
|
|
32
36
|
defaultValue,
|
|
33
37
|
value = $bindable(defaultValue),
|
|
@@ -150,7 +154,7 @@
|
|
|
150
154
|
</script>
|
|
151
155
|
|
|
152
156
|
<fieldset
|
|
153
|
-
class="space-y-3"
|
|
157
|
+
class={cn("space-y-3", className)}
|
|
154
158
|
aria-disabled={disabled ? "true" : undefined}
|
|
155
159
|
onkeydown={handleKeydown}
|
|
156
160
|
{...restProps}
|
|
@@ -5,6 +5,8 @@ export type RadioGroupOption = {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
};
|
|
7
7
|
interface Props {
|
|
8
|
+
/** Extra classes for the host element. */
|
|
9
|
+
class?: string;
|
|
8
10
|
options: RadioGroupOption[];
|
|
9
11
|
/** Selected value; `bind:value`. Use `undefined` for no selection. */
|
|
10
12
|
value?: string | undefined;
|
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { SizeVariant } from "../types/variants.js";
|
|
4
4
|
import Heading from "../atoms/Heading.svelte";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
title?: string;
|
|
8
9
|
description?: string;
|
|
9
10
|
/** Title heading level (1–6). */
|
|
10
|
-
headingLevel?:
|
|
11
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
11
12
|
size?: SizeVariant;
|
|
12
13
|
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
|
|
13
14
|
background?: "default" | "muted" | "accent" | "transparent";
|
|
14
15
|
padding?: "none" | "sm" | "md" | "lg" | "xl";
|
|
15
16
|
centered?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
/** @deprecated use `class`. */
|
|
16
19
|
className?: string;
|
|
17
20
|
children?: Snippet;
|
|
18
21
|
}
|
|
@@ -26,11 +29,16 @@
|
|
|
26
29
|
background = "default",
|
|
27
30
|
padding = "lg",
|
|
28
31
|
centered = false,
|
|
29
|
-
|
|
32
|
+
class: classAttr = "",
|
|
33
|
+
className: legacyClass = "",
|
|
30
34
|
children,
|
|
31
35
|
...restProps
|
|
32
36
|
}: Props = $props();
|
|
33
37
|
|
|
38
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
39
|
+
* Both are merged here so existing call sites keep working. */
|
|
40
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
41
|
+
|
|
34
42
|
const sizeClasses = $derived.by(() => {
|
|
35
43
|
switch (size) {
|
|
36
44
|
case "sm":
|
|
@@ -97,13 +105,13 @@
|
|
|
97
105
|
const base = "w-full";
|
|
98
106
|
const maxWidthClass = maxWidthClasses;
|
|
99
107
|
const centeredClass = centered ? "mx-auto" : "";
|
|
100
|
-
return `${base} ${maxWidthClass} ${centeredClass}
|
|
108
|
+
return cn(`${base} ${maxWidthClass} ${centeredClass}`);
|
|
101
109
|
});
|
|
102
110
|
|
|
103
111
|
const contentClasses = $derived.by(() => {
|
|
104
112
|
const base = "px-4 sm:px-6 lg:px-8";
|
|
105
113
|
const centeredClass = centered ? "text-center" : "";
|
|
106
|
-
return `${base} ${centeredClass}
|
|
114
|
+
return cn(`${base} ${centeredClass}`);
|
|
107
115
|
});
|
|
108
116
|
</script>
|
|
109
117
|
|
|
@@ -4,12 +4,14 @@ interface Props {
|
|
|
4
4
|
title?: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
/** Title heading level (1–6). */
|
|
7
|
-
headingLevel?:
|
|
7
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
8
8
|
size?: SizeVariant;
|
|
9
9
|
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
|
|
10
10
|
background?: "default" | "muted" | "accent" | "transparent";
|
|
11
11
|
padding?: "none" | "sm" | "md" | "lg" | "xl";
|
|
12
12
|
centered?: boolean;
|
|
13
|
+
class?: string;
|
|
14
|
+
/** @deprecated use `class`. */
|
|
13
15
|
className?: string;
|
|
14
16
|
children?: Snippet;
|
|
15
17
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { cn } from "../util/cn.js";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
collapsed?: boolean;
|
|
4
6
|
brandName?: string;
|
|
5
7
|
logoSrc?: string;
|
|
6
8
|
logoAlt?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
/** @deprecated use `class`. */
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -12,9 +16,14 @@
|
|
|
12
16
|
brandName = "",
|
|
13
17
|
logoSrc = "",
|
|
14
18
|
logoAlt = "",
|
|
15
|
-
|
|
19
|
+
class: classAttr = "",
|
|
20
|
+
className: legacyClass = "",
|
|
16
21
|
}: Props = $props();
|
|
17
22
|
|
|
23
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
24
|
+
* Both are merged here so existing call sites keep working. */
|
|
25
|
+
const className = $derived(`${classAttr} ${legacyClass}`.trim());
|
|
26
|
+
|
|
18
27
|
const trimmedName = $derived(brandName.trim());
|
|
19
28
|
const monogram = $derived(
|
|
20
29
|
trimmedName
|
|
@@ -35,11 +44,11 @@
|
|
|
35
44
|
|
|
36
45
|
{#if showLogo || trimmedName}
|
|
37
46
|
<div
|
|
38
|
-
class={
|
|
47
|
+
class={cn("flex w-full min-w-0 items-center gap-3", className)}
|
|
39
48
|
>
|
|
40
49
|
{#if showLogo}
|
|
41
50
|
<span
|
|
42
|
-
class="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-
|
|
51
|
+
class="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-container bg-background ring-1 ring-border"
|
|
43
52
|
>
|
|
44
53
|
<img
|
|
45
54
|
src={logoSrc}
|
|
@@ -51,7 +60,7 @@
|
|
|
51
60
|
</span>
|
|
52
61
|
{:else if showMonogram}
|
|
53
62
|
<span
|
|
54
|
-
class="flex size-9 shrink-0 items-center justify-center rounded-
|
|
63
|
+
class="flex size-9 shrink-0 items-center justify-center rounded-container bg-action-primary-subtle text-xs font-bold text-headline ring-1 ring-border"
|
|
55
64
|
aria-hidden="true"
|
|
56
65
|
>
|
|
57
66
|
{monogram}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import { fixedSidebarFlyout } from "../util/fixed-sidebar-flyout.js";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
collapsed?: boolean;
|
|
@@ -15,6 +16,8 @@
|
|
|
15
16
|
isLightMode?: boolean;
|
|
16
17
|
onLogout?: () => void;
|
|
17
18
|
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
19
|
+
class?: string;
|
|
20
|
+
/** @deprecated use `class`. */
|
|
18
21
|
className?: string;
|
|
19
22
|
profilePanelOpen?: boolean;
|
|
20
23
|
profilePanelControlsId?: string;
|
|
@@ -35,18 +38,23 @@
|
|
|
35
38
|
isLightMode = $bindable(false),
|
|
36
39
|
onLogout,
|
|
37
40
|
onThemeToggle,
|
|
38
|
-
|
|
41
|
+
class: classAttr = "",
|
|
42
|
+
className: legacyClass = "",
|
|
39
43
|
profilePanelOpen = false,
|
|
40
44
|
profilePanelControlsId = "",
|
|
41
45
|
onProfileClick,
|
|
42
46
|
profilePanel,
|
|
43
47
|
}: Props = $props();
|
|
44
48
|
|
|
49
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
50
|
+
* Both are merged here so existing call sites keep working. */
|
|
51
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
52
|
+
|
|
45
53
|
const showFooter = $derived(showProfile || showLogout || showThemeToggle);
|
|
46
54
|
const showPanelLauncher = $derived(showProfile);
|
|
47
55
|
|
|
48
56
|
const avatarClasses =
|
|
49
|
-
"size-10 rounded-
|
|
57
|
+
"size-10 rounded-container bg-action-primary text-action-primary flex items-center justify-center text-sm font-semibold shrink-0 ring-1 ring-border-focus";
|
|
50
58
|
|
|
51
59
|
function getTextToneClass(isMuted = false): string {
|
|
52
60
|
return isMuted ? "text-description" : "text-headline";
|
|
@@ -60,14 +68,14 @@
|
|
|
60
68
|
{#if showFooter}
|
|
61
69
|
<div class="w-full shrink-0" data-sidebar-flyout-root>
|
|
62
70
|
<footer
|
|
63
|
-
class={`flex w-full shrink-0 flex-col gap-
|
|
71
|
+
class={cn(`flex w-full shrink-0 flex-col gap-3 border-t border-border pt-3 pb-1 ${className}`)}
|
|
64
72
|
aria-label="Account and settings"
|
|
65
73
|
>
|
|
66
74
|
{#if showPanelLauncher}
|
|
67
75
|
<button
|
|
68
76
|
type="button"
|
|
69
77
|
data-sidebar-flyout-anchor="profile"
|
|
70
|
-
class="w-full cursor-pointer rounded-
|
|
78
|
+
class="w-full cursor-pointer rounded-container 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-surface-active focus-ring focus-ring--nav"
|
|
71
79
|
aria-haspopup="dialog"
|
|
72
80
|
aria-expanded={profilePanelOpen}
|
|
73
81
|
aria-controls={profilePanelControlsId.trim()
|
|
@@ -12,6 +12,8 @@ interface Props {
|
|
|
12
12
|
isLightMode?: boolean;
|
|
13
13
|
onLogout?: () => void;
|
|
14
14
|
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
15
|
+
class?: string;
|
|
16
|
+
/** @deprecated use `class`. */
|
|
15
17
|
className?: string;
|
|
16
18
|
profilePanelOpen?: boolean;
|
|
17
19
|
profilePanelControlsId?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
import { cn } from "../util/cn.js";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
title?: string;
|
|
@@ -8,6 +9,8 @@
|
|
|
8
9
|
/** `aria-label` on the `<ul>` (required when title is hidden in collapsed mode). */
|
|
9
10
|
listAriaLabel: string;
|
|
10
11
|
collapsed?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
/** @deprecated use `class`. */
|
|
11
14
|
className?: string;
|
|
12
15
|
children: Snippet;
|
|
13
16
|
}
|
|
@@ -17,10 +20,15 @@
|
|
|
17
20
|
sectionKey = "",
|
|
18
21
|
listAriaLabel,
|
|
19
22
|
collapsed = false,
|
|
20
|
-
|
|
23
|
+
class: classAttr = "",
|
|
24
|
+
className: legacyClass = "",
|
|
21
25
|
children,
|
|
22
26
|
}: Props = $props();
|
|
23
27
|
|
|
28
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
29
|
+
* Both are merged here so existing call sites keep working. */
|
|
30
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
31
|
+
|
|
24
32
|
const trimmedTitle = $derived(title.trim());
|
|
25
33
|
const headingId = $derived(
|
|
26
34
|
trimmedTitle
|
|
@@ -30,17 +38,17 @@
|
|
|
30
38
|
const showHeading = $derived(Boolean(trimmedTitle) && !collapsed);
|
|
31
39
|
</script>
|
|
32
40
|
|
|
33
|
-
<div class={`flex w-full flex-col gap-
|
|
41
|
+
<div class={cn(`flex w-full flex-col gap-2 ${className}`)}>
|
|
34
42
|
{#if showHeading && headingId}
|
|
35
43
|
<h2
|
|
36
|
-
class="px-0 text-
|
|
44
|
+
class="px-0 text-xs font-semibold uppercase tracking-wider text-description"
|
|
37
45
|
id={headingId}
|
|
38
46
|
>
|
|
39
47
|
{trimmedTitle}
|
|
40
48
|
</h2>
|
|
41
49
|
{/if}
|
|
42
50
|
<ul
|
|
43
|
-
class="flex w-full flex-col gap-
|
|
51
|
+
class="flex w-full flex-col gap-1 px-0"
|
|
44
52
|
aria-labelledby={showHeading && headingId ? headingId : undefined}
|
|
45
53
|
aria-label={showHeading ? undefined : listAriaLabel}
|
|
46
54
|
>
|
|
@@ -1,40 +1,66 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import {
|
|
3
4
|
focusFirstElement,
|
|
5
|
+
getFocusableElements,
|
|
4
6
|
returnFocus,
|
|
5
7
|
saveFocus,
|
|
6
|
-
trapFocus,
|
|
7
8
|
} from '../util/focus-utils.js';
|
|
8
9
|
import { generateId } from "../util/ssr-safe.js";
|
|
10
|
+
import { cn } from "../util/cn.js";
|
|
9
11
|
|
|
10
12
|
interface Props {
|
|
13
|
+
/** Extra classes for the host element. */
|
|
14
|
+
class?: string;
|
|
11
15
|
isOpen?: boolean;
|
|
12
16
|
title?: string;
|
|
13
17
|
onclick?: (event: Event) => void;
|
|
14
18
|
onkeydown?: (event: Event) => void;
|
|
19
|
+
children?: Snippet;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
let {
|
|
23
|
+
class: className = "",
|
|
18
24
|
isOpen = $bindable(false),
|
|
19
25
|
title = '',
|
|
20
26
|
onclick,
|
|
21
27
|
onkeydown,
|
|
22
28
|
children,
|
|
23
29
|
...restProps
|
|
24
|
-
} = $props
|
|
30
|
+
}: Props = $props();
|
|
25
31
|
|
|
26
32
|
const slideTitleId = generateId('slideup-title');
|
|
27
33
|
|
|
28
34
|
let slideUpContainer = $state<HTMLDivElement>();
|
|
29
|
-
let
|
|
35
|
+
let focusActive = false;
|
|
36
|
+
|
|
37
|
+
/** Ref-counted on `<body>`; shares the counter with Modal so nested overlays unlock once. */
|
|
38
|
+
function lockBodyScroll(): () => void {
|
|
39
|
+
const body = document.body;
|
|
40
|
+
const count = Number(body.dataset.zabiScrollLock ?? '0');
|
|
41
|
+
if (count === 0) {
|
|
42
|
+
body.dataset.zabiScrollLockOverflow = body.style.overflow;
|
|
43
|
+
body.style.overflow = 'hidden';
|
|
44
|
+
}
|
|
45
|
+
body.dataset.zabiScrollLock = String(count + 1);
|
|
46
|
+
return () => {
|
|
47
|
+
const next = Number(body.dataset.zabiScrollLock ?? '1') - 1;
|
|
48
|
+
if (next <= 0) {
|
|
49
|
+
body.style.overflow = body.dataset.zabiScrollLockOverflow ?? '';
|
|
50
|
+
delete body.dataset.zabiScrollLock;
|
|
51
|
+
delete body.dataset.zabiScrollLockOverflow;
|
|
52
|
+
} else {
|
|
53
|
+
body.dataset.zabiScrollLock = String(next);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
30
57
|
|
|
31
58
|
function closeSlideUp(event?: Event) {
|
|
32
59
|
isOpen = false;
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
if (focusActive) {
|
|
61
|
+
focusActive = false;
|
|
62
|
+
returnFocus();
|
|
36
63
|
}
|
|
37
|
-
returnFocus();
|
|
38
64
|
if (onclick && event) {
|
|
39
65
|
onclick(event);
|
|
40
66
|
}
|
|
@@ -44,17 +70,51 @@
|
|
|
44
70
|
const container = slideUpContainer;
|
|
45
71
|
if (isOpen && container) {
|
|
46
72
|
saveFocus();
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
focusActive = true;
|
|
74
|
+
const unlockScroll = lockBodyScroll();
|
|
75
|
+
const t = setTimeout(() => {
|
|
49
76
|
focusFirstElement(container);
|
|
50
77
|
}, 0);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
return () => {
|
|
79
|
+
clearTimeout(t);
|
|
80
|
+
unlockScroll();
|
|
81
|
+
if (focusActive) {
|
|
82
|
+
focusActive = false;
|
|
83
|
+
returnFocus();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
55
86
|
}
|
|
56
87
|
});
|
|
57
88
|
|
|
89
|
+
/** Re-queries focusables on every Tab so late-rendered content stays inside the trap. */
|
|
90
|
+
function handleTrapKeydown(event: KeyboardEvent) {
|
|
91
|
+
const container = slideUpContainer;
|
|
92
|
+
if (event.key !== 'Tab' || !container) return;
|
|
93
|
+
const owner = (event.target as Element | null)?.closest?.('[role="dialog"]');
|
|
94
|
+
if (owner && owner !== container) return;
|
|
95
|
+
|
|
96
|
+
const focusable = getFocusableElements(container);
|
|
97
|
+
if (focusable.length === 0) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
container.focus();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const first = focusable[0];
|
|
103
|
+
const last = focusable[focusable.length - 1];
|
|
104
|
+
const active = document.activeElement as HTMLElement | null;
|
|
105
|
+
|
|
106
|
+
if (!active || !focusable.includes(active)) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
first.focus();
|
|
109
|
+
} else if (event.shiftKey && active === first) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
last.focus();
|
|
112
|
+
} else if (!event.shiftKey && active === last) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
first.focus();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
function handleBackdropClick(event: Event) {
|
|
59
119
|
if (event.target === event.currentTarget) {
|
|
60
120
|
closeSlideUp(event);
|
|
@@ -63,7 +123,8 @@
|
|
|
63
123
|
|
|
64
124
|
function handleKeydown(event: Event) {
|
|
65
125
|
const keyboardEvent = event as KeyboardEvent;
|
|
66
|
-
if (keyboardEvent.key === 'Escape') {
|
|
126
|
+
if (keyboardEvent.key === 'Escape' && !keyboardEvent.defaultPrevented) {
|
|
127
|
+
keyboardEvent.preventDefault();
|
|
67
128
|
closeSlideUp(event);
|
|
68
129
|
}
|
|
69
130
|
onkeydown?.(event);
|
|
@@ -71,19 +132,22 @@
|
|
|
71
132
|
</script>
|
|
72
133
|
|
|
73
134
|
{#if isOpen}
|
|
135
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
74
136
|
<div
|
|
75
137
|
class="fixed inset-0 z-modal cursor-pointer bg-overlay"
|
|
76
138
|
onclick={handleBackdropClick}
|
|
77
139
|
onkeydown={handleKeydown}
|
|
78
|
-
role="
|
|
79
|
-
aria-modal="true"
|
|
80
|
-
aria-labelledby={title ? slideTitleId : undefined}
|
|
81
|
-
tabindex="-1"
|
|
82
|
-
{...restProps}
|
|
140
|
+
role="presentation"
|
|
83
141
|
>
|
|
84
142
|
<div
|
|
85
143
|
bind:this={slideUpContainer}
|
|
86
|
-
class="fixed bottom-0 left-0 right-0 z-modal flex max-h-[90vh] cursor-default flex-col overflow-y-auto rounded-t-
|
|
144
|
+
class={cn("fixed bottom-0 left-0 right-0 z-modal flex max-h-[90vh] cursor-default flex-col overflow-y-auto rounded-t-overlay border-t border-border-overlay bg-surface-overlay shadow-lg animate-[slideUp_0.3s_ease-out]", className)}
|
|
145
|
+
role="dialog"
|
|
146
|
+
aria-modal="true"
|
|
147
|
+
aria-labelledby={title ? slideTitleId : undefined}
|
|
148
|
+
tabindex="-1"
|
|
149
|
+
onkeydown={handleTrapKeydown}
|
|
150
|
+
{...restProps}
|
|
87
151
|
>
|
|
88
152
|
{#if title}
|
|
89
153
|
<div class="flex items-center justify-between px-6 pb-4 pt-6">
|
|
@@ -96,7 +160,7 @@
|
|
|
96
160
|
<button
|
|
97
161
|
type="button"
|
|
98
162
|
onclick={closeSlideUp}
|
|
99
|
-
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-2xl text-description transition-colors hover:bg-
|
|
163
|
+
class="focus-ring flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-2xl text-description transition-colors hover:bg-surface-overlay-hover hover:text-headline"
|
|
100
164
|
aria-label="Close"
|
|
101
165
|
>
|
|
102
166
|
×
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
interface Props {
|
|
3
|
+
/** Extra classes for the host element. */
|
|
4
|
+
class?: string;
|
|
2
5
|
isOpen?: boolean;
|
|
3
6
|
title?: string;
|
|
4
7
|
onclick?: (event: Event) => void;
|
|
5
8
|
onkeydown?: (event: Event) => void;
|
|
9
|
+
children?: Snippet;
|
|
6
10
|
}
|
|
7
|
-
|
|
8
|
-
children?: any;
|
|
9
|
-
};
|
|
10
|
-
declare const SlideUp: import("svelte").Component<$$ComponentProps, {}, "isOpen">;
|
|
11
|
+
declare const SlideUp: import("svelte").Component<Props, {}, "isOpen">;
|
|
11
12
|
type SlideUp = ReturnType<typeof SlideUp>;
|
|
12
13
|
export default SlideUp;
|