import React, { useId } from "react"; import { Box, Button, Divider, FormControl, FormControlLabel, IconButton, InputLabel, MenuItem, Paper, Select, Slider, Stack, Switch, TextField, Typography, } from "@mui/material"; import { applyCvTheme, CvTheme, CvVariantSettings, normalizeCvLanguage, SECTION_LABELS } from "../../cvBuilder"; import { useI18n } from "../../i18n/I18nProvider"; import CvTemplateGallery from "../CvTemplateGallery"; const FONTS = [ "'Segoe UI', Roboto, Arial, sans-serif", "Arial, Helvetica, sans-serif", "Georgia, 'Times New Roman', serif", "'Helvetica Neue', Arial, sans-serif", "'Roboto', Arial, sans-serif", "'Poppins', 'Segoe UI', Arial, sans-serif", ]; const FONT_LABELS = ["Segoe UI", "Arial", "Georgia (serif)", "Helvetica Neue", "Roboto", "Poppins"]; type Props = { mode: "template" | "customize"; settings: CvVariantSettings; update: (patch: Partial) => void; themes: CvTheme[]; sectionLabel: (key: string, fallback: string) => string; }; export default function CvCustomizeTab({ mode, settings, update, themes, sectionLabel }: Props) { const { t } = useI18n(); const labelId = useId(); const activeTheme = themes.find((theme) => theme.id === settings.themeId); const supports = (setting: string) => !activeTheme?.supportedSettings?.length || activeTheme.supportedSettings.includes(setting); const sidebarSections = settings.sidebarSections ?? ["contact", "skills", "languages"]; const toggleSidebarSection = (key: string) => update({ sidebarSections: sidebarSections.includes(key) ? sidebarSections.filter((item) => item !== key) : [...sidebarSections, key], }); return ( {mode === "template" && {t("cvEditorTheme")} {t("cvEditorThemeHelp")} update(applyCvTheme(settings, theme.id))} /> } {mode === "customize" && <> {supports("accent") && <>{t("cvEditorColour")} {["#3157d5", "#0f766e", "#9f1239", "#7c3aed", "#b45309", "#334155"].map((color) => ( update({ accentColor: color })} sx={{ width: 34, height: 34, bgcolor: color, border: settings.accentColor === color ? "3px solid" : "1px solid", borderColor: settings.accentColor === color ? "text.primary" : "divider", "&:hover": { bgcolor: color } }} /> ))} update({ accentColor: event.target.value })} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: 0, cursor: "pointer" }} /> {settings.accentColor && } } {supports("typography") && <>{t("cvEditorTypography")} {t("cvEditorHeadingFont")} {t("cvEditorBodyFont")} update({ baseFontSizePt: value })} /> update({ headingSizePt: value })} /> update({ lineHeight: value })} />} {supports("heading") && {t("cvEditorHeadingTreatment")}} {supports("header") && {t("cvEditorHeaderTreatment")}} {supports("skills") && <>{t("cvEditorSkillsPresentation")} {settings.skillsStyle === "grouped" && `${group.name}: ${group.items.join(", ")}`).join("\n")} onChange={(event) => update({ skillGroups: event.target.value.split("\n").filter(Boolean).map((line) => { const [name, ...items] = line.split(":"); return { name: name.trim(), items: items.join(":").split(",").map((item) => item.trim()).filter(Boolean) }; }) })} />}} {t("cvEditorDocument")} {supports("page") && <> {t("cvEditorPageSize")} {t("cvEditorDensity")} } {t("cvEditorDocumentLanguage")} {t("cvEditorDateFormat")} {supports("layout") && {t("cvEditorColumns")}} {supports("spacing") && <> update({ pageMarginMm: value })} /> update({ sectionGapMm: value })} /> update({ entryGapMm: value })} /> } {supports("sidebar") && (settings.layout === "sidebar-left" || settings.layout === "sidebar-right") && {t("cvEditorSidebarContent")} update({ sidebarWidthMm: value })} /> {["contact", "skills", "languages", "certifications", "projects", "interests"].map((key) => toggleSidebarSection(key)} />} label={key === "contact" ? t("cvEditorContactDetails") : sectionLabel(key, SECTION_LABELS[key] ?? key)} />)} } {supports("photo") && update({ showPhoto: event.target.checked })} />} label={t("cvEditorShowPhoto")} />} {supports("icons") && update({ showIcons: event.target.checked })} />} label={t("cvEditorContactIcons")} />} } ); } function ControlSlider({ label, value, min, max, step, suffix = "", onChange }: { label: string; value: number; min: number; max: number; step: number; suffix?: string; onChange: (value: number) => void }) { return {label}{Number(value.toFixed(2))}{suffix} onChange(next as number)} />; }