@exegia/corpora-ui 0.17.0 → 0.19.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.
Files changed (114) hide show
  1. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
  2. package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
  3. package/dist-lib/components/blocks/scaffold/constants.d.ts +23 -0
  4. package/dist-lib/components/blocks/scaffold/index.d.ts +27 -0
  5. package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
  6. package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +8 -0
  7. package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +10 -0
  8. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
  9. package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
  10. package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
  11. package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
  12. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
  13. package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
  14. package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
  15. package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
  16. package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +9 -0
  17. package/dist-lib/components/blocks/scaffold/type.d.ts +135 -0
  18. package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
  19. package/dist-lib/components/blocks/scaffold/utils.d.ts +21 -0
  20. package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
  21. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
  22. package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
  23. package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
  24. package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
  25. package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
  26. package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
  27. package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
  28. package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
  29. package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
  30. package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
  31. package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
  32. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
  33. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
  34. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
  35. package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
  36. package/dist-lib/components/blocks/shell/index.d.ts +16 -0
  37. package/dist-lib/components/blocks/shell/type.d.ts +90 -4
  38. package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
  39. package/dist-lib/components/motion/index.d.ts +1 -0
  40. package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
  41. package/dist-lib/components/ui/button-group.d.ts +11 -0
  42. package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
  43. package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
  44. package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
  45. package/dist-lib/components/ui/group.d.ts +17 -0
  46. package/dist-lib/components/ui/separator.d.ts +2 -3
  47. package/dist-lib/components/ui/sheet.d.ts +14 -0
  48. package/dist-lib/components/ui/tooltip.d.ts +6 -0
  49. package/dist-lib/index.d.ts +2 -1
  50. package/dist-lib/index.js +1631 -1101
  51. package/dist-lib/index.js.map +1 -1
  52. package/dist-lib/lib/ease.d.ts +3 -0
  53. package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
  54. package/package.json +2 -1
  55. package/src/components/beste/piece/browser-frame.tsx +2 -2
  56. package/src/components/blocks/nav/sidebar-block.tsx +5 -5
  57. package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
  58. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +286 -0
  59. package/src/components/blocks/scaffold/constants.ts +32 -0
  60. package/src/components/blocks/scaffold/index.ts +41 -0
  61. package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
  62. package/src/components/blocks/scaffold/panel-menu-button.tsx +218 -0
  63. package/src/components/blocks/scaffold/scaffold-actions.tsx +88 -0
  64. package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
  65. package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
  66. package/src/components/blocks/scaffold/scaffold-inspector.tsx +80 -0
  67. package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
  68. package/src/components/blocks/scaffold/scaffold-panel.tsx +88 -0
  69. package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
  70. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
  71. package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
  72. package/src/components/blocks/scaffold/scaffold-tab.tsx +66 -0
  73. package/src/components/blocks/scaffold/type.ts +172 -0
  74. package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
  75. package/src/components/blocks/scaffold/utils.ts +46 -0
  76. package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
  77. package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
  78. package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
  79. package/src/components/blocks/shell/animated-panel.tsx +135 -0
  80. package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
  81. package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
  82. package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
  83. package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
  84. package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
  85. package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
  86. package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
  87. package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
  88. package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
  89. package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
  90. package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
  91. package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
  92. package/src/components/blocks/shell/index.ts +18 -0
  93. package/src/components/blocks/shell/shell-layout.tsx +28 -36
  94. package/src/components/blocks/shell/type.ts +112 -6
  95. package/src/components/blocks/shell/use-shell-panels.ts +1 -1
  96. package/src/components/blocks/shell/utils.ts +142 -1
  97. package/src/components/docs/demo-controls.tsx +1 -1
  98. package/src/components/motion/index.ts +0 -0
  99. package/src/components/motion/overflow-actions.tsx +376 -0
  100. package/src/components/ui/button-group.tsx +88 -0
  101. package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
  102. package/src/components/ui/glasscn/glass-container.tsx +125 -0
  103. package/src/components/ui/glasscn/glass-separator.tsx +39 -0
  104. package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
  105. package/src/components/ui/group.tsx +92 -0
  106. package/src/components/ui/separator.tsx +11 -11
  107. package/src/components/ui/sheet.tsx +135 -0
  108. package/src/components/ui/tooltip.tsx +66 -0
  109. package/src/index.css +1 -1
  110. package/src/index.ts +13 -1
  111. package/src/lib/ease.ts +26 -0
  112. package/src/lib/hooks/use-hover-capable.ts +23 -0
  113. package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
  114. package/src/components/motion/animated-sidebar.tsx +0 -1171
@@ -0,0 +1,135 @@
1
+ "use client"
2
+ // beui.dev/components/motion/animated-sidebar
3
+
4
+ import { motion } from "motion/react"
5
+ import { EASE_OUT } from "@/lib/ease.ts"
6
+ import { cn } from "@/lib/utils"
7
+ import type { AnimatedSidebarProps } from "./type"
8
+ import {
9
+ AnimatedSidebarPanelContext,
10
+ SIDEBAR_MORPH_TRANSITION,
11
+ PANEL_TRANSITION,
12
+ REDUCED_TRANSITION,
13
+ useAnimatedSidebar
14
+ } from "./utils"
15
+
16
+ export function AnimatedPanel({
17
+ side = "left",
18
+ variant = "sidebar",
19
+ collapsible = "icon",
20
+ ariaLabel = "Sidebar",
21
+ children,
22
+ className,
23
+ panelClassName,
24
+ style,
25
+ ref,
26
+ ...props
27
+ }: AnimatedSidebarProps) {
28
+ const context = useAnimatedSidebar()
29
+ const sideOpen = context.open[side]
30
+ const collapsed = collapsible !== "none" && !sideOpen
31
+ const offcanvas = collapsed && collapsible === "offcanvas"
32
+ const width = offcanvas
33
+ ? "0px"
34
+ : collapsed
35
+ ? "var(--sidebar-width-icon)"
36
+ : "var(--sidebar-width)"
37
+
38
+ if (context.isMobile) return <div>TODO: Create Mobile nav experience</div>
39
+
40
+ return (
41
+ <motion.aside
42
+ {...props}
43
+ ref={ref}
44
+ initial={false}
45
+ aria-label={ariaLabel}
46
+ data-slot="sidebar"
47
+ data-state={collapsed ? "collapsed" : "expanded"}
48
+ data-collapsible={collapsible}
49
+ data-variant={variant}
50
+ data-side={side}
51
+ animate={{
52
+ width,
53
+ // Swallow the shell's gap-x-2 column gap while off canvas so the
54
+ // shell edge keeps a constant spacing-2 inset whether the panel is
55
+ // open (gap + panel + margin) or collapsed (gap + this).
56
+ ...(side === "right"
57
+ ? { marginLeft: offcanvas ? "-0.5rem" : "0rem" }
58
+ : { marginRight: offcanvas ? "-0.5rem" : "0rem" }),
59
+ }}
60
+ transition={context.reduce ? { duration: 0 } : SIDEBAR_MORPH_TRANSITION}
61
+ style={style}
62
+ className={cn(
63
+ "group/sidebar relative hidden h-full shrink-0 will-change-[width] md:block",
64
+ "peer flex!",
65
+ side === "right" && "order-last",
66
+ className
67
+ )}
68
+ >
69
+ {side === "right" && !collapsed && (
70
+ // The handle occupies the shell's gap-x-2 column gap. It must stay
71
+ // outside the clipping mask below or the gap would never receive
72
+ // hover — clipping cuts hit-testing, not just painting.
73
+ <motion.div
74
+ className="absolute inset-y-0 -left-2 z-10 w-2 cursor-col-resize"
75
+ initial="idle"
76
+ whileHover="hover"
77
+ >
78
+ <motion.div
79
+ variants={{ idle: { scaleY: 0 }, hover: { scaleY: 1 } }}
80
+ transition={
81
+ context.reduce
82
+ ? { duration: 0 }
83
+ : { duration: 0.2, ease: EASE_OUT }
84
+ }
85
+ className="h-full w-full bg-radial/decreasing from-amber-300/70 to-amber-500/0 to-40%"
86
+ />
87
+ </motion.div>
88
+ )}
89
+ {/* The off canvas panel keeps its full width and slides out of the
90
+ zero-width rail, so mask it to the aside's box here instead of
91
+ clipping the aside itself — the aside must stay unclipped for the
92
+ gap handle above to be hoverable. */}
93
+ <div
94
+ className={
95
+ collapsible === "offcanvas"
96
+ ? "absolute inset-0 overflow-hidden rounded-[inherit]"
97
+ : "contents"
98
+ }
99
+ >
100
+ <motion.div
101
+ initial={false}
102
+ animate={{
103
+ opacity: offcanvas ? 0 : 1,
104
+ x: offcanvas ? (side === "left" ? "-100%" : "100%") : "0%",
105
+ }}
106
+ transition={context.reduce ? REDUCED_TRANSITION : PANEL_TRANSITION}
107
+ className={cn(
108
+ "sticky top-0 flex flex-col overflow-hidden",
109
+ // collapsible === "offcanvas" && "w-(--sidebar-width)",
110
+ // Pin the right panel to the aside's trailing edge so the width
111
+ // animation expands the panel out of it instead of revealing a
112
+ // detached strip.
113
+ side === "right" && "ml-auto",
114
+
115
+ // Panels fill the shell's container, not the viewport — the shell
116
+ // owns its own scrolling, so a bounded host (docs demo, split
117
+ // view) must bound the panels too. Full-page apps size the shell
118
+ // itself (e.g., h-svh). The bottom gap comes from the shell
119
+ // container's padding, so panels only carry their top offset.
120
+ variant === "floating" && "mx-2 mt-2 h-[calc(100%-0.5rem)]",
121
+ variant === "inset" && "mt-2 h-[calc(100%-0.5rem)]",
122
+
123
+ panelClassName
124
+ )}
125
+ >
126
+ <AnimatedSidebarPanelContext.Provider
127
+ value={{ collapsed, collapsible, side }}
128
+ >
129
+ {children}
130
+ </AnimatedSidebarPanelContext.Provider>
131
+ </motion.div>
132
+ </div>
133
+ </motion.aside>
134
+ )
135
+ }
@@ -0,0 +1,19 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export const AnimatedSidebarContent = forwardRef<
5
+ HTMLDivElement,
6
+ HTMLAttributes<HTMLDivElement>
7
+ >(function AnimatedSidebarContent({ className, ...props }, forwardedRef) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ ref={forwardedRef}
12
+ data-slot="sidebar-content"
13
+ className={cn(
14
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto overscroll-contain px-2 py-2",
15
+ className
16
+ )}
17
+ />
18
+ )
19
+ })
@@ -0,0 +1,19 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export const AnimatedSidebarFooter = forwardRef<
5
+ HTMLDivElement,
6
+ HTMLAttributes<HTMLDivElement>
7
+ >(function AnimatedSidebarFooter({ className, ...props }, forwardedRef) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ ref={forwardedRef}
12
+ data-slot="sidebar-footer"
13
+ className={cn(
14
+ "flex shrink-0 flex-col gap-2 border-t border-border p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]",
15
+ className
16
+ )}
17
+ />
18
+ )
19
+ })
@@ -0,0 +1,16 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export const AnimatedSidebarGroupContent = forwardRef<
5
+ HTMLDivElement,
6
+ HTMLAttributes<HTMLDivElement>
7
+ >(function AnimatedSidebarGroupContent({ className, ...props }, forwardedRef) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ ref={forwardedRef}
12
+ data-slot="sidebar-group-content"
13
+ className={cn("w-full min-w-0", className)}
14
+ />
15
+ )
16
+ })
@@ -0,0 +1,29 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { useAnimatedSidebarPanel } from "./utils"
4
+
5
+ export const AnimatedSidebarGroupLabel = forwardRef<
6
+ HTMLDivElement,
7
+ HTMLAttributes<HTMLDivElement>
8
+ >(function AnimatedSidebarGroupLabel(
9
+ { children, className, ...props },
10
+ forwardedRef
11
+ ) {
12
+ const { collapsed } = useAnimatedSidebarPanel()
13
+
14
+ return (
15
+ <div
16
+ {...props}
17
+ ref={forwardedRef}
18
+ aria-hidden={collapsed}
19
+ data-slot="sidebar-group-label"
20
+ className={cn(
21
+ "mb-1 h-7 overflow-hidden px-2 text-[10px] font-medium tracking-[0.14em] text-muted-foreground uppercase transition-opacity",
22
+ collapsed ? "opacity-0" : "opacity-100",
23
+ className
24
+ )}
25
+ >
26
+ {children}
27
+ </div>
28
+ )
29
+ })
@@ -0,0 +1,16 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export const AnimatedSidebarGroup = forwardRef<
5
+ HTMLDivElement,
6
+ HTMLAttributes<HTMLDivElement>
7
+ >(function AnimatedSidebarGroup({ className, ...props }, forwardedRef) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ ref={forwardedRef}
12
+ data-slot="sidebar-group"
13
+ className={cn("flex w-full min-w-0 flex-col px-1 py-1.5", className)}
14
+ />
15
+ )
16
+ })
@@ -0,0 +1,16 @@
1
+ import { forwardRef, type HTMLAttributes } from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export const AnimatedSidebarHeader = forwardRef<
5
+ HTMLDivElement,
6
+ HTMLAttributes<HTMLDivElement>
7
+ >(function AnimatedSidebarHeader({ className, ...props }, forwardedRef) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ ref={forwardedRef}
12
+ data-slot="sidebar-header"
13
+ className={cn("flex shrink-0 flex-col gap-2 p-3", className)}
14
+ />
15
+ )
16
+ })
@@ -0,0 +1,151 @@
1
+ import type * as React from "react"
2
+ import { ChevronRight } from "lucide-react"
3
+ import { motion } from "motion/react"
4
+ import { SPRING_LAYOUT, SPRING_PRESS } from "@/lib/ease"
5
+ import { cn } from "@/lib/utils"
6
+ import type { AnimatedSidebarMenuButtonProps } from "./type"
7
+ import {
8
+ LABEL_ENTER_TRANSITION,
9
+ LABEL_EXIT_TRANSITION,
10
+ REDUCED_TRANSITION,
11
+ useAnimatedSidebar,
12
+ useAnimatedSidebarPanel,
13
+ } from "./utils"
14
+
15
+ export function AnimatedSidebarMenuButton({
16
+ children,
17
+ icon,
18
+ badge,
19
+ href,
20
+ isActive = false,
21
+ ariaExpanded,
22
+ disabled = false,
23
+ closeOnSelect,
24
+ target,
25
+ rel,
26
+ onSelect,
27
+ className,
28
+ }: AnimatedSidebarMenuButtonProps) {
29
+ const context = useAnimatedSidebar()
30
+ const panel = useAnimatedSidebarPanel()
31
+ const textLabel = typeof children === "string" ? children : undefined
32
+
33
+ const select = (
34
+ event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
35
+ ) => {
36
+ if (disabled) {
37
+ event.preventDefault()
38
+ return
39
+ }
40
+ onSelect?.()
41
+ const shouldCloseOnSelect = closeOnSelect ?? ariaExpanded === undefined
42
+ if (context.isMobile && shouldCloseOnSelect) {
43
+ context.setOpenMobile(false, panel.side)
44
+ }
45
+ }
46
+
47
+ const content = (
48
+ <>
49
+ {isActive ? (
50
+ <motion.span
51
+ layoutId={context.layoutId}
52
+ transition={context.reduce ? { duration: 0 } : SPRING_LAYOUT}
53
+ className="absolute inset-0 rounded-xl bg-muted"
54
+ />
55
+ ) : null}
56
+ {icon ? (
57
+ <span
58
+ aria-hidden="true"
59
+ className="relative z-10 grid size-5 shrink-0 place-items-center"
60
+ >
61
+ {icon}
62
+ </span>
63
+ ) : null}
64
+ <motion.span
65
+ initial={false}
66
+ animate={{
67
+ opacity: panel.collapsed ? 0 : 1,
68
+ x: panel.collapsed ? -4 : 0,
69
+ }}
70
+ transition={
71
+ context.reduce
72
+ ? REDUCED_TRANSITION
73
+ : panel.collapsed
74
+ ? LABEL_EXIT_TRANSITION
75
+ : LABEL_ENTER_TRANSITION
76
+ }
77
+ aria-hidden={panel.collapsed}
78
+ className={cn(
79
+ "relative z-10 min-w-0 flex-1 truncate",
80
+ panel.collapsed && "pointer-events-none"
81
+ )}
82
+ >
83
+ {children}
84
+ </motion.span>
85
+ {badge && !panel.collapsed ? (
86
+ <span className="relative z-10 shrink-0 text-xs text-muted-foreground">
87
+ {badge}
88
+ </span>
89
+ ) : null}
90
+ {ariaExpanded !== undefined ? (
91
+ <motion.span
92
+ aria-hidden="true"
93
+ initial={false}
94
+ animate={{
95
+ opacity: panel.collapsed ? 0 : 1,
96
+ rotate: ariaExpanded ? 90 : 0,
97
+ x: panel.collapsed ? 4 : 0,
98
+ }}
99
+ transition={context.reduce ? { duration: 0 } : SPRING_LAYOUT}
100
+ className="relative z-10 grid size-4 shrink-0 place-items-center text-muted-foreground"
101
+ >
102
+ <ChevronRight className="size-3.5" />
103
+ </motion.span>
104
+ ) : null}
105
+ </>
106
+ )
107
+
108
+ const interactiveClassName = cn(
109
+ "relative flex min-h-9 w-full min-w-0 items-center gap-2.5 overflow-hidden rounded-xl px-3 text-left text-sm font-medium outline-none",
110
+ "text-muted-foreground transition-colors hover:text-foreground",
111
+ "focus-visible:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring",
112
+ isActive && "text-foreground",
113
+ disabled && "cursor-not-allowed opacity-40",
114
+ className
115
+ )
116
+
117
+ return href ? (
118
+ <motion.a
119
+ href={href}
120
+ target={target}
121
+ rel={rel ?? (target === "_blank" ? "noreferrer noopener" : undefined)}
122
+ aria-current={isActive ? "page" : undefined}
123
+ aria-expanded={ariaExpanded}
124
+ aria-disabled={disabled || undefined}
125
+ aria-label={panel.collapsed ? textLabel : undefined}
126
+ title={panel.collapsed ? textLabel : undefined}
127
+ tabIndex={disabled ? -1 : undefined}
128
+ onClick={select}
129
+ whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
130
+ transition={SPRING_PRESS}
131
+ className={interactiveClassName}
132
+ >
133
+ {content}
134
+ </motion.a>
135
+ ) : (
136
+ <motion.button
137
+ type="button"
138
+ disabled={disabled}
139
+ aria-current={isActive ? "page" : undefined}
140
+ aria-expanded={ariaExpanded}
141
+ aria-label={panel.collapsed ? textLabel : undefined}
142
+ title={panel.collapsed ? textLabel : undefined}
143
+ onClick={select}
144
+ whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
145
+ transition={SPRING_PRESS}
146
+ className={interactiveClassName}
147
+ >
148
+ {content}
149
+ </motion.button>
150
+ )
151
+ }
@@ -0,0 +1,20 @@
1
+ import { forwardRef } from "react"
2
+ import { motion, type HTMLMotionProps } from "motion/react"
3
+ import { SPRING_LAYOUT } from "@/lib/ease"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export const AnimatedSidebarMenuItem = forwardRef<
7
+ HTMLLIElement,
8
+ HTMLMotionProps<"li">
9
+ >(function AnimatedSidebarMenuItem({ className, ...props }, forwardedRef) {
10
+ return (
11
+ <motion.li
12
+ {...props}
13
+ ref={forwardedRef}
14
+ layout="position"
15
+ transition={SPRING_LAYOUT}
16
+ data-slot="sidebar-menu-item"
17
+ className={cn("relative", className)}
18
+ />
19
+ )
20
+ })
@@ -0,0 +1,85 @@
1
+ import type * as React from "react"
2
+ import { motion } from "motion/react"
3
+ import { SPRING_PRESS } from "@/lib/ease"
4
+ import { cn } from "@/lib/utils"
5
+ import type { AnimatedSidebarMenuSubButtonProps } from "./type"
6
+ import { useAnimatedSidebar, useAnimatedSidebarPanel } from "./utils"
7
+
8
+ export function AnimatedSidebarMenuSubButton({
9
+ children,
10
+ icon,
11
+ href,
12
+ isActive = false,
13
+ disabled = false,
14
+ closeOnSelect = true,
15
+ target,
16
+ rel,
17
+ onSelect,
18
+ className,
19
+ }: AnimatedSidebarMenuSubButtonProps) {
20
+ const context = useAnimatedSidebar()
21
+ const panel = useAnimatedSidebarPanel()
22
+
23
+ const select = (
24
+ event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
25
+ ) => {
26
+ if (disabled) {
27
+ event.preventDefault()
28
+ return
29
+ }
30
+ onSelect?.()
31
+ if (context.isMobile && closeOnSelect) {
32
+ context.setOpenMobile(false, panel.side)
33
+ }
34
+ }
35
+
36
+ const content = (
37
+ <>
38
+ <span
39
+ aria-hidden="true"
40
+ className="grid size-4 shrink-0 place-items-center"
41
+ >
42
+ {icon ?? <span className="size-1 rounded-full bg-current" />}
43
+ </span>
44
+ <span className="min-w-0 flex-1 truncate">{children}</span>
45
+ </>
46
+ )
47
+
48
+ const interactiveClassName = cn(
49
+ "flex min-h-8 w-full min-w-0 items-center gap-2 rounded-lg px-2 text-left text-xs outline-none",
50
+ "text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
51
+ "focus-visible:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring",
52
+ isActive && "bg-muted/70 text-foreground",
53
+ disabled && "cursor-not-allowed opacity-40",
54
+ className
55
+ )
56
+
57
+ return href ? (
58
+ <motion.a
59
+ href={href}
60
+ target={target}
61
+ rel={rel ?? (target === "_blank" ? "noreferrer noopener" : undefined)}
62
+ aria-current={isActive ? "page" : undefined}
63
+ aria-disabled={disabled || undefined}
64
+ tabIndex={disabled ? -1 : undefined}
65
+ onClick={select}
66
+ whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
67
+ transition={SPRING_PRESS}
68
+ className={interactiveClassName}
69
+ >
70
+ {content}
71
+ </motion.a>
72
+ ) : (
73
+ <motion.button
74
+ type="button"
75
+ disabled={disabled}
76
+ aria-current={isActive ? "page" : undefined}
77
+ onClick={select}
78
+ whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
79
+ transition={SPRING_PRESS}
80
+ className={interactiveClassName}
81
+ >
82
+ {content}
83
+ </motion.button>
84
+ )
85
+ }
@@ -0,0 +1,19 @@
1
+ import { forwardRef } from "react"
2
+ import { motion, type HTMLMotionProps } from "motion/react"
3
+ import { cn } from "@/lib/utils"
4
+ import { SUBMENU_ITEM_VARIANTS } from "./utils"
5
+
6
+ export const AnimatedSidebarMenuSubItem = forwardRef<
7
+ HTMLLIElement,
8
+ HTMLMotionProps<"li">
9
+ >(function AnimatedSidebarMenuSubItem({ className, ...props }, forwardedRef) {
10
+ return (
11
+ <motion.li
12
+ {...props}
13
+ ref={forwardedRef}
14
+ variants={SUBMENU_ITEM_VARIANTS}
15
+ data-slot="sidebar-menu-sub-item"
16
+ className={cn("relative min-w-0", className)}
17
+ />
18
+ )
19
+ })
@@ -0,0 +1,44 @@
1
+ import { forwardRef } from "react"
2
+ import { AnimatePresence, motion } from "motion/react"
3
+ import { cn } from "@/lib/utils"
4
+ import type { AnimatedSidebarMenuSubProps } from "./type"
5
+ import {
6
+ SUBMENU_VARIANTS,
7
+ useAnimatedSidebar,
8
+ useAnimatedSidebarPanel,
9
+ } from "./utils"
10
+
11
+ export const AnimatedSidebarMenuSub = forwardRef<
12
+ HTMLUListElement,
13
+ AnimatedSidebarMenuSubProps
14
+ >(function AnimatedSidebarMenuSub(
15
+ { open, children, className, ...props },
16
+ forwardedRef
17
+ ) {
18
+ const context = useAnimatedSidebar()
19
+ const panel = useAnimatedSidebarPanel()
20
+
21
+ return (
22
+ <AnimatePresence initial={false} mode="popLayout">
23
+ {open && !panel.collapsed ? (
24
+ <motion.ul
25
+ {...props}
26
+ ref={forwardedRef}
27
+ key="sidebar-submenu"
28
+ variants={context.reduce ? undefined : SUBMENU_VARIANTS}
29
+ initial={context.reduce ? false : "closed"}
30
+ animate={context.reduce ? { opacity: 1 } : "open"}
31
+ exit={context.reduce ? { opacity: 0 } : "closed"}
32
+ transition={context.reduce ? { duration: 0.12 } : undefined}
33
+ data-slot="sidebar-menu-sub"
34
+ className={cn(
35
+ "relative mt-1 ml-5 flex min-w-0 flex-col gap-0.5 border-l border-border pl-3",
36
+ className
37
+ )}
38
+ >
39
+ {children}
40
+ </motion.ul>
41
+ ) : null}
42
+ </AnimatePresence>
43
+ )
44
+ })
@@ -0,0 +1,26 @@
1
+ import { forwardRef, type HTMLAttributes, type Ref } from "react"
2
+ import { SharedLayoutBg } from "@/components/motion/shared-layout-bg"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export const AnimatedSidebarMenu = forwardRef<
6
+ HTMLUListElement,
7
+ HTMLAttributes<HTMLUListElement>
8
+ >(function AnimatedSidebarMenu(
9
+ { children, className, ...props },
10
+ forwardedRef
11
+ ) {
12
+ return (
13
+ <SharedLayoutBg
14
+ {...props}
15
+ ref={forwardedRef as Ref<HTMLElement>}
16
+ as="ul"
17
+ inset={0}
18
+ pillClassName="rounded-xl bg-muted/70"
19
+ pillContainerClassName="inset-y-auto top-0 h-9"
20
+ data-slot="sidebar-menu"
21
+ className={cn("flex w-full min-w-0 list-none flex-col gap-0.5", className)}
22
+ >
23
+ {children}
24
+ </SharedLayoutBg>
25
+ )
26
+ })
@@ -4,4 +4,22 @@ export type * from "./type"
4
4
  export * from "./use-shell-panels"
5
5
  export * from "./utils"
6
6
 
7
+ export * from "./animated-panel"
8
+ export * from "./animated-panel-inset"
9
+ export * from "./animated-panel-provider"
10
+ export * from "./animated-panel-trigger"
11
+
12
+ export * from "./animated-sidebar-header"
13
+ export * from "./animated-sidebar-content"
14
+ export * from "./animated-sidebar-footer"
15
+ export * from "./animated-sidebar-group"
16
+ export * from "./animated-sidebar-group-label"
17
+ export * from "./animated-sidebar-group-content"
18
+ export * from "./animated-sidebar-menu"
19
+ export * from "./animated-sidebar-menu-item"
20
+ export * from "./animated-sidebar-menu-sub"
21
+ export * from "./animated-sidebar-menu-sub-item"
22
+ export * from "./animated-sidebar-menu-sub-button"
23
+ export * from "./animated-sidebar-menu-button"
24
+
7
25
  export { ShellLayout as default }