UNPKG

@ogasphere/theme-manager

Version:

A reusable theme management package for Next.js and Vite applications

584 lines (440 loc) 14.5 kB
# @ogasphere/theme-manager A reusable theme management package for Next.js and Vite applications. Automatically discovers themes from CSS files and provides utilities for theme management. ## Features - Automatic theme discovery from CSS files - Type-safe theme management - React hooks for client-side usage - Server-side functions for Next.js - Client-side parser for Vite - CLI tool for adding new themes - Framework-agnostic core logic - Framework auto-detection (Next.js / Vite) - **Ready-to-use branding page component** - **Base theme CSS styles included** ## Installation ```bash pnpm add @ogasphere/theme-manager ``` ## 🤖 Prompt pour IDE (Cursor / Claude Code) Copiez ce prompt dans votre IDE pour une intégration automatique complète : ``` Intègre le package @ogasphere/theme-manager dans ce projet. CONTEXTE: - Framework: [Next.js ou Vite - détecte automatiquement] - Type de projet: [nouveau projet ou projet existant - détecte automatiquement] - Package: @ogasphere/theme-manager (github:Olivier340/theme-manager) TÂCHES À EFFECTUER: 1. INSTALLATION: - Installer le package: pnpm add github:Olivier340/theme-manager - Installer les dépendances nécessaires si manquantes: lucide-react, @radix-ui/react-select (si shadcn/ui utilisé) 2. INTÉGRATION CSS: - Détecter le fichier CSS global (app/globals.css, src/index.css, src/globals.css, etc.) - Ajouter l'import: @import "@ogasphere/theme-manager/styles/themes.css"; - Placer l'import après les imports Tailwind si présents 3. CONFIGURATION THEME PROVIDER (Next.js uniquement): - Créer ou modifier app/providers.tsx (ou app/layout.tsx) - Importer ThemeProvider depuis "@ogasphere/theme-manager/client" - Envelopper l'application avec ThemeProvider - Configurer defaultTheme="system" et enableSystem 4. API ROUTE (Next.js uniquement): - Créer app/api/themes/route.ts - Importer getAvailableThemes depuis "@ogasphere/theme-manager/server" - Créer un endpoint GET qui retourne { themes } en JSON - Gérer les erreurs avec NextResponse 5. PAGE BRANDING: - Créer app/branding/page.tsx (Next.js) ou src/pages/branding.tsx (Vite) - Importer BrandingPage depuis "@ogasphere/theme-manager/client" - Importer les composants UI nécessaires (Card, Select, etc. depuis shadcn/ui ou équivalent) - Importer les icônes depuis lucide-react (SunMedium, Moon, Monitor) - Créer la logique handleThemeChange qui: * Applique le thème au body avec applyTheme depuis "@ogasphere/theme-manager/utils" * Sauvegarde dans localStorage - Restaurer le thème au chargement depuis localStorage - Passer tous les props nécessaires à BrandingPage (currentTheme, onThemeChange, components, icons) 6. APPLICATION DU THÈME GLOBALEMENT: - Créer un composant ThemeInitializer ou intégrer dans le layout - Utiliser useEffect pour appliquer le thème sauvegardé au chargement - Appliquer le thème au document.body avec applyTheme 7. VÉRIFICATIONS: - Vérifier que tous les imports sont corrects - Vérifier que les composants UI requis sont disponibles - S'assurer que le CSS est bien importé - Tester que la page branding est accessible IMPORTANT: - Adapter le code selon le framework détecté (Next.js vs Vite) - Utiliser les chemins corrects selon la structure du projet - Si shadcn/ui n'est pas installé, proposer une alternative ou installer les composants nécessaires - Créer tous les fichiers manquants - Ne pas modifier les fichiers existants de manière destructive - Documenter les changements effectués ``` ## Quick Start ### 1. Import Base Theme Styles Import the base theme CSS in your global stylesheet: ```css /* app/globals.css or styles/globals.css */ @import "@ogasphere/theme-manager/styles/themes.css"; ``` Or in your main CSS file: ```css @import "tailwindcss"; @import "@ogasphere/theme-manager/styles/themes.css"; ``` ### 2. Use the Branding Page Component ```typescript "use client"; import { BrandingPage } from "@ogasphere/theme-manager/client"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Monitor, Moon, SunMedium } from "lucide-react"; export function MyBrandingPage() { const handleThemeChange = async (themeId: string) => { // Your theme update logic here await updateTheme(themeId); }; return ( <BrandingPage currentTheme="default" onThemeChange={handleThemeChange} components={{ Card, CardHeader, CardTitle, CardDescription, CardContent, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, }} icons={{ SunMedium, Moon, Monitor, }} /> ); } ``` ## Usage ### Next.js Setup #### 1. Server-side: Get Available Themes ```typescript import { getAvailableThemes } from "@ogasphere/theme-manager/server"; const themes = getAvailableThemes(); ``` #### 2. API Route (Next.js) ```typescript // app/api/themes/route.ts import { getAvailableThemes } from "@ogasphere/theme-manager/server"; import { NextResponse } from "next/server"; export async function GET() { try { const themes = getAvailableThemes(); return NextResponse.json({ themes }); } catch { return NextResponse.json( { error: "Failed to get themes" }, { status: 500 }, ); } } ``` #### 3. Client-side: Use Themes Hook ```typescript "use client"; import { useThemes } from "@ogasphere/theme-manager/client"; export function ThemeSelector() { const { data: themes, isLoading } = useThemes(); if (isLoading) return <div>Loading themes...</div>; return ( <select> {themes?.map((theme) => ( <option key={theme.id} value={theme.id}> {theme.label} </option> ))} </select> ); } ``` #### 4. Apply Theme to Element ```typescript import { applyTheme } from "@ogasphere/theme-manager/utils"; const element = document.body; applyTheme(element, "modern-minimal"); ``` ### Branding Page Component The package includes a ready-to-use `BrandingPage` component that provides a complete UI for theme selection and color mode switching. #### Basic Usage ```typescript "use client"; import { BrandingPage } from "@ogasphere/theme-manager/client"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Monitor, Moon, SunMedium } from "lucide-react"; export function MyBrandingPage() { const handleThemeChange = async (themeId: string) => { // Your theme update logic here await updateTheme(themeId); }; return ( <BrandingPage currentTheme="default" onThemeChange={handleThemeChange} components={{ Card, CardHeader, CardTitle, CardDescription, CardContent, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, }} icons={{ SunMedium, Moon, Monitor, }} /> ); } ``` #### Customization You can customize all text labels and messages: ```typescript <BrandingPage currentTheme="default" onThemeChange={handleThemeChange} components={components} icons={icons} title="Custom Branding" description="Customize your application theme" cardTitle="Theme Settings" cardDescription="Choose a theme" colorModeLabel="Color Mode" themeSelectLabel="Select Theme" loadingText="Loading..." successMessage="Theme updated!" errorMessage="Failed to update theme" apiPath="/api/themes" /> ``` #### Props - `currentTheme`: The currently selected theme ID - `onThemeChange`: Callback function called when theme changes - `components`: Object containing your UI components (Card, Select, etc.) - `icons`: Optional object with icon components (SunMedium, Moon, Monitor) - `title`: Page title (default: "Branding") - `description`: Page description - `cardTitle`: Card title - `cardDescription`: Card description - `colorModeLabel`: Label for color mode section - `themeSelectLabel`: Label for theme select - `loadingText`: Loading message - `successMessage`: Success toast message - `errorMessage`: Error toast message - `apiPath`: Custom API path for fetching themes (default: "/api/themes") ### CSS Theme Styles The package includes base theme CSS styles. Import them in your global stylesheet: ```css /* app/globals.css */ @import "tailwindcss"; @import "@ogasphere/theme-manager/styles/themes.css"; ``` The CSS file includes: - Base theme variables for the default theme - Dark mode variants - Support for custom themes (add your own `.theme-{id}` classes) ### Advanced Configuration #### Custom CSS Path ```typescript import { getAvailableThemes } from "@ogasphere/theme-manager/server"; const themes = getAvailableThemes({ cssPath: path.join(process.cwd(), "styles", "themes.css"), prefix: "custom-theme", fallbackThemes: [ { id: "default", label: "Default" }, ], }); ``` #### Custom API Path ```typescript import { useThemes } from "@ogasphere/theme-manager/client"; const { data: themes } = useThemes({ apiPath: "/api/custom/themes", staleTime: 10 * 60 * 1000, // 10 minutes }); ``` ### Vite Setup #### 1. Client-side: Parse Themes Directly ```typescript "use client"; import { useThemes } from "@ogasphere/theme-manager/client"; export function ThemeSelector() { const { data: themes, isLoading } = useThemes({ useClientParser: true, clientParserOptions: { cssPath: "/src/index.css", // Path to your CSS file }, }); if (isLoading) return <div>Loading themes...</div>; return ( <select> {themes?.map((theme) => ( <option key={theme.id} value={theme.id}> {theme.label} </option> ))} </select> ); } ``` #### 2. Alternative: Use Client Parser Directly ```typescript "use client"; import { getAvailableThemesClient } from "@ogasphere/theme-manager/client"; async function loadThemes() { const themes = await getAvailableThemesClient({ cssPath: "/index.css", // Public path or use cssContent option }); return themes; } ``` **Note:** For Vite, you can also import CSS as text and pass it directly: ```typescript import cssContent from "./index.css?raw"; import { getAvailableThemesClient } from "@ogasphere/theme-manager/client"; const themes = await getAvailableThemesClient({ cssContent, // Pass CSS content directly }); ``` #### 3. Vite API Route (if using a backend) If you're using Vite with a backend API, you can create an API endpoint: ```typescript // src/api/themes.ts or your API route handler import { getAvailableThemes } from "@ogasphere/theme-manager/server"; export async function getThemes() { return getAvailableThemes({ cssPath: path.join(process.cwd(), "src", "index.css"), }); } ``` **Note:** For Vite projects, the package automatically detects the framework and uses appropriate default paths: - `src/index.css` - `src/main.css` - `src/styles.css` - `src/globals.css` ### Adding New Themes Use the CLI tool to add new Shadcn themes: ```bash pnpm theme-manager-add <theme-name> <theme-url> [css-path] ``` Example: ```bash pnpm theme-manager-add modern-minimal https://tweakcn.com/r/themes/modern-minimal.json ``` ### Theme Provider The package includes a theme provider that wraps `next-themes`: ```typescript "use client"; import { ThemeProvider } from "@ogasphere/theme-manager/client"; export function Providers({ children }) { return ( <ThemeProvider defaultTheme="system" enableSystem> {children} </ThemeProvider> ); } ``` ## API Reference ### Core #### `parseThemesFromCSS(cssContent: string, options?: ThemeParserOptions): Theme[]` Parse CSS content to extract theme classes. #### `formatThemeLabel(id: string): string` Convert theme ID to human-readable label (e.g., "modern-minimal" → "Modern Minimal"). #### `validateThemeId(themeId: string, availableThemes: Theme[]): boolean` Validate if a theme ID exists in the available themes. #### `getThemeById(themeId: string, availableThemes: Theme[]): Theme | undefined` Get a theme by its ID. ### Server #### `getAvailableThemes(options?: ThemeConfig): Theme[]` Get all available themes from CSS file. Returns fallback themes if parsing fails. ### Client #### `useThemes(options?: UseThemesOptions)` React hook that fetches themes from API using TanStack Query. #### `ThemeProvider` React component that wraps `next-themes` ThemeProvider. ### Utils #### `applyTheme(element: HTMLElement, themeId: string, prefix?: string): void` Apply a theme class to an HTML element. #### `removeTheme(element: HTMLElement, prefix?: string): void` Remove all theme classes from an HTML element. ## Types ```typescript type Theme = { id: string; label: string; }; type ThemeConfig = { cssPath?: string; prefix?: string; fallbackThemes?: Theme[]; }; type UseThemesOptions = { apiPath?: string; staleTime?: number; }; ``` ## Migration Guide ### From Local Implementation 1. Install the package: ```bash pnpm add @ogasphere/theme-manager ``` 2. Replace imports: ```typescript // Before import { getAvailableThemes } from "@/lib/themes/get-available-themes"; // After import { getAvailableThemes } from "@ogasphere/theme-manager/server"; ``` 3. Update API route: ```typescript // app/api/themes/route.ts import { getAvailableThemes } from "@ogasphere/theme-manager/server"; ``` 4. Update hooks: ```typescript // Before import { useThemes } from "@/hooks/use-themes"; // After import { useThemes } from "@ogasphere/theme-manager/client"; ``` 5. Use validation utilities: ```typescript import { validateThemeId } from "@ogasphere/theme-manager/core"; if (!validateThemeId(themeId, availableThemes)) { throw new Error("Invalid theme ID"); } ``` ## CSS Structure The package expects themes to be defined in CSS with the following structure: ```css .theme-{id} { --background: ...; --foreground: ...; /* ... other CSS variables */ } .dark .theme-{id} { --background: ...; --foreground: ...; /* ... dark mode variables */ } ``` ## License MIT