import JSZip from "jszip" import { saveAs } from "file-saver" // Get current design settings from contexts interface ExportSettings { primaryColor: string borderRadius: number font: string materialType: string packageType: "starter" | "full" } // Generate package.json for each package type function generatePackageJson(packageType: "starter" | "full") { const baseDependencies = { "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", "class-variance-authority": "^0.7.0", clsx: "^2.0.0", "lucide-react": "^0.294.0", next: "14.0.4", "next-themes": "^0.2.1", react: "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", } const fullPackageDependencies = { ...baseDependencies, "react-colorful": "^5.6.1", "react-icons": "^4.12.0", } return JSON.stringify( { name: `percolate-ui-${packageType}-kit`, version: "1.0.0", private: true, scripts: { dev: "next dev", build: "next build", start: "next start", lint: "next lint", }, dependencies: packageType === "full" ? fullPackageDependencies : baseDependencies, devDependencies: { "@types/node": "^20.0.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", autoprefixer: "^10.4.16", eslint: "^8.0.0", "eslint-config-next": "14.0.4", postcss: "^8.4.31", tailwindcss: "^3.3.6", typescript: "^5.0.0", }, }, null, 2, ) } // Generate globals.css with current customizations function generateGlobalsCss(settings: ExportSettings) { return `@tailwind base; @tailwind components; @tailwind utilities; :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: ${settings.primaryColor}; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96%; --secondary-foreground: 222.2 84% 4.9%; --muted: 210 40% 96%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96%; --accent-foreground: 222.2 84% 4.9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: ${settings.primaryColor}; --radius: ${settings.borderRadius}px; --background-primary: #ffffff; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: ${settings.primaryColor}; --primary-foreground: 222.2 84% 4.9%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: ${settings.primaryColor}; --background-primary: #0f172a; } * { @apply border-border; } body { @apply bg-background text-foreground; font-family: ${settings.font}, sans-serif; } ${ settings.materialType === "glass" ? ` .glass-effect { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); } .dark .glass-effect { background: rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.1); } ` : "" } ` } // Generate tailwind.config.js function generateTailwindConfig(settings: ExportSettings) { return `/** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], content: [ './pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}', ], theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", "background-primary": "var(--background-primary)", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [require("tailwindcss-animate")], } ` } // Generate next.config.js function generateNextConfig() { return `/** @type {import('next').NextConfig} */ const nextConfig = {} module.exports = nextConfig ` } // Generate tsconfig.json function generateTsConfig() { return JSON.stringify( { compilerOptions: { target: "es5", lib: ["dom", "dom.iterable", "esnext"], allowJs: true, skipLibCheck: true, strict: true, forceConsistentCasingInFileNames: true, noEmit: true, esModuleInterop: true, module: "esnext", moduleResolution: "node", resolveJsonModule: true, isolatedModules: true, jsx: "preserve", incremental: true, plugins: [ { name: "next", }, ], paths: { "@/*": ["./*"], }, }, include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], exclude: ["node_modules"], }, null, 2, ) } // Generate utils/cn.ts function generateCnUtil() { return `import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ` } // Generate theme provider function generateThemeProvider() { return `"use client" import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" import { type ThemeProviderProps } from "next-themes" export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return {children} } ` } // Generate basic UI components (Button, Card, etc.) function generateButtonComponent() { return `import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( ) } ) Button.displayName = "Button" export { Button, buttonVariants } ` } // Generate Card component function generateCardComponent() { return `import * as React from "react" import { cn } from "@/lib/utils" const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
)) Card.displayName = "Card" const CardHeader = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
)) CardHeader.displayName = "CardHeader" const CardTitle = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => (

)) CardTitle.displayName = "CardTitle" const CardDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => (

)) CardDescription.displayName = "CardDescription" const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (

)) CardContent.displayName = "CardContent" const CardFooter = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
)) CardFooter.displayName = "CardFooter" export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } ` } // Generate main layout for Starter Kit function generateStarterLayout() { return `import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' import { ThemeProvider } from '@/components/theme-provider' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Percolate UI - Starter Kit', description: 'A beautiful design system built with React and Tailwind CSS', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ` } // Generate main layout for Full Package (with design controls) function generateFullLayout() { return `import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' import { ThemeProvider } from '@/components/theme-provider' import { DesignProvider } from '@/contexts/design-context' import { ColorProvider } from '@/contexts/color-context' import { MaterialProvider } from '@/contexts/material-context' import { SidebarProvider } from '@/contexts/sidebar-context' import { AppLayout } from '@/components/layouts/app-layout' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Percolate UI - Full Package', description: 'A complete design system with customization controls', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ` } // Generate main page for Starter Kit function generateStarterMainPage() { return `"use client" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { ThemeToggle } from "@/components/theme-toggle" export default function Home() { return (

Percolate UI

Welcome to Percolate UI

A beautiful design system built with React and Tailwind CSS

Starter Kit
Foundation Colors, typography, and spacing that power your design system Components Production-ready React components with TypeScript support Documentation Complete guides and examples for using the design system

Ready to get started?

This starter kit includes all the essential components and foundation elements you need to build beautiful interfaces.

) } ` } // Generate theme toggle component function generateThemeToggle() { return `"use client" import * as React from "react" import { Moon, Sun } from 'lucide-react' import { useTheme } from "next-themes" import { Button } from "@/components/ui/button" export function ThemeToggle() { const { setTheme, theme } = useTheme() return ( ) } ` } // Generate README function generateReadme(packageType: "starter" | "full") { return `# Percolate UI ${packageType === "starter" ? "Starter Kit" : "Full Package"} A beautiful design system built with React, TypeScript, and Tailwind CSS. ## Getting Started ### Prerequisites - Node.js 18+ - npm or yarn ### Installation 1. Install dependencies: \`\`\`bash npm install \`\`\` 2. Start the development server: \`\`\`bash npm run dev \`\`\` 3. Open [http://localhost:3000](http://localhost:3000) in your browser. ## What's Included ${ packageType === "starter" ? ` ### Starter Kit Features - **Foundation**: Colors, typography, spacing, and design tokens - **Components**: 20+ production-ready React components - **Theming**: Light/dark mode support - **TypeScript**: Full TypeScript support - **Accessibility**: WCAG 2.1 AA compliant components ### Components Included - Button, Card, Badge - Input, Textarea, Select - Dialog, Popover, Tooltip - Accordion, Tabs, Collapsible - And many more... ` : ` ### Full Package Features - **Everything in Starter Kit** - **Design Controls**: Live customization interface - **Blocks**: Pre-built page sections - **Advanced Theming**: Material design system - **Export Functionality**: Export your customizations ### Additional Features - Live color customization - Typography controls - Material system (Flat/Glass) - Border radius controls - Component showcase - Block library ` } ## Project Structure \`\`\` ├── app/ # Next.js app directory ├── components/ # React components │ ├── ui/ # Base UI components │ └── ... ├── lib/ # Utility functions ${ packageType === "full" ? `├── contexts/ # React contexts for state management ├── blocks/ # Pre-built page sections` : "" } └── ... \`\`\` ## Customization ### Colors ${ packageType === "starter" ? ` Colors are defined in \`app/globals.css\` using CSS custom properties: \`\`\`css :root { --primary: your-color-here; --secondary: your-color-here; /* ... */ } \`\`\` ` : ` Use the built-in design controls to customize colors, or modify \`app/globals.css\` directly. ` } ### Typography ${ packageType === "starter" ? ` Font family is set in \`app/layout.tsx\`. To change it: 1. Import your desired font from Google Fonts 2. Update the font variable 3. Apply it to the body element ` : ` Use the typography controls in the design interface, or modify the font imports in \`app/layout.tsx\`. ` } ## Building for Production \`\`\`bash npm run build npm start \`\`\` ## Learn More - [Next.js Documentation](https://nextjs.org/docs) - [Tailwind CSS](https://tailwindcss.com) - [Radix UI](https://www.radix-ui.com) ## License This project is licensed under the MIT License. ` } // Main export function export async function exportPackage(settings: ExportSettings) { const zip = new JSZip() const { packageType } = settings // Create folder structure const appFolder = zip.folder("app")! const componentsFolder = zip.folder("components")! const uiFolder = componentsFolder.folder("ui")! const libFolder = zip.folder("lib")! // Add configuration files zip.file("package.json", generatePackageJson(packageType)) zip.file("tailwind.config.js", generateTailwindConfig(settings)) zip.file("next.config.js", generateNextConfig()) zip.file("tsconfig.json", generateTsConfig()) zip.file("README.md", generateReadme(packageType)) zip.file( "postcss.config.js", `module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }`, ) // Add app files appFolder.file("globals.css", generateGlobalsCss(settings)) appFolder.file("layout.tsx", packageType === "starter" ? generateStarterLayout() : generateFullLayout()) appFolder.file("page.tsx", generateStarterMainPage()) // Add lib files libFolder.file("utils.ts", generateCnUtil()) // Add basic components componentsFolder.file("theme-provider.tsx", generateThemeProvider()) componentsFolder.file("theme-toggle.tsx", generateThemeToggle()) uiFolder.file("button.tsx", generateButtonComponent()) uiFolder.file("card.tsx", generateCardComponent()) // Add Badge component uiFolder.file( "badge.tsx", `import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, } ) export interface BadgeProps extends React.HTMLAttributes, VariantProps {} function Badge({ className, variant, ...props }: BadgeProps) { return (
) } export { Badge, badgeVariants }`, ) // If full package, add additional components and contexts if (packageType === "full") { // Add contexts folder const contextsFolder = zip.folder("contexts")! // Add basic contexts (simplified versions) contextsFolder.file( "design-context.tsx", `"use client" import React, { createContext, useContext, useState } from "react" interface DesignContextType { borderRadius: number setBorderRadius: (radius: number) => void font: string setFont: (font: string) => void } const DesignContext = createContext(undefined) export function DesignProvider({ children }: { children: React.ReactNode }) { const [borderRadius, setBorderRadius] = useState(6) const [font, setFont] = useState("Inter") return ( {children} ) } export function useDesign() { const context = useContext(DesignContext) if (!context) throw new Error("useDesign must be used within DesignProvider") return context }`, ) // Add more contexts and components for full package... } // Generate and download the zip const content = await zip.generateAsync({ type: "blob" }) const filename = `percolate-ui-${packageType}-kit.zip` saveAs(content, filename) return { success: true, filename } }