@exegia/corpora-ui 0.17.0 → 0.18.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/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
- package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
- package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
- package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
- package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
- package/dist-lib/components/blocks/shell/index.d.ts +16 -0
- package/dist-lib/components/blocks/shell/type.d.ts +90 -4
- package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
- package/dist-lib/components/motion/index.d.ts +1 -0
- package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
- package/dist-lib/components/ui/button-group.d.ts +11 -0
- package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
- package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
- package/dist-lib/components/ui/group.d.ts +17 -0
- package/dist-lib/components/ui/separator.d.ts +2 -3
- package/dist-lib/components/ui/sheet.d.ts +14 -0
- package/dist-lib/components/ui/tooltip.d.ts +6 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +1592 -1084
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/ease.d.ts +3 -0
- package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +2 -2
- package/src/components/blocks/nav/sidebar-block.tsx +5 -5
- package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
- package/src/components/blocks/scaffold/constants.ts +28 -0
- package/src/components/blocks/scaffold/index.ts +38 -0
- package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
- package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
- package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
- package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
- package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
- package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
- package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
- package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
- package/src/components/blocks/scaffold/type.ts +171 -0
- package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
- package/src/components/blocks/scaffold/utils.ts +30 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
- package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
- package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
- package/src/components/blocks/shell/animated-panel.tsx +135 -0
- package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
- package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
- package/src/components/blocks/shell/index.ts +18 -0
- package/src/components/blocks/shell/shell-layout.tsx +28 -36
- package/src/components/blocks/shell/type.ts +112 -6
- package/src/components/blocks/shell/use-shell-panels.ts +1 -1
- package/src/components/blocks/shell/utils.ts +142 -1
- package/src/components/docs/demo-controls.tsx +1 -1
- package/src/components/motion/index.ts +0 -0
- package/src/components/motion/overflow-actions.tsx +376 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
- package/src/components/ui/glasscn/glass-container.tsx +125 -0
- package/src/components/ui/glasscn/glass-separator.tsx +39 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
- package/src/components/ui/group.tsx +92 -0
- package/src/components/ui/separator.tsx +11 -11
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/index.css +1 -1
- package/src/index.ts +13 -1
- package/src/lib/ease.ts +26 -0
- package/src/lib/hooks/use-hover-capable.ts +23 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
- package/src/components/motion/animated-sidebar.tsx +0 -1171
|
@@ -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 }
|
|
@@ -4,15 +4,15 @@ import { MotionIcon } from "motion-icons-react"
|
|
|
4
4
|
import * as React from "react"
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
AnimatedSidebarProvider,
|
|
10
|
-
AnimatedSidebarTrigger,
|
|
11
|
-
} from "@/components/motion/animated-sidebar"
|
|
7
|
+
AnimatedPanel
|
|
8
|
+
} from "./animated-panel.tsx"
|
|
12
9
|
import { cn } from "@/lib/utils"
|
|
13
10
|
import type { ShellLayoutProps, ShellPanelControlProps } from "./type"
|
|
14
11
|
import { TITLE_BAR_HEIGHT } from "./utils"
|
|
15
12
|
import type { ClassNameValue } from "tailwind-merge"
|
|
13
|
+
import { AnimatedPanelProvider } from "./animated-panel-provider.tsx"
|
|
14
|
+
import { AnimatedPanelTrigger } from "./animated-panel-trigger.tsx"
|
|
15
|
+
import { AnimatedPanelInset } from "./animated-panel-inset.tsx"
|
|
16
16
|
|
|
17
17
|
export function ShellLayout({
|
|
18
18
|
children,
|
|
@@ -41,56 +41,56 @@ export function ShellLayout({
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
|
-
<
|
|
44
|
+
<AnimatedPanelProvider
|
|
45
45
|
{...panelControlProps}
|
|
46
46
|
defaultOpen={initialOpen}
|
|
47
|
-
className={cn(
|
|
48
|
-
"relative block-full pr-2",
|
|
49
|
-
className,
|
|
50
|
-
background
|
|
51
|
-
)}
|
|
47
|
+
className={cn("relative h-full min-h-0 pb-2", className, background)}
|
|
52
48
|
style={{
|
|
53
49
|
paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0,
|
|
54
50
|
}}
|
|
55
51
|
>
|
|
56
|
-
<
|
|
52
|
+
<AnimatedPanel
|
|
57
53
|
ariaLabel="Primary navigation"
|
|
58
54
|
collapsible="icon"
|
|
59
55
|
role="navigation"
|
|
60
56
|
variant="inset"
|
|
61
57
|
>
|
|
62
58
|
{panels?.left?.component}
|
|
63
|
-
</
|
|
59
|
+
</AnimatedPanel>
|
|
64
60
|
|
|
65
|
-
<
|
|
66
|
-
<header className="flex h-12 items-center justify-between gap-2 px-2">
|
|
61
|
+
<AnimatedPanelInset className="min-w-24">
|
|
62
|
+
<header className="flex h-12 items-center justify-between gap-2 border-b px-2">
|
|
67
63
|
<div className="flex min-w-0 items-center gap-2">
|
|
68
|
-
<
|
|
64
|
+
<AnimatedPanelTrigger>
|
|
69
65
|
{panels?.left?.trigger ?? (
|
|
70
66
|
<MotionIcon name="PanelLeft" size={24} animation="press" />
|
|
71
67
|
)}
|
|
72
|
-
</
|
|
68
|
+
</AnimatedPanelTrigger>
|
|
73
69
|
</div>
|
|
74
70
|
{header && <div className="flex flex-1 items-center">{header}</div>}
|
|
75
71
|
<div className="flex shrink-0 items-center gap-2">
|
|
76
|
-
<
|
|
72
|
+
<AnimatedPanelTrigger aria-label="Toggle panel" side="right">
|
|
77
73
|
{panels?.right?.trigger ?? (
|
|
78
|
-
<MotionIcon
|
|
74
|
+
<MotionIcon
|
|
75
|
+
className="opacity-70"
|
|
76
|
+
name="PanelRight"
|
|
77
|
+
size={24}
|
|
78
|
+
/>
|
|
79
79
|
)}
|
|
80
|
-
</
|
|
80
|
+
</AnimatedPanelTrigger>
|
|
81
81
|
</div>
|
|
82
82
|
</header>
|
|
83
|
-
<div className="min-h-24 flex-1 overflow-auto">
|
|
84
|
-
|
|
85
|
-
</div>
|
|
86
|
-
</AnimatedSidebarInset>
|
|
83
|
+
<div className="min-h-24 flex-1 overflow-auto">{children}</div>
|
|
84
|
+
</AnimatedPanelInset>
|
|
87
85
|
|
|
88
|
-
<
|
|
86
|
+
<AnimatedPanel
|
|
89
87
|
ariaLabel={panels?.right?.name ?? "Secondary panel"}
|
|
90
88
|
// Below md the panel is portal led over the page, so it carries the
|
|
91
89
|
// surface itself; the desktop rail keeps it on the inner panel.
|
|
92
90
|
className={cn(
|
|
93
|
-
"
|
|
91
|
+
"mr-2 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900",
|
|
92
|
+
"outline-offset-0.5 border-t-3 border-white outline-neutral-100 dark:inset-ring-black",
|
|
93
|
+
"rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950"
|
|
94
94
|
)}
|
|
95
95
|
collapsible="offcanvas"
|
|
96
96
|
role="complementary"
|
|
@@ -98,15 +98,7 @@ export function ShellLayout({
|
|
|
98
98
|
variant="inset"
|
|
99
99
|
>
|
|
100
100
|
{panels?.right?.component}
|
|
101
|
-
</
|
|
102
|
-
</
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function ShellLayoutPlaceholder() {
|
|
107
|
-
return (
|
|
108
|
-
<div className="flex flex-col flex-1 p-4">
|
|
109
|
-
<div className="h-96 border border-border bg-muted-foreground/10 rounded-lg" />
|
|
110
|
-
</div>
|
|
101
|
+
</AnimatedPanel>
|
|
102
|
+
</AnimatedPanelProvider>
|
|
111
103
|
)
|
|
112
104
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
type ButtonHTMLAttributes,
|
|
5
|
+
type CSSProperties,
|
|
6
|
+
type HTMLAttributes,
|
|
7
|
+
} from "react"
|
|
8
|
+
import type { HTMLMotionProps } from "motion/react"
|
|
7
9
|
|
|
8
10
|
export interface ShellAction {
|
|
9
11
|
id: string
|
|
@@ -47,7 +49,7 @@ export type TPanelMap<Side extends TPanelSide = TPanelSide> = Partial<
|
|
|
47
49
|
>
|
|
48
50
|
|
|
49
51
|
/** The open/close surface of the shell's panels, lifted straight from
|
|
50
|
-
*
|
|
52
|
+
* AnimatedPanelProvider — every prop is keyed by side, there are no
|
|
51
53
|
* explicit per-side props. `useShellPanels` produces the controlled subset
|
|
52
54
|
* of these as `providerProps`. */
|
|
53
55
|
export type ShellPanelControlProps = Pick<
|
|
@@ -82,7 +84,7 @@ export interface ShellPanelControls {
|
|
|
82
84
|
/** Desktop-only convenience — the in-shell triggers already pick the
|
|
83
85
|
* mobile state themselves when the viewport is narrow. */
|
|
84
86
|
toggle: (side: SidebarSide) => void
|
|
85
|
-
/** Spread onto ShellLayout (or
|
|
87
|
+
/** Spread onto ShellLayout (or AnimatedPanelProvider directly). */
|
|
86
88
|
providerProps: ShellPanelControlProps
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -95,3 +97,107 @@ export interface ShellLayoutProps extends ShellPanelControlProps {
|
|
|
95
97
|
className?: string
|
|
96
98
|
variant?: "web" | "desktop"
|
|
97
99
|
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
export type SidebarSide = "left" | "right"
|
|
103
|
+
/** Open flags keyed by the side. Sides left out stay uncontrolled / at their
|
|
104
|
+
* default — there is no per-side prop, the record IS the API. */
|
|
105
|
+
export type SidebarOpenState = Partial<Record<SidebarSide, boolean>>
|
|
106
|
+
export type SidebarVariant = "sidebar" | "floating" | "inset"
|
|
107
|
+
export type SidebarCollapsible = "offcanvas" | "icon" | "none"
|
|
108
|
+
|
|
109
|
+
export interface AnimatedSidebarContextValue {
|
|
110
|
+
isMobile: boolean
|
|
111
|
+
layoutId: string
|
|
112
|
+
/** Desktop open state, keyed by side. */
|
|
113
|
+
open: Record<SidebarSide, boolean>
|
|
114
|
+
/** Mobile overlay open state, keyed by side. */
|
|
115
|
+
openMobile: Record<SidebarSide, boolean>
|
|
116
|
+
reduce: boolean
|
|
117
|
+
setOpen: (open: boolean, side: SidebarSide) => void
|
|
118
|
+
setOpenMobile: (open: boolean, side: SidebarSide) => void
|
|
119
|
+
/** Mobile-aware: toggles the overlay below md, the docked panel above. */
|
|
120
|
+
toggleSidebar: (side: SidebarSide) => void
|
|
121
|
+
triggerRefs: Record<SidebarSide, React.RefObject<HTMLButtonElement | null>>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
125
|
+
/** Controlled desktop open state, keyed by the side. A side left undefined
|
|
126
|
+
* stays uncontrolled. */
|
|
127
|
+
open?: SidebarOpenState
|
|
128
|
+
/** Initial desktop open state, merged over `{ left: true, right: false }`. */
|
|
129
|
+
defaultOpen?: SidebarOpenState
|
|
130
|
+
onOpenChange?: (open: boolean, side: SidebarSide) => void
|
|
131
|
+
/** Controlled mobile overlay state, keyed by side. */
|
|
132
|
+
openMobile?: SidebarOpenState
|
|
133
|
+
/** Initial mobile overlay state — every side starts closed. */
|
|
134
|
+
defaultOpenMobile?: SidebarOpenState
|
|
135
|
+
onOpenMobileChange?: (open: boolean, side: SidebarSide) => void
|
|
136
|
+
style?: SidebarProviderStyle
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type SidebarProviderStyle = CSSProperties & {
|
|
140
|
+
"--sidebar-width"?: string
|
|
141
|
+
"--sidebar-width-icon"?: string
|
|
142
|
+
"--sidebar-width-mobile"?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type AnimatedSidebarInsetProps = HTMLMotionProps<"main">
|
|
146
|
+
|
|
147
|
+
export interface AnimatedSidebarTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
148
|
+
side?: SidebarSide
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface AnimatedSidebarPanelContextValue {
|
|
152
|
+
collapsed: boolean
|
|
153
|
+
collapsible: SidebarCollapsible
|
|
154
|
+
side: SidebarSide
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface AnimatedSidebarProps extends Omit<
|
|
158
|
+
HTMLMotionProps<"aside">,
|
|
159
|
+
"children"
|
|
160
|
+
> {
|
|
161
|
+
children?: ReactNode
|
|
162
|
+
side?: SidebarSide
|
|
163
|
+
variant?: SidebarVariant
|
|
164
|
+
collapsible?: SidebarCollapsible
|
|
165
|
+
ariaLabel?: string
|
|
166
|
+
panelClassName?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface AnimatedSidebarMenuSubProps extends Omit<
|
|
170
|
+
HTMLMotionProps<"ul">,
|
|
171
|
+
"children"
|
|
172
|
+
> {
|
|
173
|
+
open: boolean
|
|
174
|
+
children?: ReactNode
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface AnimatedSidebarMenuSubButtonProps {
|
|
178
|
+
children: ReactNode
|
|
179
|
+
icon?: ReactNode
|
|
180
|
+
href?: string
|
|
181
|
+
isActive?: boolean
|
|
182
|
+
disabled?: boolean
|
|
183
|
+
closeOnSelect?: boolean
|
|
184
|
+
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
185
|
+
rel?: string
|
|
186
|
+
onSelect?: () => void
|
|
187
|
+
className?: string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface AnimatedSidebarMenuButtonProps {
|
|
191
|
+
children: ReactNode
|
|
192
|
+
icon?: ReactNode
|
|
193
|
+
badge?: ReactNode
|
|
194
|
+
href?: string
|
|
195
|
+
isActive?: boolean
|
|
196
|
+
ariaExpanded?: boolean
|
|
197
|
+
disabled?: boolean
|
|
198
|
+
closeOnSelect?: boolean
|
|
199
|
+
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
200
|
+
rel?: string
|
|
201
|
+
onSelect?: () => void
|
|
202
|
+
className?: string
|
|
203
|
+
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useMemo, useState } from "react"
|
|
4
4
|
|
|
5
|
-
import type { SidebarSide } from "@/components/motion/animated-sidebar"
|
|
6
5
|
import type {
|
|
7
6
|
ShellPanelControlProps,
|
|
8
7
|
ShellPanelControls,
|
|
8
|
+
SidebarSide,
|
|
9
9
|
UseShellPanelsOptions,
|
|
10
10
|
} from "./type"
|
|
11
11
|
|