Skip to content

Commit 6c72500

Browse files
fyalavuzruvnet
andcommitted
feat: redesign examples app as polished showcase with live iPhone previews
Transform the examples app into a professional documentation/showcase site: - New landing page with hero, interactive phone mockup previews, category cards, and feature grid — all using shadcn design tokens - Preview layout system with sidebar navigation (5 categories, 12 examples), centered iPhone mockup with live iframe, and syntax-highlighted code blocks - New shared components: PhoneMockup, CodeBlock, ExampleData registry, SidebarNav, ExampleLayout - Rewrote all 12 example pages with consistent, polished app designs (social feeds, e-commerce, dashboards, music players, etc.) - Added dark-mode and nested-scroll example pages - Deleted 11 redundant/broken pages and removed IndicatorPill from all examples - Fixed Server/Client Component serialization boundary for LucideIcon - Migrated hardcoded colors to shadcn OKLch tokens in HeaderNav - Updated E2E tests for deleted/renamed pages - Added scrollbar-hide utility with Tailwind v4 @Utility syntax Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent c0e3d6e commit 6c72500

39 files changed

Lines changed: 2230 additions & 1872 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { cn } from "@appshell/react";
5+
import { Check, Copy, ChevronDown, ChevronUp } from "lucide-react";
6+
7+
interface CodeBlockProps {
8+
code: string;
9+
language?: string;
10+
className?: string;
11+
}
12+
13+
export function CodeBlock({
14+
code,
15+
language = "tsx",
16+
className,
17+
}: CodeBlockProps) {
18+
const [copied, setCopied] = useState(false);
19+
const [expanded, setExpanded] = useState(false);
20+
21+
const handleCopy = async () => {
22+
await navigator.clipboard.writeText(code);
23+
setCopied(true);
24+
setTimeout(() => setCopied(false), 2000);
25+
};
26+
27+
return (
28+
<div
29+
className={cn(
30+
"rounded-xl border bg-zinc-950 text-zinc-50 overflow-hidden",
31+
className
32+
)}
33+
>
34+
<div className="flex items-center justify-between border-b border-zinc-800 px-4 py-2">
35+
<span className="text-xs text-zinc-400 font-mono">{language}</span>
36+
<div className="flex items-center gap-1">
37+
<button
38+
onClick={handleCopy}
39+
className="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 transition-colors"
40+
>
41+
{copied ? (
42+
<Check className="size-3.5" />
43+
) : (
44+
<Copy className="size-3.5" />
45+
)}
46+
{copied ? "Copied" : "Copy"}
47+
</button>
48+
<button
49+
onClick={() => setExpanded(!expanded)}
50+
className="flex items-center gap-1 rounded-md px-2 py-1 text-xs text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 transition-colors"
51+
>
52+
{expanded ? (
53+
<ChevronUp className="size-3.5" />
54+
) : (
55+
<ChevronDown className="size-3.5" />
56+
)}
57+
{expanded ? "Collapse" : "Expand"}
58+
</button>
59+
</div>
60+
</div>
61+
<pre
62+
className={cn(
63+
"overflow-x-auto p-4 text-sm leading-relaxed font-mono transition-all",
64+
!expanded && "max-h-48"
65+
)}
66+
>
67+
<code>{code}</code>
68+
</pre>
69+
</div>
70+
);
71+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import {
2+
PanelTop,
3+
Minus,
4+
Layers,
5+
StickyNote,
6+
Navigation,
7+
MousePointer2,
8+
PanelLeft,
9+
GalleryHorizontal,
10+
Box,
11+
Columns,
12+
Layout,
13+
Moon,
14+
} from "lucide-react";
15+
import type { LucideIcon } from "lucide-react";
16+
17+
export interface ExampleEntry {
18+
slug: string;
19+
title: string;
20+
description: string;
21+
icon: LucideIcon;
22+
code: string;
23+
}
24+
25+
export interface ExampleCategory {
26+
id: string;
27+
title: string;
28+
examples: ExampleEntry[];
29+
}
30+
31+
export const categories: ExampleCategory[] = [
32+
{
33+
id: "headers",
34+
title: "Headers",
35+
examples: [
36+
{
37+
slug: "fixed-header",
38+
title: "Fixed Header",
39+
description:
40+
"A header that stays pinned to the top of the viewport as the user scrolls through content.",
41+
icon: PanelTop,
42+
code: `<AppShell>\n <Header behavior="fixed" logo={...} nav={...} />\n <Content>...</Content>\n</AppShell>`,
43+
},
44+
{
45+
slug: "static-header",
46+
title: "Static Header",
47+
description:
48+
"A header that scrolls naturally with the page content, disappearing as the user scrolls down.",
49+
icon: Minus,
50+
code: `<AppShell>\n <Header behavior="static" logo={...} />\n <Content>...</Content>\n</AppShell>`,
51+
},
52+
{
53+
slug: "reveal-all",
54+
title: "Reveal Header",
55+
description:
56+
"The header hides on scroll down and reveals all rows when the user scrolls back up.",
57+
icon: Layers,
58+
code: `<AppShell>\n <Header behavior="reveal-all" logo={...} title="..." />\n <Content>...</Content>\n</AppShell>`,
59+
},
60+
{
61+
slug: "sticky-tabs",
62+
title: "Sticky Tabs",
63+
description:
64+
"A fixed header with a secondary tab bar that sticks below it using CSS variable syncing.",
65+
icon: StickyNote,
66+
code: `<Header behavior="fixed" />\n{/* Tabs use var(--header-height) for sticky offset */}\n<div style={{ top: "var(--header-height)" }} className="sticky" />`,
67+
},
68+
],
69+
},
70+
{
71+
id: "footers",
72+
title: "Footers",
73+
examples: [
74+
{
75+
slug: "tab-bar",
76+
title: "Tab Bar",
77+
description:
78+
"A standard mobile bottom navigation with 5 items, badges, and auto-hide on scroll.",
79+
icon: Navigation,
80+
code: `<Footer variant="tab-bar" behavior="auto-hide">\n <FooterItem icon={<Home />} label="Home" active />\n <FooterItem icon={<Search />} label="Search" />\n</Footer>`,
81+
},
82+
{
83+
slug: "floating-footer",
84+
title: "Floating Action",
85+
description:
86+
"An elevated floating action button for primary actions like cart, compose, or create.",
87+
icon: MousePointer2,
88+
code: `<Footer variant="floating" position="center">\n <button className="rounded-full bg-primary px-7 py-3.5">\n Show Cart\n </button>\n</Footer>`,
89+
},
90+
],
91+
},
92+
{
93+
id: "layout",
94+
title: "Layout",
95+
examples: [
96+
{
97+
slug: "sidebar",
98+
title: "Sidebar Menu",
99+
description:
100+
"A slide-out drawer with backdrop overlay, keyboard dismiss, and smooth animations.",
101+
icon: PanelLeft,
102+
code: `<Sidebar open={open} onClose={() => setOpen(false)}>\n <NavGroup label="Navigation">\n <NavItem label="Home" icon={<Home />} />\n </NavGroup>\n</Sidebar>`,
103+
},
104+
{
105+
slug: "nested-scroll",
106+
title: "Nested Scroll",
107+
description:
108+
"Netflix-style layout with horizontal carousels nested inside vertical scrolling content.",
109+
icon: GalleryHorizontal,
110+
code: `<Content>\n {sections.map(section => (\n <div className="overflow-x-auto snap-x snap-mandatory">\n {section.items.map(item => <Card />)}\n </div>\n ))}\n</Content>`,
111+
},
112+
{
113+
slug: "scroll-nav",
114+
title: "Scroll Navigation",
115+
description:
116+
"Horizontal pill-style scrollable tabs for category filtering and section navigation.",
117+
icon: Box,
118+
code: `<Header searchContent={\n <ScrollNav>\n <ScrollNavItem label="All" active />\n <ScrollNavItem label="Tech" />\n </ScrollNav>\n} />`,
119+
},
120+
{
121+
slug: "desktop-nav",
122+
title: "Desktop Nav",
123+
description:
124+
"Horizontal navigation with dropdown menus that adapts from mobile hamburger to desktop layout.",
125+
icon: Columns,
126+
code: `<Header\n nav={<HeaderNav>\n <HeaderNavItem label="Products" dropdown={...} />\n </HeaderNav>}\n mobileMenu={<NavGroup>...</NavGroup>}\n/>`,
127+
},
128+
],
129+
},
130+
{
131+
id: "patterns",
132+
title: "Patterns",
133+
examples: [
134+
{
135+
slug: "reveal-combined",
136+
title: "Combined Reveal",
137+
description:
138+
"The most complex pattern: header reveals on scroll up while footer auto-hides on scroll down.",
139+
icon: Layout,
140+
code: `<AppShell>\n <Header behavior="reveal-all" />\n <Content>...</Content>\n <Footer behavior="auto-hide" />\n</AppShell>`,
141+
},
142+
{
143+
slug: "dark-mode",
144+
title: "Dark Mode",
145+
description:
146+
"Live theme switching using CSS custom properties and the shadcn token system.",
147+
icon: Moon,
148+
code: `// Toggle .dark class on <html>\ndocument.documentElement.classList.toggle("dark");\n\n// All components use --background, --foreground tokens`,
149+
},
150+
],
151+
},
152+
];
153+
154+
export function findExample(
155+
slug: string
156+
): { category: ExampleCategory; example: ExampleEntry } | null {
157+
for (const category of categories) {
158+
const example = category.examples.find((e) => e.slug === slug);
159+
if (example) return { category, example };
160+
}
161+
return null;
162+
}
163+
164+
export function getAllExamples(): ExampleEntry[] {
165+
return categories.flatMap((c) => c.examples);
166+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Link from "next/link";
5+
import { cn } from "@appshell/react";
6+
import { PanelTop, Github, Menu, X, ExternalLink } from "lucide-react";
7+
import { SidebarNav } from "./sidebar-nav";
8+
import { PhoneMockup } from "./phone-mockup";
9+
import { CodeBlock } from "./code-block";
10+
11+
interface ExampleLayoutProps {
12+
slug: string;
13+
title: string;
14+
description: string;
15+
code: string;
16+
}
17+
18+
export function ExampleLayout({ slug, title, description, code }: ExampleLayoutProps) {
19+
const [sidebarOpen, setSidebarOpen] = useState(false);
20+
21+
return (
22+
<div className="min-h-screen bg-background text-foreground">
23+
{/* Top navbar */}
24+
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
25+
<div className="flex h-14 items-center px-4 sm:px-6">
26+
<button
27+
className="mr-3 rounded-md p-1.5 hover:bg-accent lg:hidden"
28+
onClick={() => setSidebarOpen(!sidebarOpen)}
29+
>
30+
{sidebarOpen ? (
31+
<X className="size-5" />
32+
) : (
33+
<Menu className="size-5" />
34+
)}
35+
</button>
36+
<Link
37+
href="/"
38+
className="flex items-center gap-2 font-bold tracking-tight"
39+
>
40+
<div className="size-6 rounded bg-primary flex items-center justify-center">
41+
<PanelTop className="size-4 text-primary-foreground" />
42+
</div>
43+
<span>AppShell</span>
44+
</Link>
45+
<div className="ml-auto flex items-center gap-4">
46+
<a
47+
href="https://github.com/fyalavuz/react-appshell"
48+
target="_blank"
49+
rel="noreferrer"
50+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
51+
>
52+
<Github className="size-5" />
53+
</a>
54+
</div>
55+
</div>
56+
</header>
57+
58+
<div className="flex">
59+
{/* Sidebar */}
60+
<aside
61+
className={cn(
62+
"fixed inset-y-0 left-0 top-14 z-40 w-64 border-r bg-background p-4 overflow-y-auto transition-transform lg:static lg:translate-x-0",
63+
sidebarOpen ? "translate-x-0" : "-translate-x-full"
64+
)}
65+
>
66+
<SidebarNav />
67+
</aside>
68+
69+
{/* Backdrop for mobile sidebar */}
70+
{sidebarOpen && (
71+
<div
72+
className="fixed inset-0 top-14 z-30 bg-black/50 lg:hidden"
73+
onClick={() => setSidebarOpen(false)}
74+
/>
75+
)}
76+
77+
{/* Main content */}
78+
<main className="flex-1 min-w-0 px-6 py-8 lg:px-12 lg:py-10">
79+
<div className="max-w-4xl mx-auto">
80+
{/* Header */}
81+
<div className="mb-8">
82+
<h1 className="text-3xl font-bold tracking-tight">
83+
{title}
84+
</h1>
85+
<p className="mt-2 text-lg text-muted-foreground">
86+
{description}
87+
</p>
88+
</div>
89+
90+
{/* Preview */}
91+
<div className="flex flex-col items-center gap-6 rounded-xl border bg-muted/30 p-8">
92+
<PhoneMockup src={`/${slug}`} />
93+
<div className="flex items-center gap-3">
94+
<Link
95+
href={`/${slug}`}
96+
target="_blank"
97+
className="inline-flex items-center gap-1.5 rounded-md border bg-background px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors"
98+
>
99+
<ExternalLink className="size-3.5" />
100+
Open Fullpage
101+
</Link>
102+
</div>
103+
</div>
104+
105+
{/* Code */}
106+
<div className="mt-8">
107+
<h2 className="mb-4 text-lg font-semibold">Usage</h2>
108+
<CodeBlock code={code} />
109+
</div>
110+
</div>
111+
</main>
112+
</div>
113+
</div>
114+
);
115+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { cn } from "@appshell/react";
4+
5+
interface PhoneMockupProps {
6+
src: string;
7+
className?: string;
8+
}
9+
10+
export function PhoneMockup({ src, className }: PhoneMockupProps) {
11+
return (
12+
<div
13+
className={cn("relative mx-auto", className)}
14+
style={{ width: 280, height: 572 }}
15+
>
16+
{/* Phone frame */}
17+
<div className="absolute inset-0 rounded-[2.5rem] border-[8px] border-foreground/90 bg-foreground/90 shadow-xl shadow-black/20 overflow-hidden">
18+
{/* Dynamic Island */}
19+
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-10 h-7 w-[120px] rounded-b-2xl bg-foreground/90" />
20+
{/* Screen */}
21+
<div className="relative h-full w-full overflow-hidden rounded-[2rem] bg-background">
22+
<iframe
23+
src={src}
24+
title="Example preview"
25+
className="h-full w-full border-0"
26+
style={{ pointerEvents: "auto" }}
27+
/>
28+
</div>
29+
{/* Home indicator */}
30+
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 z-10 h-1 w-[100px] rounded-full bg-background/60" />
31+
</div>
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)