zabi-components 5.0.22 → 7.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 +139 -1478
- package/THEME.md +473 -0
- package/THEMING.md +248 -0
- 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 +35 -16
- package/dist/atoms/Button.svelte.d.ts +3 -1
- package/dist/atoms/Card.svelte +49 -67
- package/dist/atoms/Card.svelte.d.ts +4 -4
- package/dist/atoms/CardHeader.svelte +15 -2
- package/dist/atoms/CardHeader.svelte.d.ts +5 -0
- 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 +3 -7
- 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 +33 -24
- package/dist/atoms/IconButton.svelte.d.ts +4 -8
- 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 +2 -2
- package/dist/atoms/Radio.svelte +51 -0
- package/dist/atoms/Radio.svelte.d.ts +14 -0
- package/dist/atoms/Select.svelte +28 -33
- 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 +90 -11
- 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 +42 -7
- 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 +3 -3
- 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 +42 -7
- package/dist/index.js +45 -10
- 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 +3 -0
- package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts +89 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
- package/dist/molecules/Alert.svelte +50 -23
- package/dist/molecules/Alert.svelte.d.ts +1 -1
- package/dist/molecules/ComponentDemo.svelte +54 -51
- package/dist/molecules/ContactForm.svelte +70 -65
- package/dist/molecules/Dropdown.svelte +99 -108
- 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/ImageUpload.svelte +1 -1
- package/dist/molecules/Modal.svelte +39 -26
- 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 -4
- 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 +14 -23
- 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 +21 -16
- package/dist/molecules/SlideUp.svelte.d.ts +1 -1
- package/dist/molecules/Tabs.svelte +6 -11
- 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 +286 -212
- package/dist/organisms/SidebarNavigation.svelte.d.ts +22 -1
- package/dist/organisms/{SidebarProjectPanel.svelte → SidebarPanel.svelte} +67 -34
- package/dist/organisms/{SidebarProjectPanel.svelte.d.ts → SidebarPanel.svelte.d.ts} +9 -6
- package/dist/organisms/TopNavbar.svelte +214 -0
- package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
- package/dist/organisms/index.d.ts +3 -3
- package/dist/organisms/index.js +3 -3
- 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 +3 -0
- package/dist/types/page.types.d.ts.map +1 -0
- 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 +75 -0
- 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 +360 -324
- package/dist/zabi-components-theme-dark-only.css +164 -151
- package/dist/zabi-components-theme-dark.css +164 -151
- package/dist/zabi-components-theme-only.css +253 -230
- package/dist/zabi-components-theme.css +253 -230
- package/dist/zabi-components.css +1262 -241
- package/docs/theme-imports.md +112 -0
- package/package.json +32 -10
- package/dist/organisms/Navbar.svelte +0 -92
- package/dist/organisms/Navbar.svelte.d.ts +0 -14
- package/dist/organisms/Navigation.svelte +0 -119
- package/dist/organisms/Navigation.svelte.d.ts +0 -21
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
1
2
|
import type { Component } from "svelte";
|
|
3
|
+
import type { ButtonVariant, SizeVariant } from "../types/variants.js";
|
|
2
4
|
export interface SidebarNavigationItem {
|
|
3
5
|
id: string;
|
|
4
6
|
label: string;
|
|
@@ -10,13 +12,19 @@ export interface SidebarNavigationItem {
|
|
|
10
12
|
badgeText?: string | number;
|
|
11
13
|
badgeCount?: number;
|
|
12
14
|
group?: "primary" | "secondary";
|
|
15
|
+
/** Groups items under a section heading in the rail. */
|
|
16
|
+
section?: string;
|
|
13
17
|
}
|
|
14
18
|
interface Props {
|
|
15
19
|
mode?: "expanded" | "collapsed";
|
|
20
|
+
layout?: "rail" | "card";
|
|
16
21
|
items?: SidebarNavigationItem[];
|
|
17
22
|
currentPath?: string;
|
|
18
23
|
ariaLabel?: string;
|
|
19
24
|
className?: string;
|
|
25
|
+
logoSrc?: string;
|
|
26
|
+
logoAlt?: string;
|
|
27
|
+
brandName?: string;
|
|
20
28
|
showProfile?: boolean;
|
|
21
29
|
profileName?: string;
|
|
22
30
|
profileEmail?: string;
|
|
@@ -25,6 +33,13 @@ interface Props {
|
|
|
25
33
|
searchMode?: "input" | "button";
|
|
26
34
|
searchPlaceholder?: string;
|
|
27
35
|
searchValue?: string;
|
|
36
|
+
/** Trigger icon when search is a button or rail is collapsed; input mode keeps `Search` on the field. */
|
|
37
|
+
searchTriggerIcon?: Component<{
|
|
38
|
+
size?: number;
|
|
39
|
+
class?: string;
|
|
40
|
+
}>;
|
|
41
|
+
searchTriggerVariant?: ButtonVariant;
|
|
42
|
+
searchTriggerSize?: SizeVariant;
|
|
28
43
|
showLogout?: boolean;
|
|
29
44
|
logoutLabel?: string;
|
|
30
45
|
showThemeToggle?: boolean;
|
|
@@ -38,7 +53,13 @@ interface Props {
|
|
|
38
53
|
onLogout?: () => void;
|
|
39
54
|
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
40
55
|
onEmptyStateAction?: () => void;
|
|
56
|
+
onProfileClick?: (event?: MouseEvent) => void;
|
|
57
|
+
profilePanelOpen?: boolean;
|
|
58
|
+
profilePanelControlsId?: string;
|
|
59
|
+
profilePanel?: Snippet;
|
|
60
|
+
/** Parent nav href to highlight when `currentPath` is a deeper leaf (e.g. category row). */
|
|
61
|
+
activePrimaryHref?: string;
|
|
41
62
|
}
|
|
42
|
-
declare const SidebarNavigation: Component<Props, {}, "
|
|
63
|
+
declare const SidebarNavigation: Component<Props, {}, "isLightMode" | "searchValue">;
|
|
43
64
|
type SidebarNavigation = ReturnType<typeof SidebarNavigation>;
|
|
44
65
|
export default SidebarNavigation;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Search, X } from "@lucide/svelte";
|
|
5
5
|
import type { Component } from "svelte";
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface SidebarPanelItem {
|
|
8
8
|
id: string;
|
|
9
9
|
label: string;
|
|
10
10
|
icon?: Component<{ size?: number; class?: string }>;
|
|
@@ -15,39 +15,45 @@
|
|
|
15
15
|
interface Props {
|
|
16
16
|
className?: string;
|
|
17
17
|
widthClass?: string;
|
|
18
|
+
variant?: "plain" | "elevated";
|
|
18
19
|
ariaLabel?: string;
|
|
19
20
|
title?: string;
|
|
20
21
|
subtitle?: string;
|
|
21
22
|
showSearch?: boolean;
|
|
22
23
|
searchPlaceholder?: string;
|
|
23
24
|
searchValue?: string;
|
|
24
|
-
items?:
|
|
25
|
+
items?: SidebarPanelItem[];
|
|
25
26
|
selectedItemId?: string;
|
|
26
27
|
emptyStateTitle?: string;
|
|
27
28
|
emptyStateDescription?: string;
|
|
29
|
+
emptyStateActionLabel?: string;
|
|
28
30
|
selectLabel?: string;
|
|
29
31
|
closeLabel?: string;
|
|
30
|
-
onSelect?: (item:
|
|
32
|
+
onSelect?: (item: SidebarPanelItem) => void;
|
|
31
33
|
onClose?: () => void;
|
|
34
|
+
onEmptyStateAction?: () => void;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
let {
|
|
35
38
|
className = "",
|
|
36
39
|
widthClass = "w-80",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
variant = "elevated",
|
|
41
|
+
ariaLabel = "Picker panel",
|
|
42
|
+
title = "Items",
|
|
43
|
+
subtitle = "Choose an item to continue",
|
|
40
44
|
showSearch = true,
|
|
41
|
-
searchPlaceholder = "Search
|
|
45
|
+
searchPlaceholder = "Search...",
|
|
42
46
|
searchValue = $bindable(""),
|
|
43
47
|
items = [],
|
|
44
48
|
selectedItemId = $bindable(""),
|
|
45
|
-
emptyStateTitle = "No
|
|
46
|
-
emptyStateDescription = "Try a different keyword
|
|
47
|
-
|
|
49
|
+
emptyStateTitle = "No matches",
|
|
50
|
+
emptyStateDescription = "Try a different keyword.",
|
|
51
|
+
emptyStateActionLabel = "",
|
|
52
|
+
selectLabel = "Select item",
|
|
48
53
|
closeLabel = "Close panel",
|
|
49
54
|
onSelect,
|
|
50
55
|
onClose,
|
|
56
|
+
onEmptyStateAction,
|
|
51
57
|
...restProps
|
|
52
58
|
}: Props = $props();
|
|
53
59
|
|
|
@@ -60,22 +66,27 @@
|
|
|
60
66
|
: items,
|
|
61
67
|
);
|
|
62
68
|
|
|
69
|
+
const isElevated = $derived(variant === "elevated");
|
|
70
|
+
|
|
63
71
|
const containerClasses = $derived.by(() => {
|
|
64
72
|
const resolvedWidthClass = widthClass.trim() || "w-80";
|
|
65
|
-
|
|
73
|
+
const shell = isElevated
|
|
74
|
+
? "rounded-3xl border border-border bg-card text-headline shadow-md ring-1 ring-border/50"
|
|
75
|
+
: "rounded-3xl border border-border bg-card text-headline shadow-sm";
|
|
76
|
+
return `${resolvedWidthClass} shrink-0 p-5 ${shell} ${className}`.trim();
|
|
66
77
|
});
|
|
67
78
|
|
|
68
79
|
function getItemClasses(itemId: string): string {
|
|
69
80
|
const isActive = selectedItemId === itemId;
|
|
70
81
|
const baseClasses =
|
|
71
|
-
"w-full rounded-
|
|
82
|
+
"focus-ring focus-ring--nav w-full cursor-pointer rounded-lg px-3 py-2.5 text-left transition-colors duration-150";
|
|
72
83
|
if (isActive) {
|
|
73
|
-
return `${baseClasses} bg-
|
|
84
|
+
return `${baseClasses} bg-nav-menu-active text-inherit shadow-sm ring-1 ring-border/80`;
|
|
74
85
|
}
|
|
75
|
-
return `${baseClasses} hover:bg-
|
|
86
|
+
return `${baseClasses} text-nav-menu-item hover:bg-nav-menu-hover hover:text-nav-menu-item-hover active:bg-base-200`;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
|
-
function handleSelect(item:
|
|
89
|
+
function handleSelect(item: SidebarPanelItem): void {
|
|
79
90
|
selectedItemId = item.id;
|
|
80
91
|
if (onSelect) {
|
|
81
92
|
onSelect(item);
|
|
@@ -87,65 +98,76 @@
|
|
|
87
98
|
onClose();
|
|
88
99
|
}
|
|
89
100
|
}
|
|
101
|
+
|
|
102
|
+
function handleEmptyStateAction(): void {
|
|
103
|
+
if (onEmptyStateAction) {
|
|
104
|
+
onEmptyStateAction();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
90
107
|
</script>
|
|
91
108
|
|
|
92
109
|
<aside class={containerClasses} aria-label={ariaLabel} {...restProps}>
|
|
93
|
-
<div class="mb-
|
|
94
|
-
<div class="min-w-0">
|
|
95
|
-
<h3 class="truncate text-base font-semibold text-headline">
|
|
96
|
-
|
|
110
|
+
<div class="mb-5 flex items-start justify-between gap-3">
|
|
111
|
+
<div class="min-w-0 pr-1">
|
|
112
|
+
<h3 class="truncate text-base font-semibold tracking-tight text-headline">
|
|
113
|
+
{title}
|
|
114
|
+
</h3>
|
|
115
|
+
<p class="mt-0.5 text-sm leading-relaxed text-description">{subtitle}</p>
|
|
97
116
|
</div>
|
|
98
117
|
<button
|
|
99
118
|
type="button"
|
|
100
|
-
class="inline-flex size-
|
|
119
|
+
class="-mr-0.5 -mt-0.5 inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-lg text-nav-menu-item transition-colors hover:bg-nav-menu-hover hover:text-nav-menu-item-hover focus-ring focus-ring--nav"
|
|
101
120
|
aria-label={closeLabel}
|
|
102
121
|
onclick={handleClose}
|
|
103
122
|
>
|
|
104
|
-
<X size={
|
|
123
|
+
<X size={18} />
|
|
105
124
|
</button>
|
|
106
125
|
</div>
|
|
107
126
|
|
|
108
127
|
{#if showSearch}
|
|
109
|
-
<div class="relative mb-
|
|
128
|
+
<div class="relative mb-5">
|
|
110
129
|
<span
|
|
111
|
-
class="pointer-events-none absolute left-
|
|
130
|
+
class="pointer-events-none absolute left-3 top-1/2 z-10 -translate-y-1/2 text-description"
|
|
112
131
|
aria-hidden="true"
|
|
113
132
|
>
|
|
114
|
-
<Search size={
|
|
133
|
+
<Search size={17} />
|
|
115
134
|
</span>
|
|
116
135
|
<Input
|
|
117
136
|
type="search"
|
|
118
137
|
bind:value={searchValue}
|
|
119
138
|
placeholder={searchPlaceholder}
|
|
120
139
|
aria-label={searchPlaceholder}
|
|
121
|
-
class="w-full min-w-0 rounded-xl pl-
|
|
140
|
+
class="w-full min-w-0 min-h-10 rounded-xl border-transparent !bg-transparent py-2 pl-10 text-sm ring-1 ring-border/60 hover:!bg-nav-menu-hover focus:!bg-transparent focus-ring focus-ring--nav"
|
|
122
141
|
/>
|
|
123
142
|
</div>
|
|
124
143
|
{/if}
|
|
125
144
|
|
|
126
145
|
{#if filteredItems.length > 0}
|
|
127
|
-
<ul class="space-y-
|
|
146
|
+
<ul class="space-y-1" role="listbox" aria-label={selectLabel}>
|
|
128
147
|
{#each filteredItems as item (item.id)}
|
|
129
|
-
<li>
|
|
148
|
+
<li role="presentation">
|
|
130
149
|
<button
|
|
131
150
|
type="button"
|
|
151
|
+
role="option"
|
|
132
152
|
class={getItemClasses(item.id)}
|
|
133
153
|
onclick={() => handleSelect(item)}
|
|
134
|
-
aria-
|
|
154
|
+
aria-selected={selectedItemId === item.id}
|
|
135
155
|
>
|
|
136
156
|
<span class="flex items-center justify-between gap-3">
|
|
137
|
-
<span class="flex min-w-0 items-start gap-2">
|
|
157
|
+
<span class="flex min-w-0 items-start gap-2.5">
|
|
138
158
|
{#if item.icon}
|
|
139
159
|
{@const Icon = item.icon}
|
|
140
160
|
<span
|
|
141
|
-
class="mt-0.5 inline-flex size-
|
|
161
|
+
class="mt-0.5 inline-flex size-5 shrink-0 items-center justify-center text-current"
|
|
142
162
|
aria-hidden="true"
|
|
143
163
|
>
|
|
144
|
-
<Icon size={
|
|
164
|
+
<Icon size={18} />
|
|
145
165
|
</span>
|
|
146
166
|
{/if}
|
|
147
167
|
<span class="min-w-0">
|
|
148
|
-
<span
|
|
168
|
+
<span
|
|
169
|
+
class="block truncate text-sm font-medium text-current"
|
|
170
|
+
>
|
|
149
171
|
{item.label}
|
|
150
172
|
</span>
|
|
151
173
|
{#if item.description}
|
|
@@ -166,9 +188,20 @@
|
|
|
166
188
|
{/each}
|
|
167
189
|
</ul>
|
|
168
190
|
{:else}
|
|
169
|
-
<div
|
|
191
|
+
<div
|
|
192
|
+
class="rounded-xl border border-dashed border-border bg-transparent px-3.5 py-4 ring-1 ring-border/40"
|
|
193
|
+
>
|
|
170
194
|
<h4 class="text-sm font-semibold text-headline">{emptyStateTitle}</h4>
|
|
171
|
-
<p class="mt-1 text-sm text-description">{emptyStateDescription}</p>
|
|
195
|
+
<p class="mt-1 text-sm leading-relaxed text-description">{emptyStateDescription}</p>
|
|
196
|
+
{#if emptyStateActionLabel.trim() && onEmptyStateAction}
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
class="mt-3 inline-flex min-h-10 cursor-pointer items-center rounded-lg bg-action-primary px-3 py-2 text-sm font-medium text-action-primary transition-colors hover:bg-action-primary-hover focus-ring focus-ring--nav"
|
|
200
|
+
onclick={handleEmptyStateAction}
|
|
201
|
+
>
|
|
202
|
+
{emptyStateActionLabel.trim()}
|
|
203
|
+
</button>
|
|
204
|
+
{/if}
|
|
172
205
|
</div>
|
|
173
206
|
{/if}
|
|
174
207
|
</aside>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from "svelte";
|
|
2
|
-
export interface
|
|
2
|
+
export interface SidebarPanelItem {
|
|
3
3
|
id: string;
|
|
4
4
|
label: string;
|
|
5
5
|
icon?: Component<{
|
|
@@ -12,21 +12,24 @@ export interface SidebarProjectPanelItem {
|
|
|
12
12
|
interface Props {
|
|
13
13
|
className?: string;
|
|
14
14
|
widthClass?: string;
|
|
15
|
+
variant?: "plain" | "elevated";
|
|
15
16
|
ariaLabel?: string;
|
|
16
17
|
title?: string;
|
|
17
18
|
subtitle?: string;
|
|
18
19
|
showSearch?: boolean;
|
|
19
20
|
searchPlaceholder?: string;
|
|
20
21
|
searchValue?: string;
|
|
21
|
-
items?:
|
|
22
|
+
items?: SidebarPanelItem[];
|
|
22
23
|
selectedItemId?: string;
|
|
23
24
|
emptyStateTitle?: string;
|
|
24
25
|
emptyStateDescription?: string;
|
|
26
|
+
emptyStateActionLabel?: string;
|
|
25
27
|
selectLabel?: string;
|
|
26
28
|
closeLabel?: string;
|
|
27
|
-
onSelect?: (item:
|
|
29
|
+
onSelect?: (item: SidebarPanelItem) => void;
|
|
28
30
|
onClose?: () => void;
|
|
31
|
+
onEmptyStateAction?: () => void;
|
|
29
32
|
}
|
|
30
|
-
declare const
|
|
31
|
-
type
|
|
32
|
-
export default
|
|
33
|
+
declare const SidebarPanel: Component<Props, {}, "searchValue" | "selectedItemId">;
|
|
34
|
+
type SidebarPanel = ReturnType<typeof SidebarPanel>;
|
|
35
|
+
export default SidebarPanel;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ThemeToggle from "../atoms/ThemeToggle.svelte";
|
|
3
|
+
import type { Component, Snippet } from "svelte";
|
|
4
|
+
|
|
5
|
+
export interface TopNavbarNavItem {
|
|
6
|
+
label: string;
|
|
7
|
+
href: string;
|
|
8
|
+
icon?: Component<{ size?: number; class?: string }>;
|
|
9
|
+
iconFilled?: Component<{ size?: number; class?: string }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
brand?: string;
|
|
14
|
+
brandHref?: string;
|
|
15
|
+
/** `aria-label` on `<nav>` when multiple nav landmarks exist. */
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
showThemeToggle?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Slim mode: only the `nav` region (no full chrome bar). */
|
|
20
|
+
embedded?: boolean;
|
|
21
|
+
items?: TopNavbarNavItem[];
|
|
22
|
+
navVariant?: "header" | "sidebar";
|
|
23
|
+
currentPath?: string;
|
|
24
|
+
preventNavigation?: boolean;
|
|
25
|
+
onclick?: (event: Event) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
brand = "",
|
|
30
|
+
brandHref,
|
|
31
|
+
ariaLabel,
|
|
32
|
+
showThemeToggle = true,
|
|
33
|
+
className = "",
|
|
34
|
+
embedded = false,
|
|
35
|
+
items = [],
|
|
36
|
+
navVariant = "header",
|
|
37
|
+
currentPath = "",
|
|
38
|
+
preventNavigation = false,
|
|
39
|
+
onclick,
|
|
40
|
+
nav,
|
|
41
|
+
actions,
|
|
42
|
+
...restProps
|
|
43
|
+
}: Props & { nav?: Snippet; actions?: Snippet } = $props();
|
|
44
|
+
|
|
45
|
+
let isMenuOpen = $state(false);
|
|
46
|
+
|
|
47
|
+
function toggleMenu() {
|
|
48
|
+
isMenuOpen = !isMenuOpen;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleNavLinkClick(event: MouseEvent) {
|
|
52
|
+
if (preventNavigation) {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
}
|
|
55
|
+
if (onclick) {
|
|
56
|
+
onclick(event);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** App routes: prefix match except `/` and absolute URLs (exact match). */
|
|
61
|
+
function isNavItemActive(href: string): boolean {
|
|
62
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
63
|
+
return currentPath === href;
|
|
64
|
+
}
|
|
65
|
+
if (href === "/") {
|
|
66
|
+
return currentPath === "/";
|
|
67
|
+
}
|
|
68
|
+
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ulClasses = $derived.by(() => {
|
|
72
|
+
return navVariant === "sidebar"
|
|
73
|
+
? "flex flex-col gap-1"
|
|
74
|
+
: "flex flex-col gap-1 md:flex-row md:items-center md:justify-between";
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function getNavItemClasses(): string {
|
|
78
|
+
return "flex flex-col gap-1 grow h-full items-center justify-center min-h-0 min-w-0 relative shrink-0 cursor-pointer w-full md:w-auto";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getIconContainerClasses(): string {
|
|
82
|
+
return "focus-ring focus-ring--nav group/nav-item flex flex-col items-center justify-center overflow-clip relative rounded-[20px] shrink-0 text-nav-menu-item transition-colors duration-200 outline-none hover:bg-nav-menu-hover hover:text-nav-menu-item-hover aria-[current=page]:bg-nav-menu-active aria-[current=page]:text-nav-menu-item-active";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getStateLayerClasses(): string {
|
|
86
|
+
return "box-border flex gap-1 h-10 items-center px-4 py-2 relative shrink-0";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getLabelClasses(): string {
|
|
90
|
+
return "font-medium leading-4 relative shrink-0 text-center text-nowrap tracking-[0.5px] whitespace-pre text-xs text-inherit";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getIconWrapperClasses(): string {
|
|
94
|
+
return "overflow-clip relative shrink-0 size-4 text-current";
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
{#snippet defaultNavList()}
|
|
99
|
+
<ul class="{ulClasses} list-none m-0 p-0">
|
|
100
|
+
{#each items as item (item.href)}
|
|
101
|
+
{@const isActive = isNavItemActive(item.href)}
|
|
102
|
+
<li class={getNavItemClasses()}>
|
|
103
|
+
<a
|
|
104
|
+
href={item.href}
|
|
105
|
+
class={getIconContainerClasses()}
|
|
106
|
+
onclick={handleNavLinkClick}
|
|
107
|
+
aria-current={isActive ? "page" : undefined}
|
|
108
|
+
>
|
|
109
|
+
<div class={getStateLayerClasses()}>
|
|
110
|
+
{#if item.iconFilled && isActive}
|
|
111
|
+
{@const Icon = item.iconFilled}
|
|
112
|
+
<div class={getIconWrapperClasses()}>
|
|
113
|
+
<Icon size={16} class="w-4 h-4" />
|
|
114
|
+
</div>
|
|
115
|
+
{:else if item.icon}
|
|
116
|
+
{@const Icon = item.icon}
|
|
117
|
+
<div class={getIconWrapperClasses()}>
|
|
118
|
+
<Icon size={16} class="w-4 h-4" />
|
|
119
|
+
</div>
|
|
120
|
+
{/if}
|
|
121
|
+
<p class={getLabelClasses()}>
|
|
122
|
+
{item.label}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
</a>
|
|
126
|
+
</li>
|
|
127
|
+
{/each}
|
|
128
|
+
</ul>
|
|
129
|
+
{/snippet}
|
|
130
|
+
|
|
131
|
+
{#if embedded}
|
|
132
|
+
<nav class={className} aria-label={ariaLabel} {...restProps}>
|
|
133
|
+
{#if nav}
|
|
134
|
+
{@render nav()}
|
|
135
|
+
{:else}
|
|
136
|
+
{@render defaultNavList()}
|
|
137
|
+
{/if}
|
|
138
|
+
</nav>
|
|
139
|
+
{:else}
|
|
140
|
+
<nav
|
|
141
|
+
class="border-b border-border bg-background sticky top-0 z-50 {className}"
|
|
142
|
+
aria-label={ariaLabel}
|
|
143
|
+
{...restProps}
|
|
144
|
+
>
|
|
145
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
146
|
+
<div class="flex justify-between items-center h-16">
|
|
147
|
+
<div class="shrink-0">
|
|
148
|
+
{#if brandHref}
|
|
149
|
+
<a
|
|
150
|
+
href={brandHref}
|
|
151
|
+
class="focus-ring text-xl font-bold text-headline hover:opacity-90"
|
|
152
|
+
>
|
|
153
|
+
{brand}
|
|
154
|
+
</a>
|
|
155
|
+
{:else}
|
|
156
|
+
<span class="text-xl font-bold text-headline">{brand}</span>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="hidden md:block">
|
|
161
|
+
<div class="ml-10 flex items-baseline space-x-4">
|
|
162
|
+
{#if nav}
|
|
163
|
+
{@render nav()}
|
|
164
|
+
{:else if items.length > 0}
|
|
165
|
+
{@render defaultNavList()}
|
|
166
|
+
{/if}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div class="hidden md:block">
|
|
171
|
+
<div class="ml-4 flex items-center space-x-4">
|
|
172
|
+
{@render actions?.()}
|
|
173
|
+
{#if showThemeToggle}
|
|
174
|
+
<ThemeToggle />
|
|
175
|
+
{/if}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="md:hidden">
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
class="cursor-pointer text-description hover:text-body"
|
|
183
|
+
onclick={toggleMenu}
|
|
184
|
+
aria-label="Toggle menu"
|
|
185
|
+
>
|
|
186
|
+
<span class="text-2xl">☰</span>
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{#if isMenuOpen}
|
|
192
|
+
<div class="md:hidden">
|
|
193
|
+
<div
|
|
194
|
+
class="px-2 pt-2 pb-3 space-y-1 sm:px-3 border-t border-border"
|
|
195
|
+
>
|
|
196
|
+
{#if nav}
|
|
197
|
+
{@render nav()}
|
|
198
|
+
{:else if items.length > 0}
|
|
199
|
+
{@render defaultNavList()}
|
|
200
|
+
{/if}
|
|
201
|
+
<div class="pt-4">
|
|
202
|
+
{@render actions?.()}
|
|
203
|
+
{#if showThemeToggle}
|
|
204
|
+
<div class="mt-2">
|
|
205
|
+
<ThemeToggle />
|
|
206
|
+
</div>
|
|
207
|
+
{/if}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
{/if}
|
|
212
|
+
</div>
|
|
213
|
+
</nav>
|
|
214
|
+
{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Component, Snippet } from "svelte";
|
|
2
|
+
export interface TopNavbarNavItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
icon?: Component<{
|
|
6
|
+
size?: number;
|
|
7
|
+
class?: string;
|
|
8
|
+
}>;
|
|
9
|
+
iconFilled?: Component<{
|
|
10
|
+
size?: number;
|
|
11
|
+
class?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
interface Props {
|
|
15
|
+
brand?: string;
|
|
16
|
+
brandHref?: string;
|
|
17
|
+
/** `aria-label` on `<nav>` when multiple nav landmarks exist. */
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
showThemeToggle?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Slim mode: only the `nav` region (no full chrome bar). */
|
|
22
|
+
embedded?: boolean;
|
|
23
|
+
items?: TopNavbarNavItem[];
|
|
24
|
+
navVariant?: "header" | "sidebar";
|
|
25
|
+
currentPath?: string;
|
|
26
|
+
preventNavigation?: boolean;
|
|
27
|
+
onclick?: (event: Event) => void;
|
|
28
|
+
}
|
|
29
|
+
type $$ComponentProps = Props & {
|
|
30
|
+
nav?: Snippet;
|
|
31
|
+
actions?: Snippet;
|
|
32
|
+
};
|
|
33
|
+
declare const TopNavbar: Component<$$ComponentProps, {}, "">;
|
|
34
|
+
type TopNavbar = ReturnType<typeof TopNavbar>;
|
|
35
|
+
export default TopNavbar;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as Navigation } from './Navigation.svelte';
|
|
1
|
+
export { default as TopNavbar } from './TopNavbar.svelte';
|
|
3
2
|
export { default as SidebarNavigation } from './SidebarNavigation.svelte';
|
|
4
|
-
export { default as
|
|
3
|
+
export { default as SidebarAccountPanel } from './SidebarAccountPanel.svelte';
|
|
4
|
+
export { default as SidebarPanel } from './SidebarPanel.svelte';
|
package/dist/organisms/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as Navigation } from './Navigation.svelte';
|
|
1
|
+
export { default as TopNavbar } from './TopNavbar.svelte';
|
|
3
2
|
export { default as SidebarNavigation } from './SidebarNavigation.svelte';
|
|
4
|
-
export { default as
|
|
3
|
+
export { default as SidebarAccountPanel } from './SidebarAccountPanel.svelte';
|
|
4
|
+
export { default as SidebarPanel } from './SidebarPanel.svelte';
|
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Get all focusable elements within a container
|
|
6
|
-
*/
|
|
1
|
+
/** Focus helpers for overlays (modals, sheets). Requires `app.css` focus ring classes. */
|
|
2
|
+
/** From `app.css` `@layer components` — import the library/app stylesheet. */
|
|
3
|
+
export declare const FOCUS_BRAND_CLASS = "focus-brand";
|
|
4
|
+
export declare const FOCUS_NAV_CLASS = "focus-nav";
|
|
7
5
|
export declare function getFocusableElements(container: HTMLElement): HTMLElement[];
|
|
8
|
-
/**
|
|
9
|
-
* Trap focus within a container element
|
|
10
|
-
*/
|
|
6
|
+
/** Tab wraps first↔last inside `container`. Caller should pair with `saveFocus` / `returnFocus`. */
|
|
11
7
|
export declare function trapFocus(container: HTMLElement): () => void;
|
|
12
|
-
/**
|
|
13
|
-
* Save the currently focused element
|
|
14
|
-
*/
|
|
15
8
|
export declare function saveFocus(): void;
|
|
16
|
-
/**
|
|
17
|
-
* Return focus to the previously focused element
|
|
18
|
-
*/
|
|
9
|
+
/** Restores focus from the last `saveFocus`. Skips disconnected or unfocusable nodes. */
|
|
19
10
|
export declare function returnFocus(): void;
|
|
20
|
-
/**
|
|
21
|
-
* Focus the first focusable element in a container
|
|
22
|
-
*/
|
|
23
11
|
export declare function focusFirstElement(container: HTMLElement): void;
|
|
12
|
+
//# sourceMappingURL=focus-utils.d.ts.map
|