fix: build errors

This commit is contained in:
Sahaj Jain
2025-04-17 15:11:57 +05:30
parent 837d0a8602
commit 865b359baa
28 changed files with 309 additions and 243 deletions
+9 -86
View File
@@ -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
View File
@@ -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>
+18 -10
View File
@@ -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");
+5 -3
View File
@@ -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]
);
+4 -2
View File
@@ -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.
+15 -5
View File
@@ -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)}
/>
+75
View File
@@ -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 -2
View File
@@ -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> = ({
+20 -10
View File
@@ -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 {
+1 -1
View File
@@ -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}>
+15 -3
View File
@@ -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>
+3 -3
View File
@@ -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&apos;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&apos;re constantly working to improve tweakcn and add new
features. Here&apos;s what&apos;s on our roadmap.
</p>
</motion.div>
+5 -3
View File
@@ -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" />,
}}
-33
View File
@@ -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;
-3
View File
@@ -11,7 +11,4 @@ export const themeEditorConfig: EditorConfig = {
defaultState: defaultThemeState,
controls: ThemeControlPanel,
preview: ThemePreviewPanel,
codeGenerator: {
generateComponentCode: generateThemeCode,
},
};
+1
View File
@@ -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%)",
+19
View File
@@ -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
View File
@@ -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",
+11 -3
View File
@@ -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': {}
+2 -7
View File
@@ -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 || "");
+6 -3
View File
@@ -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);
},
}),
-11
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+6 -3
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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