mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-31 02:19:56 +08:00
fix: build errors
This commit is contained in:
@@ -1,42 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { getEditorConfig } from "@/config/editors";
|
||||
import Link from "next/link";
|
||||
import { Moon, Sun, Heart } from "lucide-react";
|
||||
import GitHubIcon from "@/assets/github.svg";
|
||||
import TwitterIcon from "@/assets/twitter.svg";
|
||||
import DiscordIcon from "@/assets/discord.svg";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import Logo from "@/assets/logo.svg";
|
||||
import { useGithubStars } from "@/hooks/use-github-stars";
|
||||
import { SocialLink } from "@/components/social-link";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
import { Suspense } from "react";
|
||||
import { Loading } from "@/components/loading";
|
||||
import Editor from "@/components/editor/editor";
|
||||
import { Metadata } from "next";
|
||||
import { Header } from "../../../components/editor/header";
|
||||
|
||||
export function meta() {
|
||||
return [
|
||||
{ title: "tweakcn — Theme Generator for shadcn/ui" },
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Easily customize and preview your shadcn/ui theme with tweakcn. Modify colors, fonts, and styles in real-time.",
|
||||
},
|
||||
];
|
||||
}
|
||||
export const metadata: Metadata = {
|
||||
title: "tweakcn — Theme Generator for shadcn/ui",
|
||||
description:
|
||||
"Easily customize and preview your shadcn/ui theme with tweakcn. Modify colors, fonts, and styles in real-time.",
|
||||
};
|
||||
|
||||
export default function Component() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { stargazersCount } = useGithubStars("jnsahaj", "tweakcn");
|
||||
|
||||
const handleThemeToggle = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const { clientX: x, clientY: y } = event;
|
||||
toggleTheme({ x, y });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -44,60 +18,9 @@ export default function Component() {
|
||||
"h-screen flex flex-col text-foreground bg-background transition-colors"
|
||||
)}
|
||||
>
|
||||
<header className="border-b">
|
||||
<div className="px-2 md:px-4 py-4 flex items-center gap-2 justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<Logo className="size-6" title="tweakcn" />
|
||||
<span className="font-bold hidden md:block">tweakcn</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-3.5">
|
||||
<SocialLink
|
||||
href="https://github.com/jnsahaj/tweakcn"
|
||||
className="flex items-center gap-2 text-sm font-bold"
|
||||
>
|
||||
<GitHubIcon className="size-4" />
|
||||
{stargazersCount > 0 && stargazersCount.toLocaleString()}
|
||||
</SocialLink>
|
||||
<Separator orientation="vertical" className="h-5" />
|
||||
<div className="hidden md:flex items-center gap-3.5">
|
||||
<SocialLink
|
||||
href="https://github.com/sponsors/jnsahaj"
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md border hover:border-pink-500 hover:text-pink-500 transition-colors"
|
||||
>
|
||||
<Heart className="size-4" strokeWidth={2.5} />
|
||||
<span className="text-sm font-medium">Support</span>
|
||||
</SocialLink>
|
||||
<SocialLink href="https://discord.gg/Phs4u2NM3n">
|
||||
<DiscordIcon className="size-5" />
|
||||
</SocialLink>
|
||||
</div>
|
||||
<SocialLink href="https://x.com/iamsahaj_xyz">
|
||||
<TwitterIcon className="size-4" />
|
||||
</SocialLink>
|
||||
<Separator orientation="vertical" className="h-5" />
|
||||
<SwitchPrimitives.Root
|
||||
checked={theme === "dark"}
|
||||
onClick={handleThemeToggle}
|
||||
className="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-accent data-[state=unchecked]:bg-input"
|
||||
>
|
||||
<SwitchPrimitives.Thumb className="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 flex items-center justify-center">
|
||||
{theme === "dark" ? (
|
||||
<Moon className="size-3" />
|
||||
) : (
|
||||
<Sun className="size-3" />
|
||||
)}
|
||||
</SwitchPrimitives.Thumb>
|
||||
</SwitchPrimitives.Root>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Header />
|
||||
<main className="flex-1 overflow-hidden">
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Editor config={getEditorConfig("theme")} />
|
||||
</Suspense>
|
||||
<Editor config={getEditorConfig("theme")} />
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+30
-11
@@ -6,6 +6,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { ThemeScript } from "@/components/theme-script";
|
||||
import "./globals.css";
|
||||
import { PostHogInit } from "@/components/posthog-init";
|
||||
import { Suspense } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
|
||||
@@ -15,7 +16,8 @@ export const metadata: Metadata = {
|
||||
"theme editor, theme generator, shadcn, ui, components, react, tailwind, button, editor, visual editor, component editor, web development, frontend, design system, UI components, React components, Tailwind CSS, shadcn/ui themes",
|
||||
authors: [{ name: "Sahaj Jain" }],
|
||||
openGraph: {
|
||||
title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
|
||||
title:
|
||||
"Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
|
||||
description:
|
||||
"Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.",
|
||||
url: "https://tweakcn.com/",
|
||||
@@ -32,7 +34,8 @@ export const metadata: Metadata = {
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
|
||||
title:
|
||||
"Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
|
||||
description:
|
||||
"Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.",
|
||||
images: ["https://tweakcn.com/og-image.png"],
|
||||
@@ -41,15 +44,29 @@ export const metadata: Metadata = {
|
||||
viewport: "width=device-width, initial-scale=1.0",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<ThemeScript />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/apple-touch-icon.png"
|
||||
@@ -70,12 +87,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</head>
|
||||
<body>
|
||||
<NuqsAdapter>
|
||||
<ThemeProvider defaultTheme="light">
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
<Suspense>
|
||||
<ThemeProvider defaultTheme="light">
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</Suspense>
|
||||
</NuqsAdapter>
|
||||
<PostHogInit />
|
||||
</body>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Copy, Check, PanelRight } from "lucide-react";
|
||||
import { EditorConfig, ThemeEditorState } from "@/types/editor";
|
||||
import { ThemeEditorState } from "@/types/editor";
|
||||
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
|
||||
import { ColorFormat } from "../../types";
|
||||
import {
|
||||
@@ -14,32 +14,38 @@ import {
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import { useEditorStore } from "@/store/editor-store";
|
||||
import { usePreferencesStore } from "@/store/preferences-store";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { generateThemeCode } from "@/utils/theme-style-generator";
|
||||
interface CodePanelProps {
|
||||
config: EditorConfig;
|
||||
themeEditorState: ThemeEditorState;
|
||||
onCodePanelToggle: () => void;
|
||||
}
|
||||
|
||||
const CodePanel: React.FC<CodePanelProps> = ({
|
||||
config,
|
||||
themeEditorState,
|
||||
onCodePanelToggle,
|
||||
}) => {
|
||||
const [registryCopied, setRegistryCopied] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const posthog = usePostHog();
|
||||
|
||||
|
||||
const preset = useEditorStore((state) => state.themeState.preset);
|
||||
const colorFormat = usePreferencesStore((state) => state.colorFormat);
|
||||
const tailwindVersion = usePreferencesStore((state) => state.tailwindVersion);
|
||||
const packageManager = usePreferencesStore((state) => state.packageManager);
|
||||
const setColorFormat = usePreferencesStore((state) => state.setColorFormat);
|
||||
const setTailwindVersion = usePreferencesStore((state) => state.setTailwindVersion);
|
||||
const setPackageManager = usePreferencesStore((state) => state.setPackageManager);
|
||||
const setTailwindVersion = usePreferencesStore(
|
||||
(state) => state.setTailwindVersion
|
||||
);
|
||||
const setPackageManager = usePreferencesStore(
|
||||
(state) => state.setPackageManager
|
||||
);
|
||||
|
||||
const code = config.codeGenerator.generateComponentCode(
|
||||
const code = generateThemeCode(
|
||||
themeEditorState,
|
||||
colorFormat,
|
||||
tailwindVersion
|
||||
@@ -61,7 +67,9 @@ const CodePanel: React.FC<CodePanelProps> = ({
|
||||
|
||||
const copyRegistryCommand = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(getRegistryCommand(preset));
|
||||
await navigator.clipboard.writeText(
|
||||
getRegistryCommand(preset ?? "default")
|
||||
);
|
||||
setRegistryCopied(true);
|
||||
setTimeout(() => setRegistryCopied(false), 2000);
|
||||
captureCopyEvent("COPY_REGISTRY_COMMAND");
|
||||
|
||||
@@ -14,9 +14,11 @@ const ColorPicker = ({ color, onChange, label }: ColorPickerProps) => {
|
||||
|
||||
// Create debounced onChange handler with useCallback to maintain reference
|
||||
const debouncedOnChange = useCallback(
|
||||
debounce((value) => {
|
||||
onChange(value);
|
||||
}, 10),
|
||||
(value: string) => {
|
||||
debounce(() => {
|
||||
onChange(value);
|
||||
}, 10)();
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const CssImportDialog: React.FC<CssImportDialogProps> = ({
|
||||
setCssText("");
|
||||
setError(null);
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError("Failed to parse CSS. Please check your syntax.");
|
||||
}
|
||||
};
|
||||
@@ -62,7 +62,9 @@ const CssImportDialog: React.FC<CssImportDialogProps> = ({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-foreground">Import Custom CSS</DialogTitle>
|
||||
<DialogTitle className="text-foreground">
|
||||
Import Custom CSS
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Paste your CSS file below to customize the theme colors. Make sure
|
||||
to include variables like --primary, --background, etc.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
ResizablePanelGroup,
|
||||
@@ -5,7 +7,11 @@ import {
|
||||
ResizableHandle,
|
||||
} from "@/components/ui/resizable";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { EditorConfig, BaseEditorState, ThemeEditorState } from "@/types/editor";
|
||||
import {
|
||||
EditorConfig,
|
||||
BaseEditorState,
|
||||
ThemeEditorState,
|
||||
} from "@/types/editor";
|
||||
import { ThemeStyles } from "@/types/theme";
|
||||
import CodePanel from "./code-panel";
|
||||
import { Sliders } from "lucide-react";
|
||||
@@ -16,8 +22,14 @@ interface EditorProps {
|
||||
initialState?: BaseEditorState;
|
||||
}
|
||||
|
||||
const isThemeStyles = (styles: any): styles is ThemeStyles => {
|
||||
return !!styles && "light" in styles && "dark" in styles;
|
||||
const isThemeStyles = (styles: unknown): styles is ThemeStyles => {
|
||||
return (
|
||||
!!styles &&
|
||||
typeof styles === "object" &&
|
||||
styles !== null &&
|
||||
"light" in styles &&
|
||||
"dark" in styles
|
||||
);
|
||||
};
|
||||
|
||||
const Editor: React.FC<EditorProps> = ({ config }) => {
|
||||
@@ -67,7 +79,6 @@ const Editor: React.FC<EditorProps> = ({ config }) => {
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={25} minSize={10}>
|
||||
<CodePanel
|
||||
config={config}
|
||||
themeEditorState={themeState}
|
||||
onCodePanelToggle={() => setIsCodePanelOpen(!isCodePanelOpen)}
|
||||
/>
|
||||
@@ -113,7 +124,6 @@ const Editor: React.FC<EditorProps> = ({ config }) => {
|
||||
</TabsContent>
|
||||
<TabsContent value="code" className="h-[calc(100%-2.5rem)]">
|
||||
<CodePanel
|
||||
config={config}
|
||||
themeEditorState={themeState}
|
||||
onCodePanelToggle={() => setIsCodePanelOpen(!isCodePanelOpen)}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Moon, Sun, Heart } from "lucide-react";
|
||||
import GitHubIcon from "@/assets/github.svg";
|
||||
import TwitterIcon from "@/assets/twitter.svg";
|
||||
import DiscordIcon from "@/assets/discord.svg";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import Logo from "@/assets/logo.svg";
|
||||
import { useGithubStars } from "@/hooks/use-github-stars";
|
||||
import { SocialLink } from "@/components/social-link";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
|
||||
export function Header() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { stargazersCount } = useGithubStars("jnsahaj", "tweakcn");
|
||||
|
||||
const handleThemeToggle = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const { clientX: x, clientY: y } = event;
|
||||
toggleTheme({ x, y });
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b">
|
||||
<div className="px-2 md:px-4 py-4 flex items-center gap-2 justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<Logo className="size-6" title="tweakcn" />
|
||||
<span className="font-bold hidden md:block">tweakcn</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-3.5">
|
||||
<SocialLink
|
||||
href="https://github.com/jnsahaj/tweakcn"
|
||||
className="flex items-center gap-2 text-sm font-bold"
|
||||
>
|
||||
<GitHubIcon className="size-4" />
|
||||
{stargazersCount > 0 && stargazersCount.toLocaleString()}
|
||||
</SocialLink>
|
||||
<Separator orientation="vertical" className="h-5" />
|
||||
<div className="hidden md:flex items-center gap-3.5">
|
||||
<SocialLink
|
||||
href="https://github.com/sponsors/jnsahaj"
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md border hover:border-pink-500 hover:text-pink-500 transition-colors"
|
||||
>
|
||||
<Heart className="size-4" strokeWidth={2.5} />
|
||||
<span className="text-sm font-medium">Support</span>
|
||||
</SocialLink>
|
||||
<SocialLink href="https://discord.gg/Phs4u2NM3n">
|
||||
<DiscordIcon className="size-5" />
|
||||
</SocialLink>
|
||||
</div>
|
||||
<SocialLink href="https://x.com/iamsahaj_xyz">
|
||||
<TwitterIcon className="size-4" />
|
||||
</SocialLink>
|
||||
<Separator orientation="vertical" className="h-5" />
|
||||
<SwitchPrimitives.Root
|
||||
checked={theme === "dark"}
|
||||
onClick={handleThemeToggle}
|
||||
className="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-accent data-[state=unchecked]:bg-input"
|
||||
>
|
||||
<SwitchPrimitives.Thumb className="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 flex items-center justify-center">
|
||||
{theme === "dark" ? (
|
||||
<Moon className="size-3" />
|
||||
) : (
|
||||
<Sun className="size-3" />
|
||||
)}
|
||||
</SwitchPrimitives.Thumb>
|
||||
</SwitchPrimitives.Root>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import { Label } from "../ui/label";
|
||||
import { SliderWithInput } from "./slider-with-input";
|
||||
import ColorPicker from "./color-picker";
|
||||
import ControlSection from "./control-section";
|
||||
@@ -11,7 +10,7 @@ interface ShadowControlProps {
|
||||
shadowSpread: number;
|
||||
shadowOffsetX: number;
|
||||
shadowOffsetY: number;
|
||||
onChange: (key: string, value: any) => void;
|
||||
onChange: (key: string, value: string | number) => void;
|
||||
}
|
||||
|
||||
const ShadowControl: React.FC<ShadowControlProps> = ({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { ThemeEditorControlsProps, ThemeStyleProps } from "@/types/theme";
|
||||
import ControlSection from "./control-section";
|
||||
@@ -46,11 +48,13 @@ const ThemeControlPanel = ({
|
||||
} = useEditorStore();
|
||||
const [cssImportOpen, setCssImportOpen] = useState(false);
|
||||
|
||||
const currentStyles = {
|
||||
...defaultThemeState.styles.light,
|
||||
...defaultThemeState.styles[currentMode],
|
||||
...styles?.[currentMode],
|
||||
};
|
||||
const currentStyles = React.useMemo(
|
||||
() => ({
|
||||
...defaultThemeState.styles[currentMode],
|
||||
...styles?.[currentMode],
|
||||
}),
|
||||
[currentMode, styles]
|
||||
);
|
||||
|
||||
const updateStyle = React.useCallback(
|
||||
<K extends keyof typeof currentStyles>(
|
||||
@@ -119,7 +123,7 @@ const ThemeControlPanel = ({
|
||||
<div className="mb-6 ml-1">
|
||||
<ThemePresetSelect
|
||||
presets={presets}
|
||||
currentPreset={themeState.preset}
|
||||
currentPreset={themeState.preset || null}
|
||||
onPresetChange={applyThemePreset}
|
||||
/>
|
||||
</div>
|
||||
@@ -232,7 +236,9 @@ const ThemeControlPanel = ({
|
||||
/>
|
||||
<ColorPicker
|
||||
color={currentStyles["destructive-foreground"]}
|
||||
onChange={(color) => updateStyle("destructive-foreground", color)}
|
||||
onChange={(color) =>
|
||||
updateStyle("destructive-foreground", color)
|
||||
}
|
||||
label="Destructive Foreground"
|
||||
/>
|
||||
</ControlSection>
|
||||
@@ -313,7 +319,9 @@ const ThemeControlPanel = ({
|
||||
/>
|
||||
<ColorPicker
|
||||
color={currentStyles["sidebar-accent-foreground"]}
|
||||
onChange={(color) => updateStyle("sidebar-accent-foreground", color)}
|
||||
onChange={(color) =>
|
||||
updateStyle("sidebar-accent-foreground", color)
|
||||
}
|
||||
label="Sidebar Accent Foreground"
|
||||
/>
|
||||
<ColorPicker
|
||||
@@ -394,7 +402,9 @@ const ThemeControlPanel = ({
|
||||
value={parseFloat(
|
||||
currentStyles["letter-spacing"]?.replace("em", "")
|
||||
)}
|
||||
onChange={(value) => updateStyle("letter-spacing", `${value}em`)}
|
||||
onChange={(value) =>
|
||||
updateStyle("letter-spacing", `${value}em`)
|
||||
}
|
||||
min={-0.5}
|
||||
max={0.5}
|
||||
step={0.025}
|
||||
@@ -446,7 +456,7 @@ const ThemeControlPanel = ({
|
||||
)}
|
||||
onChange={(key, value) => {
|
||||
if (key === "shadow-color") {
|
||||
updateStyle(key, value);
|
||||
updateStyle(key, value as string);
|
||||
} else if (key === "shadow-opacity") {
|
||||
updateStyle(key, value.toString());
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ const ThemeFontSelect: React.FC<ThemeFontSelectProps> = ({
|
||||
onFontChange,
|
||||
}) => {
|
||||
const fontNames = useMemo(() => ["System", ...Object.keys(fonts)], [fonts]);
|
||||
const value = fonts[currentFont] ?? defaultValue;
|
||||
const value = currentFont ? fonts[currentFont] ?? defaultValue : defaultValue;
|
||||
|
||||
return (
|
||||
<Select value={value || ""} onValueChange={onFontChange}>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeEditorPreviewProps } from "@/types/theme";
|
||||
import { Tabs, TabsContent, TabsList } from "@/components/ui/tabs";
|
||||
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
|
||||
@@ -10,7 +12,11 @@ import { Maximize, Minimize, PanelRight, Moon, Sun } from "lucide-react";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
const DemoCards = lazy(() => import("@/components/examples/demo-cards"));
|
||||
const DemoMail = lazy(() => import("@/components/examples/mail"));
|
||||
@@ -118,13 +124,19 @@ const ThemePreviewPanel = ({
|
||||
|
||||
<ScrollArea className="rounded-lg border mt-2 flex flex-col flex-1">
|
||||
<div className="flex flex-col flex-1">
|
||||
<TabsContent value="cards" className="space-y-6 mt-0 py-4 px-4 h-full">
|
||||
<TabsContent
|
||||
value="cards"
|
||||
className="space-y-6 mt-0 py-4 px-4 h-full"
|
||||
>
|
||||
<ExamplesPreviewContainer>
|
||||
<DemoCards />
|
||||
</ExamplesPreviewContainer>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="mail" className="space-y-6 mt-0 h-full @container">
|
||||
<TabsContent
|
||||
value="mail"
|
||||
className="space-y-6 mt-0 h-full @container"
|
||||
>
|
||||
<ExamplesPreviewContainer className="min-w-[1300px]">
|
||||
<DemoMail />
|
||||
</ExamplesPreviewContainer>
|
||||
|
||||
@@ -34,8 +34,6 @@ import {
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
CheckCircle2Icon,
|
||||
CheckCircleIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
@@ -43,7 +41,6 @@ import {
|
||||
ChevronsRightIcon,
|
||||
ColumnsIcon,
|
||||
GripVerticalIcon,
|
||||
LoaderIcon,
|
||||
MoreVerticalIcon,
|
||||
PlusIcon,
|
||||
TrendingUpIcon,
|
||||
@@ -246,12 +243,17 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
Reviewer
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger className="h-8 w-40" id={`${row.original.id}-reviewer`}>
|
||||
<SelectTrigger
|
||||
className="h-8 w-40"
|
||||
id={`${row.original.id}-reviewer`}
|
||||
>
|
||||
<SelectValue placeholder="Assign reviewer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">Jamik Tashpulatov</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">
|
||||
Jamik Tashpulatov
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
@@ -316,10 +318,11 @@ export function DataTable({
|
||||
}) {
|
||||
const [data, setData] = React.useState(() => initialData);
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(
|
||||
{}
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [pagination, setPagination] = React.useState({
|
||||
pageIndex: 0,
|
||||
@@ -383,7 +386,10 @@ export function DataTable({
|
||||
View
|
||||
</Label>
|
||||
<Select defaultValue="outline">
|
||||
<SelectTrigger className="@4xl/main:hidden flex w-fit" id="view-selector">
|
||||
<SelectTrigger
|
||||
className="@4xl/main:hidden flex w-fit"
|
||||
id="view-selector"
|
||||
>
|
||||
<SelectValue placeholder="Select a view" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -430,7 +436,8 @@ export function DataTable({
|
||||
.getAllColumns()
|
||||
.filter(
|
||||
(column) =>
|
||||
typeof column.accessorFn !== "undefined" && column.getCanHide()
|
||||
typeof column.accessorFn !== "undefined" &&
|
||||
column.getCanHide()
|
||||
)
|
||||
.map((column) => {
|
||||
return (
|
||||
@@ -438,7 +445,9 @@ export function DataTable({
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
@@ -495,7 +504,10 @@ export function DataTable({
|
||||
</SortableContext>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -521,7 +533,9 @@ export function DataTable({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-20" id="rows-per-page">
|
||||
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
||||
<SelectValue
|
||||
placeholder={table.getState().pagination.pageSize}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||
@@ -580,13 +594,19 @@ export function DataTable({
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="past-performance" className="flex flex-col px-4 lg:px-6">
|
||||
<TabsContent
|
||||
value="past-performance"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent value="focus-documents" className="flex flex-col px-4 lg:px-6">
|
||||
<TabsContent
|
||||
value="focus-documents"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
@@ -680,9 +700,9 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
<TrendingUpIcon className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Showing total visitors for the last 6 months. This is just some
|
||||
random text to test the layout. It spans multiple lines and should
|
||||
wrap around.
|
||||
Showing total visitors for the last 6 months. This is just
|
||||
some random text to test the layout. It spans multiple lines
|
||||
and should wrap around.
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
@@ -712,7 +732,9 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
</SelectItem>
|
||||
<SelectItem value="Design">Design</SelectItem>
|
||||
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||
<SelectItem value="Focus Documents">Focus Documents</SelectItem>
|
||||
<SelectItem value="Focus Documents">
|
||||
Focus Documents
|
||||
</SelectItem>
|
||||
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
@@ -70,11 +70,11 @@ export function Roadmap() {
|
||||
<span className="mr-1 text-primary">✦</span> Roadmap
|
||||
</Badge>
|
||||
<h2 className="text-3xl md:text-4xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/80">
|
||||
What's Coming Next
|
||||
What's Coming Next
|
||||
</h2>
|
||||
<p className="max-w-[800px] text-muted-foreground md:text-lg">
|
||||
We're constantly working to improve tweakcn and add new features. Here's
|
||||
what's on our roadmap.
|
||||
We're constantly working to improve tweakcn and add new
|
||||
features. Here's what's on our roadmap.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
|
||||
@@ -25,18 +25,19 @@ function Calendar({
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100",
|
||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
@@ -51,6 +52,7 @@ function Calendar({
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
// @ts-expect-error: owned by shadcn
|
||||
IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
|
||||
}}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ButtonStyles } from "@/types/button";
|
||||
|
||||
const defaultButtonStyles: ButtonStyles = {
|
||||
borderColor: "#000000",
|
||||
borderWidth: 0,
|
||||
paddingX: 16,
|
||||
paddingY: 8,
|
||||
fontSize: 14,
|
||||
fontWeight: "500",
|
||||
textTransform: "none",
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1.5,
|
||||
shadowOpacity: 0,
|
||||
shadowColor: "#000000",
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0,
|
||||
shadowBlur: 0,
|
||||
shadowSpread: 0,
|
||||
hoverBackgroundColor: "#2a2a2a",
|
||||
hoverTextColor: "#ffffff",
|
||||
hoverBorderColor: "#000000",
|
||||
hoverBackgroundOpacity: 90,
|
||||
transitionDuration: 200,
|
||||
transitionEasing: "ease",
|
||||
focusBorderColor: "#000000",
|
||||
focusRingColor: "#000000",
|
||||
focusRingWidth: 2,
|
||||
activeBackgroundColor: "#1a1a1a",
|
||||
activeTextColor: "#ffffff",
|
||||
activeBorderColor: "#000000",
|
||||
};
|
||||
|
||||
export default defaultButtonStyles;
|
||||
@@ -11,7 +11,4 @@ export const themeEditorConfig: EditorConfig = {
|
||||
defaultState: defaultThemeState,
|
||||
controls: ThemeControlPanel,
|
||||
preview: ThemePreviewPanel,
|
||||
codeGenerator: {
|
||||
generateComponentCode: generateThemeCode,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -77,6 +77,7 @@ export const defaultLightThemeStyles = {
|
||||
|
||||
// Default dark theme styles
|
||||
export const defaultDarkThemeStyles = {
|
||||
...defaultLightThemeStyles,
|
||||
background: "hsl(240 10% 3.9%)",
|
||||
foreground: "hsl(0 0% 98%)",
|
||||
card: "hsl(240 10% 3.9%)",
|
||||
|
||||
@@ -11,6 +11,25 @@ const compat = new FlatCompat({
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
{
|
||||
rules: {
|
||||
"@next/next/no-page-custom-font": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
args: "all",
|
||||
argsIgnorePattern: "^_",
|
||||
caughtErrors: "all",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
|
||||
+2
-1
@@ -15,7 +15,6 @@
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@ngard/tiny-isequal": "^1.1.0",
|
||||
"@radix-ui/react-accordion": "^1.2.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.7",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.3",
|
||||
@@ -75,8 +74,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@ngard/tiny-isequal": "^1.1.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/culori": "^2.1.1",
|
||||
"@types/next": "^9.0.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
|
||||
Generated
+11
-3
@@ -23,9 +23,6 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1(react-hook-form@7.55.0(react@19.1.0))
|
||||
'@ngard/tiny-isequal':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -198,12 +195,18 @@ importers:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3
|
||||
version: 3.3.1
|
||||
'@ngard/tiny-isequal':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
'@svgr/webpack':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(typescript@5.8.3)
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4
|
||||
version: 4.1.4
|
||||
'@types/culori':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
'@types/next':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0(@babel/core@7.26.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -2075,6 +2078,9 @@ packages:
|
||||
'@tybys/wasm-util@0.9.0':
|
||||
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
||||
|
||||
'@types/culori@2.1.1':
|
||||
resolution: {integrity: sha512-NzLYD0vNHLxTdPp8+RlvGbR2NfOZkwxcYGFwxNtm+WH2NuUNV8785zv1h0sulFQ5aFQ9n/jNDUuJeo3Bh7+oFA==}
|
||||
|
||||
'@types/d3-array@3.2.1':
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
|
||||
@@ -6160,6 +6166,8 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/culori@2.1.1': {}
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
'@types/d3-color@3.1.3': {}
|
||||
|
||||
@@ -34,13 +34,8 @@ const getThemeValue = (
|
||||
const convertThemeStyles = (styles: ThemeStyles) => {
|
||||
const { light, dark } = styles;
|
||||
|
||||
const convertTheme = (
|
||||
theme: ThemeStyleProps | Partial<ThemeStyleProps>
|
||||
): ThemeStyleProps => {
|
||||
const result: ThemeStyleProps = {
|
||||
...defaultLightThemeStyles,
|
||||
...theme,
|
||||
};
|
||||
const convertTheme = (theme: ThemeStyleProps): ThemeStyleProps => {
|
||||
const result: ThemeStyleProps = theme;
|
||||
const convertColor = (color?: string) =>
|
||||
convertToRegistryColor(color || "");
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { ThemeEditorState, EditorType } from "@/types/editor";
|
||||
import { ThemeEditorState } from "@/types/editor";
|
||||
// @ts-expect-error: owned by ngard
|
||||
import { isEqual } from "@ngard/tiny-isequal";
|
||||
import { defaultThemeState } from "@/config/theme";
|
||||
import { getPresetThemeStyles } from "@/utils/theme-presets";
|
||||
@@ -47,7 +48,7 @@ export const useEditorStore = create<EditorStore>()(
|
||||
set({
|
||||
themeState: {
|
||||
...themeState,
|
||||
styles: getPresetThemeStyles(themeState.preset),
|
||||
styles: getPresetThemeStyles(themeState.preset || "default"),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -57,7 +58,9 @@ export const useEditorStore = create<EditorStore>()(
|
||||
},
|
||||
hasCurrentPresetChanged: () => {
|
||||
const state = get();
|
||||
const presetStyles = getPresetThemeStyles(state.themeState.preset);
|
||||
const presetStyles = getPresetThemeStyles(
|
||||
state.themeState.preset || "default"
|
||||
);
|
||||
return !isEqual(state.themeState.styles, presetStyles);
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ColorFormat } from ".";
|
||||
import { ThemeStyles } from "./theme";
|
||||
|
||||
// Base interface for any editor's state
|
||||
@@ -16,15 +15,6 @@ export interface EditorPreviewProps {
|
||||
styles: ThemeStyles;
|
||||
}
|
||||
|
||||
export interface EditorCodeGenerator {
|
||||
generateComponentCode: (
|
||||
themeEditorState: ThemeEditorState,
|
||||
colorFormat?: ColorFormat,
|
||||
tailwindVersion?: "3" | "4"
|
||||
) => string;
|
||||
}
|
||||
|
||||
// Theme-specific editor state
|
||||
export interface ThemeEditorState extends BaseEditorState {
|
||||
preset?: string;
|
||||
styles: ThemeStyles;
|
||||
@@ -42,5 +32,4 @@ export interface EditorConfig {
|
||||
defaultState: BaseEditorState;
|
||||
controls: React.ComponentType<any>;
|
||||
preview: React.ComponentType<any>;
|
||||
codeGenerator: EditorCodeGenerator;
|
||||
}
|
||||
|
||||
+13
-13
@@ -31,23 +31,23 @@ export interface ThemeStyleProps {
|
||||
"sidebar-accent-foreground": string;
|
||||
"sidebar-border": string;
|
||||
"sidebar-ring": string;
|
||||
"font-sans"?: string;
|
||||
"font-serif"?: string;
|
||||
"font-mono"?: string;
|
||||
radius?: string;
|
||||
"shadow-color"?: string;
|
||||
"shadow-opacity"?: string;
|
||||
"shadow-blur"?: string;
|
||||
"shadow-spread"?: string;
|
||||
"shadow-offset-x"?: string;
|
||||
"shadow-offset-y"?: string;
|
||||
"letter-spacing"?: string;
|
||||
spacing?: string;
|
||||
"font-sans": string;
|
||||
"font-serif": string;
|
||||
"font-mono": string;
|
||||
radius: string;
|
||||
"shadow-color": string;
|
||||
"shadow-opacity": string;
|
||||
"shadow-blur": string;
|
||||
"shadow-spread": string;
|
||||
"shadow-offset-x": string;
|
||||
"shadow-offset-y": string;
|
||||
"letter-spacing": string;
|
||||
spacing: string;
|
||||
}
|
||||
|
||||
export interface ThemeStyles {
|
||||
light: ThemeStyleProps;
|
||||
dark: Partial<ThemeStyleProps>;
|
||||
dark: ThemeStyleProps;
|
||||
}
|
||||
|
||||
export interface ThemeEditorState {
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
export function debounce(fn: (...args: any[]) => void, delay: number) {
|
||||
let timeoutId;
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
return function (...args: any[]) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
// @ts-expect-error: it works
|
||||
fn.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
@@ -71,7 +71,10 @@ export const parseCssInput = (input: string) => {
|
||||
return { lightColors, darkColors };
|
||||
};
|
||||
|
||||
const extractCssBlockContent = (input: string, selector: string): string | null => {
|
||||
const extractCssBlockContent = (
|
||||
input: string,
|
||||
selector: string
|
||||
): string | null => {
|
||||
const regex = new RegExp(`${escapeRegExp(selector)}\\s*{([^}]+)}`);
|
||||
return input.match(regex)?.[1]?.trim() || null;
|
||||
};
|
||||
@@ -89,13 +92,13 @@ const parseColorVariables = (
|
||||
|
||||
if (validNames.includes(cleanName)) {
|
||||
if (nonColorVariables.includes(cleanName)) {
|
||||
target[cleanName] = value;
|
||||
target[cleanName as keyof ThemeStyleProps] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const colorValue = processColorValue(value);
|
||||
const formattedValue = colorFormatter(colorValue, "hex");
|
||||
target[cleanName] = formattedValue;
|
||||
target[cleanName as keyof ThemeStyleProps] = formattedValue;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+3
-5
@@ -6,7 +6,6 @@ import { defaultThemeState } from "../config/theme";
|
||||
export const getShadowMap = (themeEditorState: ThemeEditorState) => {
|
||||
const mode = themeEditorState.currentMode;
|
||||
const styles = {
|
||||
...defaultThemeState.styles.light,
|
||||
...defaultThemeState.styles[mode],
|
||||
...themeEditorState.styles[mode],
|
||||
};
|
||||
@@ -48,10 +47,9 @@ export const getShadowMap = (themeEditorState: ThemeEditorState) => {
|
||||
"shadow-sm": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("1px", "2px")}`,
|
||||
shadow: `${offsetX} ${offsetY} ${blur} ${spread} ${color(1.0)}, ${secondLayer(
|
||||
"1px",
|
||||
"2px"
|
||||
)}`, // Alias for the 'shadow:' example line
|
||||
shadow: `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("1px", "2px")}`, // Alias for the 'shadow:' example line
|
||||
|
||||
"shadow-md": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
|
||||
@@ -32,7 +32,7 @@ const monoFontNames = [
|
||||
"Geist Mono",
|
||||
];
|
||||
|
||||
export const fonts = {
|
||||
export const fonts: Record<string, string> = {
|
||||
// Sans-serif fonts
|
||||
Inter: "Inter, sans-serif",
|
||||
Roboto: "Roboto, sans-serif",
|
||||
@@ -76,7 +76,7 @@ export const monoFonts = Object.fromEntries(
|
||||
|
||||
export const getAppliedThemeFont = (
|
||||
state: ThemeEditorState,
|
||||
fontKey: keyof ThemeStyleProps
|
||||
fontKey: "font-sans" | "font-serif" | "font-mono"
|
||||
): string | null => {
|
||||
const fontSans = state.styles.light[fontKey];
|
||||
// find key of font in fonts object based on value
|
||||
|
||||
Reference in New Issue
Block a user