From e8b0057cc44c90af24f52e44ada60493e6ef6311 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Thu, 10 Jul 2025 20:53:58 +0200 Subject: [PATCH] feat: initial fonts support --- apps/web/src/app/layout.tsx | 9 +-- .../src/components/editor/preview-panel.tsx | 8 +- .../components/editor/properties-panel.tsx | 16 +++- .../components/editor/timeline-element.tsx | 2 +- .../src/components/editor/timeline-track.tsx | 2 +- apps/web/src/components/editor/timeline.tsx | 2 +- apps/web/src/constants/font-constants.ts | 79 +++++++++++++++++++ apps/web/src/lib/font-config.ts | 39 +++++++++ 8 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/constants/font-constants.ts create mode 100644 apps/web/src/lib/font-config.ts diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 6dd74b8..6d0ebc6 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -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 ( - + {children} diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 09341ee..c5621fe 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -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 (
{element.content} diff --git a/apps/web/src/components/editor/properties-panel.tsx b/apps/web/src/components/editor/properties-panel.tsx index e9b651b..a0532ab 100644 --- a/apps/web/src/components/editor/properties-panel.tsx +++ b/apps/web/src/components/editor/properties-panel.tsx @@ -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() { />
- + updateTextElement(trackId, element.id, { fontFamily: value }) + } + > - Arial - Helvetica - Times New Roman + {FONT_OPTIONS.map((font) => ( + + {font.label} + + ))}
diff --git a/apps/web/src/components/editor/timeline-element.tsx b/apps/web/src/components/editor/timeline-element.tsx index c580165..1cd22c7 100644 --- a/apps/web/src/components/editor/timeline-element.tsx +++ b/apps/web/src/components/editor/timeline-element.tsx @@ -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, diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 8734d0f..7e2e93e 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -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, diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index ee56cbe..abbf5bb 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -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. diff --git a/apps/web/src/constants/font-constants.ts b/apps/web/src/constants/font-constants.ts new file mode 100644 index 0000000..bfddc18 --- /dev/null +++ b/apps/web/src/constants/font-constants.ts @@ -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"); diff --git a/apps/web/src/lib/font-config.ts b/apps/web/src/lib/font-config.ts new file mode 100644 index 0000000..0389054 --- /dev/null +++ b/apps/web/src/lib/font-config.ts @@ -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;