feat: add themes page back (#7503)

* feat: add themes page back

* fix: values

* fix: redirect
This commit is contained in:
shadcn
2025-05-30 17:45:02 +04:00
committed by GitHub
parent 12b7833d70
commit b5cf967848
10 changed files with 877 additions and 1 deletions

View File

@@ -0,0 +1,64 @@
import { Metadata } from "next"
import Link from "next/link"
import { Announcement } from "@/components/announcement"
import {
PageActions,
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
import { Button } from "@/registry/new-york-v4/ui/button"
const title = "Pick a Color. Make it yours."
const description =
"Try our hand-picked themes. Copy and paste them into your project. New theme editor coming soon."
export const metadata: Metadata = {
title,
description,
openGraph: {
images: [
{
url: `/og?title=${encodeURIComponent(
title
)}&description=${encodeURIComponent(description)}`,
},
],
},
twitter: {
card: "summary_large_image",
images: [
{
url: `/og?title=${encodeURIComponent(
title
)}&description=${encodeURIComponent(description)}`,
},
],
},
}
export default function ThemesLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<PageHeader>
<Announcement />
<PageHeaderHeading>{title}</PageHeaderHeading>
<PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions>
<Button asChild size="sm">
<a href="#themes">Browse Themes</a>
</Button>
<Button asChild variant="ghost" size="sm">
<Link href="/docs/theming">Documentation</Link>
</Button>
</PageActions>
</PageHeader>
{children}
</div>
)
}

View File

@@ -0,0 +1,19 @@
import { CardsDemo } from "@/components/cards"
import { ThemeCustomizer } from "@/components/theme-customizer"
export default function ThemesPage() {
return (
<>
<div id="themes" className="container-wrapper scroll-mt-20">
<div className="container flex items-center justify-between gap-8 px-6 py-4 md:px-8">
<ThemeCustomizer />
</div>
</div>
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
<div className="theme-container container flex flex-1 flex-col">
<CardsDemo />
</div>
</div>
</>
)
}

View File

@@ -9,7 +9,7 @@ const start = new Date(2025, 5, 5)
export function CardsCalendar() {
return (
<Card className="max-w-[260px] p-0">
<Card className="hidden max-w-[260px] p-0 sm:flex">
<CardContent className="p-0">
<Calendar
numberOfMonths={1}

View File

@@ -0,0 +1,569 @@
"use client"
import * as React from "react"
import template from "lodash/template"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { useThemeConfig } from "@/components/active-theme"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york-v4/ui/dialog"
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
import { Label } from "@/registry/new-york-v4/ui/label"
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import {
BaseColor,
baseColors,
baseColorsOKLCH,
} from "@/registry/registry-base-colors"
interface BaseColorOKLCH {
light: Record<string, string>
dark: Record<string, string>
}
const THEMES = baseColors.filter(
(theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name)
)
export function ThemeCustomizer({ className }: React.ComponentProps<"div">) {
const { activeTheme = "neutral", setActiveTheme } = useThemeConfig()
return (
<div className={cn("flex w-full items-center gap-2", className)}>
<ScrollArea className="hidden max-w-[96%] md:max-w-[600px] lg:flex lg:max-w-none">
<div className="flex items-center">
{THEMES.map((theme) => (
<Button
key={theme.name}
variant="link"
size="sm"
data-active={activeTheme === theme.name}
className="text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 cursor-pointer items-center justify-center px-4 text-center text-base font-medium capitalize transition-colors hover:no-underline"
onClick={() => setActiveTheme(theme.name)}
>
{theme.name === "neutral" ? "Default" : theme.name}
</Button>
))}
</div>
<ScrollBar orientation="horizontal" className="invisible" />
</ScrollArea>
<div className="flex items-center gap-2 lg:hidden">
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select
value={activeTheme === "default" ? "neutral" : activeTheme}
onValueChange={setActiveTheme}
>
<SelectTrigger
id="theme-selector"
size="sm"
className="justify-start capitalize shadow-none *:data-[slot=select-value]:w-12 *:data-[slot=select-value]:capitalize"
>
<span className="font-medium">Theme:</span>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{THEMES.map((theme) => (
<SelectItem
key={theme.name}
value={theme.name}
className="capitalize data-[state=checked]:opacity-50"
>
{theme.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<CopyCodeButton variant="secondary" size="sm" className="ml-auto" />
</div>
)
}
export function CopyCodeButton({
className,
...props
}: React.ComponentProps<typeof Button>) {
let { activeTheme: activeThemeName = "neutral" } = useThemeConfig()
activeThemeName = activeThemeName === "default" ? "neutral" : activeThemeName
return (
<>
<Drawer>
<DrawerTrigger asChild>
<Button className={cn("sm:hidden", className)} {...props}>
Copy Code
</Button>
</DrawerTrigger>
<DrawerContent className="h-auto">
<DrawerHeader>
<DrawerTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your CSS file.
</DrawerDescription>
</DrawerHeader>
<CustomizerCode themeName={activeThemeName} />
</DrawerContent>
</Drawer>
<Dialog>
<DialogTrigger asChild>
<Button className={cn("hidden sm:flex", className)} {...props}>
Copy Code
</Button>
</DialogTrigger>
<DialogContent className="outline-none md:max-w-3xl">
<DialogHeader>
<DialogTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DialogTitle>
<DialogDescription>
Copy and paste the following code into your CSS file.
</DialogDescription>
</DialogHeader>
<CustomizerCode themeName={activeThemeName} />
</DialogContent>
</Dialog>
</>
)
}
function CustomizerCode({ themeName }: { themeName: string }) {
const [hasCopied, setHasCopied] = React.useState(false)
const [tailwindVersion, setTailwindVersion] = React.useState("v4")
const activeTheme = React.useMemo(
() => baseColors.find((theme) => theme.name === themeName),
[themeName]
)
const activeThemeOKLCH = React.useMemo(
() => baseColorsOKLCH[themeName as keyof typeof baseColorsOKLCH],
[themeName]
)
React.useEffect(() => {
if (hasCopied) {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}
}, [hasCopied])
return (
<>
<Tabs
value={tailwindVersion}
onValueChange={setTailwindVersion}
className="min-w-0 px-4 pb-4 md:p-0"
>
<TabsList>
<TabsTrigger value="v4">Tailwind v4</TabsTrigger>
<TabsTrigger value="v3">Tailwind v3</TabsTrigger>
</TabsList>
<TabsContent value="v4">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
data-rehype-pretty-code-title=""
data-language="css"
data-theme="github-dark github-light-default"
>
<Icons.css className="fill-foreground" />
app/globals.css
</figcaption>
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line text-code-foreground">
&nbsp;:root &#123;
</span>
<span data-line className="line text-code-foreground">
&nbsp;&nbsp;&nbsp;--radius: 0.65rem;
</span>
{Object.entries(activeThemeOKLCH?.light).map(([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
<span data-line className="line text-code-foreground">
&nbsp;
</span>
<span data-line className="line text-code-foreground">
&nbsp;.dark &#123;
</span>
{Object.entries(activeThemeOKLCH?.dark).map(([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
<TabsContent value="v3">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
data-rehype-pretty-code-title=""
data-language="css"
data-theme="github-dark github-light-default"
>
<Icons.css className="fill-foreground" />
app/globals.css
</figcaption>
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line">
@layer base &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;:root &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</React.Fragment>
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--radius: 0.5rem;
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</React.Fragment>
)
)}
<span data-line className="line">
&nbsp;&nbsp;&#125;
</span>
<span data-line className="line">
&nbsp;
</span>
<span data-line className="line">
&nbsp;&nbsp;.dark &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</React.Fragment>
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</React.Fragment>
)
)}
<span data-line className="line">
&nbsp;&nbsp;&#125;
</span>
<span data-line className="line">
&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
</Tabs>
</>
)
}
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
if (!theme) {
return ""
}
const rootSection =
":root {\n --radius: " +
radius +
"rem;\n" +
Object.entries(theme.light)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n\n.dark {\n" +
Object.entries(theme.dark)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n"
return rootSection
}
function getThemeCode(theme: BaseColor | undefined, radius: number) {
if (!theme) {
return ""
}
return template(BASE_STYLES_WITH_VARIABLES)({
colors: theme.cssVars,
radius: radius.toString(),
})
}
const BASE_STYLES_WITH_VARIABLES = `
@layer base {
:root {
--background: <%- colors.light["background"] %>;
--foreground: <%- colors.light["foreground"] %>;
--card: <%- colors.light["card"] %>;
--card-foreground: <%- colors.light["card-foreground"] %>;
--popover: <%- colors.light["popover"] %>;
--popover-foreground: <%- colors.light["popover-foreground"] %>;
--primary: <%- colors.light["primary"] %>;
--primary-foreground: <%- colors.light["primary-foreground"] %>;
--secondary: <%- colors.light["secondary"] %>;
--secondary-foreground: <%- colors.light["secondary-foreground"] %>;
--muted: <%- colors.light["muted"] %>;
--muted-foreground: <%- colors.light["muted-foreground"] %>;
--accent: <%- colors.light["accent"] %>;
--accent-foreground: <%- colors.light["accent-foreground"] %>;
--destructive: <%- colors.light["destructive"] %>;
--destructive-foreground: <%- colors.light["destructive-foreground"] %>;
--border: <%- colors.light["border"] %>;
--input: <%- colors.light["input"] %>;
--ring: <%- colors.light["ring"] %>;
--radius: <%- radius %>rem;
--chart-1: <%- colors.light["chart-1"] %>;
--chart-2: <%- colors.light["chart-2"] %>;
--chart-3: <%- colors.light["chart-3"] %>;
--chart-4: <%- colors.light["chart-4"] %>;
--chart-5: <%- colors.light["chart-5"] %>;
}
.dark {
--background: <%- colors.dark["background"] %>;
--foreground: <%- colors.dark["foreground"] %>;
--card: <%- colors.dark["card"] %>;
--card-foreground: <%- colors.dark["card-foreground"] %>;
--popover: <%- colors.dark["popover"] %>;
--popover-foreground: <%- colors.dark["popover-foreground"] %>;
--primary: <%- colors.dark["primary"] %>;
--primary-foreground: <%- colors.dark["primary-foreground"] %>;
--secondary: <%- colors.dark["secondary"] %>;
--secondary-foreground: <%- colors.dark["secondary-foreground"] %>;
--muted: <%- colors.dark["muted"] %>;
--muted-foreground: <%- colors.dark["muted-foreground"] %>;
--accent: <%- colors.dark["accent"] %>;
--accent-foreground: <%- colors.dark["accent-foreground"] %>;
--destructive: <%- colors.dark["destructive"] %>;
--destructive-foreground: <%- colors.dark["destructive-foreground"] %>;
--border: <%- colors.dark["border"] %>;
--input: <%- colors.dark["input"] %>;
--ring: <%- colors.dark["ring"] %>;
--chart-1: <%- colors.dark["chart-1"] %>;
--chart-2: <%- colors.dark["chart-2"] %>;
--chart-3: <%- colors.dark["chart-3"] %>;
--chart-4: <%- colors.dark["chart-4"] %>;
--chart-5: <%- colors.dark["chart-5"] %>;
}
}
`

View File

@@ -25,6 +25,10 @@ export const siteConfig = {
href: "/charts/area",
label: "Charts",
},
{
href: "/themes",
label: "Themes",
},
{
href: "/colors",
label: "Colors",

View File

@@ -63,6 +63,11 @@ const nextConfig = {
destination: "/charts/area",
permanent: true,
},
{
source: "/view/styles/:style/:name",
destination: "/view/:name",
permanent: true,
},
]
},
}

View File

@@ -70,6 +70,7 @@
"fumadocs-ui": "^15.3.1",
"input-otp": "^1.4.2",
"jotai": "^2.1.0",
"lodash": "^4.17.21",
"lucide-react": "0.474.0",
"motion": "^12.12.1",
"next": "15.3.1",
@@ -95,6 +96,7 @@
"@eslint/eslintrc": "^3",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@tailwindcss/postcss": "^4",
"@types/lodash": "^4.17.7",
"@types/mdx": "^2.0.13",
"@types/node": "^20",
"@types/react": "19.1.2",

View File

@@ -1104,6 +1104,144 @@ export const baseColorsV4 = {
} as const
export const baseColorsOKLCH = {
default: {
light: {
background: "oklch(1 0 0)", // --color-neutral-50
foreground: "oklch(0.145 0 0)", // --color-neutral-950
card: "oklch(1 0 0)", // --color-neutral-50
"card-foreground": "oklch(0.145 0 0)", // --color-neutral-950
popover: "oklch(1 0 0)", // --color-neutral-50
"popover-foreground": "oklch(0.145 0 0)", // --color-neutral-950
primary: "oklch(0.205 0 0)", // --color-neutral-900
"primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
secondary: "oklch(0.97 0 0)", // --color-neutral-100
"secondary-foreground": "oklch(0.205 0 0)", // --color-neutral-900
muted: "oklch(0.97 0 0)", // --color-neutral-100
"muted-foreground": "oklch(0.556 0 0)", // --color-neutral-500
accent: "oklch(0.97 0 0)", // --color-neutral-100
"accent-foreground": "oklch(0.205 0 0)", // --color-neutral-900
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600
border: "oklch(0.922 0 0)", // --color-neutral-200
input: "oklch(0.922 0 0)", // --color-neutral-200
ring: "oklch(0.708 0 0)", // --color-neutral-400
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500
radius: "0.625rem",
sidebar: "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-foreground": "oklch(0.145 0 0)", // --color-neutral-950
"sidebar-primary": "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-accent": "oklch(0.97 0 0)", // --color-neutral-100
"sidebar-accent-foreground": "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-border": "oklch(0.922 0 0)", // --color-neutral-200
"sidebar-ring": "oklch(0.708 0 0)", // --color-neutral-400
},
dark: {
background: "oklch(0.145 0 0)", // --color-neutral-950
foreground: "oklch(0.985 0 0)", // --color-neutral-50
card: "oklch(0.205 0 0)", // --color-neutral-900
"card-foreground": "oklch(0.985 0 0)", // --color-neutral-50
popover: "oklch(0.205 0 0)", // --color-neutral-900
"popover-foreground": "oklch(0.985 0 0)", // --color-neutral-50
primary: "oklch(0.922 0 0)", // --color-neutral-200
"primary-foreground": "oklch(0.205 0 0)", // --color-neutral-900
secondary: "oklch(0.269 0 0)", // --color-neutral-800
"secondary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
muted: "oklch(0.269 0 0)", // --color-neutral-800
"muted-foreground": "oklch(0.708 0 0)", // --color-neutral-400
accent: "oklch(0.269 0 0)", // --color-neutral-800
"accent-foreground": "oklch(0.985 0 0)", // --color-neutral-50
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400
border: "oklch(1 0 0 / 10%)", // --color-white
input: "oklch(1 0 0 / 15%)", // --color-white
ring: "oklch(0.556 0 0)", // --color-neutral-500
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500
sidebar: "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-primary": "oklch(0.488 0.243 264.376)", // --color-blue-700
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-accent": "oklch(0.269 0 0)", // --color-neutral-800
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white
"sidebar-ring": "oklch(0.556 0 0)", // --color-neutral-500
},
},
neutral: {
light: {
background: "oklch(1 0 0)", // --color-neutral-50
foreground: "oklch(0.145 0 0)", // --color-neutral-950
card: "oklch(1 0 0)", // --color-neutral-50
"card-foreground": "oklch(0.145 0 0)", // --color-neutral-950
popover: "oklch(1 0 0)", // --color-neutral-50
"popover-foreground": "oklch(0.145 0 0)", // --color-neutral-950
primary: "oklch(0.205 0 0)", // --color-neutral-900
"primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
secondary: "oklch(0.97 0 0)", // --color-neutral-100
"secondary-foreground": "oklch(0.205 0 0)", // --color-neutral-900
muted: "oklch(0.97 0 0)", // --color-neutral-100
"muted-foreground": "oklch(0.556 0 0)", // --color-neutral-500
accent: "oklch(0.97 0 0)", // --color-neutral-100
"accent-foreground": "oklch(0.205 0 0)", // --color-neutral-900
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600
border: "oklch(0.922 0 0)", // --color-neutral-200
input: "oklch(0.922 0 0)", // --color-neutral-200
ring: "oklch(0.708 0 0)", // --color-neutral-400
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500
radius: "0.625rem",
sidebar: "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-foreground": "oklch(0.145 0 0)", // --color-neutral-950
"sidebar-primary": "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-accent": "oklch(0.97 0 0)", // --color-neutral-100
"sidebar-accent-foreground": "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-border": "oklch(0.922 0 0)", // --color-neutral-200
"sidebar-ring": "oklch(0.708 0 0)", // --color-neutral-400
},
dark: {
background: "oklch(0.145 0 0)", // --color-neutral-950
foreground: "oklch(0.985 0 0)", // --color-neutral-50
card: "oklch(0.205 0 0)", // --color-neutral-900
"card-foreground": "oklch(0.985 0 0)", // --color-neutral-50
popover: "oklch(0.205 0 0)", // --color-neutral-900
"popover-foreground": "oklch(0.985 0 0)", // --color-neutral-50
primary: "oklch(0.922 0 0)", // --color-neutral-200
"primary-foreground": "oklch(0.205 0 0)", // --color-neutral-900
secondary: "oklch(0.269 0 0)", // --color-neutral-800
"secondary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
muted: "oklch(0.269 0 0)", // --color-neutral-800
"muted-foreground": "oklch(0.708 0 0)", // --color-neutral-400
accent: "oklch(0.269 0 0)", // --color-neutral-800
"accent-foreground": "oklch(0.985 0 0)", // --color-neutral-50
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400
border: "oklch(1 0 0 / 10%)", // --color-white
input: "oklch(1 0 0 / 15%)", // --color-white
ring: "oklch(0.556 0 0)", // --color-neutral-500
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500
sidebar: "oklch(0.205 0 0)", // --color-neutral-900
"sidebar-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-primary": "oklch(0.488 0.243 264.376)", // --color-blue-700
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-accent": "oklch(0.269 0 0)", // --color-neutral-800
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-neutral-50
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white
"sidebar-ring": "oklch(0.556 0 0)", // --color-neutral-500
},
},
zinc: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50

View File

@@ -263,3 +263,72 @@
padding-bottom: 0;
}
}
.theme-red .theme-container {
--primary: var(--color-red-600);
--primary-foreground: var(--color-red-50);
--ring: var(--color-red-400);
--chart-1: var(--color-red-300);
--chart-2: var(--color-red-500);
--chart-3: var(--color-red-600);
--chart-4: var(--color-red-700);
--chart-5: var(--color-red-800);
--sidebar-primary: var(--color-red-600);
--sidebar-primary-foreground: var(--color-red-50);
--sidebar-ring: var(--color-red-400);
@variant dark {
--primary: var(--color-red-500);
--primary-foreground: var(--color-red-50);
--ring: var(--color-red-900);
--sidebar-primary: var(--color-red-500);
--sidebar-primary-foreground: var(--color-red-50);
--sidebar-ring: var(--color-red-900);
}
}
.theme-yellow .theme-container {
--primary: var(--color-yellow-400);
--primary-foreground: var(--color-yellow-900);
--ring: var(--color-yellow-400);
--chart-1: var(--color-yellow-300);
--chart-2: var(--color-yellow-500);
--chart-3: var(--color-yellow-600);
--chart-4: var(--color-yellow-700);
--chart-5: var(--color-yellow-800);
--sidebar-primary: var(--color-yellow-600);
--sidebar-primary-foreground: var(--color-yellow-50);
--sidebar-ring: var(--color-yellow-400);
@variant dark {
--primary: var(--color-yellow-500);
--primary-foreground: var(--color-yellow-900);
--ring: var(--color-yellow-900);
--sidebar-primary: var(--color-yellow-500);
--sidebar-primary-foreground: var(--color-yellow-50);
--sidebar-ring: var(--color-yellow-900);
}
}
.theme-violet .theme-container {
--primary: var(--color-violet-600);
--primary-foreground: var(--color-violet-50);
--ring: var(--color-violet-400);
--chart-1: var(--color-violet-300);
--chart-2: var(--color-violet-500);
--chart-3: var(--color-violet-600);
--chart-4: var(--color-violet-700);
--chart-5: var(--color-violet-800);
--sidebar-primary: var(--color-violet-600);
--sidebar-primary-foreground: var(--color-violet-50);
--sidebar-ring: var(--color-violet-400);
@variant dark {
--primary: var(--color-violet-500);
--primary-foreground: var(--color-violet-50);
--ring: var(--color-violet-900);
--sidebar-primary: var(--color-violet-500);
--sidebar-primary-foreground: var(--color-violet-50);
--sidebar-ring: var(--color-violet-900);
}
}

6
pnpm-lock.yaml generated
View File

@@ -273,6 +273,9 @@ importers:
jotai:
specifier: ^2.1.0
version: 2.11.3(@types/react@19.1.2)(react@19.1.0)
lodash:
specifier: ^4.17.21
version: 4.17.21
lucide-react:
specifier: 0.474.0
version: 0.474.0(react@19.1.0)
@@ -340,6 +343,9 @@ importers:
'@ianvs/prettier-plugin-sort-imports':
specifier: ^4.4.1
version: 4.4.2(prettier@3.4.2)
'@types/lodash':
specifier: ^4.17.7
version: 4.17.15
'@types/mdx':
specifier: ^2.0.13
version: 2.0.13