Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions app/component/[slug]/CodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client";

import * as React from "react";
import { Button } from "@/registry/mini-app/ui/button";
import { Clipboard as ClipboardIcon, Check as CheckIcon } from "lucide-react";

type CodeSnippetProps = {
code: string;
title?: string;
};

export function CodeSnippet({ code, title = "Example Code" }: CodeSnippetProps) {
const [copied, setCopied] = React.useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 3000);
};

return (
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">{title}</h3>
<Button variant="outline" size="sm" onClick={handleCopy}>
{copied ? (
<>
<CheckIcon className="w-4 h-4 mr-2" />
Copied
</>
) : (
<>
<ClipboardIcon className="w-4 h-4 mr-2" />
Copy Code
</>
)}
</Button>
</div>
<div className="relative">
<pre className="bg-gray-800 text-white p-4 rounded-md overflow-x-auto">
<code>{code}</code>
</pre>
</div>
</div>
);
}
29 changes: 17 additions & 12 deletions app/component/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { notFound, useParams } from "next/navigation";
import { componentItems } from "@/lib/components-config";
import { ArrowLeft } from "lucide-react";
import { ComponentWrapper } from "./component-wrapper";
import { InstallSnippet } from "./InstallSnippet"; // Import the new InstallSnippet component
import { InstallSnippet } from "./InstallSnippet";
import { CodeSnippet } from "./CodeSnippet";

export default function ComponentPage() {
const params = useParams();
Expand All @@ -17,7 +18,9 @@ export default function ComponentPage() {
notFound();
}

const item = componentItems.find((item) => item.installName === slug);
const item = componentItems.find((item) =>
item.installName === slug || item.slug === slug
);

if (!item) {
notFound();
Expand All @@ -28,18 +31,17 @@ export default function ComponentPage() {
<div className="flex flex-col min-h-screen bg-gradient-to-b from-background to-background/95">
{/* Header */}
<header className="w-full py-4 px-6 border-b border-border/50 flex items-center justify-between bg-background/80 backdrop-blur-sm sticky top-0 z-10">
<div className="flex items-center gap-3">
<Link
href="/"
className="p-1.5 hover:bg-muted rounded-full transition-colors"
aria-label="Back"
>
<Link
href="/"
className="flex items-center gap-3 hover:opacity-80 transition-opacity"
>
<div className="p-1.5 hover:bg-muted rounded-full transition-colors">
<ArrowLeft className="h-5 w-5 text-foreground/80" />
</Link>
</div>
<h1 className="text-muted-foreground tracking-tight">
hellno/mini-app-ui
</h1>
</div>
</Link>
</header>

<main className="flex-1 max-w-4xl mx-auto w-full px-4 py-6">
Expand All @@ -58,8 +60,11 @@ export default function ComponentPage() {
</div>

<div className="border rounded-xl p-6 bg-card/30 shadow-sm backdrop-blur-sm">
<InstallSnippet installName={item.installName} />{" "}
{/* Use the new InstallSnippet component */}
{item.isDemo && item.demoCode ? (
<CodeSnippet code={item.demoCode} title="Example Implementation" />
) : (
item.installName && <InstallSnippet installName={item.installName} />
)}
</div>
</div>
</main>
Expand Down
55 changes: 55 additions & 0 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";

import { AppSidebar } from "@/components/app-sidebar"
import { componentGroups } from "@/lib/components-config"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Separator } from "@/components/ui/separator"
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"

export default function Page() {
return (
<SidebarProvider>
<AppSidebar componentGroups={componentGroups} />
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b">
<div className="flex items-center gap-2 px-3">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="bg-muted/50 aspect-video rounded-xl" />
<div className="bg-muted/50 aspect-video rounded-xl" />
<div className="bg-muted/50 aspect-video rounded-xl" />
</div>
<div className="bg-muted/50 min-h-[100vh] flex-1 rounded-xl md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}
206 changes: 123 additions & 83 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import Link from "next/link";
import Image from "next/image";
import { OpenInVibesEngineeringButton } from "@/components/open-in-vibes-engineering-button";
import { useMiniAppSdk } from "@/registry/mini-app/hooks/use-miniapp-sdk";
import { componentItems } from "@/lib/components-config";
import { componentGroups } from "@/lib/components-config";
import {
Clipboard as ClipboardIcon,
Check as CheckIcon,
ExternalLink,
Github,
} from "lucide-react";
import { Button } from "@/registry/mini-app/ui/button";
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";

function InstallSnippet({ installName }: { installName: string }) {
const [tab, setTab] = React.useState<"pnpm" | "npm" | "bun">("pnpm");
Expand Down Expand Up @@ -81,96 +83,134 @@ function InstallSnippet({ installName }: { installName: string }) {
export default function Home() {
useMiniAppSdk();

const handleComponentClick = (componentId: string) => {
const element = document.getElementById(`component-${componentId}`);
if (element) {
const headerOffset = 80; // Approximate header height
const elementPosition = element.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;

window.scrollTo({
top: offsetPosition,
behavior: "smooth"
});
}
};

return (
<div className="flex flex-col min-h-screen bg-gradient-to-b from-background to-background/95">
{/* Header */}
<header className="w-full py-4 px-6 border-b border-border/50 flex items-center justify-between bg-background/80 backdrop-blur-sm sticky top-0 z-10">
<div className="flex items-center gap-3">
<h1 className="text-xl font-bold tracking-tight">
hellno/mini-app-ui
</h1>
</div>
<SidebarProvider>
<div className="flex w-full min-h-screen bg-gradient-to-b from-background to-background/95">
<AppSidebar
onComponentClick={handleComponentClick}
componentGroups={componentGroups}
/>
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="w-full py-4 px-6 border-b border-border/50 flex items-center justify-between bg-background/80 backdrop-blur-sm sticky top-0 z-10">
<div className="flex items-center gap-3">
<SidebarTrigger />
<h1 className="text-xl font-bold tracking-tight">
hellno/mini-app-ui
</h1>
</div>

{/* Action Icons */}
<div className="flex items-center gap-4">
<a
href="https://farcaster.xyz/hellno.eth"
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-muted rounded-full transition-colors flex items-center"
aria-label="hellno.eth on Farcaster"
>
<div className="w-5 h-5 text-foreground/80">
<Image
src="/farcaster.svg"
alt="Farcaster"
width={20}
height={20}
className="text-foreground"
/>
{/* Action Icons */}
<div className="flex items-center gap-4">
<a
href="https://farcaster.xyz/hellno.eth"
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-muted rounded-full transition-colors flex items-center"
aria-label="hellno.eth on Farcaster"
>
<div className="w-5 h-5 text-foreground/80">
<Image
src="/farcaster.svg"
alt="Farcaster"
width={20}
height={20}
className="text-foreground"
/>
</div>
</a>
<a
href="https://github.com/hellno/mini-app-ui"
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-muted rounded-full transition-colors"
aria-label="GitHub"
>
<Github className="h-5 w-5 text-foreground/80" />
</a>
</div>
</a>
<a
href="https://github.com/hellno/mini-app-ui"
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-muted rounded-full transition-colors"
aria-label="GitHub"
>
<Github className="h-5 w-5 text-foreground/80" />
</a>
</div>
</header>
</header>

<main className="flex-1 max-w-4xl mx-auto w-full px-4 py-6">
<div className="mb-8">
<p className="text-muted-foreground">
A collection of components, hooks and utilities for mini apps using
shadcn. Build beautiful and functional mini-apps with these
ready-to-use components.
</p>
</div>
<main className="flex-1 max-w-4xl mx-auto w-full px-4 py-6">
<div className="mb-8">
<p className="text-muted-foreground">
A collection of components, hooks and utilities for mini apps
using shadcn. Build beautiful and functional mini-apps with
these ready-to-use components.
</p>
</div>

<div className="grid grid-cols-1 gap-8">
{componentItems.map((item, index) => (
<div
key={index}
className="flex flex-col border rounded-xl overflow-hidden bg-card/30 shadow-sm transition-all hover:shadow-md backdrop-blur-sm"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between p-4 border-b border-border/30">
<h2 className="text-lg font-medium text-foreground">
{item.title}
</h2>
<div className="flex items-center gap-2 flex-shrink-0 sm:justify-end">
<Button
asChild
variant="outline"
size="sm"
className="h-8 text-xs"
>
<Link href={`/component/${item.installName}`}>
<ExternalLink className="w-3 h-3 mr-1" />
Fullscreen
</Link>
</Button>
<OpenInVibesEngineeringButton className="h-8" />
</div>
</div>
<div className="space-y-12">
{componentGroups.map((group, groupIndex) => (
<div key={group.title}>
<h2 className="text-2xl font-bold mb-6">{group.title}</h2>
<div className="grid grid-cols-1 gap-8">
{group.items.map((item, index) => (
<div
key={index}
id={`component-${item.installName || item.slug || `${groupIndex}-${index}`}`}
className="flex flex-col border rounded-xl overflow-hidden bg-card/30 shadow-sm transition-all hover:shadow-md backdrop-blur-sm"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between p-4 border-b border-border/30">
<Link
href={`/component/${item.installName || item.slug || index}`}
className="text-lg font-medium text-foreground hover:underline"
>
{item.title}
</Link>
<div className="flex items-center gap-2 flex-shrink-0 sm:justify-end">
<Button
asChild
variant="outline"
size="sm"
className="h-8 text-xs"
>
<Link
href={`/component/${item.installName || item.slug || index}`}
>
<ExternalLink className="w-3 h-3 mr-1" />
Fullscreen
</Link>
</Button>
<OpenInVibesEngineeringButton className="h-8" />
</div>
</div>

<div className="flex items-center justify-center p-8 bg-gradient-to-b from-background/20 to-background/5 relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(var(--primary-rgb),0.07),transparent_60%)] pointer-events-none"></div>
<div className="relative z-10 flex items-center justify-center min-h-[280px] w-full">
{item.component}
</div>
</div>
<div className="flex items-center justify-center p-8 bg-gradient-to-b from-background/20 to-background/5 relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(var(--primary-rgb),0.07),transparent_60%)] pointer-events-none"></div>
<div className="relative z-10 flex items-center justify-center min-h-[280px] w-full">
{item.component}
</div>
</div>

<div className="p-4 bg-card/40 border-t border-border/30">
<InstallSnippet installName={item.installName} />
</div>
{item.installName && (
<div className="p-4 bg-card/40 border-t border-border/30">
<InstallSnippet installName={item.installName} />
</div>
)}
</div>
))}
</div>
</div>
))}
</div>
))}
</main>
</div>
</main>
</div>
</div>
</SidebarProvider>
);
}
Loading