Display15
Form1
Playground3
File Viewer
A component for displaying a file viewer
Default
Default file viewer
Explorer
src
app
layout.tsx
page.tsx
globals.css
api
auth
route.ts
users
route.ts
components
ui
button.tsx
card.tsx
file-tree.tsx
header.tsx
footer.tsx
lib
utils.ts
constants.ts
hooks
use-auth.ts
use-theme.ts
public
favicon.ico
logo.svg
images
hero.png
avatar.jpg
package.json
tsconfig.json
next.config.mjs
tailwind.config.ts
.env.local
.gitignore
README.md
Select a file to view its contents
file-viewer-examples.tsx
1"use client";23import { FileTree, FileTreeNode } from "@/registry/ui/file-tree";4import {5 FileExplorer,6 FileExplorerContent,7 FileExplorerSidebar,8 FileViewer,9} from "@/registry/ui/file-viewer";10import type { FileContent } from "@/registry/ui/file-viewer";11import { useState } from "react";1213const fileContents: Record<string, string> = {14 "src/app/layout.tsx": `import type { Metadata } from "next"15 import { Inter } from "next/font/google"16 import "./globals.css"1718 const inter = Inter({ subsets: ["latin"] })1920 export const metadata: Metadata = {21 title: "My App",22 description: "A Next.js application",23 }2425 export default function RootLayout({26 children,27 }: {28 children: React.ReactNode29 }) {30 return (31 <html lang="en">32 <body className={inter.className}>{children}</body>33 </html>34 )35 }`,36 "src/app/page.tsx": `export default function Home() {37 return (38 <main className="flex min-h-screen flex-col items-center justify-center p-24">39 <h1 className="text-4xl font-bold">Welcome to Next.js</h1>40 <p className="mt-4 text-lg text-muted-foreground">41 Get started by editing app/page.tsx42 </p>43 </main>44 )45 }`,46 "src/app/globals.css": `@tailwind base;47 @tailwind components;48 @tailwind utilities;4950 :root {51 --foreground-rgb: 0, 0, 0;52 --background-start-rgb: 214, 219, 220;53 --background-end-rgb: 255, 255, 255;54 }5556 @media (prefers-color-scheme: dark) {57 :root {58 --foreground-rgb: 255, 255, 255;59 --background-start-rgb: 0, 0, 0;60 --background-end-rgb: 0, 0, 0;61 }62 }`,63 "src/app/api/auth/route.ts": `import { NextResponse } from "next/server"6465 export async function POST(request: Request) {66 const body = await request.json()67 const { email, password } = body6869 // Validate credentials70 if (!email || !password) {71 return NextResponse.json(72 { error: "Missing credentials" },73 { status: 400 }74 )75 }7677 // TODO: Implement actual authentication78 return NextResponse.json({ success: true })79 }`,80 "src/app/api/users/route.ts": `import { NextResponse } from "next/server"8182 const users = [83 { id: 1, name: "Alice", email: "alice@example.com" },84 { id: 2, name: "Bob", email: "bob@example.com" },85 ]8687 export async function GET() {88 return NextResponse.json(users)89 }9091 export async function POST(request: Request) {92 const body = await request.json()93 const newUser = { id: users.length + 1, ...body }94 users.push(newUser)95 return NextResponse.json(newUser, { status: 201 })96 }`,97 "src/components/ui/button.tsx": `import * as React from "react"98 import { cva, type VariantProps } from "class-variance-authority"99 import { cn } from "@/lib/utils"100101 const buttonVariants = cva(102 "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",103 {104 variants: {105 variant: {106 default: "bg-primary text-primary-foreground hover:bg-primary/90",107 destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",108 outline: "border border-input hover:bg-accent hover:text-accent-foreground",109 secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",110 ghost: "hover:bg-accent hover:text-accent-foreground",111 link: "underline-offset-4 hover:underline text-primary",112 },113 size: {114 default: "h-10 px-4 py-2",115 sm: "h-9 rounded-md px-3",116 lg: "h-11 rounded-md px-8",117 icon: "h-10 w-10",118 },119 },120 defaultVariants: {121 variant: "default",122 size: "default",123 },124 }125 )126127 export interface ButtonProps128 extends React.ButtonHTMLAttributes<HTMLButtonElement>,129 VariantProps<typeof buttonVariants> {}130131 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(132 ({ className, variant, size, ...props }, ref) => {133 return (134 <button135 className={cn(buttonVariants({ variant, size, className }))}136 ref={ref}137 {...props}138 />139 )140 }141 )142 Button.displayName = "Button"143144 export { Button, buttonVariants }`,145 "src/components/ui/card.tsx": `import * as React from "react"146 import { cn } from "@/lib/utils"147148 const Card = React.forwardRef<149 HTMLDivElement,150 React.HTMLAttributes<HTMLDivElement>151 >(({ className, ...props }, ref) => (152 <div153 ref={ref}154 className={cn(155 "rounded-lg border bg-card text-card-foreground shadow-sm",156 className157 )}158 {...props}159 />160 ))161 Card.displayName = "Card"162163 const CardHeader = React.forwardRef<164 HTMLDivElement,165 React.HTMLAttributes<HTMLDivElement>166 >(({ className, ...props }, ref) => (167 <div168 ref={ref}169 className={cn("flex flex-col space-y-1.5 p-6", className)}170 {...props}171 />172 ))173 CardHeader.displayName = "CardHeader"174175 const CardTitle = React.forwardRef<176 HTMLParagraphElement,177 React.HTMLAttributes<HTMLHeadingElement>178 >(({ className, ...props }, ref) => (179 <h3180 ref={ref}181 className={cn("text-2xl font-semibold leading-none tracking-tight", className)}182 {...props}183 />184 ))185 CardTitle.displayName = "CardTitle"186187 export { Card, CardHeader, CardTitle }`,188 "src/components/ui/file-tree.tsx": `// See the actual file-tree.tsx component189 // This is a simplified preview190191 import { cva } from "class-variance-authority"192193 export const fileTreeVariants = cva(194 "font-mono text-sm select-none",195 {196 variants: {197 variant: {198 default: "bg-background text-foreground",199 ghost: "bg-transparent",200 bordered: "bg-background border border-border rounded-lg p-2",201 elevated: "bg-card text-card-foreground shadow-md rounded-lg p-3",202 },203 },204 }205 )`,206 "src/components/header.tsx": `import Link from "next/link"207 import { Button } from "@/components/ui/button"208209 export function Header() {210 return (211 <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur">212 <div className="container flex h-14 items-center">213 <Link href="/" className="mr-6 flex items-center space-x-2">214 <span className="font-bold">My App</span>215 </Link>216 <nav className="flex flex-1 items-center space-x-6 text-sm font-medium">217 <Link href="/about">About</Link>218 <Link href="/docs">Docs</Link>219 </nav>220 <Button size="sm">Sign In</Button>221 </div>222 </header>223 )224 }`,225 "src/components/footer.tsx": `export function Footer() {226 return (227 <footer className="border-t py-6 md:py-0">228 <div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">229 <p className="text-center text-sm leading-loose text-muted-foreground md:text-left">230 Built with Next.js and Tailwind CSS.231 </p>232 </div>233 </footer>234 )235 }`,236 "src/lib/utils.ts": `import { type ClassValue, clsx } from "clsx"237 import { twMerge } from "tailwind-merge"238239 export function cn(...inputs: ClassValue[]) {240 return twMerge(clsx(inputs))241 }`,242 "src/lib/constants.ts": `export const APP_NAME = "My Application"243 export const APP_VERSION = "1.0.0"244 export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000/api"245246 export const ROUTES = {247 home: "/",248 about: "/about",249 dashboard: "/dashboard",250 settings: "/settings",251 } as const`,252 "src/hooks/use-auth.ts": `import { useState, useEffect, useCallback } from "react"253254 interface User {255 id: string256 email: string257 name: string258 }259260 export function useAuth() {261 const [user, setUser] = useState<User | null>(null)262 const [loading, setLoading] = useState(true)263264 useEffect(() => {265 // Check for existing session266 const checkAuth = async () => {267 try {268 const response = await fetch("/api/auth/me")269 if (response.ok) {270 const userData = await response.json()271 setUser(userData)272 }273 } catch (error) {274 console.error("Auth check failed:", error)275 } finally {276 setLoading(false)277 }278 }279 checkAuth()280 }, [])281282 const signOut = useCallback(async () => {283 await fetch("/api/auth/signout", { method: "POST" })284 setUser(null)285 }, [])286287 return { user, loading, signOut }288 }`,289 "src/hooks/use-theme.ts": `import { useState, useEffect } from "react"290291 type Theme = "light" | "dark" | "system"292293 export function useTheme() {294 const [theme, setTheme] = useState<Theme>("system")295296 useEffect(() => {297 const stored = localStorage.getItem("theme") as Theme | null298 if (stored) {299 setTheme(stored)300 }301 }, [])302303 useEffect(() => {304 const root = document.documentElement305 root.classList.remove("light", "dark")306307 if (theme === "system") {308 const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches309 ? "dark"310 : "light"311 root.classList.add(systemTheme)312 } else {313 root.classList.add(theme)314 }315316 localStorage.setItem("theme", theme)317 }, [theme])318319 return { theme, setTheme }320 }`,321 "package.json": `{322 "name": "my-nextjs-app",323 "version": "0.1.0",324 "private": true,325 "scripts": {326 "dev": "next dev",327 "build": "next build",328 "start": "next start",329 "lint": "next lint"330 },331 "dependencies": {332 "next": "14.2.0",333 "react": "^18",334 "react-dom": "^18",335 "class-variance-authority": "^0.7.0",336 "clsx": "^2.1.0",337 "tailwind-merge": "^2.2.0",338 "lucide-react": "^0.344.0"339 },340 "devDependencies": {341 "typescript": "^5",342 "@types/node": "^20",343 "@types/react": "^18",344 "@types/react-dom": "^18",345 "tailwindcss": "^3.4.1",346 "postcss": "^8"347 }348 }`,349 "tsconfig.json": `{350 "compilerOptions": {351 "lib": ["dom", "dom.iterable", "esnext"],352 "allowJs": true,353 "skipLibCheck": true,354 "strict": true,355 "noEmit": true,356 "esModuleInterop": true,357 "module": "esnext",358 "moduleResolution": "bundler",359 "resolveJsonModule": true,360 "isolatedModules": true,361 "jsx": "preserve",362 "incremental": true,363 "plugins": [{ "name": "next" }],364 "paths": {365 "@/*": ["./*"]366 }367 },368 "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],369 "exclude": ["node_modules"]370 }`,371 "next.config.mjs": `/** @type {import('next').NextConfig} */372 const nextConfig = {373 reactStrictMode: true,374 images: {375 domains: [],376 },377 }378379 export default nextConfig`,380 "tailwind.config.ts": `import type { Config } from "tailwindcss"381382 const config: Config = {383 darkMode: ["class"],384 content: [385 "./pages/**/*.{js,ts,jsx,tsx,mdx}",386 "./components/**/*.{js,ts,jsx,tsx,mdx}",387 "./app/**/*.{js,ts,jsx,tsx,mdx}",388 ],389 theme: {390 extend: {391 colors: {392 border: "hsl(var(--border))",393 background: "hsl(var(--background))",394 foreground: "hsl(var(--foreground))",395 },396 },397 },398 plugins: [],399 }400 export default config`,401 ".env.local": `# Environment Variables402 DATABASE_URL="postgresql://user:password@localhost:5432/mydb"403 NEXT_PUBLIC_API_URL="http://localhost:3000/api"404 AUTH_SECRET="your-secret-key-here"`,405 ".gitignore": `# Dependencies406 node_modules407 .pnp408 .pnp.js409410 # Testing411 coverage412413 # Next.js414 .next/415 out/416417 # Production418 build419420 # Misc421 .DS_Store422 *.pem423424 # Debug425 npm-debug.log*426427 # Local env files428 .env*.local429430 # Vercel431 .vercel432433 # TypeScript434 *.tsbuildinfo435 next-env.d.ts`,436 "README.md": `# My Next.js App437438 A modern web application built with Next.js 14, React, and Tailwind CSS.439440 ## Getting Started441442 First, install dependencies:443444 \`\`\`bash445 npm install446 # or447 yarn install448 # or449 pnpm install450 \`\`\`451452 Then, run the development server:453454 \`\`\`bash455 npm run dev456 \`\`\`457458 Open [http://localhost:3000](http://localhost:3000) to see the result.459460 ## Features461462 - Next.js 14 App Router463 - TypeScript464 - Tailwind CSS465 - Component library with CVA variants466 `,467};468469const sampleData: FileTreeNode[] = [470 {471 name: "src",472 type: "folder",473 children: [474 {475 name: "app",476 type: "folder",477 children: [478 { name: "layout.tsx", type: "file" },479 { name: "page.tsx", type: "file" },480 { name: "globals.css", type: "file" },481 {482 name: "api",483 type: "folder",484 children: [485 {486 name: "auth",487 type: "folder",488 children: [{ name: "route.ts", type: "file" }],489 },490 {491 name: "users",492 type: "folder",493 children: [{ name: "route.ts", type: "file" }],494 },495 ],496 },497 ],498 },499 {500 name: "components",501 type: "folder",502 children: [503 {504 name: "ui",505 type: "folder",506 children: [507 { name: "button.tsx", type: "file" },508 { name: "card.tsx", type: "file" },509 { name: "file-tree.tsx", type: "file" },510 ],511 },512 { name: "header.tsx", type: "file" },513 { name: "footer.tsx", type: "file" },514 ],515 },516 {517 name: "lib",518 type: "folder",519 children: [520 { name: "utils.ts", type: "file" },521 { name: "constants.ts", type: "file" },522 ],523 },524 {525 name: "hooks",526 type: "folder",527 children: [528 { name: "use-auth.ts", type: "file" },529 { name: "use-theme.ts", type: "file" },530 ],531 },532 ],533 },534 {535 name: "public",536 type: "folder",537 children: [538 { name: "favicon.ico", type: "file" },539 { name: "logo.svg", type: "file" },540 {541 name: "images",542 type: "folder",543 children: [544 { name: "hero.png", type: "file" },545 { name: "avatar.jpg", type: "file" },546 ],547 },548 ],549 },550 { name: "package.json", type: "file" },551 { name: "tsconfig.json", type: "file" },552 { name: "next.config.mjs", type: "file" },553 { name: "tailwind.config.ts", type: "file" },554 { name: ".env.local", type: "file" },555 { name: ".gitignore", type: "file" },556 { name: "README.md", type: "file" },557];558559export const FileViewerDefault = () => {560 const [selectedPath, setSelectedPath] = useState<string>("");561 const [selectedFile, setSelectedFile] = useState<FileContent | null>(null);562563 const handleSelect = (node: FileTreeNode, path: string) => {564 setSelectedPath(path);565566 if (node.type === "file") {567 const content = fileContents[path];568 if (content) {569 setSelectedFile({570 path,571 name: node.name,572 content,573 });574 } else {575 setSelectedFile({576 path,577 name: node.name,578 content: `// Content for ${node.name} not available in demo`,579 });580 }581 }582 };583584 const handleClose = () => {585 setSelectedFile(null);586 setSelectedPath("");587 };588 return (589 <div className="w-full">590 <FileExplorer591 variant="elevated"592 rounded="lg"593 sidebarWidth="300px"594 className="h-[600px]"595 >596 <FileExplorerSidebar className="p-3">597 <div className="mb-3 px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">598 Explorer599 </div>600 <FileTree601 data={sampleData}602 variant="ghost"603 size="default"604 onSelect={handleSelect}605 selectedPath={selectedPath}606 />607 </FileExplorerSidebar>608 <FileExplorerContent>609 <FileViewer610 file={selectedFile}611 variant="ghost"612 size="default"613 rounded="none"614 maxHeight="100%"615 onClose={handleClose}616 className="h-full"617 />618 </FileExplorerContent>619 </FileExplorer>620 </div>621 );622};623624export const FileViewerStandalone = () => {625 return (626 <section className="grid gap-8 lg:grid-cols-2">627 <div>628 <FileViewer629 file={{630 path: "src/lib/utils.ts",631 name: "utils.ts",632 content: fileContents["src/lib/utils.ts"],633 }}634 variant="bordered"635 size="default"636 maxHeight="300px"637 highlightedLines={{638 4: "modified",639 }}640 />641 </div>642 <div>643 <FileViewer644 file={{645 path: "package.json",646 name: "package.json",647 content: fileContents["package.json"],648 }}649 variant="elevated"650 size="sm"651 maxHeight="300px"652 />653 </div>654 </section>655 );656};657658export const FileViewerElevated = () => {659 return (660 <FileViewer661 file={{662 path: "package.json",663 name: "package.json",664 content: fileContents["package.json"],665 }}666 variant="elevated"667 size="sm"668 maxHeight="300px"669 />670 );671};
Standalone
Standalone file viewer
src/lib/utils.ts
1
import { type ClassValue, clsx } from "clsx"2
import { twMerge } from "tailwind-merge"3
4
export function cn(...inputs: ClassValue[]) {5
return twMerge(clsx(inputs))6
}package.json
1
{2
"name": "my-nextjs-app",3
"version": "0.1.0",4
"private": true,5
"scripts": {6
"dev": "next dev",7
"build": "next build",8
"start": "next start",9
"lint": "next lint"10
},11
"dependencies": {12
"next": "14.2.0",13
"react": "^18",14
"react-dom": "^18",15
"class-variance-authority": "^0.7.0",16
"clsx": "^2.1.0",17
"tailwind-merge": "^2.2.0",18
"lucide-react": "^0.344.0"19
},20
"devDependencies": {21
"typescript": "^5",22
"@types/node": "^20",23
"@types/react": "^18",24
"@types/react-dom": "^18",25
"tailwindcss": "^3.4.1",26
"postcss": "^8"27
}28
}tsx
1import { FileViewerStandalone } from "@/components/examples/file-viewer-examples"23<FileViewerStandalone />
Props
| Name | Type | Default | Description |
|---|---|---|---|
| file | FileContent | undefined | The file to display |
| showLineNumbers | boolean | true | Show line numbers |
| highlightedLines | Record<number, string> | {} | Highlighted lines |
| onClose | () => void | undefined | Callback when the file viewer is closed |
| showHeader | boolean | true | Show the header |
| maxHeight | string | 100% | The maximum height of the file viewer |
| emptyMessage | string | Select a file to view its contents | The message to display when no file is selected |