zabi-components 6.0.0 → 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 +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 +35 -16
- package/dist/atoms/Button.svelte.d.ts +3 -1
- 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 +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 +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 +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 +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 +14 -1
- package/dist/zabi-components-theme-dark-only.css +5 -0
- package/dist/zabi-components-theme-dark.css +5 -0
- package/dist/zabi-components-theme-only.css +9 -1
- package/dist/zabi-components-theme.css +9 -1
- package/dist/zabi-components.css +877 -192
- package/package.json +19 -7
- package/dist/organisms/Navbar.svelte.d.ts +0 -41
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Badge from "../atoms/Badge.svelte";
|
|
3
|
+
import Button from "../atoms/Button.svelte";
|
|
4
|
+
import IconButton from "../atoms/IconButton.svelte";
|
|
3
5
|
import Input from "../atoms/Input.svelte";
|
|
4
|
-
import
|
|
6
|
+
import SidebarBrandHeader from "../molecules/SidebarBrandHeader.svelte";
|
|
7
|
+
import SidebarFooter from "../molecules/SidebarFooter.svelte";
|
|
8
|
+
import SidebarNavSection from "../molecules/SidebarNavSection.svelte";
|
|
9
|
+
import { Command, Search } from "@lucide/svelte";
|
|
10
|
+
import type { Snippet } from "svelte";
|
|
5
11
|
import type { Component } from "svelte";
|
|
12
|
+
import type { ButtonVariant, SizeVariant } from "../types/variants.js";
|
|
6
13
|
|
|
7
14
|
export interface SidebarNavigationItem {
|
|
8
15
|
id: string;
|
|
@@ -12,14 +19,25 @@
|
|
|
12
19
|
badgeText?: string | number;
|
|
13
20
|
badgeCount?: number;
|
|
14
21
|
group?: "primary" | "secondary";
|
|
22
|
+
/** Groups items under a section heading in the rail. */
|
|
23
|
+
section?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface NavSectionGroup {
|
|
27
|
+
sectionLabel: string | null;
|
|
28
|
+
items: SidebarNavigationItem[];
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
interface Props {
|
|
18
32
|
mode?: "expanded" | "collapsed";
|
|
33
|
+
layout?: "rail" | "card";
|
|
19
34
|
items?: SidebarNavigationItem[];
|
|
20
35
|
currentPath?: string;
|
|
21
36
|
ariaLabel?: string;
|
|
22
37
|
className?: string;
|
|
38
|
+
logoSrc?: string;
|
|
39
|
+
logoAlt?: string;
|
|
40
|
+
brandName?: string;
|
|
23
41
|
showProfile?: boolean;
|
|
24
42
|
profileName?: string;
|
|
25
43
|
profileEmail?: string;
|
|
@@ -28,6 +46,10 @@
|
|
|
28
46
|
searchMode?: "input" | "button";
|
|
29
47
|
searchPlaceholder?: string;
|
|
30
48
|
searchValue?: string;
|
|
49
|
+
/** Trigger icon when search is a button or rail is collapsed; input mode keeps `Search` on the field. */
|
|
50
|
+
searchTriggerIcon?: Component<{ size?: number; class?: string }>;
|
|
51
|
+
searchTriggerVariant?: ButtonVariant;
|
|
52
|
+
searchTriggerSize?: SizeVariant;
|
|
31
53
|
showLogout?: boolean;
|
|
32
54
|
logoutLabel?: string;
|
|
33
55
|
showThemeToggle?: boolean;
|
|
@@ -41,20 +63,25 @@
|
|
|
41
63
|
onLogout?: () => void;
|
|
42
64
|
onThemeToggle?: (nextIsLightMode: boolean) => void;
|
|
43
65
|
onEmptyStateAction?: () => void;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
onProfileClick?: (event?: MouseEvent) => void;
|
|
67
|
+
profilePanelOpen?: boolean;
|
|
68
|
+
profilePanelControlsId?: string;
|
|
69
|
+
profilePanel?: Snippet;
|
|
70
|
+
/** Parent nav href to highlight when `currentPath` is a deeper leaf (e.g. category row). */
|
|
48
71
|
activePrimaryHref?: string;
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
let {
|
|
52
75
|
mode = "expanded",
|
|
76
|
+
layout = "rail",
|
|
53
77
|
items = [],
|
|
54
78
|
currentPath = "",
|
|
55
79
|
activePrimaryHref = "",
|
|
56
80
|
ariaLabel = "Sidebar navigation",
|
|
57
81
|
className = "",
|
|
82
|
+
logoSrc = "",
|
|
83
|
+
logoAlt = "",
|
|
84
|
+
brandName = "",
|
|
58
85
|
showProfile = true,
|
|
59
86
|
profileName = "Zabi",
|
|
60
87
|
profileEmail = "hello@zabi.dev",
|
|
@@ -63,6 +90,9 @@
|
|
|
63
90
|
searchMode = "input",
|
|
64
91
|
searchPlaceholder = "Search...",
|
|
65
92
|
searchValue = $bindable(""),
|
|
93
|
+
searchTriggerIcon = Command,
|
|
94
|
+
searchTriggerVariant = "outline",
|
|
95
|
+
searchTriggerSize = "sm",
|
|
66
96
|
showLogout = true,
|
|
67
97
|
logoutLabel = "Logout",
|
|
68
98
|
showThemeToggle = true,
|
|
@@ -76,10 +106,19 @@
|
|
|
76
106
|
onLogout,
|
|
77
107
|
onThemeToggle,
|
|
78
108
|
onEmptyStateAction,
|
|
109
|
+
onProfileClick,
|
|
110
|
+
profilePanelOpen = false,
|
|
111
|
+
profilePanelControlsId = "",
|
|
112
|
+
profilePanel,
|
|
79
113
|
...restProps
|
|
80
114
|
}: Props = $props();
|
|
81
115
|
|
|
82
116
|
const isCollapsed = $derived(mode === "collapsed");
|
|
117
|
+
const isCard = $derived(layout === "card");
|
|
118
|
+
const showBrandRow = $derived(
|
|
119
|
+
Boolean(logoSrc.trim() || brandName.trim()),
|
|
120
|
+
);
|
|
121
|
+
|
|
83
122
|
const normalizedSearchTerm = $derived(searchValue.trim().toLowerCase());
|
|
84
123
|
const primaryItems = $derived(
|
|
85
124
|
items.filter((item) => item.group !== "secondary"),
|
|
@@ -87,11 +126,11 @@
|
|
|
87
126
|
const secondaryItems = $derived(
|
|
88
127
|
items.filter((item) => item.group === "secondary"),
|
|
89
128
|
);
|
|
129
|
+
|
|
90
130
|
function itemMatchesSearch(item: SidebarNavigationItem): boolean {
|
|
91
131
|
return item.label.toLowerCase().includes(normalizedSearchTerm);
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
/** Keep the current (and section) row visible even when it does not match the filter. */
|
|
95
134
|
function itemPinnedBySelection(item: SidebarNavigationItem): boolean {
|
|
96
135
|
if (currentPath === item.href) {
|
|
97
136
|
return true;
|
|
@@ -119,36 +158,52 @@
|
|
|
119
158
|
filteredPrimaryItems.length + filteredSecondaryItems.length > 0,
|
|
120
159
|
);
|
|
121
160
|
|
|
161
|
+
function partitionBySection(
|
|
162
|
+
list: SidebarNavigationItem[],
|
|
163
|
+
): NavSectionGroup[] {
|
|
164
|
+
const groups: NavSectionGroup[] = [];
|
|
165
|
+
for (const item of list) {
|
|
166
|
+
const label = item.section?.trim() || null;
|
|
167
|
+
const last = groups[groups.length - 1];
|
|
168
|
+
if (last && last.sectionLabel === label) {
|
|
169
|
+
last.items = [...last.items, item];
|
|
170
|
+
} else {
|
|
171
|
+
groups.push({ sectionLabel: label, items: [item] });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return groups;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const primarySectionGroups = $derived(
|
|
178
|
+
partitionBySection(filteredPrimaryItems),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const insetX = $derived(isCollapsed ? "px-2.5" : "px-4");
|
|
182
|
+
|
|
122
183
|
const containerClasses = $derived.by(() => {
|
|
123
184
|
const widthClass = isCollapsed ? "w-[104px]" : "w-[266px]";
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
"
|
|
185
|
+
const railSurface = "border-r border-border bg-background text-headline";
|
|
186
|
+
const cardSurface =
|
|
187
|
+
"border-r border-border bg-background text-headline shadow-sm";
|
|
188
|
+
const surfaceClasses = isCard ? cardSurface : railSurface;
|
|
189
|
+
const verticalPad = isCard ? "py-4" : "py-5";
|
|
190
|
+
const baseClasses = `flex h-full min-h-0 max-h-full flex-col overflow-visible ${verticalPad}`;
|
|
127
191
|
|
|
128
192
|
return `${baseClasses} ${widthClass} ${surfaceClasses} ${className}`.trim();
|
|
129
193
|
});
|
|
130
194
|
|
|
131
|
-
const
|
|
132
|
-
|
|
195
|
+
const headerStackClasses = $derived(
|
|
196
|
+
`flex w-full shrink-0 flex-col gap-5 ${insetX}`,
|
|
133
197
|
);
|
|
134
198
|
|
|
135
|
-
/** Search trigger (collapsed icon strip or button mode): matches nav item interaction tokens. */
|
|
136
|
-
const searchControlStates =
|
|
137
|
-
"text-nav-menu-item outline-none transition-colors duration-150 hover:bg-nav-menu-hover hover:text-nav-menu-item-hover active:bg-base-200 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-nav-menu-focus";
|
|
138
|
-
|
|
139
|
-
const searchButtonClasses = $derived(
|
|
140
|
-
`flex min-h-11 w-full cursor-pointer items-center justify-center rounded-2xl border border-input-border bg-input px-0 py-2.5 ${searchControlStates}`,
|
|
141
|
-
);
|
|
142
|
-
const searchTriggerClasses = $derived.by(() => {
|
|
143
|
-
return isCollapsed
|
|
144
|
-
? searchButtonClasses
|
|
145
|
-
: `flex min-h-11 w-full cursor-pointer items-center gap-3 rounded-2xl border border-input-border bg-input px-4 py-2.5 text-left ${searchControlStates}`;
|
|
146
|
-
});
|
|
147
199
|
const iconContainerClasses = $derived(
|
|
148
200
|
"flex size-6 shrink-0 items-center justify-center leading-none text-current",
|
|
149
201
|
);
|
|
202
|
+
/** With `searchMode: "input"`, the field stays visible even if `onSearchClick` is set (e.g. Storybook). */
|
|
150
203
|
const shouldRenderSearchButton = $derived(
|
|
151
|
-
isCollapsed ||
|
|
204
|
+
isCollapsed ||
|
|
205
|
+
searchMode === "button" ||
|
|
206
|
+
(Boolean(onSearchClick) && searchMode !== "input"),
|
|
152
207
|
);
|
|
153
208
|
|
|
154
209
|
function getTextToneClass(isMuted = false): string {
|
|
@@ -160,35 +215,18 @@
|
|
|
160
215
|
currentPath === item.href ||
|
|
161
216
|
(Boolean(activePrimaryHref) && item.href === activePrimaryHref);
|
|
162
217
|
const layoutClasses = isCollapsed
|
|
163
|
-
? "flex min-h-
|
|
164
|
-
: "flex min-h-
|
|
218
|
+
? "flex min-h-10 items-center justify-center px-0 py-2"
|
|
219
|
+
: "flex min-h-10 items-center gap-2.5 px-2.5 py-2";
|
|
165
220
|
const structural =
|
|
166
|
-
"w-full cursor-pointer rounded-lg no-underline transition-colors duration-150 outline-none
|
|
221
|
+
"focus-ring focus-ring--nav w-full cursor-pointer rounded-lg no-underline transition-colors duration-150 outline-none";
|
|
167
222
|
|
|
168
223
|
if (isActive) {
|
|
169
|
-
/* Current page: keep active surface on hover (do not apply inactive hover bg). */
|
|
170
224
|
return `${structural} ${layoutClasses} bg-nav-menu-active text-inherit hover:bg-nav-menu-active hover:text-inherit active:opacity-90`;
|
|
171
225
|
}
|
|
172
226
|
|
|
173
227
|
return `${structural} ${layoutClasses} text-nav-menu-item hover:bg-nav-menu-hover hover:text-nav-menu-item-hover active:bg-base-200`;
|
|
174
228
|
}
|
|
175
229
|
|
|
176
|
-
function getControlButtonClasses(): string {
|
|
177
|
-
const baseClasses =
|
|
178
|
-
"w-full cursor-pointer rounded-lg text-nav-menu-item transition-colors duration-150 outline-none hover:bg-nav-menu-hover hover:text-nav-menu-item-hover active:bg-base-200 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-nav-menu-focus";
|
|
179
|
-
const layoutClasses = isCollapsed
|
|
180
|
-
? "flex min-h-11 items-center justify-center px-0 py-2.5"
|
|
181
|
-
: "flex min-h-11 items-center gap-3 px-4 py-2.5";
|
|
182
|
-
|
|
183
|
-
return `${baseClasses} ${layoutClasses}`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const themeToggleContainerClasses = $derived(
|
|
187
|
-
isCollapsed
|
|
188
|
-
? "flex w-full justify-center py-1"
|
|
189
|
-
: "flex w-full justify-start py-1",
|
|
190
|
-
);
|
|
191
|
-
|
|
192
230
|
function handleNavigate(item: SidebarNavigationItem, event: MouseEvent) {
|
|
193
231
|
if (onNavigate) {
|
|
194
232
|
onNavigate(item, event);
|
|
@@ -201,19 +239,6 @@
|
|
|
201
239
|
}
|
|
202
240
|
}
|
|
203
241
|
|
|
204
|
-
function handleLogout() {
|
|
205
|
-
if (onLogout) {
|
|
206
|
-
onLogout();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function handleThemeToggle() {
|
|
211
|
-
isLightMode = !isLightMode;
|
|
212
|
-
if (onThemeToggle) {
|
|
213
|
-
onThemeToggle(isLightMode);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
242
|
function handleEmptyStateAction() {
|
|
218
243
|
if (onEmptyStateAction) {
|
|
219
244
|
onEmptyStateAction();
|
|
@@ -244,150 +269,186 @@
|
|
|
244
269
|
</script>
|
|
245
270
|
|
|
246
271
|
<nav class={containerClasses} aria-label={ariaLabel} {...restProps}>
|
|
247
|
-
<div
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
272
|
+
<div
|
|
273
|
+
class="flex min-h-0 w-full min-w-0 flex-1 flex-col"
|
|
274
|
+
>
|
|
275
|
+
<div class={`${headerStackClasses} pb-3`.trim()}>
|
|
276
|
+
{#if showBrandRow}
|
|
277
|
+
<SidebarBrandHeader
|
|
278
|
+
collapsed={isCollapsed}
|
|
279
|
+
{brandName}
|
|
280
|
+
logoSrc={logoSrc.trim()}
|
|
281
|
+
{logoAlt}
|
|
282
|
+
/>
|
|
283
|
+
{/if}
|
|
284
|
+
|
|
285
|
+
{#if showSearch}
|
|
286
|
+
{#if shouldRenderSearchButton}
|
|
287
|
+
{@const TriggerIcon = searchTriggerIcon}
|
|
288
|
+
{#if isCollapsed}
|
|
289
|
+
<span
|
|
290
|
+
class="inline-flex"
|
|
291
|
+
data-sidebar-flyout-anchor="search"
|
|
292
|
+
>
|
|
293
|
+
<IconButton
|
|
294
|
+
variant={searchTriggerVariant}
|
|
295
|
+
size={searchTriggerSize}
|
|
296
|
+
label={searchPlaceholder}
|
|
297
|
+
onclick={handleSearchClick}
|
|
298
|
+
>
|
|
299
|
+
<TriggerIcon size={18} />
|
|
300
|
+
</IconButton>
|
|
301
|
+
</span>
|
|
302
|
+
{:else}
|
|
303
|
+
<div
|
|
304
|
+
class="w-full min-w-0"
|
|
305
|
+
data-sidebar-flyout-anchor="search"
|
|
257
306
|
>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
307
|
+
<Button
|
|
308
|
+
variant={searchTriggerVariant}
|
|
309
|
+
size={searchTriggerSize}
|
|
310
|
+
isFullWidth
|
|
311
|
+
onclick={handleSearchClick}
|
|
312
|
+
>
|
|
313
|
+
<span class="flex w-full items-center justify-start gap-2.5">
|
|
314
|
+
<span class={iconContainerClasses} aria-hidden="true">
|
|
315
|
+
<TriggerIcon size={17} />
|
|
316
|
+
</span>
|
|
317
|
+
<span class="truncate text-sm text-inherit">
|
|
318
|
+
{searchValue || searchPlaceholder}
|
|
319
|
+
</span>
|
|
320
|
+
</span>
|
|
321
|
+
</Button>
|
|
322
|
+
</div>
|
|
323
|
+
{/if}
|
|
324
|
+
{:else}
|
|
325
|
+
<div class="relative w-full">
|
|
326
|
+
<span
|
|
327
|
+
class="pointer-events-none absolute left-3 top-1/2 z-10 -translate-y-1/2"
|
|
328
|
+
aria-hidden="true"
|
|
329
|
+
>
|
|
330
|
+
<Search size={17} class={getTextToneClass(true)} />
|
|
331
|
+
</span>
|
|
332
|
+
<Input
|
|
333
|
+
type="search"
|
|
334
|
+
bind:value={searchValue}
|
|
335
|
+
placeholder={searchPlaceholder}
|
|
336
|
+
aria-label={searchPlaceholder}
|
|
337
|
+
class="focus-ring focus-ring--nav 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"
|
|
338
|
+
/>
|
|
263
339
|
</div>
|
|
264
340
|
{/if}
|
|
265
|
-
</div>
|
|
266
|
-
{/if}
|
|
267
|
-
|
|
268
|
-
{#if showSearch}
|
|
269
|
-
{#if shouldRenderSearchButton}
|
|
270
|
-
<button
|
|
271
|
-
type="button"
|
|
272
|
-
class={searchTriggerClasses}
|
|
273
|
-
onclick={handleSearchClick}
|
|
274
|
-
aria-label={searchPlaceholder}
|
|
275
|
-
>
|
|
276
|
-
<span
|
|
277
|
-
class="truncate text-base text-inherit transition-colors"
|
|
278
|
-
>
|
|
279
|
-
{searchValue || searchPlaceholder}
|
|
280
|
-
</span>
|
|
281
|
-
</button>
|
|
282
|
-
{:else}
|
|
283
|
-
<div class="relative w-full">
|
|
284
|
-
<span
|
|
285
|
-
class="pointer-events-none absolute left-4 top-1/2 z-10 -translate-y-1/2"
|
|
286
|
-
aria-hidden="true"
|
|
287
|
-
>
|
|
288
|
-
<Search size={20} class={getTextToneClass(true)} />
|
|
289
|
-
</span>
|
|
290
|
-
<Input
|
|
291
|
-
type="search"
|
|
292
|
-
bind:value={searchValue}
|
|
293
|
-
placeholder={searchPlaceholder}
|
|
294
|
-
aria-label={searchPlaceholder}
|
|
295
|
-
class="w-full min-w-0 rounded-2xl pl-12"
|
|
296
|
-
/>
|
|
297
|
-
</div>
|
|
298
341
|
{/if}
|
|
299
|
-
|
|
300
|
-
</div>
|
|
342
|
+
</div>
|
|
301
343
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
344
|
+
<div
|
|
345
|
+
class={`flex min-h-0 min-w-0 flex-1 flex-col gap-3 overflow-y-auto overflow-x-hidden overscroll-y-contain pt-4 pb-1.5 ${insetX}`}
|
|
346
|
+
role="region"
|
|
347
|
+
aria-label="Navigation links"
|
|
348
|
+
>
|
|
307
349
|
{#if hasFilteredItems}
|
|
308
|
-
<
|
|
309
|
-
class="flex w-full flex-col
|
|
310
|
-
aria-label="Primary navigation"
|
|
350
|
+
<div
|
|
351
|
+
class="flex w-full min-w-0 flex-col divide-y divide-border"
|
|
311
352
|
>
|
|
312
|
-
{#each
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
353
|
+
{#each primarySectionGroups as group, gi (`${gi}-${group.sectionLabel ?? "x"}`)}
|
|
354
|
+
<div class="py-4 first:pt-1 last:pb-1">
|
|
355
|
+
<SidebarNavSection
|
|
356
|
+
title={group.sectionLabel ?? ""}
|
|
357
|
+
sectionKey={`p-${gi}`}
|
|
358
|
+
listAriaLabel={group.sectionLabel
|
|
359
|
+
? `${group.sectionLabel} navigation`
|
|
360
|
+
: "Primary navigation"}
|
|
361
|
+
collapsed={isCollapsed}
|
|
321
362
|
>
|
|
322
|
-
{#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
{#each group.items as item (item.id)}
|
|
364
|
+
{@const Icon = item.icon}
|
|
365
|
+
<li>
|
|
366
|
+
<a
|
|
367
|
+
href={item.href}
|
|
368
|
+
class={getNavItemClasses(item)}
|
|
369
|
+
onclick={(event) => handleNavigate(item, event)}
|
|
370
|
+
aria-current={getAriaCurrent(item)}
|
|
371
|
+
aria-label={isCollapsed ? item.label : undefined}
|
|
372
|
+
>
|
|
373
|
+
{#if Icon}
|
|
374
|
+
<span
|
|
375
|
+
class={iconContainerClasses}
|
|
376
|
+
aria-hidden="true"
|
|
377
|
+
>
|
|
378
|
+
<Icon size={18} />
|
|
379
|
+
</span>
|
|
380
|
+
{/if}
|
|
381
|
+
{#if !isCollapsed}
|
|
382
|
+
<span
|
|
383
|
+
class="text-sm font-medium leading-snug text-inherit"
|
|
384
|
+
>{item.label}</span
|
|
385
|
+
>
|
|
386
|
+
{/if}
|
|
387
|
+
{#if !isCollapsed && getItemBadgeText(item) !== null}
|
|
388
|
+
<span class="ml-auto">
|
|
389
|
+
<Badge
|
|
390
|
+
variant="default"
|
|
391
|
+
size="sm"
|
|
392
|
+
text={getItemBadgeText(item) ?? ""}
|
|
393
|
+
/>
|
|
394
|
+
</span>
|
|
395
|
+
{/if}
|
|
396
|
+
</a>
|
|
397
|
+
</li>
|
|
398
|
+
{/each}
|
|
399
|
+
</SidebarNavSection>
|
|
400
|
+
</div>
|
|
347
401
|
{/each}
|
|
348
|
-
</
|
|
402
|
+
</div>
|
|
349
403
|
|
|
350
404
|
{#if filteredSecondaryItems.length > 0}
|
|
351
|
-
<
|
|
352
|
-
class="
|
|
353
|
-
|
|
405
|
+
<div
|
|
406
|
+
class="mt-3 border-t border-border pt-4"
|
|
407
|
+
role="presentation"
|
|
354
408
|
>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
409
|
+
<SidebarNavSection
|
|
410
|
+
sectionKey="secondary"
|
|
411
|
+
listAriaLabel="Secondary navigation"
|
|
412
|
+
collapsed={isCollapsed}
|
|
413
|
+
>
|
|
414
|
+
{#each filteredSecondaryItems as item (item.id)}
|
|
415
|
+
{@const Icon = item.icon}
|
|
416
|
+
<li>
|
|
417
|
+
<a
|
|
418
|
+
href={item.href}
|
|
419
|
+
class={getNavItemClasses(item)}
|
|
420
|
+
onclick={(event) =>
|
|
421
|
+
handleNavigate(item, event)}
|
|
422
|
+
aria-current={getAriaCurrent(item)}
|
|
423
|
+
aria-label={isCollapsed
|
|
424
|
+
? item.label
|
|
425
|
+
: undefined}
|
|
426
|
+
>
|
|
427
|
+
{#if Icon}
|
|
428
|
+
<span
|
|
429
|
+
class={iconContainerClasses}
|
|
430
|
+
aria-hidden="true"
|
|
431
|
+
>
|
|
432
|
+
<Icon size={18} />
|
|
433
|
+
</span>
|
|
434
|
+
{/if}
|
|
435
|
+
{#if !isCollapsed}
|
|
436
|
+
<span
|
|
437
|
+
class="text-sm font-medium leading-snug text-inherit"
|
|
438
|
+
>{item.label}</span
|
|
439
|
+
>
|
|
440
|
+
{/if}
|
|
441
|
+
</a>
|
|
442
|
+
</li>
|
|
443
|
+
{/each}
|
|
444
|
+
</SidebarNavSection>
|
|
445
|
+
</div>
|
|
385
446
|
{/if}
|
|
386
447
|
{:else}
|
|
387
448
|
<div
|
|
388
|
-
class="rounded-
|
|
449
|
+
class="rounded-xl border border-border border-dashed bg-transparent px-3.5 py-4 ring-1 ring-border/60"
|
|
389
450
|
>
|
|
390
|
-
<h3 class="text-
|
|
451
|
+
<h3 class="text-sm font-semibold {getTextToneClass()}">
|
|
391
452
|
{normalizedSearchTerm && searchMode === "input"
|
|
392
453
|
? "No matching navigation items"
|
|
393
454
|
: emptyStateTitle}
|
|
@@ -400,7 +461,7 @@
|
|
|
400
461
|
{#if !(normalizedSearchTerm && searchMode === "input")}
|
|
401
462
|
<button
|
|
402
463
|
type="button"
|
|
403
|
-
class="mt-
|
|
464
|
+
class="focus-ring focus-ring--nav 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 outline-none transition-colors hover:bg-action-primary-hover"
|
|
404
465
|
onclick={handleEmptyStateAction}
|
|
405
466
|
>
|
|
406
467
|
{emptyStateActionLabel}
|
|
@@ -408,57 +469,26 @@
|
|
|
408
469
|
{/if}
|
|
409
470
|
</div>
|
|
410
471
|
{/if}
|
|
472
|
+
</div>
|
|
411
473
|
</div>
|
|
412
474
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
{#if showThemeToggle}
|
|
434
|
-
<div class={themeToggleContainerClasses}>
|
|
435
|
-
<button
|
|
436
|
-
type="button"
|
|
437
|
-
class="inline-flex h-8 w-14 cursor-pointer items-center rounded-2xl p-1 outline-none transition-colors duration-150 hover:opacity-95 active:opacity-90 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-nav-menu-focus {isLightMode
|
|
438
|
-
? 'bg-action-primary'
|
|
439
|
-
: 'bg-base-200'}"
|
|
440
|
-
onclick={handleThemeToggle}
|
|
441
|
-
role="switch"
|
|
442
|
-
aria-checked={isLightMode}
|
|
443
|
-
title={lightModeLabel}
|
|
444
|
-
aria-label={isLightMode
|
|
445
|
-
? "Switch to dark mode"
|
|
446
|
-
: "Switch to light mode"}
|
|
447
|
-
>
|
|
448
|
-
<span
|
|
449
|
-
class="flex h-6 w-6 items-center justify-center rounded-xl leading-none shadow-sm transition-transform duration-150 active:scale-95 {isLightMode
|
|
450
|
-
? 'translate-x-6 bg-base-900 text-action-primary'
|
|
451
|
-
: 'translate-x-0 bg-base-900 text-base-50'}"
|
|
452
|
-
>
|
|
453
|
-
{#if isLightMode}
|
|
454
|
-
<Sun size={14} />
|
|
455
|
-
{:else}
|
|
456
|
-
<Moon size={14} />
|
|
457
|
-
{/if}
|
|
458
|
-
</span>
|
|
459
|
-
</button>
|
|
460
|
-
</div>
|
|
461
|
-
{/if}
|
|
462
|
-
</div>
|
|
463
|
-
{/if}
|
|
475
|
+
<SidebarFooter
|
|
476
|
+
collapsed={isCollapsed}
|
|
477
|
+
{showProfile}
|
|
478
|
+
{profileName}
|
|
479
|
+
{profileEmail}
|
|
480
|
+
{profileInitials}
|
|
481
|
+
{showLogout}
|
|
482
|
+
{logoutLabel}
|
|
483
|
+
{showThemeToggle}
|
|
484
|
+
{lightModeLabel}
|
|
485
|
+
bind:isLightMode
|
|
486
|
+
{onLogout}
|
|
487
|
+
{onThemeToggle}
|
|
488
|
+
{onProfileClick}
|
|
489
|
+
{profilePanelOpen}
|
|
490
|
+
{profilePanelControlsId}
|
|
491
|
+
{profilePanel}
|
|
492
|
+
className={insetX}
|
|
493
|
+
/>
|
|
464
494
|
</nav>
|