Skip to content

Commit ef23497

Browse files
committed
feat(docs): add interactive component previews and refactor documentation
- Add component preview system with collapsible code wrapper and tabbed interface - Create example components directory with demo implementations for all utilities and UI components - Add registry system for managing component examples and build script for example generation - Refactor documentation pages to use new preview components for interactive examples - Update MdxComponents to integrate new preview system - Add base UI tabs component for preview interface - Replace flex-1 with grow utility class for better semantic clarity across dev pages - Rename DragScroll.Container to DragScroll.List for improved semantic naming - Refactor PageThree to use useState for dynamic repo fetching with error handling - Update error-boundary, carousel, drop-zone, and presence components with type improvements - Add cnJoin utility import to dev components - Update package dependencies and ESLint configuration
1 parent 2a815ad commit ef23497

61 files changed

Lines changed: 2354 additions & 861 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/dev/src/components/MainForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ function MainForm() {
204204
className="mt-1 size-4 rounded-sm border-slate-300 text-indigo-600
205205
focus:ring-indigo-500"
206206
/>
207-
<div className="pointer-events-none flex flex-1 flex-col gap-0.5">
207+
<div className="pointer-events-none flex grow flex-col gap-0.5">
208208
<Form.Label
209209
className="text-sm/tight font-semibold tracking-tight text-slate-900"
210210
>

apps/dev/src/pages/four.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function PageFour() {
3333
className="size-16 rounded-2xl object-cover shadow-sm ring-4 ring-white"
3434
/>
3535

36-
<div className="flex-1">
36+
<div className="grow">
3737
<Card.Title className="text-xl/tight font-bold text-slate-900">
3838
Sarah Anderson
3939
</Card.Title>

apps/dev/src/pages/seven.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function PageSeven() {
6767
<Icon className="size-6" icon="lucide:chevron-left" />
6868
</DragScroll.Prev>
6969

70-
<DragScroll.Container
70+
<DragScroll.List
7171
className="scrollbar-hidden flex items-stretch gap-6 overflow-x-auto scroll-smooth p-4
7272
data-[dragging=true]:cursor-grabbing"
7373
>
@@ -113,7 +113,7 @@ function PageSeven() {
113113
</DragScroll.Item>
114114
)}
115115
/>
116-
</DragScroll.Container>
116+
</DragScroll.List>
117117

118118
<DragScroll.Next
119119
className="absolute top-1/2 -right-5 z-20 flex size-12 -translate-y-1/2 cursor-pointer

apps/dev/src/pages/six.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function PageSix() {
6464
Target Container (#six-target)
6565
</div>
6666

67-
<div className="flex flex-1 flex-col justify-end">
67+
<div className="flex grow flex-col justify-end">
6868
<h1 className="text-2xl font-black text-slate-900">Landing Zone</h1>
6969
<p className="text-slate-500">Elements will appear at the top of this container.</p>
7070
</div>

apps/dev/src/pages/three.tsx

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Icon } from "@iconify/react";
22
import { callApi } from "@zayne-labs/callapi";
3-
import { useConstant } from "@zayne-labs/toolkit-react";
43
import type { InferProps } from "@zayne-labs/toolkit-react/utils";
54
import { Await } from "@zayne-labs/ui-react/common/await";
65
import { ForWithWrapper } from "@zayne-labs/ui-react/common/for";
76
import { Card } from "@zayne-labs/ui-react/ui/card";
8-
import { cache } from "react";
7+
import { useState } from "react";
98
import { tv, type VariantProps } from "tailwind-variants";
10-
import { cnMerge } from "../lib/utils/cn";
9+
import { cnJoin, cnMerge } from "../lib/utils/cn";
1110

1211
type GitHubRepoDetails = {
1312
description: string | null;
@@ -22,17 +21,33 @@ type GitHubRepoDetails = {
2221
updated_at: string;
2322
};
2423

25-
const fetchRepos = cache(() => {
24+
const fetchRepos = (shouldFail = false) => {
25+
if (shouldFail) {
26+
return Promise.reject(new Error("Failed to fetch repositories"));
27+
}
28+
2629
return callApi("https://api.github.com/orgs/zayne-labs/repos", {
30+
dedupeStrategy: "defer",
2731
query: { per_page: 12, sort: "updated" },
2832
resultMode: "onlyData",
2933
schema: { data: (data) => data as GitHubRepoDetails[] },
3034
throwOnError: true,
3135
});
32-
});
36+
};
3337

3438
function PageThree() {
35-
const promise = useConstant(() => fetchRepos());
39+
const [shouldFail, setShouldFail] = useState(false);
40+
const [promise, setPromise] = useState(() => fetchRepos());
41+
42+
const handleLoadRepos = () => {
43+
setShouldFail(false);
44+
setPromise(fetchRepos(false));
45+
};
46+
47+
const handleTriggerError = () => {
48+
setShouldFail(true);
49+
setPromise(fetchRepos(true));
50+
};
3651

3752
return (
3853
<div className="flex flex-col gap-10">
@@ -50,10 +65,37 @@ function PageThree() {
5065
<p className="max-w-2xl text-lg/relaxed text-slate-600">
5166
Showcasing the project portfolio of Zayne Labs. Fetching real-time data from the GitHub API.
5267
</p>
68+
69+
<div className="flex gap-2">
70+
<button
71+
type="button"
72+
onClick={handleLoadRepos}
73+
className={cnJoin(
74+
"rounded-md px-4 py-2 text-sm transition-colors",
75+
!shouldFail ?
76+
"bg-zu-primary text-white ring-2 ring-zu-primary ring-offset-2"
77+
: "bg-zu-primary/50 text-white/70 hover:bg-zu-primary/80"
78+
)}
79+
>
80+
Load Repos
81+
</button>
82+
<button
83+
type="button"
84+
onClick={handleTriggerError}
85+
className={cnJoin(
86+
"rounded-md px-4 py-2 text-sm transition-colors",
87+
shouldFail ?
88+
"bg-red-600 text-white ring-2 ring-red-500 ring-offset-2"
89+
: "bg-red-600/50 text-white/70 hover:bg-red-600/80"
90+
)}
91+
>
92+
Trigger Error
93+
</button>
94+
</div>
5395
</header>
5496

5597
<section>
56-
<Await.Root promise={promise}>
98+
<Await.Root promise={promise} onErrorReset={handleLoadRepos}>
5799
<Await.Pending>
58100
<LoadingSpinner />
59101
</Await.Pending>
@@ -91,9 +133,7 @@ function PageThree() {
91133
<h3 className="mb-2 text-lg font-semibold text-gray-900">
92134
Unable to load projects
93135
</h3>
94-
<p className="mb-6 text-sm text-gray-500">
95-
{(error as Error | null)?.message ?? "An unknown error occurred"}
96-
</p>
136+
<p className="mb-6 text-sm text-gray-500">{error.message}</p>
97137
<Button
98138
variant="outline"
99139
className="w-full justify-center border-red-200 text-red-700
@@ -178,7 +218,7 @@ function RepoCard(props: RepoCardProps) {
178218
</div>
179219
</Card.Header>
180220

181-
<Card.Content className="flex-1 pt-0 pb-6">
221+
<Card.Content className="grow pt-0 pb-6">
182222
<p className="line-clamp-3 text-sm/relaxed text-gray-600">
183223
{repo.description ?? "No description provided for this repository."}
184224
</p>

apps/docs/app/docs/[[...slug]]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
import type { Metadata } from "next";
1111
import { notFound } from "next/navigation";
1212
import { LLMCopyButton, ViewOptions } from "@/components/ai/page-actions";
13-
import { getMDXComponents } from "@/components/common";
1413
import { EditOnGithub } from "@/components/common/EditOnGithub";
14+
import { getMDXComponents } from "@/components/common/MdxComponents";
1515
import { HoverCard } from "@/components/ui";
1616
import { repoName, repoOwner } from "@/lib/github";
1717
import { createMetadata, defaultDescription } from "@/lib/metadata";

apps/docs/components/common/MdxComponents.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ import * as Tabs from "fumadocs-ui/components/tabs";
44
import * as TypeTable from "fumadocs-ui/components/type-table";
55
import defaultComponents from "fumadocs-ui/mdx";
66
import type { MDXComponents } from "mdx/types";
7+
import { ComponentSource } from "@/components/preview/component-source";
8+
import { ComponentTabs } from "@/components/preview/component-tabs";
79

8-
export const getMDXComponents = (extraComponents?: MDXComponents) => {
10+
export const getMDXComponents = (extraComponents?: MDXComponents): MDXComponents => {
911
return {
1012
...defaultComponents,
1113
...Accordion,
1214
...Steps,
1315
...Tabs,
1416
...TypeTable,
17+
ComponentSource,
18+
ComponentTabs,
1519
...extraComponents,
16-
} satisfies MDXComponents;
20+
};
1721
};
1822

1923
declare global {
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from "./Logo";
22
export { IconBox } from "./IconBox";
33
export { Slot } from "./slot";
4-
export { getMDXComponents } from "./MdxComponents";
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Button } from "@/components/ui/button";
5+
import { cnMerge } from "@/lib/utils/cn";
6+
7+
export function CodeCollapsibleWrapper(props: { children: React.ReactNode; className?: string }) {
8+
const { children, className } = props;
9+
const [isOpen, setIsOpen] = useState(false);
10+
11+
return (
12+
<div
13+
data-state={isOpen ? "open" : "closed"}
14+
className={cnMerge("relative overflow-hidden data-[state=closed]:max-h-64", className)}
15+
>
16+
{children}
17+
18+
{!isOpen && (
19+
<div
20+
className="absolute inset-x-0 bottom-0 flex h-16 items-center justify-center bg-linear-to-t
21+
from-fd-background to-transparent"
22+
>
23+
<Button theme="outline" onClick={() => setIsOpen(true)}>
24+
Expand
25+
</Button>
26+
</div>
27+
)}
28+
</div>
29+
);
30+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Suspense } from "react";
2+
import { getExampleComponent } from "@/lib/registry";
3+
import { cnMerge } from "@/lib/utils/cn";
4+
5+
export type ComponentPreviewProps = React.ComponentPropsWithoutRef<"div"> & {
6+
align?: "center" | "end" | "start";
7+
fullPreview?: boolean;
8+
name: Parameters<typeof getExampleComponent>[0];
9+
preventPreviewFocus?: boolean;
10+
scalePreview?: boolean;
11+
};
12+
13+
export function ComponentPreview(props: ComponentPreviewProps) {
14+
const { align = "center", className, fullPreview, name, preventPreviewFocus, scalePreview } = props;
15+
16+
const Component = getExampleComponent(name);
17+
18+
return (
19+
<div
20+
className={cnMerge(
21+
"relative",
22+
preventPreviewFocus && "focus-visible:ring-0 focus-visible:outline-hidden"
23+
)}
24+
tabIndex={preventPreviewFocus ? -1 : 0}
25+
>
26+
<div
27+
className={cnMerge(
28+
// eslint-disable-next-line tailwindcss-better/no-unknown-classes
29+
`not-prose fd-scroll-container flex h-[450px] w-full flex-col items-center-safe
30+
overflow-auto p-10`,
31+
fullPreview && "h-full p-0",
32+
align === "center" && "justify-center-safe",
33+
align === "end" && "justify-end",
34+
align === "start" && "justify-start",
35+
scalePreview && "sm:p-10",
36+
className
37+
)}
38+
>
39+
<div>
40+
<Suspense fallback={<p className="text-fd-muted-foreground">Loading...</p>}>
41+
{/* eslint-disable-next-line react-hooks/static-components */}
42+
<Component />
43+
</Suspense>
44+
</div>
45+
</div>
46+
</div>
47+
);
48+
}

0 commit comments

Comments
 (0)