@exegia/corpora-ui 0.7.0 → 0.9.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/auth/__tests__/signup-block.test.d.ts +1 -0
- package/dist-lib/components/blocks/auth/signup-block.d.ts +16 -1
- package/dist-lib/components/blocks/nav/__tests__/sidebar-block.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +67 -0
- package/dist-lib/components/blocks/profile/__tests__/profile-card-block.test.d.ts +1 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +77 -0
- package/dist-lib/components/composed/__tests__/user-avatar.test.d.ts +1 -0
- package/dist-lib/components/composed/user-avatar.d.ts +30 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +98 -0
- package/dist-lib/components/motion/shared-layout-bg.d.ts +13 -0
- package/dist-lib/components/ui/avatar.d.ts +6 -0
- package/dist-lib/components/ui/menu.d.ts +43 -0
- package/dist-lib/components/ui/skeleton.d.ts +2 -0
- package/dist-lib/index.d.ts +9 -0
- package/dist-lib/index.js +1916 -874
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/ease.d.ts +46 -0
- package/package.json +1 -1
- package/src/components/blocks/auth/__tests__/signup-block.test.tsx +176 -0
- package/src/components/blocks/auth/signup-block.tsx +43 -10
- package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +125 -0
- package/src/components/blocks/nav/sidebar-block.tsx +264 -0
- package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +134 -0
- package/src/components/blocks/profile/profile-card-block.tsx +281 -0
- package/src/components/composed/__tests__/user-avatar.test.tsx +85 -0
- package/src/components/composed/user-avatar.tsx +98 -0
- package/src/components/motion/animated-sidebar.tsx +1145 -0
- package/src/components/motion/shared-layout-bg.tsx +151 -0
- package/src/components/ui/avatar.tsx +52 -0
- package/src/components/ui/menu.tsx +352 -0
- package/src/components/ui/skeleton.tsx +18 -0
- package/src/index.css +4 -0
- package/src/index.ts +11 -0
- package/src/lib/ease.ts +53 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AnimatePresence,
|
|
5
|
+
type HTMLMotionProps,
|
|
6
|
+
motion,
|
|
7
|
+
useReducedMotion,
|
|
8
|
+
type Variants,
|
|
9
|
+
} from "motion/react";
|
|
10
|
+
import {
|
|
11
|
+
Children,
|
|
12
|
+
cloneElement,
|
|
13
|
+
forwardRef,
|
|
14
|
+
type HTMLAttributes,
|
|
15
|
+
isValidElement,
|
|
16
|
+
type MouseEvent,
|
|
17
|
+
type ReactElement,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
type Ref,
|
|
20
|
+
useId,
|
|
21
|
+
useState,
|
|
22
|
+
} from "react";
|
|
23
|
+
import { SPRING_LAYOUT } from "@/lib/ease";
|
|
24
|
+
import { cn } from "@/lib/utils";
|
|
25
|
+
|
|
26
|
+
export interface SharedLayoutBgProps
|
|
27
|
+
extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
/** Semantic container used for the children. */
|
|
30
|
+
as?: "div" | "ul";
|
|
31
|
+
/** Tailwind class applied to the moving pill. Defaults to a subtle foreground tint. */
|
|
32
|
+
pillClassName?: string;
|
|
33
|
+
/** Horizontal inset of the pill relative to each row (px). Default 20. */
|
|
34
|
+
inset?: number;
|
|
35
|
+
/** Optional positioning override for the pill wrapper inside each item. */
|
|
36
|
+
pillContainerClassName?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const variants: Variants = {
|
|
40
|
+
initial: { opacity: 0, filter: "blur(6px)" },
|
|
41
|
+
animate: { opacity: 1, filter: "blur(0px)" },
|
|
42
|
+
exit: (isActive: boolean) =>
|
|
43
|
+
!isActive ? { opacity: 0, filter: "blur(6px)" } : {},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const reducedVariants: Variants = {
|
|
47
|
+
initial: { opacity: 0 },
|
|
48
|
+
animate: { opacity: 1 },
|
|
49
|
+
exit: (isActive: boolean) => (!isActive ? { opacity: 0 } : {}),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const SharedLayoutBg = forwardRef<HTMLElement, SharedLayoutBgProps>(
|
|
53
|
+
function SharedLayoutBg(
|
|
54
|
+
{
|
|
55
|
+
children,
|
|
56
|
+
as = "div",
|
|
57
|
+
className,
|
|
58
|
+
onMouseLeave,
|
|
59
|
+
pillClassName,
|
|
60
|
+
pillContainerClassName,
|
|
61
|
+
inset = 20,
|
|
62
|
+
...props
|
|
63
|
+
},
|
|
64
|
+
forwardedRef,
|
|
65
|
+
) {
|
|
66
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
67
|
+
const uid = useId();
|
|
68
|
+
const reduce = useReducedMotion();
|
|
69
|
+
|
|
70
|
+
const renderedChildren = Children.toArray(children)
|
|
71
|
+
.filter(isValidElement)
|
|
72
|
+
.map((child, index) => {
|
|
73
|
+
const el = child as ReactElement<{
|
|
74
|
+
className?: string;
|
|
75
|
+
onMouseEnter?: () => void;
|
|
76
|
+
children?: ReactNode;
|
|
77
|
+
}>;
|
|
78
|
+
const childKey = el.key ? String(el.key) : `item-${index}`;
|
|
79
|
+
return cloneElement(
|
|
80
|
+
el,
|
|
81
|
+
{
|
|
82
|
+
key: childKey,
|
|
83
|
+
className: cn("relative", el.props.className),
|
|
84
|
+
onMouseEnter: () => {
|
|
85
|
+
el.props.onMouseEnter?.();
|
|
86
|
+
setActiveId(childKey);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
<>
|
|
90
|
+
<AnimatePresence custom={activeId !== null}>
|
|
91
|
+
{activeId !== null ? (
|
|
92
|
+
<motion.div
|
|
93
|
+
variants={reduce ? reducedVariants : variants}
|
|
94
|
+
initial="initial"
|
|
95
|
+
animate="animate"
|
|
96
|
+
exit="exit"
|
|
97
|
+
custom={activeId !== null}
|
|
98
|
+
className={cn(
|
|
99
|
+
"pointer-events-none absolute inset-y-0",
|
|
100
|
+
pillContainerClassName,
|
|
101
|
+
)}
|
|
102
|
+
style={{ left: -inset, right: -inset }}
|
|
103
|
+
>
|
|
104
|
+
{activeId === childKey ? (
|
|
105
|
+
<motion.div
|
|
106
|
+
layoutId={`shared-bg-${uid}`}
|
|
107
|
+
transition={reduce ? { duration: 0 } : SPRING_LAYOUT}
|
|
108
|
+
className={cn(
|
|
109
|
+
"pointer-events-none h-full w-full rounded-2xl bg-primary/[0.06]",
|
|
110
|
+
pillClassName,
|
|
111
|
+
)}
|
|
112
|
+
/>
|
|
113
|
+
) : null}
|
|
114
|
+
</motion.div>
|
|
115
|
+
) : null}
|
|
116
|
+
</AnimatePresence>
|
|
117
|
+
<div className="relative z-10">{el.props.children}</div>
|
|
118
|
+
</>,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const handleMouseLeave = (event: MouseEvent<HTMLElement>) => {
|
|
123
|
+
setActiveId(null);
|
|
124
|
+
onMouseLeave?.(event);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// layoutRoot scopes the pill's layout projection to this list, so fixed or
|
|
128
|
+
// scrolled ancestors can't smear scroll offsets into its movement.
|
|
129
|
+
return as === "ul" ? (
|
|
130
|
+
<motion.ul
|
|
131
|
+
{...(props as HTMLMotionProps<"ul">)}
|
|
132
|
+
ref={forwardedRef as Ref<HTMLUListElement>}
|
|
133
|
+
layoutRoot
|
|
134
|
+
onMouseLeave={handleMouseLeave}
|
|
135
|
+
className={cn("flex w-full flex-col", className)}
|
|
136
|
+
>
|
|
137
|
+
{renderedChildren}
|
|
138
|
+
</motion.ul>
|
|
139
|
+
) : (
|
|
140
|
+
<motion.div
|
|
141
|
+
{...(props as HTMLMotionProps<"div">)}
|
|
142
|
+
ref={forwardedRef as Ref<HTMLDivElement>}
|
|
143
|
+
layoutRoot
|
|
144
|
+
onMouseLeave={handleMouseLeave}
|
|
145
|
+
className={cn("flex w-full flex-col", className)}
|
|
146
|
+
>
|
|
147
|
+
{renderedChildren}
|
|
148
|
+
</motion.div>
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
4
|
+
import type React from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
export function Avatar({
|
|
8
|
+
className,
|
|
9
|
+
...props
|
|
10
|
+
}: AvatarPrimitive.Root.Props): React.ReactElement {
|
|
11
|
+
return (
|
|
12
|
+
<AvatarPrimitive.Root
|
|
13
|
+
className={cn(
|
|
14
|
+
"inline-flex size-8 shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-background align-middle font-medium text-xs",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AvatarImage({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: AvatarPrimitive.Image.Props): React.ReactElement {
|
|
27
|
+
return (
|
|
28
|
+
<AvatarPrimitive.Image
|
|
29
|
+
className={cn("size-full object-cover", className)}
|
|
30
|
+
data-slot="avatar-image"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function AvatarFallback({
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
}: AvatarPrimitive.Fallback.Props): React.ReactElement {
|
|
40
|
+
return (
|
|
41
|
+
<AvatarPrimitive.Fallback
|
|
42
|
+
className={cn(
|
|
43
|
+
"flex size-full items-center justify-center rounded-full bg-muted",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
data-slot="avatar-fallback"
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { AvatarPrimitive };
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
4
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export const MenuCreateHandle: typeof MenuPrimitive.createHandle =
|
|
9
|
+
MenuPrimitive.createHandle;
|
|
10
|
+
|
|
11
|
+
export const Menu: typeof MenuPrimitive.Root = MenuPrimitive.Root;
|
|
12
|
+
|
|
13
|
+
export const MenuPortal: typeof MenuPrimitive.Portal = MenuPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
export function MenuTrigger({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: MenuPrimitive.Trigger.Props): React.ReactElement {
|
|
20
|
+
return (
|
|
21
|
+
<MenuPrimitive.Trigger
|
|
22
|
+
className={className}
|
|
23
|
+
data-slot="menu-trigger"
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</MenuPrimitive.Trigger>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function MenuPopup({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
sideOffset = 4,
|
|
35
|
+
align = "center",
|
|
36
|
+
alignOffset,
|
|
37
|
+
side = "bottom",
|
|
38
|
+
anchor,
|
|
39
|
+
portalProps,
|
|
40
|
+
...props
|
|
41
|
+
}: MenuPrimitive.Popup.Props & {
|
|
42
|
+
align?: MenuPrimitive.Positioner.Props["align"];
|
|
43
|
+
sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
|
|
44
|
+
alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
|
|
45
|
+
side?: MenuPrimitive.Positioner.Props["side"];
|
|
46
|
+
anchor?: MenuPrimitive.Positioner.Props["anchor"];
|
|
47
|
+
portalProps?: MenuPrimitive.Portal.Props;
|
|
48
|
+
}): React.ReactElement {
|
|
49
|
+
return (
|
|
50
|
+
<MenuPortal {...portalProps}>
|
|
51
|
+
<MenuPrimitive.Positioner
|
|
52
|
+
align={align}
|
|
53
|
+
alignOffset={alignOffset}
|
|
54
|
+
anchor={anchor}
|
|
55
|
+
className="z-50"
|
|
56
|
+
data-slot="menu-positioner"
|
|
57
|
+
side={side}
|
|
58
|
+
sideOffset={sideOffset}
|
|
59
|
+
>
|
|
60
|
+
<MenuPrimitive.Popup
|
|
61
|
+
className={cn(
|
|
62
|
+
"relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] focus:outline-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
data-slot="menu-popup"
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<div className="max-h-(--available-height) w-full overflow-y-auto p-1">
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
</MenuPrimitive.Popup>
|
|
72
|
+
</MenuPrimitive.Positioner>
|
|
73
|
+
</MenuPortal>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function MenuGroup(
|
|
78
|
+
props: MenuPrimitive.Group.Props,
|
|
79
|
+
): React.ReactElement {
|
|
80
|
+
return <MenuPrimitive.Group data-slot="menu-group" {...props} />;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function MenuItem({
|
|
84
|
+
className,
|
|
85
|
+
inset,
|
|
86
|
+
variant = "default",
|
|
87
|
+
...props
|
|
88
|
+
}: MenuPrimitive.Item.Props & {
|
|
89
|
+
inset?: boolean;
|
|
90
|
+
variant?: "default" | "destructive";
|
|
91
|
+
}): React.ReactElement {
|
|
92
|
+
return (
|
|
93
|
+
<MenuPrimitive.Item
|
|
94
|
+
className={cn(
|
|
95
|
+
"flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>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]:-mx-0.5 [&>svg]:shrink-0",
|
|
96
|
+
className,
|
|
97
|
+
)}
|
|
98
|
+
data-inset={inset}
|
|
99
|
+
data-slot="menu-item"
|
|
100
|
+
data-variant={variant}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function MenuLinkItem({
|
|
107
|
+
className,
|
|
108
|
+
inset,
|
|
109
|
+
variant = "default",
|
|
110
|
+
closeOnClick = true,
|
|
111
|
+
...props
|
|
112
|
+
}: MenuPrimitive.LinkItem.Props & {
|
|
113
|
+
inset?: boolean;
|
|
114
|
+
variant?: "default" | "destructive";
|
|
115
|
+
}): React.ReactElement {
|
|
116
|
+
return (
|
|
117
|
+
<MenuPrimitive.LinkItem
|
|
118
|
+
className={cn(
|
|
119
|
+
"flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>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]:-mx-0.5 [&>svg]:shrink-0",
|
|
120
|
+
className,
|
|
121
|
+
)}
|
|
122
|
+
closeOnClick={closeOnClick}
|
|
123
|
+
data-inset={inset}
|
|
124
|
+
data-slot="menu-link-item"
|
|
125
|
+
data-variant={variant}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function MenuCheckboxItem({
|
|
132
|
+
className,
|
|
133
|
+
children,
|
|
134
|
+
checked,
|
|
135
|
+
variant = "default",
|
|
136
|
+
...props
|
|
137
|
+
}: MenuPrimitive.CheckboxItem.Props & {
|
|
138
|
+
variant?: "default" | "switch";
|
|
139
|
+
}): React.ReactElement {
|
|
140
|
+
return (
|
|
141
|
+
<MenuPrimitive.CheckboxItem
|
|
142
|
+
checked={checked}
|
|
143
|
+
className={cn(
|
|
144
|
+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 ps-2 text-base text-foreground 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",
|
|
145
|
+
variant === "switch"
|
|
146
|
+
? "grid-cols-[1fr_auto] gap-4 pe-1.5"
|
|
147
|
+
: "grid-cols-[.75rem_1fr] pe-4",
|
|
148
|
+
className,
|
|
149
|
+
)}
|
|
150
|
+
data-slot="menu-checkbox-item"
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
{variant === "switch" ? (
|
|
154
|
+
<>
|
|
155
|
+
<span className="col-start-1">{children}</span>
|
|
156
|
+
<MenuPrimitive.CheckboxItemIndicator
|
|
157
|
+
className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]"
|
|
158
|
+
keepMounted
|
|
159
|
+
>
|
|
160
|
+
<span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />
|
|
161
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
162
|
+
</>
|
|
163
|
+
) : (
|
|
164
|
+
<>
|
|
165
|
+
<MenuPrimitive.CheckboxItemIndicator className="col-start-1 -ms-0.5">
|
|
166
|
+
<svg
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
fill="none"
|
|
169
|
+
height="24"
|
|
170
|
+
stroke="currentColor"
|
|
171
|
+
strokeLinecap="round"
|
|
172
|
+
strokeLinejoin="round"
|
|
173
|
+
strokeWidth="2"
|
|
174
|
+
viewBox="0 0 24 24"
|
|
175
|
+
width="24"
|
|
176
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
177
|
+
>
|
|
178
|
+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
|
|
179
|
+
</svg>
|
|
180
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
181
|
+
<span className="col-start-2">{children}</span>
|
|
182
|
+
</>
|
|
183
|
+
)}
|
|
184
|
+
</MenuPrimitive.CheckboxItem>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function MenuRadioGroup(
|
|
189
|
+
props: MenuPrimitive.RadioGroup.Props,
|
|
190
|
+
): React.ReactElement {
|
|
191
|
+
return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function MenuRadioItem({
|
|
195
|
+
className,
|
|
196
|
+
children,
|
|
197
|
+
...props
|
|
198
|
+
}: MenuPrimitive.RadioItem.Props): React.ReactElement {
|
|
199
|
+
return (
|
|
200
|
+
<MenuPrimitive.RadioItem
|
|
201
|
+
className={cn(
|
|
202
|
+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[.75rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground 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",
|
|
203
|
+
className,
|
|
204
|
+
)}
|
|
205
|
+
data-slot="menu-radio-item"
|
|
206
|
+
{...props}
|
|
207
|
+
>
|
|
208
|
+
<MenuPrimitive.RadioItemIndicator className="col-start-1 -ms-0.5">
|
|
209
|
+
<svg
|
|
210
|
+
aria-hidden="true"
|
|
211
|
+
fill="none"
|
|
212
|
+
height="24"
|
|
213
|
+
stroke="currentColor"
|
|
214
|
+
strokeLinecap="round"
|
|
215
|
+
strokeLinejoin="round"
|
|
216
|
+
strokeWidth="2"
|
|
217
|
+
viewBox="0 0 24 24"
|
|
218
|
+
width="24"
|
|
219
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
220
|
+
>
|
|
221
|
+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
|
|
222
|
+
</svg>
|
|
223
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
224
|
+
<span className="col-start-2">{children}</span>
|
|
225
|
+
</MenuPrimitive.RadioItem>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function MenuGroupLabel({
|
|
230
|
+
className,
|
|
231
|
+
inset,
|
|
232
|
+
...props
|
|
233
|
+
}: MenuPrimitive.GroupLabel.Props & {
|
|
234
|
+
inset?: boolean;
|
|
235
|
+
}): React.ReactElement {
|
|
236
|
+
return (
|
|
237
|
+
<MenuPrimitive.GroupLabel
|
|
238
|
+
className={cn(
|
|
239
|
+
"px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",
|
|
240
|
+
className,
|
|
241
|
+
)}
|
|
242
|
+
data-inset={inset}
|
|
243
|
+
data-slot="menu-label"
|
|
244
|
+
{...props}
|
|
245
|
+
/>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function MenuSeparator({
|
|
250
|
+
className,
|
|
251
|
+
...props
|
|
252
|
+
}: MenuPrimitive.Separator.Props): React.ReactElement {
|
|
253
|
+
return (
|
|
254
|
+
<MenuPrimitive.Separator
|
|
255
|
+
className={cn("mx-2 my-1 h-px bg-border", className)}
|
|
256
|
+
data-slot="menu-separator"
|
|
257
|
+
{...props}
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function MenuShortcut({
|
|
263
|
+
className,
|
|
264
|
+
...props
|
|
265
|
+
}: React.ComponentProps<"kbd">): React.ReactElement {
|
|
266
|
+
return (
|
|
267
|
+
<kbd
|
|
268
|
+
className={cn(
|
|
269
|
+
"ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest",
|
|
270
|
+
className,
|
|
271
|
+
)}
|
|
272
|
+
data-slot="menu-shortcut"
|
|
273
|
+
{...props}
|
|
274
|
+
/>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function MenuSub(
|
|
279
|
+
props: MenuPrimitive.SubmenuRoot.Props,
|
|
280
|
+
): React.ReactElement {
|
|
281
|
+
return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function MenuSubTrigger({
|
|
285
|
+
className,
|
|
286
|
+
inset,
|
|
287
|
+
children,
|
|
288
|
+
...props
|
|
289
|
+
}: MenuPrimitive.SubmenuTrigger.Props & {
|
|
290
|
+
inset?: boolean;
|
|
291
|
+
}): React.ReactElement {
|
|
292
|
+
return (
|
|
293
|
+
<MenuPrimitive.SubmenuTrigger
|
|
294
|
+
className={cn(
|
|
295
|
+
"flex min-h-8 items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not(:last-child)]:-mx-0.5 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
296
|
+
className,
|
|
297
|
+
)}
|
|
298
|
+
data-inset={inset}
|
|
299
|
+
data-slot="menu-sub-trigger"
|
|
300
|
+
{...props}
|
|
301
|
+
>
|
|
302
|
+
{children}
|
|
303
|
+
<ChevronRightIcon className="ms-auto -me-0.5 opacity-80" />
|
|
304
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function MenuSubPopup({
|
|
309
|
+
className,
|
|
310
|
+
sideOffset = 0,
|
|
311
|
+
alignOffset,
|
|
312
|
+
align = "start",
|
|
313
|
+
...props
|
|
314
|
+
}: MenuPrimitive.Popup.Props & {
|
|
315
|
+
align?: MenuPrimitive.Positioner.Props["align"];
|
|
316
|
+
sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
|
|
317
|
+
alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
|
|
318
|
+
}): React.ReactElement {
|
|
319
|
+
const defaultAlignOffset = align !== "center" ? -5 : undefined;
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<MenuPopup
|
|
323
|
+
align={align}
|
|
324
|
+
alignOffset={alignOffset ?? defaultAlignOffset}
|
|
325
|
+
className={className}
|
|
326
|
+
data-slot="menu-sub-content"
|
|
327
|
+
side="inline-end"
|
|
328
|
+
sideOffset={sideOffset}
|
|
329
|
+
{...props}
|
|
330
|
+
/>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export {
|
|
335
|
+
MenuPrimitive,
|
|
336
|
+
MenuCreateHandle as DropdownMenuCreateHandle,
|
|
337
|
+
Menu as DropdownMenu,
|
|
338
|
+
MenuPortal as DropdownMenuPortal,
|
|
339
|
+
MenuTrigger as DropdownMenuTrigger,
|
|
340
|
+
MenuPopup as DropdownMenuContent,
|
|
341
|
+
MenuGroup as DropdownMenuGroup,
|
|
342
|
+
MenuItem as DropdownMenuItem,
|
|
343
|
+
MenuCheckboxItem as DropdownMenuCheckboxItem,
|
|
344
|
+
MenuRadioGroup as DropdownMenuRadioGroup,
|
|
345
|
+
MenuRadioItem as DropdownMenuRadioItem,
|
|
346
|
+
MenuGroupLabel as DropdownMenuLabel,
|
|
347
|
+
MenuSeparator as DropdownMenuSeparator,
|
|
348
|
+
MenuShortcut as DropdownMenuShortcut,
|
|
349
|
+
MenuSub as DropdownMenuSub,
|
|
350
|
+
MenuSubTrigger as DropdownMenuSubTrigger,
|
|
351
|
+
MenuSubPopup as DropdownMenuSubContent,
|
|
352
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export function Skeleton({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={cn(
|
|
11
|
+
"animate-skeleton rounded-sm [--skeleton-highlight:--alpha(var(--color-white)/64%)] [background:linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)_var(--color-muted)_0_0/200%_100%_fixed] dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
data-slot="skeleton"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
package/src/index.css
CHANGED
|
@@ -139,6 +139,10 @@
|
|
|
139
139
|
--animate-caret-blink: 1s ease-out infinite caret-blink
|
|
140
140
|
;
|
|
141
141
|
--animate-input-shake: input-shake 0.4s ease-in-out;
|
|
142
|
+
--animate-skeleton: skeleton 2s -1s infinite linear;
|
|
143
|
+
@keyframes skeleton {
|
|
144
|
+
to { background-position: -200% 0; }
|
|
145
|
+
}
|
|
142
146
|
@keyframes input-shake {
|
|
143
147
|
0%, 100% { translate: 0; }
|
|
144
148
|
20% { translate: -6px 0; }
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// atoms
|
|
10
|
+
export * from "./components/ui/avatar"
|
|
10
11
|
export * from "./components/ui/button"
|
|
11
12
|
export * from "./components/ui/card"
|
|
12
13
|
export * from "./components/ui/checkbox"
|
|
@@ -15,14 +16,17 @@ export * from "./components/ui/frame"
|
|
|
15
16
|
export * from "./components/ui/input"
|
|
16
17
|
export * from "./components/ui/input-group"
|
|
17
18
|
export * from "./components/ui/label"
|
|
19
|
+
export * from "./components/ui/menu"
|
|
18
20
|
export * from "./components/ui/otp-field"
|
|
19
21
|
export * from "./components/ui/separator"
|
|
22
|
+
export * from "./components/ui/skeleton"
|
|
20
23
|
export * from "./components/ui/spinner"
|
|
21
24
|
export * from "./components/ui/textarea"
|
|
22
25
|
|
|
23
26
|
// components
|
|
24
27
|
export * from "./components/composed/password-input"
|
|
25
28
|
export * from "./components/composed/social-providers"
|
|
29
|
+
export * from "./components/composed/user-avatar"
|
|
26
30
|
|
|
27
31
|
// blocks
|
|
28
32
|
export * from "./components/blocks/auth/auth-shell"
|
|
@@ -35,8 +39,15 @@ export * from "./components/blocks/auth/passkey-manager-block"
|
|
|
35
39
|
export * from "./components/blocks/auth/passkey-sign-in-block"
|
|
36
40
|
export * from "./components/blocks/auth/signup-block"
|
|
37
41
|
export * from "./components/blocks/auth/update-password-block"
|
|
42
|
+
export * from "./components/blocks/nav/sidebar-block"
|
|
43
|
+
export * from "./components/blocks/profile/profile-card-block"
|
|
44
|
+
|
|
45
|
+
// motion primitives
|
|
46
|
+
export * from "./components/motion/animated-sidebar"
|
|
47
|
+
export * from "./components/motion/shared-layout-bg"
|
|
38
48
|
|
|
39
49
|
// lib
|
|
40
50
|
export * from "./lib/auth-accent"
|
|
51
|
+
export * from "./lib/ease"
|
|
41
52
|
export * from "./lib/sound"
|
|
42
53
|
export * from "./lib/utils"
|
package/src/lib/ease.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const EASE_OUT = [0.16, 1, 0.3, 1] as const;
|
|
2
|
+
export const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;
|
|
3
|
+
export const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;
|
|
4
|
+
|
|
5
|
+
/** CSS string form of EASE_OUT for inline style transitions. */
|
|
6
|
+
export const EASE_OUT_CSS = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
7
|
+
|
|
8
|
+
/** Press feedback on buttons and other tappable surfaces. */
|
|
9
|
+
export const SPRING_PRESS = {
|
|
10
|
+
type: "spring",
|
|
11
|
+
stiffness: 500,
|
|
12
|
+
damping: 30,
|
|
13
|
+
mass: 0.6,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/** Content swaps — label/icon slots trading places inside a control. */
|
|
17
|
+
export const SPRING_SWAP = {
|
|
18
|
+
type: "spring",
|
|
19
|
+
stiffness: 460,
|
|
20
|
+
damping: 30,
|
|
21
|
+
mass: 0.55,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/** Overlay panel entrances — modals and sheets summoned by pointer. */
|
|
25
|
+
export const SPRING_PANEL = {
|
|
26
|
+
type: "spring",
|
|
27
|
+
stiffness: 420,
|
|
28
|
+
damping: 40,
|
|
29
|
+
mass: 0.5,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
/** Shared-layout glides — pills, indicators and panels morphing between positions. */
|
|
33
|
+
export const SPRING_LAYOUT = {
|
|
34
|
+
type: "spring",
|
|
35
|
+
stiffness: 360,
|
|
36
|
+
damping: 32,
|
|
37
|
+
mass: 0.6,
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */
|
|
41
|
+
export const SPRING_MOUSE = {
|
|
42
|
+
stiffness: 200,
|
|
43
|
+
damping: 15,
|
|
44
|
+
mass: 0.3,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/** Dragged handles and fills (sliders) — critically damped `useSpring` config,
|
|
48
|
+
* so the value follows the pointer butterily and never rebounds off an end. */
|
|
49
|
+
export const SPRING_GLIDE = {
|
|
50
|
+
stiffness: 700,
|
|
51
|
+
damping: 50,
|
|
52
|
+
mass: 0.5,
|
|
53
|
+
} as const;
|