@ogasphere/theme-manager
v1.2.1
Published
A reusable theme management package for Next.js and Vite applications
Maintainers
Readme
@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
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ésQuick Start
1. Import Base Theme Styles
Import the base theme CSS in your global stylesheet:
/* app/globals.css or styles/globals.css */
@import "@ogasphere/theme-manager/styles/themes.css";Or in your main CSS file:
@import "tailwindcss";
@import "@ogasphere/theme-manager/styles/themes.css";2. Use the Branding Page Component
"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
import { getAvailableThemes } from "@ogasphere/theme-manager/server";
const themes = getAvailableThemes();2. API Route (Next.js)
// 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
"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
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
"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:
<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 IDonThemeChange: Callback function called when theme changescomponents: Object containing your UI components (Card, Select, etc.)icons: Optional object with icon components (SunMedium, Moon, Monitor)title: Page title (default: "Branding")description: Page descriptioncardTitle: Card titlecardDescription: Card descriptioncolorModeLabel: Label for color mode sectionthemeSelectLabel: Label for theme selectloadingText: Loading messagesuccessMessage: Success toast messageerrorMessage: Error toast messageapiPath: 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:
/* 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
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
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
"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
"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:
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:
// 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.csssrc/main.csssrc/styles.csssrc/globals.css
Adding New Themes
Use the CLI tool to add new Shadcn themes:
pnpm theme-manager-add <theme-name> <theme-url> [css-path]Example:
pnpm theme-manager-add modern-minimal https://tweakcn.com/r/themes/modern-minimal.jsonTheme Provider
The package includes a theme provider that wraps next-themes:
"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
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
Install the package:
pnpm add @ogasphere/theme-managerReplace imports:
// Before import { getAvailableThemes } from "@/lib/themes/get-available-themes"; // After import { getAvailableThemes } from "@ogasphere/theme-manager/server";Update API route:
// app/api/themes/route.ts import { getAvailableThemes } from "@ogasphere/theme-manager/server";Update hooks:
// Before import { useThemes } from "@/hooks/use-themes"; // After import { useThemes } from "@ogasphere/theme-manager/client";Use validation utilities:
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:
.theme-{id} {
--background: ...;
--foreground: ...;
/* ... other CSS variables */
}
.dark .theme-{id} {
--background: ...;
--foreground: ...;
/* ... dark mode variables */
}License
MIT
