ruler-components 0.1.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 (114) hide show
  1. package/AGENTS.md +9 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +45 -0
  5. package/app/docs/[slug]/page.tsx +147 -0
  6. package/app/docs/layout.tsx +26 -0
  7. package/app/docs/page.tsx +148 -0
  8. package/app/favicon.ico +0 -0
  9. package/app/globals.css +168 -0
  10. package/app/icon.png +0 -0
  11. package/app/layout.tsx +90 -0
  12. package/app/opengraph-image.tsx +10 -0
  13. package/app/page.tsx +34 -0
  14. package/app/twitter-image.tsx +10 -0
  15. package/components/docs/accordion-doc-page.tsx +352 -0
  16. package/components/docs/alert-doc-page.tsx +293 -0
  17. package/components/docs/allinput-doc-page.tsx +183 -0
  18. package/components/docs/avatar-doc-page.tsx +424 -0
  19. package/components/docs/badge-doc-page.tsx +363 -0
  20. package/components/docs/button-doc-page.tsx +406 -0
  21. package/components/docs/card-doc-page.tsx +364 -0
  22. package/components/docs/checkbox-doc-page.tsx +277 -0
  23. package/components/docs/colors-doc-page.tsx +430 -0
  24. package/components/docs/dialog-doc-page.tsx +364 -0
  25. package/components/docs/docs-footer.tsx +11 -0
  26. package/components/docs/docs-mobile-menu-bar.tsx +22 -0
  27. package/components/docs/docs-mobile-nav-context.tsx +27 -0
  28. package/components/docs/docs-page.tsx +263 -0
  29. package/components/docs/docs-search.tsx +303 -0
  30. package/components/docs/docs-sidebar.tsx +165 -0
  31. package/components/docs/docs-topbar.tsx +32 -0
  32. package/components/docs/dropdown-doc-page.tsx +522 -0
  33. package/components/docs/getting-started-doc-page.tsx +205 -0
  34. package/components/docs/label-doc-page.tsx +173 -0
  35. package/components/docs/loaders-doc-page.tsx +244 -0
  36. package/components/docs/menu-doc-page.tsx +373 -0
  37. package/components/docs/modal-doc-page.tsx +436 -0
  38. package/components/docs/next-steps.tsx +53 -0
  39. package/components/docs/pagination-doc-page.tsx +316 -0
  40. package/components/docs/progress-doc-page.tsx +387 -0
  41. package/components/docs/radio-doc-page.tsx +270 -0
  42. package/components/docs/skeleton-doc-page.tsx +230 -0
  43. package/components/docs/stepper-doc-page.tsx +309 -0
  44. package/components/docs/switch-doc-page.tsx +264 -0
  45. package/components/docs/table-doc-page.tsx +689 -0
  46. package/components/docs/tabs-doc-page.tsx +359 -0
  47. package/components/docs/theming-doc-page.tsx +243 -0
  48. package/components/docs/tooltip-doc-page.tsx +357 -0
  49. package/components/docs/usage-section.tsx +21 -0
  50. package/components/icons/discord.tsx +9 -0
  51. package/components/icons/github.tsx +9 -0
  52. package/components/icons/x.tsx +9 -0
  53. package/components/installation-section.tsx +34 -0
  54. package/components/landing/feature-highlights.tsx +105 -0
  55. package/components/landing/footer-strip.tsx +45 -0
  56. package/components/landing/hero-card.tsx +14 -0
  57. package/components/landing/hero-spotlight.tsx +126 -0
  58. package/components/landing/hero.tsx +64 -0
  59. package/components/landing/navbar.tsx +44 -0
  60. package/components/landing/quick-start-section.tsx +59 -0
  61. package/components/landing/site-footer.tsx +55 -0
  62. package/components/theme-provider.tsx +60 -0
  63. package/components/ui/accordion.tsx +174 -0
  64. package/components/ui/alert.tsx +103 -0
  65. package/components/ui/allinput.tsx +210 -0
  66. package/components/ui/avatar.tsx +179 -0
  67. package/components/ui/badge.tsx +52 -0
  68. package/components/ui/button.tsx +75 -0
  69. package/components/ui/card.tsx +105 -0
  70. package/components/ui/checkbox.tsx +130 -0
  71. package/components/ui/code-block.tsx +60 -0
  72. package/components/ui/data-table.tsx +235 -0
  73. package/components/ui/dialog.tsx +173 -0
  74. package/components/ui/dropdown.tsx +78 -0
  75. package/components/ui/feature-card.tsx +38 -0
  76. package/components/ui/input.tsx +44 -0
  77. package/components/ui/label.tsx +110 -0
  78. package/components/ui/loaders.tsx +139 -0
  79. package/components/ui/menu.tsx +118 -0
  80. package/components/ui/modal.tsx +399 -0
  81. package/components/ui/package-manager-tabs.tsx +40 -0
  82. package/components/ui/pagination.tsx +200 -0
  83. package/components/ui/progress.tsx +134 -0
  84. package/components/ui/radio.tsx +262 -0
  85. package/components/ui/skeleton.tsx +102 -0
  86. package/components/ui/steppers.tsx +309 -0
  87. package/components/ui/switch.tsx +153 -0
  88. package/components/ui/table.tsx +58 -0
  89. package/components/ui/tabs.tsx +166 -0
  90. package/components/ui/theme-toggle.tsx +60 -0
  91. package/components/ui/tooltip.tsx +238 -0
  92. package/data/color-palette.ts +35 -0
  93. package/data/docs-nav.ts +84 -0
  94. package/eslint.config.mjs +18 -0
  95. package/index.ts +8 -0
  96. package/lib/cn.ts +4 -0
  97. package/lib/color.ts +85 -0
  98. package/lib/og-image.tsx +138 -0
  99. package/lib/theme.ts +33 -0
  100. package/next.config.ts +7 -0
  101. package/package.json +30 -0
  102. package/postcss.config.mjs +7 -0
  103. package/public/dark.png +0 -0
  104. package/public/dark1.png +0 -0
  105. package/public/dark2.png +0 -0
  106. package/public/file.svg +1 -0
  107. package/public/globe.svg +1 -0
  108. package/public/light.png +0 -0
  109. package/public/light1.png +0 -0
  110. package/public/next.svg +1 -0
  111. package/public/vercel.svg +1 -0
  112. package/public/video/hero-demo.mp4 +0 -0
  113. package/public/window.svg +1 -0
  114. package/tsconfig.json +34 -0
@@ -0,0 +1,424 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Check, CircleUserRound, Copy, Mail, UserPlus } from "lucide-react";
5
+ import { Avatar, AvatarGroup, type AvatarProps } from "@/components/ui/avatar";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Tooltip } from "@/components/ui/tooltip";
9
+ import {
10
+ Card,
11
+ CardContent,
12
+ CardDescription,
13
+ CardFooter,
14
+ CardHeader,
15
+ CardTitle,
16
+ } from "@/components/ui/card";
17
+ import {
18
+ DocsAccessibility,
19
+ DocsApiSection,
20
+ DocsApiTable,
21
+ DocsDemoPanel,
22
+ DocsPage,
23
+ DocsPageHeader,
24
+ DocsSection,
25
+ DocsSectionGrid,
26
+ } from "@/components/docs/docs-page";
27
+ import { cn } from "@/lib/cn";
28
+
29
+ const API_ROWS = [
30
+ { prop: "src", type: "string", default: "—", description: "Image URL for the avatar" },
31
+ { prop: "alt", type: "string", default: "—", description: "Accessible label for the image" },
32
+ { prop: "name", type: "string", default: "—", description: "Used for initials fallback and alt text" },
33
+ { prop: "size", type: '"sm" | "md" | "lg" | "xl"', default: '"md"', description: "The size of the avatar" },
34
+ { prop: "variant", type: '"default" | "outline"', default: '"default"', description: "Ring style for stacked groups" },
35
+ { prop: "status", type: '"online" | "offline" | "busy" | null', default: "null", description: "Optional presence indicator" },
36
+ { prop: "className", type: "string", default: "—", description: "Additional CSS classes" },
37
+ ];
38
+
39
+ const GROUP_API_ROWS = [
40
+ { prop: "max", type: "number", default: "5", description: "Maximum avatars shown before +N overflow" },
41
+ { prop: "size", type: '"sm" | "md" | "lg" | "xl"', default: '"md"', description: "Size applied to overflow badge" },
42
+ { prop: "children", type: "ReactNode", default: "—", description: "Avatar elements to display" },
43
+ ];
44
+
45
+ const ACCESSIBILITY_ITEMS = [
46
+ "Always provide alt text or a name for image avatars.",
47
+ "Use initials fallback when no image is available.",
48
+ "Status indicators are decorative — convey status in text when important.",
49
+ "Avatar groups should include a text label describing the group.",
50
+ "Ensure sufficient contrast for initials on fallback backgrounds.",
51
+ ];
52
+
53
+ const TEAM = [
54
+ { name: "Zuhaib Khan", src: "https://i.pravatar.cc/150?img=11", role: "Lead Designer" },
55
+ { name: "Aisha Malik", src: "https://i.pravatar.cc/150?img=5", role: "Product Manager" },
56
+ { name: "James Chen", src: "https://i.pravatar.cc/150?img=12", role: "Engineer" },
57
+ { name: "Sara Patel", src: "https://i.pravatar.cc/150?img=9", role: "Research" },
58
+ { name: "Leo Martin", src: "https://i.pravatar.cc/150?img=15", role: "Marketing" },
59
+ { name: "Nina Rossi", src: "https://i.pravatar.cc/150?img=20", role: "Support" },
60
+ ];
61
+
62
+ type Size = NonNullable<AvatarProps["size"]>;
63
+ type Variant = NonNullable<AvatarProps["variant"]>;
64
+ type Status = AvatarProps["status"];
65
+
66
+ const SIZES: Size[] = ["sm", "md", "lg", "xl"];
67
+ const VARIANTS: Variant[] = ["default", "outline"];
68
+ const STATUSES: { value: Status; label: string }[] = [
69
+ { value: null, label: "None" },
70
+ { value: "online", label: "Online" },
71
+ { value: "busy", label: "Busy" },
72
+ { value: "offline", label: "Offline" },
73
+ ];
74
+
75
+ function codeFor(size: Size, variant: Variant, status: Status, showPhoto: boolean) {
76
+ const lines = [
77
+ showPhoto ? `src="https://i.pravatar.cc/150?img=5"` : null,
78
+ `name="Aisha Malik"`,
79
+ size !== "md" ? `size="${size}"` : null,
80
+ variant !== "default" ? `variant="${variant}"` : null,
81
+ status ? `status="${status}"` : null,
82
+ ].filter(Boolean);
83
+ return `<Avatar\n ${lines.join("\n ")}\n/>`;
84
+ }
85
+
86
+ export function AvatarDocPage() {
87
+ const [size, setSize] = useState<Size>("xl");
88
+ const [variant, setVariant] = useState<Variant>("default");
89
+ const [status, setStatus] = useState<Status>("online");
90
+ const [showPhoto, setShowPhoto] = useState(true);
91
+ const [copied, setCopied] = useState(false);
92
+
93
+ const snippet = codeFor(size, variant, status, showPhoto);
94
+
95
+ function copySnippet() {
96
+ navigator.clipboard.writeText(snippet).catch(() => {});
97
+ setCopied(true);
98
+ setTimeout(() => setCopied(false), 1200);
99
+ }
100
+
101
+ return (
102
+ <DocsPage>
103
+ <DocsPageHeader
104
+ icon={CircleUserRound}
105
+ title="Avatar"
106
+ description="Represents a user or entity with a photo, initials fallback, and optional presence status."
107
+ />
108
+
109
+ <DocsSection
110
+ first
111
+ title="Playground"
112
+ description="Every size, variant, and status — live, with the exact markup to match."
113
+ >
114
+ <div className="overflow-hidden rounded-2xl border border-zinc-200 shadow-[0_2px_8px_rgba(0,0,0,0.04)] dark:border-zinc-800 dark:shadow-[0_2px_8px_rgba(0,0,0,0.2)]">
115
+ <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 dark:bg-zinc-900/40">
116
+ <div
117
+ aria-hidden
118
+ className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
119
+ style={{
120
+ backgroundImage:
121
+ "linear-gradient(to right, rgb(161 161 170 / 0.35) 1px, transparent 1px), linear-gradient(to bottom, rgb(161 161 170 / 0.35) 1px, transparent 1px)",
122
+ backgroundSize: "24px 24px",
123
+ }}
124
+ />
125
+ <div key={`${size}-${variant}-${status}-${showPhoto}`} className="relative animate-[pagination-pop_0.25s_ease-out]">
126
+ <Avatar
127
+ src={showPhoto ? "https://i.pravatar.cc/150?img=5" : undefined}
128
+ name="Aisha Malik"
129
+ size={size}
130
+ variant={variant}
131
+ status={status}
132
+ />
133
+ </div>
134
+ </div>
135
+
136
+ <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
137
+ <div className="flex flex-wrap items-start justify-between gap-6">
138
+ <div>
139
+ <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
140
+ Size
141
+ </p>
142
+ <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
143
+ {SIZES.map((s) => (
144
+ <button
145
+ key={s}
146
+ type="button"
147
+ onClick={() => setSize(s)}
148
+ className={cn(
149
+ "rounded-md px-3 py-1.5 text-xs font-medium uppercase tracking-wide transition-colors",
150
+ size === s
151
+ ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
152
+ : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
153
+ )}
154
+ >
155
+ {s}
156
+ </button>
157
+ ))}
158
+ </div>
159
+ </div>
160
+
161
+ <div>
162
+ <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
163
+ Status
164
+ </p>
165
+ <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
166
+ {STATUSES.map((s) => (
167
+ <button
168
+ key={s.label}
169
+ type="button"
170
+ onClick={() => setStatus(s.value)}
171
+ className={cn(
172
+ "rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
173
+ status === s.value
174
+ ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
175
+ : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
176
+ )}
177
+ >
178
+ {s.label}
179
+ </button>
180
+ ))}
181
+ </div>
182
+ </div>
183
+
184
+ <div>
185
+ <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
186
+ Variant
187
+ </p>
188
+ <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
189
+ {VARIANTS.map((v) => (
190
+ <button
191
+ key={v}
192
+ type="button"
193
+ onClick={() => setVariant(v)}
194
+ className={cn(
195
+ "rounded-md px-3 py-1.5 text-xs font-medium capitalize transition-colors",
196
+ variant === v
197
+ ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
198
+ : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
199
+ )}
200
+ >
201
+ {v}
202
+ </button>
203
+ ))}
204
+ </div>
205
+ </div>
206
+
207
+ <div>
208
+ <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
209
+ Photo
210
+ </p>
211
+ <button
212
+ type="button"
213
+ onClick={() => setShowPhoto((v) => !v)}
214
+ className={cn(
215
+ "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
216
+ showPhoto
217
+ ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
218
+ : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
219
+ )}
220
+ >
221
+ {showPhoto ? "Photo" : "Initials"}
222
+ </button>
223
+ </div>
224
+ </div>
225
+
226
+ <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
227
+ <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
228
+ <code>{snippet}</code>
229
+ </pre>
230
+ <Tooltip content={copied ? "Copied!" : "Copy code"}>
231
+ <button
232
+ type="button"
233
+ onClick={copySnippet}
234
+ className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-zinc-400 transition-colors hover:bg-white/10 hover:text-white"
235
+ >
236
+ {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
237
+ </button>
238
+ </Tooltip>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </DocsSection>
243
+
244
+ <DocsSection
245
+ title="Team Directory"
246
+ description="Combine avatars, metadata, and actions in a single scannable surface."
247
+ >
248
+ <Card className="max-w-2xl">
249
+ <CardHeader className="border-b border-zinc-100 dark:border-zinc-800">
250
+ <div className="flex flex-wrap items-start justify-between gap-4">
251
+ <div>
252
+ <CardTitle>Design team</CardTitle>
253
+ <CardDescription>6 members · 4 active now</CardDescription>
254
+ </div>
255
+ <Badge variant="success">Collaborating</Badge>
256
+ </div>
257
+ </CardHeader>
258
+ <CardContent className="flex flex-col gap-6 pt-6 sm:flex-row sm:items-center sm:justify-between">
259
+ <div>
260
+ <AvatarGroup max={5} size="md">
261
+ {TEAM.map((member) => (
262
+ <Tooltip key={member.name} content={`${member.name} · ${member.role}`}>
263
+ <Avatar src={member.src} name={member.name} variant="outline" />
264
+ </Tooltip>
265
+ ))}
266
+ </AvatarGroup>
267
+ <p className="mt-3 text-sm text-zinc-600 dark:text-zinc-300">
268
+ Zuhaib, Aisha, James, Sara, and 2 more
269
+ </p>
270
+ </div>
271
+ <Button size="sm">
272
+ <UserPlus className="h-4 w-4" />
273
+ Invite
274
+ </Button>
275
+ </CardContent>
276
+ </Card>
277
+ </DocsSection>
278
+
279
+ <DocsSection
280
+ title="Photos & Initials"
281
+ description="Gradient initials automatically match each name when no photo is available."
282
+ >
283
+ <DocsDemoPanel>
284
+ {[
285
+ { name: "Zuhaib Khan", src: "https://i.pravatar.cc/150?img=11", status: "online" as const },
286
+ { name: "Aisha Malik", status: "online" as const },
287
+ { name: "James Chen", status: null },
288
+ { name: "Sara Patel", status: "busy" as const },
289
+ { name: "Leo Martin", status: "offline" as const },
290
+ ].map((person) => (
291
+ <Tooltip key={person.name} content={person.name}>
292
+ <div className="flex flex-col items-center gap-2.5">
293
+ <Avatar src={person.src} name={person.name} size="lg" status={person.status} />
294
+ <span className="text-xs font-medium text-zinc-500 dark:text-zinc-400">
295
+ {person.name.split(" ")[0]}
296
+ </span>
297
+ </div>
298
+ </Tooltip>
299
+ ))}
300
+ </DocsDemoPanel>
301
+ </DocsSection>
302
+
303
+ <DocsSection>
304
+ <DocsSectionGrid className="lg:grid-cols-2">
305
+ <Card>
306
+ <CardHeader>
307
+ <CardTitle className="text-base">Presence</CardTitle>
308
+ <CardDescription>Status dots with a soft pulse for active users.</CardDescription>
309
+ </CardHeader>
310
+ <CardContent>
311
+ <div className="grid grid-cols-3 gap-4">
312
+ {[
313
+ { label: "Online", status: "online" as const, src: "https://i.pravatar.cc/150?img=8", name: "Online" },
314
+ { label: "Busy", status: "busy" as const, name: "Busy User" },
315
+ { label: "Offline", status: "offline" as const, name: "Offline User" },
316
+ ].map((item) => (
317
+ <div
318
+ key={item.label}
319
+ className="flex flex-col items-center gap-3 rounded-xl border border-zinc-100 bg-zinc-50/80 p-4 dark:border-zinc-800 dark:bg-zinc-900/40"
320
+ >
321
+ <Avatar src={item.src} name={item.name} size="lg" status={item.status} />
322
+ <span className="text-xs font-medium text-zinc-600 dark:text-zinc-300">{item.label}</span>
323
+ </div>
324
+ ))}
325
+ </div>
326
+ </CardContent>
327
+ </Card>
328
+
329
+ <Card>
330
+ <CardHeader>
331
+ <CardTitle className="text-base">Stacked Group</CardTitle>
332
+ <CardDescription>Hover avatars to preview — overflow shows +N count.</CardDescription>
333
+ </CardHeader>
334
+ <CardContent>
335
+ <AvatarGroup max={4} size="lg">
336
+ {TEAM.map((member) => (
337
+ <Tooltip key={member.name} content={`${member.name} · ${member.role}`}>
338
+ <Avatar src={member.src} name={member.name} variant="outline" />
339
+ </Tooltip>
340
+ ))}
341
+ </AvatarGroup>
342
+ <div className="mt-5 flex items-center justify-between rounded-xl border border-zinc-100 bg-zinc-50/80 px-4 py-3 dark:border-zinc-800 dark:bg-zinc-900/40">
343
+ <p className="text-sm font-medium text-zinc-800 dark:text-zinc-100">6 teammates</p>
344
+ <Badge variant="outline">+2 hidden</Badge>
345
+ </div>
346
+ </CardContent>
347
+ </Card>
348
+ </DocsSectionGrid>
349
+ </DocsSection>
350
+
351
+ <DocsSection
352
+ title="Profile Card"
353
+ description="Layer avatars over a header band for profile-style layouts."
354
+ >
355
+ <Card className="max-w-md overflow-hidden">
356
+ <div className="h-24 bg-gradient-to-br from-zinc-100 via-zinc-50 to-zinc-200 dark:from-zinc-800 dark:via-zinc-900 dark:to-zinc-950" />
357
+ <CardContent className="relative px-6 pb-6 pt-0">
358
+ <Avatar
359
+ src="https://i.pravatar.cc/150?img=11"
360
+ name="Zuhaib Khan"
361
+ size="xl"
362
+ status="online"
363
+ className="absolute -top-12 ring-4 ring-white dark:ring-zinc-900"
364
+ />
365
+ <div className="pt-14">
366
+ <div className="flex flex-wrap items-center gap-2">
367
+ <h3 className="text-lg font-semibold tracking-tight text-zinc-900 dark:text-white">
368
+ Zuhaib Khan
369
+ </h3>
370
+ <Badge variant="success">Pro</Badge>
371
+ </div>
372
+ <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">Product Designer · San Francisco</p>
373
+ <div className="mt-4 flex flex-wrap gap-2">
374
+ <Button size="sm">View profile</Button>
375
+ <Button variant="ghost" size="sm">
376
+ <Mail className="h-4 w-4" />
377
+ Message
378
+ </Button>
379
+ </div>
380
+ </div>
381
+ </CardContent>
382
+ </Card>
383
+ </DocsSection>
384
+
385
+ <DocsSection
386
+ title="Member List"
387
+ description="Compact rows with hover states for settings and team management."
388
+ >
389
+ <Card className="max-w-md">
390
+ <CardHeader className="border-b border-zinc-100 pb-4 dark:border-zinc-800">
391
+ <CardTitle className="text-base">Workspace members</CardTitle>
392
+ <CardDescription>Manage roles and access levels.</CardDescription>
393
+ </CardHeader>
394
+ <CardContent className="p-0">
395
+ {TEAM.slice(0, 4).map((member) => (
396
+ <div
397
+ key={member.name}
398
+ className="flex items-center gap-3 border-b border-zinc-100 px-6 py-3.5 transition-colors last:border-0 hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900/50"
399
+ >
400
+ <Avatar src={member.src} name={member.name} size="md" status="online" />
401
+ <div className="min-w-0 flex-1">
402
+ <p className="truncate text-sm font-medium text-zinc-900 dark:text-white">
403
+ {member.name}
404
+ </p>
405
+ <p className="text-xs text-zinc-500 dark:text-zinc-400">{member.role}</p>
406
+ </div>
407
+ <Badge variant="outline">Editor</Badge>
408
+ </div>
409
+ ))}
410
+ </CardContent>
411
+ <CardFooter className="border-t border-zinc-100 dark:border-zinc-800">
412
+ <Button variant="ghost" size="sm" className="w-full">View all members</Button>
413
+ </CardFooter>
414
+ </Card>
415
+ </DocsSection>
416
+
417
+ <DocsApiSection>
418
+ <DocsApiTable title="API Reference — Avatar" rows={API_ROWS} />
419
+ <DocsApiTable title="API Reference — AvatarGroup" rows={GROUP_API_ROWS} />
420
+ <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
421
+ </DocsApiSection>
422
+ </DocsPage>
423
+ );
424
+ }