diff --git a/app/Http/Controllers/ThemesController.php b/app/Http/Controllers/ThemesController.php
index 9ddf9bc..e656395 100644
--- a/app/Http/Controllers/ThemesController.php
+++ b/app/Http/Controllers/ThemesController.php
@@ -10,7 +10,7 @@ class ThemesController extends Controller
{
public function index()
{
- $availableCategories = Cache::remember('themes:available_categories', 3600, fn() => Theme::query()
+ $availableCategories = Cache::remember('themes:available_categories', 3600, fn () => Theme::query()
->select('categories')
->get()
->pluck('categories')
@@ -24,7 +24,7 @@ public function index()
'themes' => Inertia::scroll(Theme::paginate(12)->withQueryString()),
'filters' => request()->only(['search', 'category']),
'availableCategories' => $availableCategories,
- 'totalThemesCount' => Cache::remember('themes:total_count', 3600, fn() => Theme::count()),
+ 'totalThemesCount' => Cache::remember('themes:total_count', 3600, fn () => Theme::count()),
]);
}
@@ -33,7 +33,7 @@ public function apiIndex()
$query = Theme::query();
if ($search = request('search')) {
- $query->where(fn($q) => $q
+ $query->where(fn ($q) => $q
->where('name', 'like', "%{$search}%")
->orWhere('title', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%")
@@ -43,6 +43,13 @@ public function apiIndex()
return $query->paginate(50)->withQueryString();
}
+ public function show(Theme $theme)
+ {
+ return Inertia::render('themes/show', [
+ 'theme' => $theme,
+ ]);
+ }
+
public function css(string $name)
{
return response(
diff --git a/resources/js/pages/themes/show.tsx b/resources/js/pages/themes/show.tsx
new file mode 100644
index 0000000..0ba76e2
--- /dev/null
+++ b/resources/js/pages/themes/show.tsx
@@ -0,0 +1,382 @@
+import { Head } from '@inertiajs/react';
+import { Clipboard, Download, Moon, Sun } from 'lucide-react';
+import { useState, useMemo } from 'react';
+import { toast } from 'sonner';
+import { wcagContrast } from 'culori';
+
+import Heading from '@/components/heading';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Separator } from '@/components/ui/separator';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import MainLayout from '@/layouts/main-layout';
+import MainWrapper from '@/layouts/main/main-wrapper';
+import { useCSSVars } from '@/hooks/use-css-vars';
+import { useClipboard } from '@/hooks/use-clipboard';
+import { convertColor } from '@/lib/color-utils';
+import type { Registry } from '@/types/registry';
+
+interface ThemesShowProps {
+ theme: Registry;
+}
+
+function ColorSwatch({ name, value }: { name: string; value: string }) {
+ const [, copy] = useClipboard();
+ const [format, setFormat] = useState<'hex' | 'rgb' | 'hsl'>('hex');
+
+ const displayValue = useMemo(() => {
+ return convertColor(value, format) || value;
+ }, [value, format]);
+
+ const handleCopy = () => {
+ copy(displayValue);
+ toast.success(`Copied ${name} to clipboard`);
+ };
+
+ return (
+
The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+
+ {label === 'Monospace' ? (
+
+{`function resolveTheme(name: string) {
+ const theme = themes.find(t => t.name === name);
+ return theme ?? defaultTheme;
+}`}
+
+ ) : (
+ "Design is not just what it looks like and feels like. Design is how it works. Typography is the craft of endowing human language with a durable visual form."
+ )}
+
The foundational color palette of the theme.
+Font families and scale used in this theme.
+How the theme looks applied to standard interface elements.
++ Cards are used to group related information and provide a clear hierarchy. +
+Copy these into your main CSS file.
+
+{`:root {
+${Object.entries(theme.vars_light || {}).map(([k, v]) => ` --${k}: ${v};`).join('\n')}
+}
+
+.dark {
+${Object.entries(theme.vars_dark || {}).map(([k, v]) => ` --${k}: ${v};`).join('\n')}
+}`}
+
+
+ The registry representation of the theme.
+
+ {JSON.stringify(theme, null, 2)}
+
+
+