@exegia/corpora-ui 0.16.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/shell-layout.d.ts +1 -1
- package/dist-lib/components/blocks/shell/type.d.ts +91 -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 +1432 -932
- 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/__tests__/shell-layout.test.tsx +3 -3
- 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 +44 -64
- package/src/components/blocks/shell/type.ts +113 -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/button.tsx +3 -3
- 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 -109
- package/src/components/motion/animated-sidebar.tsx +0 -1196
|
@@ -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
|
|
|
@@ -1,2 +1,143 @@
|
|
|
1
|
-
|
|
2
1
|
export const TITLE_BAR_HEIGHT = 48
|
|
2
|
+
// The desktop rail settles at a hard zero-width boundary. Keep the spring
|
|
3
|
+
// critically damped so it cannot overshoot, pause against that boundary, and
|
|
4
|
+
// then snap back during the final frame.
|
|
5
|
+
import { EASE_OUT, EASE_DRAWER } from "@/lib/ease.ts"
|
|
6
|
+
import type { Variants } from "motion/react"
|
|
7
|
+
import { createContext, useContext, useSyncExternalStore } from "react"
|
|
8
|
+
import type {
|
|
9
|
+
AnimatedSidebarContextValue,
|
|
10
|
+
AnimatedSidebarPanelContextValue,
|
|
11
|
+
} from "./type"
|
|
12
|
+
|
|
13
|
+
export const SIDEBAR_MORPH_TRANSITION = {
|
|
14
|
+
type: "spring",
|
|
15
|
+
stiffness: 380,
|
|
16
|
+
damping: 35,
|
|
17
|
+
mass: 0.75,
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
export const LABEL_ENTER_TRANSITION = {
|
|
21
|
+
duration: 0.2,
|
|
22
|
+
delay: 0.08,
|
|
23
|
+
ease: EASE_OUT,
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
export const LABEL_EXIT_TRANSITION = {
|
|
27
|
+
duration: 0.12,
|
|
28
|
+
ease: EASE_OUT,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
export const PANEL_TRANSITION = {
|
|
32
|
+
duration: 0.36,
|
|
33
|
+
ease: EASE_DRAWER,
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
export const REDUCED_TRANSITION = {
|
|
37
|
+
duration: 0.16,
|
|
38
|
+
ease: EASE_OUT,
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
const SUBMENU_TRANSITION = {
|
|
42
|
+
duration: 0.18,
|
|
43
|
+
ease: EASE_OUT,
|
|
44
|
+
} as const
|
|
45
|
+
|
|
46
|
+
export const SUBMENU_VARIANTS: Variants = {
|
|
47
|
+
closed: {
|
|
48
|
+
opacity: 0,
|
|
49
|
+
clipPath: "inset(0 0 100% 0 round 8px)",
|
|
50
|
+
transition: {
|
|
51
|
+
duration: 0.14,
|
|
52
|
+
ease: EASE_OUT,
|
|
53
|
+
staggerChildren: 0.025,
|
|
54
|
+
staggerDirection: -1,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
open: {
|
|
58
|
+
opacity: 1,
|
|
59
|
+
clipPath: "inset(0 0 0% 0 round 8px)",
|
|
60
|
+
transition: {
|
|
61
|
+
duration: 0.2,
|
|
62
|
+
delayChildren: 0.035,
|
|
63
|
+
ease: EASE_OUT,
|
|
64
|
+
staggerChildren: 0.045,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const SUBMENU_ITEM_VARIANTS: Variants = {
|
|
70
|
+
closed: {
|
|
71
|
+
opacity: 0,
|
|
72
|
+
y: -6,
|
|
73
|
+
filter: "blur(3px)",
|
|
74
|
+
},
|
|
75
|
+
open: {
|
|
76
|
+
opacity: 1,
|
|
77
|
+
y: 0,
|
|
78
|
+
filter: "blur(0px)",
|
|
79
|
+
transition: SUBMENU_TRANSITION,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const FOCUSABLE_SELECTOR = [
|
|
84
|
+
"a[href]",
|
|
85
|
+
"button:not([disabled])",
|
|
86
|
+
"input:not([disabled])",
|
|
87
|
+
"select:not([disabled])",
|
|
88
|
+
"textarea:not([disabled])",
|
|
89
|
+
"[tabindex]:not([tabindex='-1'])",
|
|
90
|
+
].join(",")
|
|
91
|
+
|
|
92
|
+
export const MOBILE_QUERY = "(max-width: 767px)"
|
|
93
|
+
export const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export const AnimatedSidebarContext =
|
|
97
|
+
createContext<AnimatedSidebarContextValue | null>(null)
|
|
98
|
+
|
|
99
|
+
export const AnimatedSidebarPanelContext =
|
|
100
|
+
createContext<AnimatedSidebarPanelContextValue | null>(null)
|
|
101
|
+
|
|
102
|
+
export function useAnimatedSidebar() {
|
|
103
|
+
const context = useContext(AnimatedSidebarContext)
|
|
104
|
+
if (!context) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"useAnimatedSidebar must be used inside AnimatedPanelProvider."
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return context
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function subscribeToMobileQuery(callback: () => void) {
|
|
113
|
+
const query = window.matchMedia(MOBILE_QUERY)
|
|
114
|
+
query.addEventListener("change", callback)
|
|
115
|
+
return () => query.removeEventListener("change", callback)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getMobileSnapshot() {
|
|
119
|
+
return window.matchMedia(MOBILE_QUERY).matches
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getServerMobileSnapshot() {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function useIsMobile() {
|
|
127
|
+
return useSyncExternalStore(
|
|
128
|
+
subscribeToMobileQuery,
|
|
129
|
+
getMobileSnapshot,
|
|
130
|
+
getServerMobileSnapshot
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
export function useAnimatedSidebarPanel() {
|
|
136
|
+
const context = useContext(AnimatedSidebarPanelContext)
|
|
137
|
+
if (!context) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"Animated Sidebar parts must be used inside AnimatedPanel."
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
return context
|
|
143
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// beui.dev/components/blocks/overflow-actions
|
|
3
|
+
|
|
4
|
+
import { MoreHorizontal, X } from "lucide-react";
|
|
5
|
+
import {
|
|
6
|
+
AnimatePresence,
|
|
7
|
+
motion,
|
|
8
|
+
useReducedMotion,
|
|
9
|
+
type Transition,
|
|
10
|
+
type Variants,
|
|
11
|
+
} from "motion/react";
|
|
12
|
+
import {
|
|
13
|
+
useCallback,
|
|
14
|
+
useId,
|
|
15
|
+
useLayoutEffect,
|
|
16
|
+
useRef,
|
|
17
|
+
useState,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
} from "react";
|
|
20
|
+
import { EASE_OUT } from "@/lib/ease";
|
|
21
|
+
import { useHoverCapable } from "@/lib/hooks/use-hover-capable";
|
|
22
|
+
import { cn } from "@/lib/utils";
|
|
23
|
+
|
|
24
|
+
export type OverflowActionsSize = "sm" | "md";
|
|
25
|
+
|
|
26
|
+
export type OverflowActionItem = {
|
|
27
|
+
id: string;
|
|
28
|
+
label: ReactNode;
|
|
29
|
+
icon?: ReactNode;
|
|
30
|
+
onClick?: () => void;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type OverflowActionsClassNames = {
|
|
36
|
+
root?: string;
|
|
37
|
+
track?: string;
|
|
38
|
+
action?: string;
|
|
39
|
+
primaryAction?: string;
|
|
40
|
+
overflowAction?: string;
|
|
41
|
+
toggle?: string;
|
|
42
|
+
icon?: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface OverflowActionsProps {
|
|
47
|
+
primaryActions: OverflowActionItem[];
|
|
48
|
+
overflowActions: OverflowActionItem[];
|
|
49
|
+
expanded?: boolean;
|
|
50
|
+
defaultExpanded?: boolean;
|
|
51
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
52
|
+
onAction?: (item: OverflowActionItem) => void;
|
|
53
|
+
collapseOnAction?: boolean;
|
|
54
|
+
size?: OverflowActionsSize;
|
|
55
|
+
openLabel?: string;
|
|
56
|
+
closeLabel?: string;
|
|
57
|
+
className?: string;
|
|
58
|
+
classNames?: OverflowActionsClassNames;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// This needs a softer layout spring than the app defaults, so the overflow group
|
|
62
|
+
// stays visually attached to the toggle while entering and leaving.
|
|
63
|
+
const SHELL_TRANSITION: Transition = {
|
|
64
|
+
type: "spring",
|
|
65
|
+
stiffness: 220,
|
|
66
|
+
damping: 17,
|
|
67
|
+
mass: 0.85,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const ICON_VARIANTS: Variants = {
|
|
71
|
+
hidden: { opacity: 0, filter: "blur(3px)" },
|
|
72
|
+
visible: {
|
|
73
|
+
opacity: 1,
|
|
74
|
+
filter: "blur(0px)",
|
|
75
|
+
transition: { duration: 0.18, ease: EASE_OUT },
|
|
76
|
+
},
|
|
77
|
+
exit: {
|
|
78
|
+
opacity: 0,
|
|
79
|
+
filter: "blur(3px)",
|
|
80
|
+
transition: { duration: 0.18, ease: EASE_OUT },
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const OVERFLOW_ACTION_VARIANTS: Variants = {
|
|
85
|
+
hidden: { opacity: 0, filter: "blur(4px)" },
|
|
86
|
+
visible: { opacity: 1, filter: "blur(0px)" },
|
|
87
|
+
exit: { opacity: 0, filter: "blur(4px)" },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const TRACK_SIZE_CLASS: Record<OverflowActionsSize, string> = {
|
|
91
|
+
sm: "gap-1 p-1 text-xs",
|
|
92
|
+
md: "gap-1.5 p-1.5 text-sm",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const GROUP_GAP_CLASS: Record<OverflowActionsSize, string> = {
|
|
96
|
+
sm: "gap-1",
|
|
97
|
+
md: "gap-1.5",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const ACTION_SIZE_CLASS: Record<OverflowActionsSize, string> = {
|
|
101
|
+
sm: "h-8 min-w-8 gap-1.5 px-3",
|
|
102
|
+
md: "h-9 min-w-9 gap-2 px-3.5",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const TOGGLE_SIZE_CLASS: Record<OverflowActionsSize, string> = {
|
|
106
|
+
sm: "h-8 w-8",
|
|
107
|
+
md: "h-9 w-9",
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const ICON_SIZE_CLASS: Record<OverflowActionsSize, string> = {
|
|
111
|
+
sm: "h-3.5 w-3.5",
|
|
112
|
+
md: "h-4 w-4",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function useControllableExpanded({
|
|
116
|
+
expanded,
|
|
117
|
+
defaultExpanded,
|
|
118
|
+
onExpandedChange,
|
|
119
|
+
}: {
|
|
120
|
+
expanded?: boolean;
|
|
121
|
+
defaultExpanded?: boolean;
|
|
122
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
123
|
+
}) {
|
|
124
|
+
const [internalExpanded, setInternalExpanded] = useState(
|
|
125
|
+
defaultExpanded ?? false,
|
|
126
|
+
);
|
|
127
|
+
const isControlled = expanded !== undefined;
|
|
128
|
+
const value = expanded ?? internalExpanded;
|
|
129
|
+
|
|
130
|
+
const setValue = useCallback(
|
|
131
|
+
(next: boolean) => {
|
|
132
|
+
if (!isControlled) setInternalExpanded(next);
|
|
133
|
+
onExpandedChange?.(next);
|
|
134
|
+
},
|
|
135
|
+
[isControlled, onExpandedChange],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return [value, setValue] as const;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function OverflowActions({
|
|
142
|
+
primaryActions,
|
|
143
|
+
overflowActions,
|
|
144
|
+
expanded,
|
|
145
|
+
defaultExpanded = false,
|
|
146
|
+
onExpandedChange,
|
|
147
|
+
onAction,
|
|
148
|
+
collapseOnAction = false,
|
|
149
|
+
size = "md",
|
|
150
|
+
openLabel = "Show extra actions",
|
|
151
|
+
closeLabel = "Hide extra actions",
|
|
152
|
+
className,
|
|
153
|
+
classNames,
|
|
154
|
+
}: OverflowActionsProps) {
|
|
155
|
+
const reduce = useReducedMotion();
|
|
156
|
+
const canHover = useHoverCapable();
|
|
157
|
+
const overflowId = useId();
|
|
158
|
+
const overflowWrapperRef = useRef<HTMLDivElement>(null);
|
|
159
|
+
const overflowWrapperLeftRef = useRef(0);
|
|
160
|
+
const [isExpanded, setIsExpanded] = useControllableExpanded({
|
|
161
|
+
expanded,
|
|
162
|
+
defaultExpanded,
|
|
163
|
+
onExpandedChange,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const transition = reduce ? { duration: 0 } : SHELL_TRANSITION;
|
|
167
|
+
|
|
168
|
+
useLayoutEffect(() => {
|
|
169
|
+
const overflowNode = overflowWrapperRef.current;
|
|
170
|
+
if (!overflowNode) return;
|
|
171
|
+
|
|
172
|
+
if (!isExpanded) {
|
|
173
|
+
overflowNode.style.left = `${
|
|
174
|
+
overflowWrapperLeftRef.current -
|
|
175
|
+
overflowNode.getBoundingClientRect().left
|
|
176
|
+
}px`;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
overflowNode.style.left = "";
|
|
181
|
+
overflowWrapperLeftRef.current = overflowNode.getBoundingClientRect().left;
|
|
182
|
+
}, [isExpanded]);
|
|
183
|
+
|
|
184
|
+
const handleAction = (item: OverflowActionItem) => {
|
|
185
|
+
item.onClick?.();
|
|
186
|
+
onAction?.(item);
|
|
187
|
+
if (collapseOnAction) setIsExpanded(false);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<motion.div
|
|
192
|
+
layout
|
|
193
|
+
transition={transition}
|
|
194
|
+
className={cn("inline-flex", classNames?.root, className)}
|
|
195
|
+
>
|
|
196
|
+
<motion.div
|
|
197
|
+
layout
|
|
198
|
+
transition={transition}
|
|
199
|
+
className={cn(
|
|
200
|
+
"relative inline-flex items-center overflow-hidden rounded-full border border-border bg-card",
|
|
201
|
+
TRACK_SIZE_CLASS[size],
|
|
202
|
+
classNames?.track,
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
<motion.div
|
|
206
|
+
layout
|
|
207
|
+
transition={transition}
|
|
208
|
+
className={cn("inline-flex items-center", GROUP_GAP_CLASS[size])}
|
|
209
|
+
>
|
|
210
|
+
{primaryActions.map((item) => (
|
|
211
|
+
<ActionButton
|
|
212
|
+
key={item.id}
|
|
213
|
+
item={item}
|
|
214
|
+
size={size}
|
|
215
|
+
reduce={reduce}
|
|
216
|
+
canHover={canHover}
|
|
217
|
+
onAction={handleAction}
|
|
218
|
+
layoutTransition={transition}
|
|
219
|
+
className={cn(classNames?.action, classNames?.primaryAction)}
|
|
220
|
+
iconClassName={classNames?.icon}
|
|
221
|
+
labelClassName={classNames?.label}
|
|
222
|
+
/>
|
|
223
|
+
))}
|
|
224
|
+
</motion.div>
|
|
225
|
+
|
|
226
|
+
<AnimatePresence mode="popLayout" initial={false}>
|
|
227
|
+
{isExpanded ? (
|
|
228
|
+
<motion.div
|
|
229
|
+
key="overflow-actions"
|
|
230
|
+
ref={overflowWrapperRef}
|
|
231
|
+
id={overflowId}
|
|
232
|
+
layout
|
|
233
|
+
aria-hidden={!isExpanded}
|
|
234
|
+
transition={transition}
|
|
235
|
+
className={cn(
|
|
236
|
+
"relative inline-flex w-max items-center",
|
|
237
|
+
GROUP_GAP_CLASS[size],
|
|
238
|
+
)}
|
|
239
|
+
>
|
|
240
|
+
{overflowActions.map((item) => (
|
|
241
|
+
<ActionButton
|
|
242
|
+
key={item.id}
|
|
243
|
+
item={item}
|
|
244
|
+
size={size}
|
|
245
|
+
reduce={reduce}
|
|
246
|
+
canHover={canHover}
|
|
247
|
+
overflow
|
|
248
|
+
visible={isExpanded}
|
|
249
|
+
variants={OVERFLOW_ACTION_VARIANTS}
|
|
250
|
+
onAction={handleAction}
|
|
251
|
+
layoutTransition={transition}
|
|
252
|
+
className={cn(classNames?.action, classNames?.overflowAction)}
|
|
253
|
+
iconClassName={classNames?.icon}
|
|
254
|
+
labelClassName={classNames?.label}
|
|
255
|
+
/>
|
|
256
|
+
))}
|
|
257
|
+
</motion.div>
|
|
258
|
+
) : null}
|
|
259
|
+
</AnimatePresence>
|
|
260
|
+
|
|
261
|
+
<motion.button
|
|
262
|
+
type="button"
|
|
263
|
+
layout
|
|
264
|
+
aria-expanded={isExpanded}
|
|
265
|
+
aria-controls={isExpanded ? overflowId : undefined}
|
|
266
|
+
aria-label={isExpanded ? closeLabel : openLabel}
|
|
267
|
+
title={isExpanded ? closeLabel : openLabel}
|
|
268
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
269
|
+
whileTap={reduce ? undefined : { scale: 0.96 }}
|
|
270
|
+
whileHover={reduce || !canHover ? undefined : { scale: 1.03 }}
|
|
271
|
+
transition={transition}
|
|
272
|
+
className={cn(
|
|
273
|
+
"relative inline-grid shrink-0 place-items-center rounded-full bg-primary text-primary-foreground outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
274
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
275
|
+
TOGGLE_SIZE_CLASS[size],
|
|
276
|
+
classNames?.toggle,
|
|
277
|
+
)}
|
|
278
|
+
>
|
|
279
|
+
<AnimatePresence mode="popLayout" initial={false}>
|
|
280
|
+
<motion.span
|
|
281
|
+
key={isExpanded ? "close" : "open"}
|
|
282
|
+
variants={ICON_VARIANTS}
|
|
283
|
+
initial={reduce ? { opacity: 0 } : "hidden"}
|
|
284
|
+
animate={reduce ? { opacity: 1 } : "visible"}
|
|
285
|
+
exit={reduce ? { opacity: 0 } : "exit"}
|
|
286
|
+
className="inline-grid place-items-center"
|
|
287
|
+
>
|
|
288
|
+
{isExpanded ? (
|
|
289
|
+
<X className={ICON_SIZE_CLASS[size]} />
|
|
290
|
+
) : (
|
|
291
|
+
<MoreHorizontal className={ICON_SIZE_CLASS[size]} />
|
|
292
|
+
)}
|
|
293
|
+
</motion.span>
|
|
294
|
+
</AnimatePresence>
|
|
295
|
+
</motion.button>
|
|
296
|
+
</motion.div>
|
|
297
|
+
</motion.div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function ActionButton({
|
|
302
|
+
item,
|
|
303
|
+
size,
|
|
304
|
+
reduce,
|
|
305
|
+
canHover,
|
|
306
|
+
overflow,
|
|
307
|
+
visible = true,
|
|
308
|
+
variants,
|
|
309
|
+
onAction,
|
|
310
|
+
layoutTransition,
|
|
311
|
+
className,
|
|
312
|
+
iconClassName,
|
|
313
|
+
labelClassName,
|
|
314
|
+
}: {
|
|
315
|
+
item: OverflowActionItem;
|
|
316
|
+
size: OverflowActionsSize;
|
|
317
|
+
reduce: boolean | null;
|
|
318
|
+
canHover: boolean;
|
|
319
|
+
overflow?: boolean;
|
|
320
|
+
visible?: boolean;
|
|
321
|
+
variants?: Variants;
|
|
322
|
+
onAction: (item: OverflowActionItem) => void;
|
|
323
|
+
layoutTransition: Transition;
|
|
324
|
+
className?: string;
|
|
325
|
+
iconClassName?: string;
|
|
326
|
+
labelClassName?: string;
|
|
327
|
+
}) {
|
|
328
|
+
const label = typeof item.label === "string" ? item.label : undefined;
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<motion.span
|
|
332
|
+
layout="position"
|
|
333
|
+
variants={variants}
|
|
334
|
+
initial={variants ? (reduce ? { opacity: 0 } : "hidden") : undefined}
|
|
335
|
+
animate={variants ? (reduce ? { opacity: 1 } : "visible") : undefined}
|
|
336
|
+
exit={variants ? (reduce ? { opacity: 0 } : "exit") : undefined}
|
|
337
|
+
whileTap={reduce || item.disabled ? undefined : { scale: 0.97 }}
|
|
338
|
+
whileHover={
|
|
339
|
+
reduce || !canHover || item.disabled ? undefined : { scale: 1.008 }
|
|
340
|
+
}
|
|
341
|
+
transition={layoutTransition}
|
|
342
|
+
className="inline-flex shrink-0"
|
|
343
|
+
>
|
|
344
|
+
<button
|
|
345
|
+
type="button"
|
|
346
|
+
disabled={item.disabled}
|
|
347
|
+
aria-label={item.ariaLabel}
|
|
348
|
+
tabIndex={overflow && !visible ? -1 : undefined}
|
|
349
|
+
title={label}
|
|
350
|
+
onClick={() => onAction(item)}
|
|
351
|
+
className={cn(
|
|
352
|
+
"inline-flex shrink-0 items-center justify-center rounded-full bg-background font-medium text-foreground outline-none",
|
|
353
|
+
"disabled:pointer-events-none disabled:opacity-45",
|
|
354
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
355
|
+
ACTION_SIZE_CLASS[size],
|
|
356
|
+
className,
|
|
357
|
+
)}
|
|
358
|
+
>
|
|
359
|
+
{item.icon ? (
|
|
360
|
+
<span
|
|
361
|
+
className={cn(
|
|
362
|
+
"inline-flex shrink-0 items-center justify-center",
|
|
363
|
+
ICON_SIZE_CLASS[size],
|
|
364
|
+
iconClassName,
|
|
365
|
+
)}
|
|
366
|
+
>
|
|
367
|
+
{item.icon}
|
|
368
|
+
</span>
|
|
369
|
+
) : null}
|
|
370
|
+
<span className={cn("whitespace-nowrap", labelClassName)}>
|
|
371
|
+
{item.label}
|
|
372
|
+
</span>
|
|
373
|
+
</button>
|
|
374
|
+
</motion.span>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { motion, type HTMLMotionProps } from "framer-motion"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { Separator } from "@/components/ui/separator"
|
|
8
|
+
|
|
9
|
+
const buttonGroupVariants = cva(
|
|
10
|
+
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
orientation: {
|
|
14
|
+
horizontal:
|
|
15
|
+
"*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
|
|
16
|
+
vertical:
|
|
17
|
+
"flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
orientation: "horizontal",
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
function ButtonGroup({
|
|
27
|
+
className,
|
|
28
|
+
orientation,
|
|
29
|
+
...props
|
|
30
|
+
}: HTMLMotionProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
|
31
|
+
return (
|
|
32
|
+
<motion.div
|
|
33
|
+
role="group"
|
|
34
|
+
data-slot="button-group"
|
|
35
|
+
data-orientation={orientation}
|
|
36
|
+
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ButtonGroupText({
|
|
43
|
+
className,
|
|
44
|
+
render,
|
|
45
|
+
...props
|
|
46
|
+
}: useRender.ComponentProps<"div">) {
|
|
47
|
+
return useRender({
|
|
48
|
+
defaultTagName: "div",
|
|
49
|
+
props: mergeProps<"div">(
|
|
50
|
+
{
|
|
51
|
+
className: cn(
|
|
52
|
+
"flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
53
|
+
className
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
props
|
|
57
|
+
),
|
|
58
|
+
render,
|
|
59
|
+
state: {
|
|
60
|
+
slot: "button-group-text",
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ButtonGroupSeparator({
|
|
66
|
+
className,
|
|
67
|
+
orientation = "vertical",
|
|
68
|
+
...props
|
|
69
|
+
}: React.ComponentProps<typeof Separator>) {
|
|
70
|
+
return (
|
|
71
|
+
<Separator
|
|
72
|
+
data-slot="button-group-separator"
|
|
73
|
+
orientation={orientation}
|
|
74
|
+
className={cn(
|
|
75
|
+
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
ButtonGroup,
|
|
85
|
+
ButtonGroupSeparator,
|
|
86
|
+
ButtonGroupText,
|
|
87
|
+
buttonGroupVariants,
|
|
88
|
+
}
|
|
@@ -46,10 +46,10 @@ export const buttonVariants = cva(
|
|
|
46
46
|
size: {
|
|
47
47
|
default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
|
|
48
48
|
icon: "size-9 sm:size-8",
|
|
49
|
-
"icon-lg": "size-
|
|
50
|
-
"icon-sm": "size-
|
|
49
|
+
"icon-lg": "size-8 sm:size-8",
|
|
50
|
+
"icon-sm": "size-7 sm:size-6",
|
|
51
51
|
"icon-xl":
|
|
52
|
-
"size-
|
|
52
|
+
"size-10 sm:size-9 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
53
53
|
"icon-xs":
|
|
54
54
|
"size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
55
55
|
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type FrostGlassVariantProp,
|
|
5
|
+
glassVariantStyles,
|
|
6
|
+
} from "@/lib/glass-variants"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
import { ButtonGroup } from "../button-group"
|
|
10
|
+
import { LiquidGlass } from "./liquid-glass"
|
|
11
|
+
|
|
12
|
+
type GlassButtonGroupProps = React.ComponentProps<typeof ButtonGroup> &
|
|
13
|
+
FrostGlassVariantProp
|
|
14
|
+
|
|
15
|
+
function GlassButtonGroup({
|
|
16
|
+
className,
|
|
17
|
+
glassVariant = "liquid-refract",
|
|
18
|
+
children,
|
|
19
|
+
...props
|
|
20
|
+
}: GlassButtonGroupProps) {
|
|
21
|
+
if (glassVariant === "liquid-refract") {
|
|
22
|
+
return (
|
|
23
|
+
<LiquidGlass className={cn("", className)}>
|
|
24
|
+
<ButtonGroup
|
|
25
|
+
data-slot="glass-button-group"
|
|
26
|
+
data-glass-variant={glassVariant}
|
|
27
|
+
className={cn("bg-transparent", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</ButtonGroup>
|
|
32
|
+
</LiquidGlass>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ButtonGroup
|
|
38
|
+
data-slot="glass-button-group"
|
|
39
|
+
data-glass-variant={glassVariant}
|
|
40
|
+
className={cn("rounded-lg", glassVariantStyles[glassVariant], className)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</ButtonGroup>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { GlassButtonGroup }
|