feat: initial fonts support

This commit is contained in:
Maze Winther
2025-07-10 20:53:58 +02:00
parent 055a6af055
commit e8b0057cc4
8 changed files with 142 additions and 15 deletions

View File

@ -1,4 +1,3 @@
import { Inter } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { Analytics } from "@vercel/analytics/react";
import Script from "next/script";
@ -8,11 +7,7 @@ import { TooltipProvider } from "../components/ui/tooltip";
import { DevelopmentDebug } from "../components/development-debug";
import { StorageProvider } from "../components/storage-provider";
import { baseMetaData } from "./metadata";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
import { defaultFont } from "../lib/font-config";
export const metadata = baseMetaData;
@ -23,7 +18,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${inter.variable} font-sans antialiased`}>
<body className={`${defaultFont.className} font-sans antialiased`}>
<ThemeProvider attribute="class" forcedTheme="dark" enableSystem>
<TooltipProvider>
<StorageProvider>{children}</StorageProvider>

View File

@ -20,6 +20,7 @@ import { Play, Pause } from "lucide-react";
import { useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";
import { formatTimeCode } from "@/lib/time";
import { FONT_CLASS_MAP } from "@/lib/font-config";
interface ActiveElement {
element: TimelineElement;
@ -141,6 +142,9 @@ export function PreviewPanel() {
// Text elements
if (element.type === "text") {
const fontClassName =
FONT_CLASS_MAP[element.fontFamily as keyof typeof FONT_CLASS_MAP] || "";
return (
<div
key={element.id}
@ -154,9 +158,9 @@ export function PreviewPanel() {
}}
>
<div
className={fontClassName}
style={{
fontSize: `${element.fontSize}px`,
fontFamily: element.fontFamily,
color: element.color,
backgroundColor: element.backgroundColor,
textAlign: element.textAlign,
@ -166,6 +170,8 @@ export function PreviewPanel() {
padding: "4px 8px",
borderRadius: "2px",
whiteSpace: "pre-wrap",
// Fallback for system fonts that don't have classes
...(fontClassName === "" && { fontFamily: element.fontFamily }),
}}
>
{element.content}

View File

@ -15,6 +15,7 @@ import {
SelectValue,
SelectItem,
} from "../ui/select";
import { FONT_OPTIONS, type FontFamily } from "@/constants/font-constants";
export function PropertiesPanel() {
const { activeProject } = useProjectStore();
@ -49,14 +50,21 @@ export function PropertiesPanel() {
/>
<div className="flex items-center justify-between gap-6">
<Label className="text-xs">Font</Label>
<Select>
<Select
defaultValue={element.fontFamily}
onValueChange={(value: FontFamily) =>
updateTextElement(trackId, element.id, { fontFamily: value })
}
>
<SelectTrigger className="w-full text-xs">
<SelectValue placeholder="Select a font" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Arial">Arial</SelectItem>
<SelectItem value="Helvetica">Helvetica</SelectItem>
<SelectItem value="Times New Roman">Times New Roman</SelectItem>
{FONT_OPTIONS.map((font) => (
<SelectItem key={font.value} value={font.value}>
{font.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

View File

@ -24,7 +24,7 @@ import { useTimelineElementResize } from "@/hooks/use-timeline-element-resize";
import {
getTrackElementClasses,
TIMELINE_CONSTANTS,
} from "@/lib/timeline-constants";
} from "@/constants/timeline-constants";
import {
DropdownMenu,
DropdownMenuContent,

View File

@ -17,7 +17,7 @@ import type {
TimelineElement as TimelineElementType,
DragData,
} from "@/types/timeline";
import { TIMELINE_CONSTANTS } from "@/lib/timeline-constants";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
export function TimelineTrackContent({
track,

View File

@ -50,7 +50,7 @@ import {
getCumulativeHeightBefore,
getTotalTracksHeight,
TIMELINE_CONSTANTS,
} from "@/lib/timeline-constants";
} from "@/constants/timeline-constants";
export function Timeline() {
// Timeline shows all tracks (video, audio, effects) and their elements.

View File

@ -0,0 +1,79 @@
export interface FontOption {
value: string;
label: string;
category: "system" | "google" | "custom";
weights?: number[];
hasClassName?: boolean;
}
export const FONT_OPTIONS: FontOption[] = [
// System fonts (always available)
{ value: "Arial", label: "Arial", category: "system", hasClassName: false },
{
value: "Helvetica",
label: "Helvetica",
category: "system",
hasClassName: false,
},
{
value: "Times New Roman",
label: "Times New Roman",
category: "system",
hasClassName: false,
},
{
value: "Georgia",
label: "Georgia",
category: "system",
hasClassName: false,
},
// Google Fonts (loaded in layout.tsx)
{
value: "Inter",
label: "Inter",
category: "google",
weights: [400, 700],
hasClassName: true,
},
{
value: "Roboto",
label: "Roboto",
category: "google",
weights: [400, 700],
hasClassName: true,
},
{
value: "Open Sans",
label: "Open Sans",
category: "google",
hasClassName: true,
},
{
value: "Playfair Display",
label: "Playfair Display",
category: "google",
hasClassName: true,
},
{
value: "Comic Neue",
label: "Comic Neue",
category: "google",
hasClassName: false,
},
] as const;
export const DEFAULT_FONT = "Arial";
// Type-safe font family union
export type FontFamily = (typeof FONT_OPTIONS)[number]["value"];
// Helper functions
export const getFontByValue = (value: string): FontOption | undefined =>
FONT_OPTIONS.find((font) => font.value === value);
export const getGoogleFonts = (): FontOption[] =>
FONT_OPTIONS.filter((font) => font.category === "google");
export const getSystemFonts = (): FontOption[] =>
FONT_OPTIONS.filter((font) => font.category === "system");

View File

@ -0,0 +1,39 @@
import {
Inter,
Roboto,
Open_Sans,
Playfair_Display,
Comic_Neue,
} from "next/font/google";
// Configure all fonts
const inter = Inter({ subsets: ["latin"] });
const roboto = Roboto({ subsets: ["latin"], weight: ["400", "700"] });
const openSans = Open_Sans({ subsets: ["latin"] });
const playfairDisplay = Playfair_Display({ subsets: ["latin"] });
const comicNeue = Comic_Neue({ subsets: ["latin"], weight: ["400", "700"] });
// Export font class mapping for use in components
export const FONT_CLASS_MAP = {
Inter: inter.className,
Roboto: roboto.className,
"Open Sans": openSans.className,
"Playfair Display": playfairDisplay.className,
"Comic Neue": comicNeue.className,
Arial: "",
Helvetica: "",
"Times New Roman": "",
Georgia: "",
} as const;
// Export individual fonts for use in layout
export const fonts = {
inter,
roboto,
openSans,
playfairDisplay,
comicNeue,
};
// Default font for the body
export const defaultFont = inter;