@datum-cloud/datum-ui 2.1.0 → 2.3.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.
@@ -1,3 +1,3 @@
1
- import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "../sidebar-BRUNOqXi.mjs";
2
- import { a as hasActiveDescendant, i as findItemByHref, n as NavMenu, o as isNavItemActive, r as NavMenuItem, s as normalizePath, t as AppNavigation } from "../app-navigation-CNig4Maj.mjs";
1
+ import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "../sidebar-D6BoAr6C.mjs";
2
+ import { a as hasActiveDescendant, i as findItemByHref, n as NavMenu, o as isNavItemActive, r as NavMenuItem, s as normalizePath, t as AppNavigation } from "../app-navigation--tEBCrfl.mjs";
3
3
  export { AppNavigation, NavMenu, NavMenuItem, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, findItemByHref, hasActiveDescendant, isNavItemActive, normalizePath, useSidebar };
@@ -1,7 +1,7 @@
1
1
  import { t as cn } from "./utils-Bu32wN-x.mjs";
2
2
  import { t as Icon } from "./icon-wrapper-DKfJlJd0.mjs";
3
3
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./collapsible/index.mjs";
4
- import { S as SidebarGroupLabel, c as SidebarSeparator, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, p as SidebarMenuButton, r as SidebarFooter, t as Sidebar, u as SidebarMenu, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "./sidebar-BRUNOqXi.mjs";
4
+ import { c as SidebarSeparator, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, p as SidebarMenuButton, r as SidebarFooter, t as Sidebar, u as SidebarMenu, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "./sidebar-D6BoAr6C.mjs";
5
5
  import { ChevronRight, ExternalLinkIcon } from "lucide-react";
6
6
  import { createContext, memo, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -132,28 +132,66 @@ function NavSidebarMenuButton({ ref, item, isActive, disableTooltip, className,
132
132
  NavSidebarMenuButton.displayName = "NavSidebarMenuButton";
133
133
  //#endregion
134
134
  //#region src/components/features/app-navigation/nav-menu/nav-menu-item.tsx
135
+ const COLLAPSIBLE_CONTAINER_VARIANTS = {
136
+ hidden: { opacity: 0 },
137
+ visible: {
138
+ opacity: 1,
139
+ transition: {
140
+ staggerChildren: .04,
141
+ delayChildren: .06
142
+ }
143
+ }
144
+ };
145
+ const COLLAPSIBLE_CHILD_VARIANTS = {
146
+ hidden: {
147
+ opacity: 0,
148
+ y: -4
149
+ },
150
+ visible: {
151
+ opacity: 1,
152
+ y: 0,
153
+ transition: {
154
+ duration: .2,
155
+ ease: [
156
+ .32,
157
+ .72,
158
+ 0,
159
+ 1
160
+ ]
161
+ }
162
+ }
163
+ };
135
164
  /** Stable React key for a rendered nav item at a given depth. */
136
165
  function itemKeyOf(item, level) {
137
166
  return `${item.title}-${item.href || ""}-${level}`;
138
167
  }
139
168
  /**
169
+ * Cloudflare-style nav status chip: short inline pill with a dashed border,
170
+ * sitting immediately after the title (never overlapping / corner-pinned).
171
+ * Uses `div` (not `span`) so SidebarMenuButton's `[&>span:last-child]:truncate`
172
+ * does not clip the chip.
173
+ */
174
+ function NavBadge({ badge }) {
175
+ return /* @__PURE__ */ jsx("div", {
176
+ className: cn("text-muted-foreground inline-flex shrink-0 items-center rounded-full border border-dashed px-1.5 py-px", "text-[10px] leading-none font-medium tracking-wide", "border-border/80"),
177
+ children: badge.label
178
+ });
179
+ }
180
+ /**
140
181
  * Recursive nav row. Dispatches to the correct presentational part based on the
141
182
  * item type and the current sidebar state, and renders itself for children.
142
183
  * Memoized so unchanged subtrees skip re-rendering when their props are stable.
184
+ *
185
+ * Items with children use {@link NavCollapsibleItem}. Section headers use
186
+ * {@link NavGroup} (always open, no dropdown).
143
187
  */
144
188
  const NavMenuItem = memo(({ item, level = 0 }) => {
145
- const { state, isMobile } = useNavMenuContext();
146
189
  if (item.hidden) return null;
147
190
  if (item.type === "group") return /* @__PURE__ */ jsx(NavGroup, {
148
191
  item,
149
192
  level
150
193
  });
151
- const hasChildren = (item.children || []).length > 0;
152
- if (state === "collapsed" && !isMobile && level <= 2 && hasChildren) return /* @__PURE__ */ jsx(NavCollapsedItem, {
153
- item,
154
- level
155
- });
156
- if (hasChildren) return /* @__PURE__ */ jsx(NavCollapsibleItem, {
194
+ if ((item.children || []).length > 0) return /* @__PURE__ */ jsx(NavCollapsibleItem, {
157
195
  item,
158
196
  level
159
197
  });
@@ -163,116 +201,85 @@ const NavMenuItem = memo(({ item, level = 0 }) => {
163
201
  });
164
202
  });
165
203
  NavMenuItem.displayName = "NavMenuItem";
166
- /** A labelled group of nav items (recurses into `NavMenuItem` for children). */
204
+ /** A labelled group of nav items (always open Vercel-style section headers). */
167
205
  function NavGroup({ item, level }) {
168
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(SidebarGroup, {
169
- className: "mb-2 p-0! px-2",
170
- children: [item.title && /* @__PURE__ */ jsx(SidebarGroupLabel, {
171
- className: "lowercase group-data-[state=collapsed]:hidden first-letter:uppercase",
172
- children: item.title
173
- }), /* @__PURE__ */ jsx(SidebarGroupContent, {
174
- className: "flex flex-col gap-1",
175
- children: (item.children || []).map((child) => /* @__PURE__ */ jsx(NavMenuItem, {
176
- item: child,
177
- level: level + 1
178
- }, itemKeyOf(child, level + 1)))
179
- })]
180
- }), /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-2 hidden group-data-[state=collapsed]:block" })] });
181
- }
182
- /**
183
- * Collapsed-sidebar rendering for an item with children: the parent button
184
- * expands the sidebar, and active descendants are represented as dots.
185
- */
186
- function NavCollapsedItem({ item, level }) {
187
206
  const ctx = useNavMenuContext();
188
- const { pathname, getLinkProps, handleNavigation } = ctx;
189
- const stateKey = getNavItemKey(item, level);
190
- const isActive = isNavItemActive(item, pathname);
191
- const hasActiveChild = (item.children?.filter((child) => hasActiveDescendant(child, pathname)) || []).length > 0;
192
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
207
+ const { pathname, isIconRail, isMobile } = ctx;
208
+ const hasActiveChild = item.children?.some((child) => hasActiveDescendant(child, pathname)) ?? false;
209
+ if (isIconRail && !isMobile) return /* @__PURE__ */ jsxs(Fragment$1, { children: [
193
210
  item.showSeparatorAbove && /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-1" }),
194
- /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsxs("div", {
195
- className: "flex flex-col px-2",
196
- children: [/* @__PURE__ */ jsx(NavSidebarMenuButton, {
211
+ /* @__PURE__ */ jsx(SidebarMenu, {
212
+ className: "px-2",
213
+ children: /* @__PURE__ */ jsx(NavSidebarMenuButton, {
197
214
  item,
198
- isActive,
215
+ isActive: hasActiveChild,
199
216
  disableTooltip: ctx.disableTooltip,
200
- className: ctx.itemClassName,
217
+ className: cn(ctx.itemClassName, hasActiveChild && "[&>svg:first-of-type]:text-primary"),
201
218
  activeClassName: ctx.activeItemClassName,
202
- onClick: () => {
203
- ctx.setOpen(true);
204
- ctx.setOpenItems((prev) => ({
205
- ...prev,
206
- [stateKey]: true
207
- }));
208
- }
209
- }), hasActiveChild && /* @__PURE__ */ jsx(motion.div, {
210
- variants: {
211
- hidden: { opacity: 0 },
212
- visible: {
213
- opacity: 1,
214
- transition: {
215
- staggerChildren: .05,
216
- delayChildren: .1
217
- }
218
- }
219
- },
220
- initial: "hidden",
221
- animate: "visible",
222
- className: "flex flex-col gap-0.5",
223
- children: item.children?.map((subItem) => {
224
- const isSubItemActive = isNavItemActive(subItem, pathname);
225
- return /* @__PURE__ */ jsx(motion.div, {
226
- variants: {
227
- hidden: { opacity: 0 },
228
- visible: {
229
- opacity: 1,
230
- transition: {
231
- duration: .2,
232
- ease: "easeOut"
233
- }
234
- }
235
- },
236
- children: /* @__PURE__ */ jsx(SidebarMenuButton, {
237
- tooltip: subItem.title,
238
- isActive: isSubItemActive,
239
- className: "h-6 p-0 group-data-[collapsible=icon]:h-6! group-data-[collapsible=icon]:p-0!",
240
- asChild: true,
241
- children: /* @__PURE__ */ jsx(ctx.linkComponent, {
242
- className: "flex items-center justify-center",
243
- ...getLinkProps(subItem.href || ""),
244
- onClick: () => {
245
- handleNavigation();
246
- },
247
- children: /* @__PURE__ */ jsx("span", { className: cn("size-1 rounded-full", isSubItemActive ? "bg-primary" : "bg-sidebar-primary-foreground") })
248
- })
249
- })
250
- }, `collapsed-dot-${subItem.href}-${level}`);
219
+ onClick: () => ctx.setOpen(true)
220
+ })
221
+ }),
222
+ item.showSeparatorBelow && /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-2" })
223
+ ] });
224
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
225
+ item.showSeparatorAbove && /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-2" }),
226
+ /* @__PURE__ */ jsxs(SidebarGroup, {
227
+ className: "mb-1 p-0! px-2",
228
+ children: [item.title && /* @__PURE__ */ jsx(SidebarMenu, {
229
+ className: "w-full",
230
+ children: /* @__PURE__ */ jsx(NavSidebarMenuButton, {
231
+ item,
232
+ isActive: hasActiveChild,
233
+ disableTooltip: true,
234
+ className: cn(ctx.itemClassName, hasActiveChild && "[&>svg:first-of-type]:text-primary", "pointer-events-none h-7 text-xs font-medium text-muted-foreground hover:bg-transparent", hasActiveChild && "text-primary"),
235
+ activeClassName: ctx.activeItemClassName,
236
+ children: /* @__PURE__ */ jsx("span", {
237
+ className: "min-w-0 truncate",
238
+ children: item.title
239
+ })
251
240
  })
241
+ }), /* @__PURE__ */ jsx(SidebarGroupContent, {
242
+ className: "flex flex-col gap-0.5",
243
+ children: (item.children || []).map((child) => /* @__PURE__ */ jsx(NavMenuItem, {
244
+ item: child,
245
+ level: level + 1
246
+ }, itemKeyOf(child, level + 1)))
252
247
  })]
253
- }) }),
248
+ }),
254
249
  item.showSeparatorBelow && /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-2" })
255
250
  ] });
256
251
  }
257
252
  /**
258
- * Expanded-sidebar rendering for an item with children: a collapsible section
259
- * whose contents recurse into `NavMenuItem`.
253
+ * Collapsible section for items with children. Used in both expanded and
254
+ * icon-collapsed sidebar modes so width animation does not remount the tree.
255
+ * When the sidebar is icon-collapsed, the panel is forced closed; clicking the
256
+ * parent expands the sidebar and opens this section.
260
257
  */
261
258
  function NavCollapsibleItem({ item, level }) {
262
259
  const ctx = useNavMenuContext();
263
- const { pathname, state, openItems, isInitialMount, previousOpenItems, previousState, previousPathname } = ctx;
260
+ const { pathname, state, isIconRail, isMobile, openItems, isInitialMount, previousOpenItems, previousState, previousPathname } = ctx;
264
261
  const stateKey = getNavItemKey(item, level);
265
262
  const isActive = isNavItemActive(item, pathname);
266
263
  const hasActiveChild = (item.children?.filter((child) => hasActiveDescendant(child, pathname)) || []).length > 0;
264
+ const sidebarCollapsed = isIconRail && !isMobile;
267
265
  const isOpen = openItems[stateKey] !== void 0 ? openItems[stateKey] : hasActiveChild;
266
+ const panelOpen = sidebarCollapsed ? false : isOpen;
268
267
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
269
268
  item.showSeparatorAbove && /* @__PURE__ */ jsx(SidebarSeparator, { className: "my-2" }),
270
269
  /* @__PURE__ */ jsx(SidebarMenu, {
271
270
  className: "px-2",
272
271
  children: /* @__PURE__ */ jsx(Collapsible, {
273
272
  asChild: true,
274
- open: isOpen,
273
+ open: panelOpen,
275
274
  onOpenChange: (open) => {
275
+ if (sidebarCollapsed) {
276
+ ctx.setOpen(true);
277
+ ctx.setOpenItems((prev) => ({
278
+ ...prev,
279
+ [stateKey]: true
280
+ }));
281
+ return;
282
+ }
276
283
  ctx.setOpenItems((prev) => ({
277
284
  ...prev,
278
285
  [stateKey]: open
@@ -286,55 +293,47 @@ function NavCollapsibleItem({ item, level }) {
286
293
  className: "w-full",
287
294
  children: /* @__PURE__ */ jsxs(NavSidebarMenuButton, {
288
295
  item,
289
- isActive,
296
+ isActive: isActive || sidebarCollapsed && hasActiveChild,
290
297
  disableTooltip: ctx.disableTooltip,
291
- className: ctx.itemClassName,
298
+ className: cn(ctx.itemClassName, hasActiveChild && "font-semibold [&>svg:first-of-type]:text-primary"),
292
299
  activeClassName: ctx.activeItemClassName,
293
- children: [/* @__PURE__ */ jsx("span", { children: item.title }), /* @__PURE__ */ jsx(Icon, {
294
- icon: ChevronRight,
295
- className: "ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
296
- })]
300
+ children: [
301
+ /* @__PURE__ */ jsx("span", {
302
+ className: "min-w-0 truncate group-data-[collapsible=icon]:hidden",
303
+ children: item.title
304
+ }),
305
+ item.badge && /* @__PURE__ */ jsx("span", {
306
+ className: "group-data-[collapsible=icon]:hidden",
307
+ children: /* @__PURE__ */ jsx(NavBadge, { badge: item.badge })
308
+ }),
309
+ /* @__PURE__ */ jsx(Icon, {
310
+ icon: ChevronRight,
311
+ className: "ml-auto shrink-0 transition-transform duration-200 group-data-[collapsible=icon]:hidden group-data-[state=open]/collapsible:rotate-90"
312
+ })
313
+ ]
297
314
  })
298
315
  }), /* @__PURE__ */ jsx(CollapsibleContent, {
299
- className: "data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up overflow-hidden",
316
+ className: "data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up overflow-hidden group-data-[collapsible=icon]:hidden",
300
317
  children: /* @__PURE__ */ jsx("div", {
301
318
  style: {
302
319
  minHeight: 0,
303
320
  overflow: "hidden"
304
321
  },
305
322
  children: /* @__PURE__ */ jsx(motion.div, {
306
- variants: {
307
- hidden: { opacity: 0 },
308
- visible: {
309
- opacity: 1,
310
- transition: {
311
- staggerChildren: .05,
312
- delayChildren: .1
313
- }
314
- }
315
- },
323
+ variants: COLLAPSIBLE_CONTAINER_VARIANTS,
316
324
  initial: isInitialMount.current || previousOpenItems.current[stateKey] === openItems[stateKey] && previousState.current === state && previousPathname.current === pathname && !hasActiveChild ? "visible" : "hidden",
317
- animate: isOpen ? "visible" : "hidden",
325
+ animate: panelOpen ? "visible" : "hidden",
318
326
  children: /* @__PURE__ */ jsx(SidebarMenuSub, {
319
327
  className: cn(level >= 1 ? "mr-0 pr-[.1rem]" : "", level === 2 ? "pl-4" : "", level === 3 ? "pl-6" : "", "mr-0 gap-0.5 pr-0"),
320
328
  children: item.children?.map((subItem, index) => /* @__PURE__ */ jsx(motion.div, {
321
- variants: {
322
- hidden: { opacity: 0 },
323
- visible: {
324
- opacity: 1,
325
- transition: {
326
- duration: .2,
327
- ease: "easeOut"
328
- }
329
- }
330
- },
329
+ variants: COLLAPSIBLE_CHILD_VARIANTS,
331
330
  children: /* @__PURE__ */ jsx(NavMenuItem, {
332
331
  item: subItem,
333
332
  level: level + 1
334
333
  })
335
334
  }, `${subItem.href}-${level}-${index}`))
336
335
  })
337
- }, `collapsible-${stateKey}-${isOpen}`)
336
+ }, `collapsible-${stateKey}-${panelOpen}`)
338
337
  })
339
338
  })]
340
339
  })
@@ -360,31 +359,49 @@ function NavLeafItem({ item, level }) {
360
359
  item,
361
360
  isActive: isActive && !hasActiveChild,
362
361
  disableTooltip: ctx.disableTooltip,
363
- className: cn(level >= 1 && "h-6", ctx.itemClassName),
362
+ className: cn(level >= 1 && "h-7", item.muted && "[&_svg]:text-muted-foreground", ctx.itemClassName),
364
363
  activeClassName: ctx.activeItemClassName,
365
364
  children: item.type === "externalLink" ? /* @__PURE__ */ jsxs("a", {
366
365
  href: item.href || "",
367
366
  target: "_blank",
368
367
  rel: "noopener noreferrer",
369
- className: "flex items-center justify-between",
370
- children: [/* @__PURE__ */ jsxs("div", {
371
- className: "flex items-center gap-2",
372
- children: [item?.icon && /* @__PURE__ */ jsx(Icon, {
368
+ className: "flex min-w-0 items-center gap-2",
369
+ children: [
370
+ item?.icon && /* @__PURE__ */ jsx(Icon, {
373
371
  icon: item.icon,
374
- className: "size-4 transition-all duration-300"
375
- }), /* @__PURE__ */ jsx("span", { children: item.title })]
376
- }), /* @__PURE__ */ jsx(Icon, {
377
- icon: ExternalLinkIcon,
378
- className: "ml-auto size-4"
379
- })]
372
+ className: "size-4 shrink-0 transition-all duration-300"
373
+ }),
374
+ /* @__PURE__ */ jsx("span", {
375
+ className: "min-w-0 truncate group-data-[collapsible=icon]:hidden",
376
+ children: item.title
377
+ }),
378
+ item.badge ? /* @__PURE__ */ jsx("span", {
379
+ className: "group-data-[collapsible=icon]:hidden",
380
+ children: /* @__PURE__ */ jsx(NavBadge, { badge: item.badge })
381
+ }) : /* @__PURE__ */ jsx(Icon, {
382
+ icon: ExternalLinkIcon,
383
+ className: "ml-auto size-4 shrink-0 group-data-[collapsible=icon]:hidden"
384
+ })
385
+ ]
380
386
  }) : /* @__PURE__ */ jsxs(ctx.linkComponent, {
381
387
  ...getLinkProps(item.href || ""),
382
388
  onClick: handleNavigation,
383
389
  onMouseEnter: () => item.onPrefetch?.(),
384
- children: [item?.icon && /* @__PURE__ */ jsx(Icon, {
385
- icon: item.icon,
386
- className: "text-sidebar-primary transition-all duration-300"
387
- }), /* @__PURE__ */ jsx("span", { children: item.title })]
390
+ className: "flex min-w-0 items-center gap-2",
391
+ children: [
392
+ item?.icon && /* @__PURE__ */ jsx(Icon, {
393
+ icon: item.icon,
394
+ className: "text-sidebar-primary shrink-0 transition-all duration-300"
395
+ }),
396
+ /* @__PURE__ */ jsx("span", {
397
+ className: "min-w-0 truncate group-data-[collapsible=icon]:hidden",
398
+ children: item.title
399
+ }),
400
+ item.badge && /* @__PURE__ */ jsx("span", {
401
+ className: "group-data-[collapsible=icon]:hidden",
402
+ children: /* @__PURE__ */ jsx(NavBadge, { badge: item.badge })
403
+ })
404
+ ]
388
405
  })
389
406
  })
390
407
  })
@@ -396,12 +413,13 @@ function NavLeafItem({ item, level }) {
396
413
  //#region src/components/features/app-navigation/nav-menu/nav-menu.tsx
397
414
  function NavMenu({ ref, className, items, currentPath, linkComponent: LinkComp = "a", overrideState, itemClassName, activeItemClassName, disableTooltip, closeOnNavigation, ...props }) {
398
415
  const pathname = currentPath;
399
- const { state: sidebarState, isMobile, closeForNavigation, setOpen } = useSidebar();
416
+ const { state: sidebarState, isIconRail: sidebarIsIconRail, isMobile, closeForNavigation, setOpen } = useSidebar();
400
417
  const [openItems, setOpenItems] = useState({});
401
418
  const isInitialMount = useRef(true);
402
419
  const previousOpenItems = useRef({});
403
420
  const previousPathname = useRef(pathname);
404
421
  const state = overrideState ?? sidebarState;
422
+ const isIconRail = overrideState ? overrideState === "collapsed" : sidebarIsIconRail;
405
423
  const previousState = useRef(state);
406
424
  useEffect(() => {
407
425
  previousOpenItems.current = openItems;
@@ -443,6 +461,7 @@ function NavMenu({ ref, className, items, currentPath, linkComponent: LinkComp =
443
461
  const contextValue = useMemo(() => ({
444
462
  pathname,
445
463
  state,
464
+ isIconRail,
446
465
  isMobile,
447
466
  openItems,
448
467
  setOpenItems,
@@ -460,6 +479,7 @@ function NavMenu({ ref, className, items, currentPath, linkComponent: LinkComp =
460
479
  }), [
461
480
  pathname,
462
481
  state,
482
+ isIconRail,
463
483
  isMobile,
464
484
  openItems,
465
485
  setOpen,
@@ -540,37 +560,33 @@ function AppNavigation({ navItems, title, closeOnNavigation, defaultOpen, curren
540
560
  useEffect(() => {
541
561
  if (defaultOpen === false) setOpen(false);
542
562
  }, [defaultOpen, setOpen]);
543
- return /* @__PURE__ */ jsx(Sidebar, {
563
+ return /* @__PURE__ */ jsxs(Sidebar, {
544
564
  collapsible: props.collapsible ?? "offcanvas",
545
565
  ...props,
546
- children: /* @__PURE__ */ jsxs(SidebarContent, {
566
+ children: [/* @__PURE__ */ jsxs(SidebarContent, {
547
567
  className: "gap-0",
548
- children: [
549
- title && /* @__PURE__ */ jsx(SidebarHeader, {
550
- className: "px-4 pt-4 pb-0",
551
- children: title
552
- }),
553
- loading ? /* @__PURE__ */ jsx(SidebarGroup, {
554
- className: "mb-2 p-0! px-0",
555
- children: /* @__PURE__ */ jsx(SidebarGroupContent, {
556
- className: "flex flex-col gap-0 py-0",
557
- children: /* @__PURE__ */ jsx(NavSkeleton, {})
558
- })
559
- }) : navItems.length > 0 && /* @__PURE__ */ jsx(NavMenu, {
560
- className: "h-fit py-2",
561
- items: navItems,
562
- currentPath,
563
- linkComponent,
564
- closeOnNavigation,
565
- itemClassName,
566
- activeItemClassName
567
- }),
568
- props.collapsible !== "none" && /* @__PURE__ */ jsx(SidebarFooter, {
569
- className: "mt-auto p-2",
570
- children: /* @__PURE__ */ jsx(SidebarTrigger, {})
568
+ children: [title && /* @__PURE__ */ jsx(SidebarHeader, {
569
+ className: "px-4 pt-4 pb-0",
570
+ children: title
571
+ }), loading ? /* @__PURE__ */ jsx(SidebarGroup, {
572
+ className: "mb-2 p-0! px-0",
573
+ children: /* @__PURE__ */ jsx(SidebarGroupContent, {
574
+ className: "flex flex-col gap-0 py-0",
575
+ children: /* @__PURE__ */ jsx(NavSkeleton, {})
571
576
  })
572
- ]
573
- })
577
+ }) : navItems.length > 0 && /* @__PURE__ */ jsx(NavMenu, {
578
+ className: "h-fit py-2",
579
+ items: navItems,
580
+ currentPath,
581
+ linkComponent,
582
+ closeOnNavigation,
583
+ itemClassName,
584
+ activeItemClassName
585
+ })]
586
+ }), props.collapsible !== "none" && /* @__PURE__ */ jsx(SidebarFooter, {
587
+ className: "border-sidebar-border shrink-0 border-t p-2",
588
+ children: /* @__PURE__ */ jsx(SidebarTrigger, {})
589
+ })]
574
590
  });
575
591
  }
576
592
  //#endregion
@@ -4,6 +4,8 @@ declare const SIDEBAR_WIDTH_MOBILE = "18rem";
4
4
  declare const SIDEBAR_WIDTH_ICON = "3rem";
5
5
  interface SidebarContext {
6
6
  state: 'expanded' | 'collapsed';
7
+ /** True when the sidebar is width-collapsed (icon rail). */
8
+ isIconRail: boolean;
7
9
  open: boolean;
8
10
  setOpen: (open: boolean) => void;
9
11
  openMobile: boolean;
@@ -12,6 +14,7 @@ interface SidebarContext {
12
14
  toggleSidebar: () => void;
13
15
  handleMouseEnter: () => void;
14
16
  handleMouseLeave: () => void;
17
+ cancelHoverLeave: () => void;
15
18
  forceClose: () => void;
16
19
  closeForNavigation: () => void;
17
20
  hasSubLayout: boolean;
@@ -12,7 +12,7 @@ declare function SidebarRail({ className, ...props }: React.ComponentProps<'butt
12
12
  declare function SidebarInset({ className, ...props }: React.ComponentProps<'main'>): React.JSX.Element;
13
13
  declare function SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>): React.JSX.Element;
14
14
  declare function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
15
- declare function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
15
+ declare function SidebarFooter({ className, onMouseEnter, onMouseLeave, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
16
16
  declare function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>): React.JSX.Element;
17
- declare function SidebarContent({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
17
+ declare function SidebarContent({ className, onMouseEnter, onMouseLeave, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
18
18
  export { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInput, SidebarInset, SidebarRail, SidebarSeparator, SidebarTrigger, };
@@ -1,4 +1,4 @@
1
1
  export { findItemByHref, hasActiveDescendant, isNavItemActive, normalizePath, } from './active-path';
2
2
  export { NavMenu } from './nav-menu';
3
3
  export { NavMenuItem } from './nav-menu-item';
4
- export type { NavItem } from './types';
4
+ export type { NavItem, NavItemBadge } from './types';
@@ -11,6 +11,10 @@ export interface NavMenuContextValue {
11
11
  pathname: string;
12
12
  /** Resolved sidebar state (override wins over the sidebar provider). */
13
13
  state: 'expanded' | 'collapsed';
14
+ /**
15
+ * True when the sidebar is in icon-rail (collapsed) mode.
16
+ */
17
+ isIconRail: boolean;
14
18
  isMobile: boolean;
15
19
  openItems: Record<string, boolean>;
16
20
  setOpenItems: Dispatch<SetStateAction<Record<string, boolean>>>;
@@ -5,6 +5,9 @@ declare function itemKeyOf(item: NavItem, level: number): string;
5
5
  * Recursive nav row. Dispatches to the correct presentational part based on the
6
6
  * item type and the current sidebar state, and renders itself for children.
7
7
  * Memoized so unchanged subtrees skip re-rendering when their props are stable.
8
+ *
9
+ * Items with children use {@link NavCollapsibleItem}. Section headers use
10
+ * {@link NavGroup} (always open, no dropdown).
8
11
  */
9
12
  export declare const NavMenuItem: import("react").MemoExoticComponent<({ item, level }: {
10
13
  item: NavItem;
@@ -1,4 +1,18 @@
1
1
  import type { LucideIcon } from 'lucide-react';
2
+ import type { BadgeProps } from '../../../base/badge';
3
+ /**
4
+ * Declarative status chip rendered inline after a nav item title
5
+ * (Cloudflare-style dashed pill: "Beta", "New", "Soon").
6
+ * Data-only so hosts/plugins never pass React nodes into the sidebar.
7
+ *
8
+ * `type` / `theme` are retained for API compatibility but unused — nav chips
9
+ * always use the shared dashed-pill treatment.
10
+ */
11
+ export interface NavItemBadge {
12
+ label: string;
13
+ type?: BadgeProps['type'];
14
+ theme?: BadgeProps['theme'];
15
+ }
2
16
  export interface NavItem {
3
17
  title: string;
4
18
  href: string | null;
@@ -11,6 +25,10 @@ export interface NavItem {
11
25
  hidden?: boolean;
12
26
  showSeparatorAbove?: boolean;
13
27
  showSeparatorBelow?: boolean;
28
+ /** Optional status badge (e.g. "Coming soon", "Beta"). */
29
+ badge?: NavItemBadge;
30
+ /** Soften label/icon color (e.g. planned services) without disabling the link. */
31
+ muted?: boolean;
14
32
  excludePaths?: string[];
15
33
  tabChildLinks?: string[];
16
34
  /** Called when the user hovers over the link (e.g. to prefetch route data). */
package/dist/index.mjs CHANGED
@@ -33,7 +33,7 @@ import { t as useBreakpoint } from "./use-breakpoint-CwiUJSmk.mjs";
33
33
  import { t as ResponsiveDropdown } from "./responsive-dropdown-n0Txkax5.mjs";
34
34
  import { t as ResponsivePopover } from "./responsive-popover-C_1nzIvm.mjs";
35
35
  import { a as SelectLabel, c as SelectSeparator, i as SelectItem, l as SelectTrigger, n as SelectContent, o as SelectScrollDownButton, r as SelectGroup, s as SelectScrollUpButton, t as Select, u as SelectValue } from "./select-DCU44UO6.mjs";
36
- import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "./sidebar-BRUNOqXi.mjs";
36
+ import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "./sidebar-D6BoAr6C.mjs";
37
37
  import { t as Tooltip } from "./tooltip-C_SL1imw.mjs";
38
38
  import { Skeleton } from "./skeleton/index.mjs";
39
39
  import { t as Switch } from "./switch-DPHr3TcN.mjs";
@@ -42,7 +42,7 @@ import { Tabs, TabsContent, TabsLinkTrigger, TabsList, TabsTrigger } from "./tab
42
42
  import { t as Textarea } from "./textarea-B6YhjFVC.mjs";
43
43
  import { a as ListItem, c as Title, d as titleVariants, i as List, l as paragraphVariants, n as Code, o as Paragraph, r as Link, s as Text, t as Blockquote, u as textVariants } from "./typography-BXIBvq3-.mjs";
44
44
  import { t as VisuallyHidden } from "./visuallyhidden-D7L_qs8b.mjs";
45
- import { a as hasActiveDescendant, i as findItemByHref, n as NavMenu, o as isNavItemActive, r as NavMenuItem, s as normalizePath, t as AppNavigation } from "./app-navigation-CNig4Maj.mjs";
45
+ import { a as hasActiveDescendant, i as findItemByHref, n as NavMenu, o as isNavItemActive, r as NavMenuItem, s as normalizePath, t as AppNavigation } from "./app-navigation--tEBCrfl.mjs";
46
46
  import { t as LoaderOverlay } from "./loader-overlay-DD94azZX.mjs";
47
47
  import { t as Autocomplete } from "./autocomplete-DZ_HSBak.mjs";
48
48
  import { Autosearch, defaultAutosearchValue } from "./autosearch/index.mjs";
@@ -1,2 +1,2 @@
1
- import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "../sidebar-BRUNOqXi.mjs";
1
+ import { C as SidebarProvider, S as SidebarGroupLabel, _ as SidebarMenuSubButton, a as SidebarInput, b as SidebarGroupAction, c as SidebarSeparator, d as SidebarMenuAction, f as SidebarMenuBadge, g as SidebarMenuSub, h as SidebarMenuSkeleton, i as SidebarHeader, l as SidebarTrigger, m as SidebarMenuItem, n as SidebarContent, o as SidebarInset, p as SidebarMenuButton, r as SidebarFooter, s as SidebarRail, t as Sidebar, u as SidebarMenu, v as SidebarMenuSubItem, w as useSidebar, x as SidebarGroupContent, y as SidebarGroup } from "../sidebar-D6BoAr6C.mjs";
2
2
  export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar };
@@ -78,18 +78,30 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
78
78
  return () => mql.removeEventListener("change", onChange);
79
79
  }, [isMobile, setOpen]);
80
80
  const effectiveOpen = open || expandOnHover && isHovered;
81
+ const isIconRail = !effectiveOpen;
81
82
  const handleMouseEnter = React.useCallback(() => {
82
83
  if (!expandOnHover) return;
83
84
  if (hoverLockRef.current) return;
84
- if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
85
+ if (hoverTimeoutRef.current) {
86
+ clearTimeout(hoverTimeoutRef.current);
87
+ hoverTimeoutRef.current = null;
88
+ }
85
89
  setIsHovered(true);
86
90
  }, [expandOnHover]);
87
91
  const handleMouseLeave = React.useCallback(() => {
88
92
  if (!expandOnHover) return;
89
- hoverTimeoutRef.current = setTimeout(() => {
90
- setIsHovered(false);
91
- }, expandBehavior === "overlay" ? 200 : 300);
92
- }, [expandOnHover, expandBehavior]);
93
+ if (hoverTimeoutRef.current) {
94
+ clearTimeout(hoverTimeoutRef.current);
95
+ hoverTimeoutRef.current = null;
96
+ }
97
+ setIsHovered(false);
98
+ }, [expandOnHover]);
99
+ const cancelHoverLeave = React.useCallback(() => {
100
+ if (hoverTimeoutRef.current) {
101
+ clearTimeout(hoverTimeoutRef.current);
102
+ hoverTimeoutRef.current = null;
103
+ }
104
+ }, []);
93
105
  const toggleSidebar = React.useCallback(() => {
94
106
  if (isMobile) {
95
107
  setOpenMobile((current) => !current);
@@ -154,6 +166,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
154
166
  const state = effectiveOpen ? "expanded" : "collapsed";
155
167
  const contextValue = React.useMemo(() => ({
156
168
  state,
169
+ isIconRail,
157
170
  open: effectiveOpen,
158
171
  setOpen,
159
172
  isMobile,
@@ -162,6 +175,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
162
175
  toggleSidebar,
163
176
  handleMouseEnter,
164
177
  handleMouseLeave,
178
+ cancelHoverLeave,
165
179
  forceClose,
166
180
  closeForNavigation,
167
181
  hasSubLayout,
@@ -170,6 +184,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
170
184
  showBackdrop
171
185
  }), [
172
186
  state,
187
+ isIconRail,
173
188
  effectiveOpen,
174
189
  setOpen,
175
190
  isMobile,
@@ -178,6 +193,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
178
193
  toggleSidebar,
179
194
  handleMouseEnter,
180
195
  handleMouseLeave,
196
+ cancelHoverLeave,
181
197
  forceClose,
182
198
  closeForNavigation,
183
199
  hasSubLayout,
@@ -341,7 +357,7 @@ function SidebarMenuSub({ className, ...props }) {
341
357
  return /* @__PURE__ */ jsx("ul", {
342
358
  "data-slot": "sidebar-menu-sub",
343
359
  "data-sidebar": "menu-sub",
344
- className: cn("border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col px-2.5", "group-data-[collapsible=icon]:hidden", className),
360
+ className: cn("border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-0.5 border-l px-2.5 py-0.5", "group-data-[collapsible=icon]:hidden", className),
345
361
  ...props
346
362
  });
347
363
  }
@@ -366,7 +382,7 @@ function SidebarMenuSubButton({ asChild = false, size = "md", isActive = false,
366
382
  //#endregion
367
383
  //#region src/components/base/sidebar/shell.tsx
368
384
  function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }) {
369
- const { isMobile, state, openMobile, setOpenMobile, handleMouseEnter, handleMouseLeave, expandBehavior, showBackdrop, forceClose } = useSidebar();
385
+ const { isMobile, state, openMobile, setOpenMobile, expandBehavior, showBackdrop, forceClose } = useSidebar();
370
386
  const sidebarRef = React.useRef(null);
371
387
  const [showBackdropElement, setShowBackdropElement] = React.useState(false);
372
388
  const [backdropVisible, setBackdropVisible] = React.useState(false);
@@ -422,18 +438,19 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
422
438
  "data-variant": variant,
423
439
  "data-side": side,
424
440
  "data-slot": "sidebar",
425
- onMouseEnter: handleMouseEnter,
426
- onMouseLeave: handleMouseLeave,
427
441
  children: [
428
- /* @__PURE__ */ jsx("div", { className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-300 ease-[cubic-bezier(0.25,0.1,0.25,1)]", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", expandBehavior === "overlay" ? variant === "floating" || variant === "inset" ? "w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "w-(--sidebar-width-icon)" : variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)") }),
442
+ /* @__PURE__ */ jsx("div", { className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", expandBehavior === "overlay" ? variant === "floating" || variant === "inset" ? "w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "w-(--sidebar-width-icon)" : variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)") }),
429
443
  /* @__PURE__ */ jsx("div", {
430
444
  ref: sidebarRef,
431
- className: cn("border-sidebar-border fixed inset-y-0 hidden w-(--sidebar-width) transition-[left,right,width] duration-300 ease-[cubic-bezier(0.25,0.1,0.25,1)] md:flex", expandBehavior === "overlay" ? "z-50" : "z-10", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", className),
445
+ className: cn("fixed inset-y-0 hidden w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", expandBehavior === "overlay" ? "z-50" : "z-20", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "bg-sidebar group-data-[collapsible=icon]:w-(--sidebar-width-icon)", className),
432
446
  ...props,
433
- children: /* @__PURE__ */ jsx("div", {
447
+ children: /* @__PURE__ */ jsxs("div", {
434
448
  "data-sidebar": "sidebar",
435
- className: cn("bg-sidebar flex h-full w-full flex-col", "group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm", expandBehavior === "overlay" && state === "expanded" && "shadow-lg"),
436
- children
449
+ className: cn("relative flex h-full w-full flex-col", variant === "floating" || variant === "inset" ? "bg-sidebar" : null, "group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm", expandBehavior === "overlay" && state === "expanded" && "shadow-lg"),
450
+ children: [children, variant !== "floating" && variant !== "inset" && /* @__PURE__ */ jsx("div", {
451
+ "aria-hidden": true,
452
+ className: cn("bg-sidebar-border pointer-events-none absolute inset-y-0 z-20 w-px", side === "left" ? "right-0" : "left-0")
453
+ })]
437
454
  })
438
455
  }),
439
456
  showBackdropElement && /* @__PURE__ */ jsx("div", {
@@ -512,11 +529,21 @@ function SidebarHeader({ className, ...props }) {
512
529
  ...props
513
530
  });
514
531
  }
515
- function SidebarFooter({ className, ...props }) {
532
+ function SidebarFooter({ className, onMouseEnter, onMouseLeave, ...props }) {
533
+ const { cancelHoverLeave, handleMouseLeave } = useSidebar();
516
534
  return /* @__PURE__ */ jsx("div", {
517
535
  "data-slot": "sidebar-footer",
518
536
  "data-sidebar": "footer",
519
537
  className: cn("flex flex-col gap-2 p-2", className),
538
+ onMouseEnter: (event) => {
539
+ cancelHoverLeave();
540
+ onMouseEnter?.(event);
541
+ },
542
+ onMouseLeave: (event) => {
543
+ const next = event.relatedTarget;
544
+ if (!(next instanceof Element && next.closest("[data-sidebar=\"content\"]"))) handleMouseLeave();
545
+ onMouseLeave?.(event);
546
+ },
520
547
  ...props
521
548
  });
522
549
  }
@@ -528,11 +555,21 @@ function SidebarSeparator({ className, ...props }) {
528
555
  ...props
529
556
  });
530
557
  }
531
- function SidebarContent({ className, ...props }) {
558
+ function SidebarContent({ className, onMouseEnter, onMouseLeave, ...props }) {
559
+ const { handleMouseEnter, handleMouseLeave } = useSidebar();
532
560
  return /* @__PURE__ */ jsx("div", {
533
561
  "data-slot": "sidebar-content",
534
562
  "data-sidebar": "content",
535
563
  className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", className),
564
+ onMouseEnter: (event) => {
565
+ handleMouseEnter();
566
+ onMouseEnter?.(event);
567
+ },
568
+ onMouseLeave: (event) => {
569
+ const next = event.relatedTarget;
570
+ if (!(next instanceof Element && next.closest("[data-sidebar=\"footer\"]"))) handleMouseLeave();
571
+ onMouseLeave?.(event);
572
+ },
536
573
  ...props
537
574
  });
538
575
  }
@@ -92,10 +92,10 @@
92
92
  }
93
93
 
94
94
  .animate-collapsible-down {
95
- animation: collapsibleDown 0.2s ease-out;
95
+ animation: collapsibleDown 0.28s cubic-bezier(0.32, 0.72, 0, 1);
96
96
  }
97
97
 
98
98
  .animate-collapsible-up {
99
- animation: collapsibleUp 0.2s ease-out;
99
+ animation: collapsibleUp 0.22s cubic-bezier(0.32, 0.72, 0, 1);
100
100
  }
101
101
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datum-cloud/datum-ui",
3
3
  "type": "module",
4
- "version": "2.1.0",
4
+ "version": "2.3.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "url": "https://github.com/datum-cloud/datum-ui"