@exegia/corpora-ui 0.10.0 → 0.11.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/agents/ai-sidebar.d.ts +2 -0
- package/dist-lib/components/agents/index.d.ts +12 -0
- package/dist-lib/components/agents/marquee-label.d.ts +4 -0
- package/dist-lib/components/agents/sidebar-icon.d.ts +2 -0
- package/dist-lib/components/agents/sidebar-row.d.ts +2 -0
- package/dist-lib/components/agents/type.d.ts +77 -0
- package/dist-lib/components/agents/use-ai-sidebar.d.ts +31 -0
- package/dist-lib/components/agents/utils.d.ts +16 -0
- package/dist-lib/components/beste/piece/browser-frame.d.ts +2 -0
- package/dist-lib/components/beste/piece/type.d.ts +9 -0
- package/dist-lib/components/beste/piece/utils.d.ts +3 -0
- package/dist-lib/components/blocks/auth/signup-block.d.ts +5 -5
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +3 -56
- package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +3 -0
- package/dist-lib/components/blocks/nav/types.d.ts +60 -0
- package/dist-lib/components/blocks/nav/utils.d.ts +1 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +6 -1
- package/dist-lib/components/blocks/shell/index.d.ts +1 -0
- package/dist-lib/components/blocks/shell/shell-layout.d.ts +3 -0
- package/dist-lib/components/blocks/shell/type.d.ts +26 -0
- package/dist-lib/components/blocks/shell/utils.d.ts +1 -0
- package/dist-lib/components/composed/social-providers.d.ts +9 -1
- package/dist-lib/components/docs/demo-controls.d.ts +4 -4
- package/dist-lib/components/motion/animated-sidebar.d.ts +12 -1
- package/dist-lib/components/motion/popover-morph.d.ts +35 -0
- package/dist-lib/components/motion/popover-position.d.ts +15 -0
- package/dist-lib/components/ui/badge.d.ts +7 -0
- package/dist-lib/components/ui/select.d.ts +30 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.js +644 -524
- package/dist-lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/ai-sidebar.tsx +116 -0
- package/src/components/agents/index.ts +14 -0
- package/src/components/agents/marquee-label.tsx +59 -0
- package/src/components/agents/sidebar-icon.tsx +14 -0
- package/src/components/agents/sidebar-row.tsx +271 -0
- package/src/components/agents/type.ts +89 -0
- package/src/components/agents/use-ai-sidebar.ts +386 -0
- package/src/components/agents/utils.ts +142 -0
- package/src/components/beste/piece/browser-frame.tsx +55 -0
- package/src/components/beste/piece/type.ts +11 -0
- package/src/components/beste/piece/utils.ts +21 -0
- package/src/components/blocks/auth/linked-accounts-block.tsx +55 -57
- package/src/components/blocks/auth/passkey-manager-block.tsx +88 -81
- package/src/components/blocks/auth/signup-block.tsx +6 -6
- package/src/components/blocks/nav/sidebar-block.tsx +25 -155
- package/src/components/blocks/nav/sidebar-nav-row.tsx +79 -0
- package/src/components/blocks/nav/types.ts +65 -0
- package/src/components/blocks/nav/utils.ts +4 -0
- package/src/components/blocks/profile/profile-card-block.tsx +17 -6
- package/src/components/blocks/shell/index.ts +1 -0
- package/src/components/blocks/shell/shell-layout.tsx +115 -0
- package/src/components/blocks/shell/type.ts +50 -0
- package/src/components/blocks/shell/utils.ts +2 -0
- package/src/components/composed/social-providers.tsx +83 -27
- package/src/components/docs/component-preview.tsx +13 -12
- package/src/components/docs/demo-controls.tsx +37 -21
- package/src/components/motion/animated-sidebar.tsx +392 -364
- package/src/components/motion/popover-morph.tsx +292 -0
- package/src/components/motion/popover-position.ts +86 -0
- package/src/components/ui/badge.tsx +52 -0
- package/src/components/ui/select.tsx +257 -0
- package/src/index.css +18 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
4
|
+
import {
|
|
5
|
+
cloneElement,
|
|
6
|
+
createContext,
|
|
7
|
+
isValidElement,
|
|
8
|
+
type ReactElement,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
type Ref,
|
|
11
|
+
useCallback,
|
|
12
|
+
useContext,
|
|
13
|
+
useEffect,
|
|
14
|
+
useId,
|
|
15
|
+
useMemo,
|
|
16
|
+
useRef,
|
|
17
|
+
useState,
|
|
18
|
+
} from "react";
|
|
19
|
+
import { createPortal } from "react-dom";
|
|
20
|
+
import { usePopoverPortalPosition } from "@/components/motion/popover-position";
|
|
21
|
+
import { EASE_OUT, SPRING_PANEL } from "@/lib/ease";
|
|
22
|
+
import { cn } from "@/lib/utils";
|
|
23
|
+
|
|
24
|
+
type Side = "top" | "bottom";
|
|
25
|
+
type Align = "start" | "end";
|
|
26
|
+
|
|
27
|
+
type MorphContextValue = {
|
|
28
|
+
open: boolean;
|
|
29
|
+
setOpen: (open: boolean) => void;
|
|
30
|
+
toggle: () => void;
|
|
31
|
+
triggerId: string;
|
|
32
|
+
contentId: string;
|
|
33
|
+
triggerRef: React.MutableRefObject<HTMLElement | null>;
|
|
34
|
+
setContentRef: (node: HTMLDivElement | null) => void;
|
|
35
|
+
contentRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const MorphContext = createContext<MorphContextValue | null>(null);
|
|
39
|
+
|
|
40
|
+
function useMorphContext(component: string) {
|
|
41
|
+
const ctx = useContext(MorphContext);
|
|
42
|
+
if (!ctx) throw new Error(`${component} must be used within <MorphPopover>`);
|
|
43
|
+
return ctx;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MorphPopoverProps {
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
/** Controlled open state. */
|
|
49
|
+
open?: boolean;
|
|
50
|
+
/** Uncontrolled initial open state. */
|
|
51
|
+
defaultOpen?: boolean;
|
|
52
|
+
onOpenChange?: (open: boolean) => void;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A popover whose panel morphs open from the trigger corner: it's laid out at
|
|
58
|
+
* full size but clipped to the corner nearest the trigger, then unclips as one
|
|
59
|
+
* piece. Closes on outside pointer / Escape. Controlled or uncontrolled.
|
|
60
|
+
*/
|
|
61
|
+
export function MorphPopover({
|
|
62
|
+
children,
|
|
63
|
+
open: controlledOpen,
|
|
64
|
+
defaultOpen = false,
|
|
65
|
+
onOpenChange,
|
|
66
|
+
className,
|
|
67
|
+
}: MorphPopoverProps) {
|
|
68
|
+
const baseId = useId();
|
|
69
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
70
|
+
const triggerRef = useRef<HTMLElement | null>(null);
|
|
71
|
+
const contentRef = useRef<HTMLDivElement | null>(null);
|
|
72
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
73
|
+
const controlled = controlledOpen !== undefined;
|
|
74
|
+
const open = controlled ? controlledOpen : internalOpen;
|
|
75
|
+
|
|
76
|
+
const setOpen = useCallback(
|
|
77
|
+
(next: boolean) => {
|
|
78
|
+
if (!controlled) setInternalOpen(next);
|
|
79
|
+
onOpenChange?.(next);
|
|
80
|
+
},
|
|
81
|
+
[controlled, onOpenChange],
|
|
82
|
+
);
|
|
83
|
+
const toggle = useCallback(() => setOpen(!open), [setOpen, open]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!open) return;
|
|
87
|
+
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
|
|
88
|
+
const onPointer = (e: PointerEvent) => {
|
|
89
|
+
const target = e.target as Node;
|
|
90
|
+
if (
|
|
91
|
+
rootRef.current &&
|
|
92
|
+
!rootRef.current.contains(target) &&
|
|
93
|
+
!contentRef.current?.contains(target)
|
|
94
|
+
)
|
|
95
|
+
setOpen(false);
|
|
96
|
+
};
|
|
97
|
+
window.addEventListener("keydown", onKey);
|
|
98
|
+
window.addEventListener("pointerdown", onPointer);
|
|
99
|
+
return () => {
|
|
100
|
+
window.removeEventListener("keydown", onKey);
|
|
101
|
+
window.removeEventListener("pointerdown", onPointer);
|
|
102
|
+
};
|
|
103
|
+
}, [open, setOpen]);
|
|
104
|
+
|
|
105
|
+
const ctx = useMemo<MorphContextValue>(
|
|
106
|
+
() => ({
|
|
107
|
+
open,
|
|
108
|
+
setOpen,
|
|
109
|
+
toggle,
|
|
110
|
+
triggerId: `${baseId}-trigger`,
|
|
111
|
+
contentId: `${baseId}-content`,
|
|
112
|
+
triggerRef,
|
|
113
|
+
setContentRef: (node) => {
|
|
114
|
+
contentRef.current = node;
|
|
115
|
+
},
|
|
116
|
+
contentRef,
|
|
117
|
+
}),
|
|
118
|
+
[open, setOpen, toggle, baseId],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<MorphContext.Provider value={ctx}>
|
|
123
|
+
<div ref={rootRef} className={cn("relative inline-flex", className)}>
|
|
124
|
+
{children}
|
|
125
|
+
</div>
|
|
126
|
+
</MorphContext.Provider>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface MorphPopoverTriggerProps {
|
|
131
|
+
children: ReactElement;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function mergeRefs<T>(...refs: Array<Ref<T> | undefined>) {
|
|
135
|
+
return (node: T | null) => {
|
|
136
|
+
for (const ref of refs) {
|
|
137
|
+
if (typeof ref === "function") ref(node);
|
|
138
|
+
else if (ref && typeof ref === "object")
|
|
139
|
+
(ref as React.MutableRefObject<T | null>).current = node;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Wraps a single element, toggling the popover on click. */
|
|
145
|
+
export function MorphPopoverTrigger({ children }: MorphPopoverTriggerProps) {
|
|
146
|
+
const ctx = useMorphContext("MorphPopoverTrigger");
|
|
147
|
+
if (!isValidElement(children)) return children;
|
|
148
|
+
|
|
149
|
+
const child = children as ReactElement<Record<string, unknown>>;
|
|
150
|
+
const childOnClick = child.props.onClick as
|
|
151
|
+
| ((e: unknown) => void)
|
|
152
|
+
| undefined;
|
|
153
|
+
const childRef = (child.props as { ref?: Ref<HTMLElement> }).ref;
|
|
154
|
+
|
|
155
|
+
return cloneElement(child, {
|
|
156
|
+
id: ctx.triggerId,
|
|
157
|
+
ref: mergeRefs(childRef, (node: HTMLElement | null) => {
|
|
158
|
+
ctx.triggerRef.current = node;
|
|
159
|
+
}),
|
|
160
|
+
onClick: (e: unknown) => {
|
|
161
|
+
childOnClick?.(e);
|
|
162
|
+
ctx.toggle();
|
|
163
|
+
},
|
|
164
|
+
"aria-haspopup": "dialog",
|
|
165
|
+
"aria-expanded": ctx.open,
|
|
166
|
+
"aria-controls": ctx.open ? ctx.contentId : undefined,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const originFor = (side: Side, align: Align) =>
|
|
171
|
+
`${side === "bottom" ? "top" : "bottom"} ${align === "end" ? "right" : "left"}`;
|
|
172
|
+
|
|
173
|
+
// A clip that hides everything but the corner nearest the trigger, so the
|
|
174
|
+
// panel appears to grow out of it. inset(top right bottom left).
|
|
175
|
+
function clipHidden(side: Side, align: Align, radius: number) {
|
|
176
|
+
const top = side === "bottom" ? "0%" : "92%";
|
|
177
|
+
const bottom = side === "bottom" ? "92%" : "0%";
|
|
178
|
+
const right = align === "end" ? "0%" : "92%";
|
|
179
|
+
const left = align === "end" ? "92%" : "0%";
|
|
180
|
+
return `inset(${top} ${right} ${bottom} ${left} round ${radius}px)`;
|
|
181
|
+
}
|
|
182
|
+
const clipShown = (radius: number) => `inset(0% 0% 0% 0% round ${radius}px)`;
|
|
183
|
+
|
|
184
|
+
// Preserve the original spring character on the wrapper, but tween the complex
|
|
185
|
+
// clip-path so it cannot snap when the spring resolves its final distance.
|
|
186
|
+
const MORPH_CLIP_TRANSITION = { duration: 0.32, ease: EASE_OUT } as const;
|
|
187
|
+
|
|
188
|
+
export interface MorphPopoverContentProps {
|
|
189
|
+
children: ReactNode;
|
|
190
|
+
side?: Side;
|
|
191
|
+
align?: Align;
|
|
192
|
+
/** Gap between trigger and panel, in px. Default 8. */
|
|
193
|
+
sideOffset?: number;
|
|
194
|
+
/** Panel corner radius, in px. Default 16. */
|
|
195
|
+
radius?: number;
|
|
196
|
+
className?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function MorphPopoverContent({
|
|
200
|
+
children,
|
|
201
|
+
side = "bottom",
|
|
202
|
+
align = "end",
|
|
203
|
+
sideOffset = 8,
|
|
204
|
+
radius = 16,
|
|
205
|
+
className,
|
|
206
|
+
}: MorphPopoverContentProps) {
|
|
207
|
+
const ctx = useMorphContext("MorphPopoverContent");
|
|
208
|
+
const { contentId, setContentRef, triggerId } = ctx;
|
|
209
|
+
const reduce = useReducedMotion() ?? false;
|
|
210
|
+
const [portalReady, setPortalReady] = useState(false);
|
|
211
|
+
const layout = usePopoverPortalPosition(
|
|
212
|
+
ctx.triggerRef,
|
|
213
|
+
ctx.contentRef,
|
|
214
|
+
portalReady && ctx.open,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
useEffect(() => setPortalReady(true), []);
|
|
218
|
+
const left = layout
|
|
219
|
+
? align === "end"
|
|
220
|
+
? layout.trigger.left + layout.trigger.width - layout.content.width
|
|
221
|
+
: layout.trigger.left
|
|
222
|
+
: 0;
|
|
223
|
+
const top = layout
|
|
224
|
+
? side === "bottom"
|
|
225
|
+
? layout.trigger.top + layout.trigger.height + sideOffset
|
|
226
|
+
: layout.trigger.top - layout.content.height - sideOffset
|
|
227
|
+
: 0;
|
|
228
|
+
|
|
229
|
+
// Both directions travel between the exact same hidden/show states. Exit
|
|
230
|
+
// targets "hidden" directly instead of introducing separate choreography.
|
|
231
|
+
const wrap = reduce
|
|
232
|
+
? undefined
|
|
233
|
+
: {
|
|
234
|
+
hidden: { opacity: 0, scale: 0.96, transition: SPRING_PANEL },
|
|
235
|
+
show: { opacity: 1, scale: 1, transition: SPRING_PANEL },
|
|
236
|
+
};
|
|
237
|
+
const clip = reduce
|
|
238
|
+
? undefined
|
|
239
|
+
: {
|
|
240
|
+
hidden: {
|
|
241
|
+
clipPath: clipHidden(side, align, radius),
|
|
242
|
+
transition: MORPH_CLIP_TRANSITION,
|
|
243
|
+
},
|
|
244
|
+
show: {
|
|
245
|
+
clipPath: clipShown(radius),
|
|
246
|
+
transition: MORPH_CLIP_TRANSITION,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Keep the server and first client render identical, then mount the portal.
|
|
251
|
+
if (!portalReady) return null;
|
|
252
|
+
|
|
253
|
+
return createPortal(
|
|
254
|
+
<AnimatePresence>
|
|
255
|
+
{ctx.open ? (
|
|
256
|
+
<motion.div
|
|
257
|
+
data-morph-popover-portal=""
|
|
258
|
+
// Wrapper carries the shadow as a drop-shadow filter, which hugs the
|
|
259
|
+
// clipped shape below (box-shadow would just get clipped away).
|
|
260
|
+
variants={wrap}
|
|
261
|
+
initial={reduce ? { opacity: 0 } : "hidden"}
|
|
262
|
+
animate={reduce ? { opacity: 1 } : "show"}
|
|
263
|
+
exit={reduce ? { opacity: 0 } : "hidden"}
|
|
264
|
+
transition={reduce ? { duration: 0.12 } : undefined}
|
|
265
|
+
style={{
|
|
266
|
+
left,
|
|
267
|
+
top,
|
|
268
|
+
visibility: layout ? "visible" : "hidden",
|
|
269
|
+
transformOrigin: originFor(side, align),
|
|
270
|
+
}}
|
|
271
|
+
className="fixed z-[9999] [filter:drop-shadow(0_10px_18px_rgba(0,0,0,0.14))]"
|
|
272
|
+
>
|
|
273
|
+
<motion.div
|
|
274
|
+
ref={setContentRef}
|
|
275
|
+
id={contentId}
|
|
276
|
+
role="dialog"
|
|
277
|
+
aria-labelledby={triggerId}
|
|
278
|
+
variants={clip}
|
|
279
|
+
style={{ borderRadius: radius }}
|
|
280
|
+
className={cn(
|
|
281
|
+
"overflow-hidden border border-border bg-background",
|
|
282
|
+
className,
|
|
283
|
+
)}
|
|
284
|
+
>
|
|
285
|
+
{children}
|
|
286
|
+
</motion.div>
|
|
287
|
+
</motion.div>
|
|
288
|
+
) : null}
|
|
289
|
+
</AnimatePresence>,
|
|
290
|
+
document.body,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type MutableRefObject,
|
|
5
|
+
useCallback,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
export type PortalLayout = {
|
|
11
|
+
trigger: {
|
|
12
|
+
left: number;
|
|
13
|
+
top: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
content: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function sameLayout(a: PortalLayout | null, b: PortalLayout) {
|
|
24
|
+
return (
|
|
25
|
+
a?.trigger.left === b.trigger.left &&
|
|
26
|
+
a.trigger.top === b.trigger.top &&
|
|
27
|
+
a.trigger.width === b.trigger.width &&
|
|
28
|
+
a.trigger.height === b.trigger.height &&
|
|
29
|
+
a.content.width === b.content.width &&
|
|
30
|
+
a.content.height === b.content.height
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Measures a trigger and portalled panel in viewport coordinates. */
|
|
35
|
+
export function usePopoverPortalPosition<
|
|
36
|
+
TriggerElement extends HTMLElement,
|
|
37
|
+
ContentElement extends HTMLElement,
|
|
38
|
+
>(
|
|
39
|
+
triggerRef: MutableRefObject<TriggerElement | null>,
|
|
40
|
+
contentRef: MutableRefObject<ContentElement | null>,
|
|
41
|
+
active: boolean,
|
|
42
|
+
) {
|
|
43
|
+
const [layout, setLayout] = useState<PortalLayout | null>(null);
|
|
44
|
+
|
|
45
|
+
const update = useCallback(() => {
|
|
46
|
+
const trigger = triggerRef.current;
|
|
47
|
+
const content = contentRef.current;
|
|
48
|
+
if (!trigger || !content) return;
|
|
49
|
+
|
|
50
|
+
const rect = trigger.getBoundingClientRect();
|
|
51
|
+
const next: PortalLayout = {
|
|
52
|
+
trigger: {
|
|
53
|
+
left: rect.left,
|
|
54
|
+
top: rect.top,
|
|
55
|
+
width: rect.width,
|
|
56
|
+
height: rect.height,
|
|
57
|
+
},
|
|
58
|
+
content: {
|
|
59
|
+
width: content.offsetWidth,
|
|
60
|
+
height: content.offsetHeight,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
setLayout((current) => (sameLayout(current, next) ? current : next));
|
|
64
|
+
}, [contentRef, triggerRef]);
|
|
65
|
+
|
|
66
|
+
useLayoutEffect(() => {
|
|
67
|
+
update();
|
|
68
|
+
if (!active) return;
|
|
69
|
+
|
|
70
|
+
const trigger = triggerRef.current;
|
|
71
|
+
const content = contentRef.current;
|
|
72
|
+
const observer = new ResizeObserver(update);
|
|
73
|
+
if (trigger) observer.observe(trigger);
|
|
74
|
+
if (content) observer.observe(content);
|
|
75
|
+
|
|
76
|
+
window.addEventListener("scroll", update, true);
|
|
77
|
+
window.addEventListener("resize", update);
|
|
78
|
+
return () => {
|
|
79
|
+
observer.disconnect();
|
|
80
|
+
window.removeEventListener("scroll", update, true);
|
|
81
|
+
window.removeEventListener("resize", update);
|
|
82
|
+
};
|
|
83
|
+
}, [active, contentRef, triggerRef, update]);
|
|
84
|
+
|
|
85
|
+
return layout;
|
|
86
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-2.5!",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border bg-input/20 text-foreground dark:bg-input/30 [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function Badge({
|
|
31
|
+
className,
|
|
32
|
+
variant = "default",
|
|
33
|
+
render,
|
|
34
|
+
...props
|
|
35
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
36
|
+
return useRender({
|
|
37
|
+
defaultTagName: "span",
|
|
38
|
+
props: mergeProps<"span">(
|
|
39
|
+
{
|
|
40
|
+
className: cn(badgeVariants({ variant }), className),
|
|
41
|
+
},
|
|
42
|
+
props
|
|
43
|
+
),
|
|
44
|
+
render,
|
|
45
|
+
state: {
|
|
46
|
+
slot: "badge",
|
|
47
|
+
variant,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
5
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
7
|
+
import {
|
|
8
|
+
ChevronDownIcon,
|
|
9
|
+
ChevronsUpDownIcon,
|
|
10
|
+
ChevronUpIcon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import type * as React from "react";
|
|
13
|
+
import { cn } from "@/lib/utils";
|
|
14
|
+
|
|
15
|
+
export const Select: typeof SelectPrimitive.Root = SelectPrimitive.Root;
|
|
16
|
+
|
|
17
|
+
export const selectTriggerVariants = cva(
|
|
18
|
+
"relative inline-flex min-h-9 w-full min-w-36 select-none items-center justify-between gap-2 rounded-lg border border-input bg-background not-dark:bg-clip-padding px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
|
|
19
|
+
{
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
size: "default",
|
|
22
|
+
},
|
|
23
|
+
variants: {
|
|
24
|
+
size: {
|
|
25
|
+
default: "",
|
|
26
|
+
lg: "min-h-10 sm:min-h-9",
|
|
27
|
+
sm: "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const selectTriggerIconClassName = "-me-1 size-4.5 opacity-80 sm:size-4";
|
|
34
|
+
|
|
35
|
+
export interface SelectButtonProps extends useRender.ComponentProps<"button"> {
|
|
36
|
+
size?: VariantProps<typeof selectTriggerVariants>["size"];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function SelectButton({
|
|
40
|
+
className,
|
|
41
|
+
size,
|
|
42
|
+
render,
|
|
43
|
+
children,
|
|
44
|
+
...props
|
|
45
|
+
}: SelectButtonProps): React.ReactElement {
|
|
46
|
+
const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
|
|
47
|
+
render ? undefined : "button";
|
|
48
|
+
|
|
49
|
+
const defaultProps = {
|
|
50
|
+
children: (
|
|
51
|
+
<>
|
|
52
|
+
<span className="flex-1 truncate in-data-placeholder:text-muted-foreground/72">
|
|
53
|
+
{children}
|
|
54
|
+
</span>
|
|
55
|
+
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
|
|
56
|
+
</>
|
|
57
|
+
),
|
|
58
|
+
className: cn(selectTriggerVariants({ size }), "min-w-0", className),
|
|
59
|
+
"data-slot": "select-button",
|
|
60
|
+
type: typeValue,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return useRender({
|
|
64
|
+
defaultTagName: "button",
|
|
65
|
+
props: mergeProps<"button">(defaultProps, props),
|
|
66
|
+
render,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function SelectTrigger({
|
|
71
|
+
className,
|
|
72
|
+
size = "default",
|
|
73
|
+
children,
|
|
74
|
+
...props
|
|
75
|
+
}: SelectPrimitive.Trigger.Props &
|
|
76
|
+
VariantProps<typeof selectTriggerVariants>): React.ReactElement {
|
|
77
|
+
return (
|
|
78
|
+
<SelectPrimitive.Trigger
|
|
79
|
+
className={cn(selectTriggerVariants({ size }), className)}
|
|
80
|
+
data-slot="select-trigger"
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
<SelectPrimitive.Icon data-slot="select-icon">
|
|
85
|
+
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
|
|
86
|
+
</SelectPrimitive.Icon>
|
|
87
|
+
</SelectPrimitive.Trigger>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function SelectValue({
|
|
92
|
+
className,
|
|
93
|
+
...props
|
|
94
|
+
}: SelectPrimitive.Value.Props): React.ReactElement {
|
|
95
|
+
return (
|
|
96
|
+
<SelectPrimitive.Value
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex-1 truncate data-placeholder:text-muted-foreground",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
data-slot="select-value"
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function SelectPopup({
|
|
108
|
+
className,
|
|
109
|
+
children,
|
|
110
|
+
side = "bottom",
|
|
111
|
+
sideOffset = 4,
|
|
112
|
+
align = "start",
|
|
113
|
+
alignOffset = 0,
|
|
114
|
+
alignItemWithTrigger = true,
|
|
115
|
+
anchor,
|
|
116
|
+
portalProps,
|
|
117
|
+
...props
|
|
118
|
+
}: SelectPrimitive.Popup.Props & {
|
|
119
|
+
portalProps?: SelectPrimitive.Portal.Props;
|
|
120
|
+
side?: SelectPrimitive.Positioner.Props["side"];
|
|
121
|
+
sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
|
|
122
|
+
align?: SelectPrimitive.Positioner.Props["align"];
|
|
123
|
+
alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"];
|
|
124
|
+
alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"];
|
|
125
|
+
anchor?: SelectPrimitive.Positioner.Props["anchor"];
|
|
126
|
+
}): React.ReactElement {
|
|
127
|
+
return (
|
|
128
|
+
<SelectPrimitive.Portal {...portalProps}>
|
|
129
|
+
<SelectPrimitive.Positioner
|
|
130
|
+
align={align}
|
|
131
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
132
|
+
alignOffset={alignOffset}
|
|
133
|
+
anchor={anchor}
|
|
134
|
+
className="z-50 select-none"
|
|
135
|
+
data-slot="select-positioner"
|
|
136
|
+
side={side}
|
|
137
|
+
sideOffset={sideOffset}
|
|
138
|
+
>
|
|
139
|
+
<SelectPrimitive.Popup
|
|
140
|
+
className="origin-(--transform-origin) text-foreground outline-none"
|
|
141
|
+
data-slot="select-popup"
|
|
142
|
+
{...props}
|
|
143
|
+
>
|
|
144
|
+
<SelectPrimitive.ScrollUpArrow
|
|
145
|
+
className="top-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:top-px before:h-[200%] before:rounded-t-[calc(var(--radius-lg)-1px)] before:bg-linear-to-b before:from-50% before:from-popover"
|
|
146
|
+
data-slot="select-scroll-up-arrow"
|
|
147
|
+
>
|
|
148
|
+
<ChevronUpIcon className="relative size-4.5 sm:size-4" />
|
|
149
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
150
|
+
<div className="relative h-full min-w-(--anchor-width) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]">
|
|
151
|
+
<SelectPrimitive.List
|
|
152
|
+
className={cn(
|
|
153
|
+
"max-h-(--available-height) overflow-y-auto p-1",
|
|
154
|
+
className,
|
|
155
|
+
)}
|
|
156
|
+
data-slot="select-list"
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</SelectPrimitive.List>
|
|
160
|
+
</div>
|
|
161
|
+
<SelectPrimitive.ScrollDownArrow
|
|
162
|
+
className="bottom-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:bottom-px before:h-[200%] before:rounded-b-[calc(var(--radius-lg)-1px)] before:bg-linear-to-t before:from-50% before:from-popover"
|
|
163
|
+
data-slot="select-scroll-down-arrow"
|
|
164
|
+
>
|
|
165
|
+
<ChevronDownIcon className="relative size-4.5 sm:size-4" />
|
|
166
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
167
|
+
</SelectPrimitive.Popup>
|
|
168
|
+
</SelectPrimitive.Positioner>
|
|
169
|
+
</SelectPrimitive.Portal>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function SelectItem({
|
|
174
|
+
className,
|
|
175
|
+
children,
|
|
176
|
+
...props
|
|
177
|
+
}: SelectPrimitive.Item.Props): React.ReactElement {
|
|
178
|
+
return (
|
|
179
|
+
<SelectPrimitive.Item
|
|
180
|
+
className={cn(
|
|
181
|
+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
182
|
+
className,
|
|
183
|
+
)}
|
|
184
|
+
data-slot="select-item"
|
|
185
|
+
{...props}
|
|
186
|
+
>
|
|
187
|
+
<SelectPrimitive.ItemIndicator className="col-start-1">
|
|
188
|
+
<svg
|
|
189
|
+
aria-hidden="true"
|
|
190
|
+
fill="none"
|
|
191
|
+
height="24"
|
|
192
|
+
stroke="currentColor"
|
|
193
|
+
strokeLinecap="round"
|
|
194
|
+
strokeLinejoin="round"
|
|
195
|
+
strokeWidth="2"
|
|
196
|
+
viewBox="0 0 24 24"
|
|
197
|
+
width="24"
|
|
198
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
199
|
+
>
|
|
200
|
+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
|
|
201
|
+
</svg>
|
|
202
|
+
</SelectPrimitive.ItemIndicator>
|
|
203
|
+
<SelectPrimitive.ItemText className="col-start-2 min-w-0">
|
|
204
|
+
{children}
|
|
205
|
+
</SelectPrimitive.ItemText>
|
|
206
|
+
</SelectPrimitive.Item>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function SelectSeparator({
|
|
211
|
+
className,
|
|
212
|
+
...props
|
|
213
|
+
}: SelectPrimitive.Separator.Props): React.ReactElement {
|
|
214
|
+
return (
|
|
215
|
+
<SelectPrimitive.Separator
|
|
216
|
+
className={cn("mx-2 my-1 h-px bg-border", className)}
|
|
217
|
+
data-slot="select-separator"
|
|
218
|
+
{...props}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function SelectGroup(
|
|
224
|
+
props: SelectPrimitive.Group.Props,
|
|
225
|
+
): React.ReactElement {
|
|
226
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function SelectLabel({
|
|
230
|
+
className,
|
|
231
|
+
...props
|
|
232
|
+
}: SelectPrimitive.Label.Props): React.ReactElement {
|
|
233
|
+
return (
|
|
234
|
+
<SelectPrimitive.Label
|
|
235
|
+
className={cn(
|
|
236
|
+
"not-in-data-[slot=field]:mb-2 inline-flex cursor-default items-center gap-2 font-medium text-base/4.5 text-foreground sm:text-sm/4",
|
|
237
|
+
className,
|
|
238
|
+
)}
|
|
239
|
+
data-slot="select-label"
|
|
240
|
+
{...props}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function SelectGroupLabel(
|
|
246
|
+
props: SelectPrimitive.GroupLabel.Props,
|
|
247
|
+
): React.ReactElement {
|
|
248
|
+
return (
|
|
249
|
+
<SelectPrimitive.GroupLabel
|
|
250
|
+
className="px-2 py-1.5 font-medium text-muted-foreground text-xs"
|
|
251
|
+
data-slot="select-group-label"
|
|
252
|
+
{...props}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export { SelectPrimitive, SelectPopup as SelectContent };
|