@exegia/corpora-ui 0.8.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.
Files changed (30) hide show
  1. package/dist-lib/components/blocks/nav/__tests__/sidebar-block.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +67 -0
  3. package/dist-lib/components/blocks/profile/__tests__/profile-card-block.test.d.ts +1 -0
  4. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +77 -0
  5. package/dist-lib/components/composed/__tests__/user-avatar.test.d.ts +1 -0
  6. package/dist-lib/components/composed/user-avatar.d.ts +30 -0
  7. package/dist-lib/components/motion/animated-sidebar.d.ts +98 -0
  8. package/dist-lib/components/motion/shared-layout-bg.d.ts +13 -0
  9. package/dist-lib/components/ui/avatar.d.ts +6 -0
  10. package/dist-lib/components/ui/menu.d.ts +43 -0
  11. package/dist-lib/components/ui/skeleton.d.ts +2 -0
  12. package/dist-lib/index.d.ts +9 -0
  13. package/dist-lib/index.js +1896 -864
  14. package/dist-lib/index.js.map +1 -1
  15. package/dist-lib/lib/ease.d.ts +46 -0
  16. package/package.json +1 -1
  17. package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +125 -0
  18. package/src/components/blocks/nav/sidebar-block.tsx +264 -0
  19. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +134 -0
  20. package/src/components/blocks/profile/profile-card-block.tsx +281 -0
  21. package/src/components/composed/__tests__/user-avatar.test.tsx +85 -0
  22. package/src/components/composed/user-avatar.tsx +98 -0
  23. package/src/components/motion/animated-sidebar.tsx +1145 -0
  24. package/src/components/motion/shared-layout-bg.tsx +151 -0
  25. package/src/components/ui/avatar.tsx +52 -0
  26. package/src/components/ui/menu.tsx +352 -0
  27. package/src/components/ui/skeleton.tsx +18 -0
  28. package/src/index.css +4 -0
  29. package/src/index.ts +11 -0
  30. 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"
@@ -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;