@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.
Files changed (34) hide show
  1. package/dist-lib/components/blocks/auth/__tests__/signup-block.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/auth/signup-block.d.ts +16 -1
  3. package/dist-lib/components/blocks/nav/__tests__/sidebar-block.test.d.ts +1 -0
  4. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +67 -0
  5. package/dist-lib/components/blocks/profile/__tests__/profile-card-block.test.d.ts +1 -0
  6. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +77 -0
  7. package/dist-lib/components/composed/__tests__/user-avatar.test.d.ts +1 -0
  8. package/dist-lib/components/composed/user-avatar.d.ts +30 -0
  9. package/dist-lib/components/motion/animated-sidebar.d.ts +98 -0
  10. package/dist-lib/components/motion/shared-layout-bg.d.ts +13 -0
  11. package/dist-lib/components/ui/avatar.d.ts +6 -0
  12. package/dist-lib/components/ui/menu.d.ts +43 -0
  13. package/dist-lib/components/ui/skeleton.d.ts +2 -0
  14. package/dist-lib/index.d.ts +9 -0
  15. package/dist-lib/index.js +1916 -874
  16. package/dist-lib/index.js.map +1 -1
  17. package/dist-lib/lib/ease.d.ts +46 -0
  18. package/package.json +1 -1
  19. package/src/components/blocks/auth/__tests__/signup-block.test.tsx +176 -0
  20. package/src/components/blocks/auth/signup-block.tsx +43 -10
  21. package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +125 -0
  22. package/src/components/blocks/nav/sidebar-block.tsx +264 -0
  23. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +134 -0
  24. package/src/components/blocks/profile/profile-card-block.tsx +281 -0
  25. package/src/components/composed/__tests__/user-avatar.test.tsx +85 -0
  26. package/src/components/composed/user-avatar.tsx +98 -0
  27. package/src/components/motion/animated-sidebar.tsx +1145 -0
  28. package/src/components/motion/shared-layout-bg.tsx +151 -0
  29. package/src/components/ui/avatar.tsx +52 -0
  30. package/src/components/ui/menu.tsx +352 -0
  31. package/src/components/ui/skeleton.tsx +18 -0
  32. package/src/index.css +4 -0
  33. package/src/index.ts +11 -0
  34. package/src/lib/ease.ts +53 -0
@@ -0,0 +1,85 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { render, screen } from "@testing-library/react"
3
+
4
+ import { UserAvatar, initialsFrom } from "../user-avatar"
5
+
6
+ describe("initialsFrom", () => {
7
+ test("takes the first and last word", () => {
8
+ expect(initialsFrom("Jenny Hamilton")).toBe("JH")
9
+ expect(initialsFrom("ada b lovelace")).toBe("AL")
10
+ expect(initialsFrom("luna")).toBe("L")
11
+ expect(initialsFrom(" ")).toBe("")
12
+ })
13
+ })
14
+
15
+ describe("UserAvatar", () => {
16
+ test("shows initials right away when there is no image", () => {
17
+ const { container } = render(<UserAvatar name="Luna Wyen" />)
18
+
19
+ expect(screen.getByText("LW")).toBeDefined()
20
+ expect(container.querySelector('[data-slot="avatar-skeleton"]')).toBeNull()
21
+ expect(container.querySelector("img")).toBeNull()
22
+ })
23
+
24
+ test("holds a skeleton while a passed image is still loading", () => {
25
+ // happy-dom resolves every Image synchronously (complete, naturalWidth 0),
26
+ // so stand in a request that never answers.
27
+ const RealImage = window.Image
28
+ window.Image = class {
29
+ complete = false
30
+ naturalWidth = 0
31
+ onload: (() => void) | null = null
32
+ onerror: (() => void) | null = null
33
+ src = ""
34
+ } as unknown as typeof window.Image
35
+
36
+ try {
37
+ const { container } = render(
38
+ <UserAvatar name="Jenny Hamilton" src="https://example.com/j.jpg" />
39
+ )
40
+
41
+ expect(
42
+ container.querySelector('[data-slot="avatar-skeleton"]')
43
+ ).not.toBeNull()
44
+ expect(screen.queryByText("JH")).toBeNull()
45
+ expect(
46
+ container
47
+ .querySelector('[data-slot="user-avatar"]')
48
+ ?.hasAttribute("data-loading")
49
+ ).toBe(true)
50
+ } finally {
51
+ window.Image = RealImage
52
+ }
53
+ })
54
+
55
+ test("settles on the initials when the image fails", () => {
56
+ // The stubbed DOM never fetches, so every src lands in the error branch.
57
+ const { container } = render(
58
+ <UserAvatar name="Jenny Hamilton" src="https://example.com/missing.jpg" />
59
+ )
60
+
61
+ expect(screen.getByText("JH")).toBeDefined()
62
+ expect(container.querySelector('[data-slot="avatar-skeleton"]')).toBeNull()
63
+ })
64
+
65
+ test("loading forces the skeleton over a resolvable image", () => {
66
+ const { container } = render(
67
+ <UserAvatar
68
+ loading
69
+ name="Jenny Hamilton"
70
+ src="https://example.com/j.jpg"
71
+ />
72
+ )
73
+
74
+ expect(
75
+ container.querySelector('[data-slot="avatar-skeleton"]')
76
+ ).not.toBeNull()
77
+ expect(container.querySelector("img")).toBeNull()
78
+ expect(screen.queryByText("JH")).toBeNull()
79
+ })
80
+
81
+ test("explicit initials win over the derived ones", () => {
82
+ render(<UserAvatar initials="EX" name="Jenny Hamilton" />)
83
+ expect(screen.getByText("EX")).toBeDefined()
84
+ })
85
+ })
@@ -0,0 +1,98 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import {
6
+ Avatar,
7
+ AvatarFallback,
8
+ AvatarImage,
9
+ type AvatarPrimitive,
10
+ } from "@/components/ui/avatar"
11
+ import { Skeleton } from "@/components/ui/skeleton"
12
+ import { cn } from "@/lib/utils"
13
+
14
+ type ImageStatus = Parameters<
15
+ NonNullable<AvatarPrimitive.Image.Props["onLoadingStatusChange"]>
16
+ >[0]
17
+
18
+ export interface UserAvatarProps extends Omit<
19
+ AvatarPrimitive.Root.Props,
20
+ "children"
21
+ > {
22
+ /** Image URL. Without one the initials show immediately — no skeleton. */
23
+ src?: string
24
+ /** Drives the initials, and the alt text unless `alt` overrides it. */
25
+ name?: string
26
+ /** Overrides the initials derived from `name`. */
27
+ initials?: string
28
+ /**
29
+ * Alt text for the image. Pass "" when adjacent text already names the
30
+ * person — the image is then decorative.
31
+ */
32
+ alt?: string
33
+ /**
34
+ * Force the skeleton, for when the identity itself is still being fetched.
35
+ * Omitted, it follows the image: a passed `src` skeletons until it resolves.
36
+ */
37
+ loading?: boolean
38
+ }
39
+
40
+ /** "Jenny Hamilton" → "JH", "luna" → "L". */
41
+ export function initialsFrom(name: string): string {
42
+ const parts = name.trim().split(/\s+/).filter(Boolean)
43
+ if (parts.length === 0) return ""
44
+ const first = parts[0]?.[0] ?? ""
45
+ const last = parts.length > 1 ? (parts.at(-1)?.[0] ?? "") : ""
46
+ return (first + last).toUpperCase()
47
+ }
48
+
49
+ /**
50
+ * Identity avatar: an image when one is given, initials otherwise.
51
+ *
52
+ * A passed `src` is assumed to be remote, so the slot holds a skeleton until
53
+ * the image resolves rather than flashing initials that are about to be
54
+ * replaced. A failed load settles on the initials.
55
+ */
56
+ export function UserAvatar({
57
+ src,
58
+ name = "",
59
+ initials,
60
+ alt,
61
+ loading: loadingProp,
62
+ className,
63
+ ...props
64
+ }: UserAvatarProps): React.ReactElement {
65
+ const [status, setStatus] = React.useState<ImageStatus>("idle")
66
+ const loading =
67
+ loadingProp ??
68
+ (src !== undefined && (status === "idle" || status === "loading"))
69
+
70
+ return (
71
+ <Avatar
72
+ className={cn("size-8", className)}
73
+ data-loading={loading || undefined}
74
+ data-slot="user-avatar"
75
+ {...props}
76
+ >
77
+ {/* Held back while forced-loading: a loaded image would hide the
78
+ fallback the skeleton lives in. */}
79
+ {src === undefined || loadingProp === true ? null : (
80
+ <AvatarImage
81
+ alt={alt ?? name}
82
+ onLoadingStatusChange={setStatus}
83
+ src={src}
84
+ />
85
+ )}
86
+ <AvatarFallback>
87
+ {loading ? (
88
+ <Skeleton
89
+ className="size-full rounded-full"
90
+ data-slot="avatar-skeleton"
91
+ />
92
+ ) : (
93
+ (initials ?? initialsFrom(name))
94
+ )}
95
+ </AvatarFallback>
96
+ </Avatar>
97
+ )
98
+ }