mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-29 07:34:11 +00:00
feat: add themes page back (#7503)
* feat: add themes page back * fix: values * fix: redirect
This commit is contained in:
64
apps/v4/app/(app)/themes/layout.tsx
Normal file
64
apps/v4/app/(app)/themes/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
19
apps/v4/app/(app)/themes/page.tsx
Normal file
19
apps/v4/app/(app)/themes/page.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
569
apps/v4/components/theme-customizer.tsx
Normal file
569
apps/v4/components/theme-customizer.tsx
Normal 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">
|
||||
:root {
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
--radius: 0.65rem;
|
||||
</span>
|
||||
{Object.entries(activeThemeOKLCH?.light).map(([key, value]) => (
|
||||
<span
|
||||
data-line
|
||||
className="line text-code-foreground"
|
||||
key={key}
|
||||
>
|
||||
--{key}: {value};
|
||||
</span>
|
||||
))}
|
||||
<span data-line className="line text-code-foreground">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
.dark {
|
||||
</span>
|
||||
{Object.entries(activeThemeOKLCH?.dark).map(([key, value]) => (
|
||||
<span
|
||||
data-line
|
||||
className="line text-code-foreground"
|
||||
key={key}
|
||||
>
|
||||
--{key}: {value};
|
||||
</span>
|
||||
))}
|
||||
<span data-line className="line text-code-foreground">
|
||||
}
|
||||
</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 {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
:root {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--background:{" "}
|
||||
{activeTheme?.cssVars.light["background"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--foreground:{" "}
|
||||
{activeTheme?.cssVars.light["foreground"]};
|
||||
</span>
|
||||
{[
|
||||
"card",
|
||||
"popover",
|
||||
"primary",
|
||||
"secondary",
|
||||
"muted",
|
||||
"accent",
|
||||
"destructive",
|
||||
].map((prefix) => (
|
||||
<React.Fragment key={prefix}>
|
||||
<span data-line className="line">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
prefix as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--{prefix}-foreground:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span data-line className="line">
|
||||
--border:{" "}
|
||||
{activeTheme?.cssVars.light["border"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--input:{" "}
|
||||
{activeTheme?.cssVars.light["input"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--ring:{" "}
|
||||
{activeTheme?.cssVars.light["ring"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--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">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
prefix as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
<span data-line className="line">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
.dark {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--background:{" "}
|
||||
{activeTheme?.cssVars.dark["background"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--foreground:{" "}
|
||||
{activeTheme?.cssVars.dark["foreground"]};
|
||||
</span>
|
||||
{[
|
||||
"card",
|
||||
"popover",
|
||||
"primary",
|
||||
"secondary",
|
||||
"muted",
|
||||
"accent",
|
||||
"destructive",
|
||||
].map((prefix) => (
|
||||
<React.Fragment key={prefix}>
|
||||
<span data-line className="line">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
prefix as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--{prefix}-foreground:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span data-line className="line">
|
||||
--border:{" "}
|
||||
{activeTheme?.cssVars.dark["border"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--input:{" "}
|
||||
{activeTheme?.cssVars.dark["input"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--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">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
prefix as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
<span data-line className="line">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
}
|
||||
</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"] %>;
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -25,6 +25,10 @@ export const siteConfig = {
|
||||
href: "/charts/area",
|
||||
label: "Charts",
|
||||
},
|
||||
{
|
||||
href: "/themes",
|
||||
label: "Themes",
|
||||
},
|
||||
{
|
||||
href: "/colors",
|
||||
label: "Colors",
|
||||
|
||||
@@ -63,6 +63,11 @@ const nextConfig = {
|
||||
destination: "/charts/area",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/view/styles/:style/:name",
|
||||
destination: "/view/:name",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
6
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user