@kopexa/sidebar 17.1.73 → 17.2.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.
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ // src/v2/context.tsx
4
+ import { createContext } from "@kopexa/react-utils";
5
+ import { TooltipProvider } from "@kopexa/tooltip";
6
+ import { useIsMobile } from "@kopexa/use-is-mobile";
7
+ import {
8
+ useCallback,
9
+ useEffect,
10
+ useMemo,
11
+ useState
12
+ } from "react";
13
+ import { jsx } from "react/jsx-runtime";
14
+ var PIN_COOKIE = "kpx_sidebar_v2_pinned";
15
+ var PIN_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
16
+ var [Provider, useSidebarV2] = createContext({
17
+ name: "SidebarV2Context",
18
+ errorMessage: "useSidebarV2 must be used within <SidebarV2> (the provider component)."
19
+ });
20
+ var defaultRenderLink = (props) => {
21
+ const { href, className, children, ...rest } = props;
22
+ return /* @__PURE__ */ jsx("a", { href, className, ...rest, children });
23
+ };
24
+ function SidebarV2Provider({
25
+ children,
26
+ tone = "dark",
27
+ flyoutTrigger = "click",
28
+ activeHref,
29
+ renderLink = defaultRenderLink,
30
+ pinned: pinnedProp,
31
+ onPinnedChange,
32
+ defaultPinned = true
33
+ }) {
34
+ const isMobile = useIsMobile();
35
+ const [drawerOpen, setDrawerOpen] = useState(false);
36
+ const [flyoutValue, setFlyoutValue] = useState(null);
37
+ const [selectedRail, setSelectedRail] = useState(null);
38
+ const [openGroup, setOpenGroup] = useState(null);
39
+ const [pinnedUncontrolled, setPinnedUncontrolled] = useState(defaultPinned);
40
+ const pinned = pinnedProp != null ? pinnedProp : pinnedUncontrolled;
41
+ const [panelHost, setPanelHost] = useState(null);
42
+ const [overrideCount, setOverrideCount] = useState(0);
43
+ const registerPanelOverride = useCallback(() => {
44
+ setOverrideCount((c) => c + 1);
45
+ return () => setOverrideCount((c) => c - 1);
46
+ }, []);
47
+ const panelOverrideActive = overrideCount > 0;
48
+ const navPreviewActive = pinned ? selectedRail !== null : flyoutValue !== null;
49
+ const setPinned = useCallback(
50
+ (value2) => {
51
+ onPinnedChange == null ? void 0 : onPinnedChange(value2);
52
+ if (pinnedProp === void 0) {
53
+ setPinnedUncontrolled(value2);
54
+ }
55
+ document.cookie = `${PIN_COOKIE}=${value2}; path=/; max-age=${PIN_COOKIE_MAX_AGE}`;
56
+ },
57
+ [onPinnedChange, pinnedProp]
58
+ );
59
+ const togglePin = useCallback(() => {
60
+ setPinned(!pinned);
61
+ setFlyoutValue(null);
62
+ }, [pinned, setPinned]);
63
+ const openFlyout = useCallback((value2) => {
64
+ setFlyoutValue((curr) => curr === value2 ? null : value2);
65
+ }, []);
66
+ const setFlyout = useCallback((value2) => {
67
+ setFlyoutValue(value2);
68
+ }, []);
69
+ const closeFlyout = useCallback(() => setFlyoutValue(null), []);
70
+ const resetPanelSelection = useCallback(() => {
71
+ setSelectedRail(null);
72
+ setFlyoutValue(null);
73
+ }, []);
74
+ const toggleGroup = useCallback((key) => {
75
+ setOpenGroup((curr) => curr === key ? null : key);
76
+ }, []);
77
+ const isActive = useCallback(
78
+ (href) => activeHref === href || href !== "/" && activeHref.startsWith(`${href}/`),
79
+ [activeHref]
80
+ );
81
+ const value = useMemo(
82
+ () => ({
83
+ tone,
84
+ pinned,
85
+ togglePin,
86
+ setPinned,
87
+ selectedRail,
88
+ setSelectedRail,
89
+ flyoutTrigger,
90
+ flyoutValue,
91
+ openFlyout,
92
+ setFlyout,
93
+ closeFlyout,
94
+ resetPanelSelection,
95
+ openGroup,
96
+ toggleGroup,
97
+ setOpenGroup,
98
+ activeHref,
99
+ isActive,
100
+ renderLink,
101
+ isMobile,
102
+ drawerOpen,
103
+ setDrawerOpen,
104
+ panelHost,
105
+ setPanelHost,
106
+ panelOverrideActive,
107
+ registerPanelOverride,
108
+ navPreviewActive
109
+ }),
110
+ [
111
+ tone,
112
+ pinned,
113
+ togglePin,
114
+ setPinned,
115
+ selectedRail,
116
+ flyoutTrigger,
117
+ flyoutValue,
118
+ openFlyout,
119
+ setFlyout,
120
+ closeFlyout,
121
+ resetPanelSelection,
122
+ openGroup,
123
+ toggleGroup,
124
+ activeHref,
125
+ isActive,
126
+ renderLink,
127
+ isMobile,
128
+ drawerOpen,
129
+ panelHost,
130
+ panelOverrideActive,
131
+ registerPanelOverride,
132
+ navPreviewActive
133
+ ]
134
+ );
135
+ useEffect(() => {
136
+ setDrawerOpen(false);
137
+ setFlyoutValue(null);
138
+ setSelectedRail(null);
139
+ setOpenGroup(null);
140
+ }, [activeHref]);
141
+ return /* @__PURE__ */ jsx(Provider, { value, children: /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 0, children }) });
142
+ }
143
+
144
+ export {
145
+ useSidebarV2,
146
+ SidebarV2Provider
147
+ };
@@ -0,0 +1,274 @@
1
+ "use client";
2
+ import {
3
+ SidebarV2Panel,
4
+ SidebarV2PanelItems,
5
+ SidebarV2Rail,
6
+ SidebarV2RailItem,
7
+ SidebarV2RailLink
8
+ } from "./chunk-EIXUCY5M.mjs";
9
+ import {
10
+ panelItemHasChildren,
11
+ panelItemIsSection
12
+ } from "./chunk-SDMGFB6V.mjs";
13
+ import {
14
+ useSidebarV2
15
+ } from "./chunk-3L2F566G.mjs";
16
+
17
+ // src/v2/from-config.tsx
18
+ import { Drawer } from "@kopexa/drawer";
19
+ import { cn } from "@kopexa/shared-utils";
20
+ import { AnimatePresence, motion } from "motion/react";
21
+ import {
22
+ useCallback,
23
+ useEffect,
24
+ useMemo,
25
+ useRef,
26
+ useState
27
+ } from "react";
28
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
29
+ var RAIL_WIDTH = "4rem";
30
+ var PANEL_WIDTH = "15rem";
31
+ var railVars = {
32
+ "--kpx-rail-width": RAIL_WIDTH,
33
+ "--kpx-panel-width": PANEL_WIDTH
34
+ };
35
+ function entryValue(entry) {
36
+ var _a;
37
+ if (entry.type === "divider") return "divider";
38
+ return entry.type === "panel" ? (_a = entry.value) != null ? _a : entry.label : entry.label;
39
+ }
40
+ function SidebarV2FromConfig({
41
+ items,
42
+ header
43
+ }) {
44
+ var _a, _b, _c;
45
+ const {
46
+ pinned,
47
+ flyoutTrigger,
48
+ flyoutValue,
49
+ openFlyout,
50
+ setFlyout,
51
+ closeFlyout,
52
+ selectedRail,
53
+ setSelectedRail,
54
+ isActive,
55
+ isMobile,
56
+ drawerOpen,
57
+ setDrawerOpen,
58
+ setPanelHost,
59
+ panelOverrideActive,
60
+ navPreviewActive
61
+ } = useSidebarV2();
62
+ const hoverMode = flyoutTrigger === "hover" && !pinned && !isMobile;
63
+ const closeTimer = useRef(null);
64
+ const cancelClose = useCallback(() => {
65
+ if (closeTimer.current) clearTimeout(closeTimer.current);
66
+ closeTimer.current = null;
67
+ }, []);
68
+ const scheduleClose = useCallback(() => {
69
+ cancelClose();
70
+ closeTimer.current = setTimeout(() => closeFlyout(), 160);
71
+ }, [cancelClose, closeFlyout]);
72
+ useEffect(() => cancelClose, [cancelClose]);
73
+ useEffect(() => {
74
+ if (pinned || isMobile || !flyoutValue) return;
75
+ const onPointerDown = (event) => {
76
+ const t = event.target;
77
+ if (!t) return;
78
+ if (t.closest('[data-slot="sidebar-v2-rail"]')) return;
79
+ if (t.closest('[data-floating="true"]')) return;
80
+ closeFlyout();
81
+ };
82
+ const onKeyDown = (event) => {
83
+ if (event.key === "Escape") closeFlyout();
84
+ };
85
+ document.addEventListener("pointerdown", onPointerDown);
86
+ document.addEventListener("keydown", onKeyDown);
87
+ return () => {
88
+ document.removeEventListener("pointerdown", onPointerDown);
89
+ document.removeEventListener("keydown", onKeyDown);
90
+ };
91
+ }, [pinned, isMobile, flyoutValue, closeFlyout]);
92
+ const panels = useMemo(
93
+ () => items.filter(
94
+ (e) => e.type === "panel"
95
+ ),
96
+ [items]
97
+ );
98
+ const activeRailValue = useMemo(() => {
99
+ const match = panels.find(
100
+ (p) => p.items.some((item) => {
101
+ if (panelItemIsSection(item)) return false;
102
+ return panelItemHasChildren(item) ? item.children.some((c) => isActive(c.href)) : isActive(item.href);
103
+ })
104
+ );
105
+ return match ? entryValue(match) : null;
106
+ }, [panels, isActive]);
107
+ const shownValue = pinned ? selectedRail != null ? selectedRail : activeRailValue : flyoutValue;
108
+ const shownPanel = (_a = panels.find((p) => entryValue(p) === shownValue)) != null ? _a : null;
109
+ const topEntries = items.filter((e) => e.slot !== "bottom");
110
+ const bottomEntries = items.filter((e) => e.slot === "bottom");
111
+ function renderRailEntry(entry, index) {
112
+ if (entry.type === "divider") {
113
+ return /* @__PURE__ */ jsx(
114
+ "div",
115
+ {
116
+ "aria-hidden": true,
117
+ className: "mx-3 my-1.5 h-px shrink-0 bg-sidebar-border/70"
118
+ },
119
+ `rail-divider-${index}`
120
+ );
121
+ }
122
+ const value = entryValue(entry);
123
+ if (entry.type === "link") {
124
+ return /* @__PURE__ */ jsx(
125
+ SidebarV2RailLink,
126
+ {
127
+ icon: entry.icon,
128
+ label: entry.label,
129
+ href: entry.href,
130
+ badge: entry.badge
131
+ },
132
+ value
133
+ );
134
+ }
135
+ if (entry.type === "action") {
136
+ return /* @__PURE__ */ jsx(
137
+ SidebarV2RailItem,
138
+ {
139
+ icon: entry.icon,
140
+ label: entry.label,
141
+ active: false,
142
+ onClick: entry.onSelect
143
+ },
144
+ value
145
+ );
146
+ }
147
+ const active = flyoutValue ? value === flyoutValue : value === (selectedRail != null ? selectedRail : activeRailValue);
148
+ return /* @__PURE__ */ jsx(
149
+ SidebarV2RailItem,
150
+ {
151
+ icon: entry.icon,
152
+ label: entry.label,
153
+ active,
154
+ hasPanel: true,
155
+ onClick: () => {
156
+ if (pinned) setSelectedRail(value);
157
+ else openFlyout(value);
158
+ },
159
+ onMouseEnter: hoverMode ? () => {
160
+ cancelClose();
161
+ setFlyout(value);
162
+ } : void 0,
163
+ onMouseLeave: hoverMode ? scheduleClose : void 0
164
+ },
165
+ value
166
+ );
167
+ }
168
+ const railContent = /* @__PURE__ */ jsxs(Fragment, { children: [
169
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: [
170
+ header && /* @__PURE__ */ jsxs(Fragment, { children: [
171
+ header,
172
+ /* @__PURE__ */ jsx("div", { className: "mx-3 my-1 h-px shrink-0 bg-sidebar-border/60" })
173
+ ] }),
174
+ topEntries.map(renderRailEntry)
175
+ ] }),
176
+ bottomEntries.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-col gap-1 pt-1", children: bottomEntries.map(renderRailEntry) })
177
+ ] });
178
+ const navData = pinned && shownPanel && (navPreviewActive || !panelOverrideActive) ? shownPanel : null;
179
+ const [bufferedPanel, setBufferedPanel] = useState(navData);
180
+ useEffect(() => {
181
+ if (navData) {
182
+ setBufferedPanel(navData);
183
+ return;
184
+ }
185
+ const t = setTimeout(() => setBufferedPanel(null), 220);
186
+ return () => clearTimeout(t);
187
+ }, [navData]);
188
+ const navToRender = navData != null ? navData : bufferedPanel;
189
+ const renderNav = (!panelOverrideActive || navPreviewActive) && navToRender;
190
+ const panelContent = navToRender && renderNav ? /* @__PURE__ */ jsx(
191
+ SidebarV2Panel,
192
+ {
193
+ title: (_b = navToRender.title) != null ? _b : navToRender.label,
194
+ subtitle: navToRender.subtitle,
195
+ children: /* @__PURE__ */ jsx(SidebarV2PanelItems, { items: navToRender.items })
196
+ },
197
+ entryValue(navToRender)
198
+ ) : null;
199
+ const docked = Boolean(navData) || panelOverrideActive;
200
+ const showFlyout = !pinned && Boolean(shownPanel);
201
+ if (isMobile) {
202
+ return /* @__PURE__ */ jsx(
203
+ Drawer.Root,
204
+ {
205
+ open: drawerOpen,
206
+ onOpenChange: setDrawerOpen,
207
+ placement: "left",
208
+ children: /* @__PURE__ */ jsxs(Drawer.Content, { className: "w-auto max-w-[85vw] border-0 bg-sidebar p-0 [&>button]:hidden", children: [
209
+ /* @__PURE__ */ jsxs(Drawer.Header, { className: "sr-only", children: [
210
+ /* @__PURE__ */ jsx(Drawer.Title, { children: "Navigation" }),
211
+ /* @__PURE__ */ jsx(Drawer.Description, { children: "Hauptnavigation" })
212
+ ] }),
213
+ /* @__PURE__ */ jsxs("div", { className: "flex h-full", style: railVars, children: [
214
+ /* @__PURE__ */ jsx(SidebarV2Rail, { children: railContent }),
215
+ /* @__PURE__ */ jsx(
216
+ "div",
217
+ {
218
+ ref: setPanelHost,
219
+ "data-slot": "sidebar-v2-panel-zone",
220
+ className: "flex min-h-0",
221
+ children: panelContent
222
+ }
223
+ )
224
+ ] })
225
+ ] })
226
+ }
227
+ );
228
+ }
229
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ /* @__PURE__ */ jsx(SidebarV2Rail, { style: { gridArea: "rail" }, children: railContent }),
231
+ /* @__PURE__ */ jsxs(
232
+ "div",
233
+ {
234
+ ref: setPanelHost,
235
+ "data-slot": "sidebar-v2-panel-zone",
236
+ className: cn(
237
+ "relative shrink-0 transition-[width] duration-200 ease-out motion-reduce:transition-none",
238
+ pinned && "overflow-hidden"
239
+ ),
240
+ style: { gridArea: "panel", width: docked ? PANEL_WIDTH : "0px" },
241
+ children: [
242
+ panelContent,
243
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showFlyout && shownPanel && /* @__PURE__ */ jsx(
244
+ motion.div,
245
+ {
246
+ "data-floating": "true",
247
+ className: "absolute inset-y-2 left-1 z-30",
248
+ initial: { x: -14, opacity: 0 },
249
+ animate: { x: 0, opacity: 1 },
250
+ exit: { x: -14, opacity: 0 },
251
+ transition: { duration: 0.16, ease: "easeOut" },
252
+ onMouseEnter: hoverMode ? cancelClose : void 0,
253
+ onMouseLeave: hoverMode ? scheduleClose : void 0,
254
+ children: /* @__PURE__ */ jsx(
255
+ SidebarV2Panel,
256
+ {
257
+ floating: true,
258
+ title: (_c = shownPanel.title) != null ? _c : shownPanel.label,
259
+ subtitle: shownPanel.subtitle,
260
+ children: /* @__PURE__ */ jsx(SidebarV2PanelItems, { items: shownPanel.items })
261
+ }
262
+ )
263
+ },
264
+ "sidebar-v2-flyout"
265
+ ) })
266
+ ]
267
+ }
268
+ )
269
+ ] });
270
+ }
271
+
272
+ export {
273
+ SidebarV2FromConfig
274
+ };