feat(cv): organize template and document controls

This commit is contained in:
cesnimda
2026-10-08 00:16:44 +02:00
parent 673cff1145
commit d8d262e4d1
99 changed files with 27605 additions and 82 deletions
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useId, useMemo, useState } from "react";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { Box, Button, Chip, Paper, Stack, Typography } from "@mui/material";
@@ -16,14 +16,15 @@ type Props = {
export default function CvTemplateGallery({ themes, selectedId, onSelect, previewHeight = 170, showDescriptions = true }: Props) {
const { t } = useI18n();
const id = useId();
const [atsOnly, setAtsOnly] = useState(true);
const visibleThemes = useMemo(() => getVisibleCvThemes(themes, atsOnly, selectedId), [atsOnly, selectedId, themes]);
return (
<Stack spacing={1.25}>
<Stack direction="row" spacing={0.75} alignItems="center" flexWrap="wrap" useFlexGap>
<Button size="small" variant={atsOnly ? "contained" : "outlined"} onClick={() => setAtsOnly(true)}>{t("cvEditorAtsTemplates")}</Button>
<Button size="small" variant={!atsOnly ? "contained" : "outlined"} onClick={() => setAtsOnly(false)}>{t("cvEditorAllTemplates")}</Button>
<Button size="small" aria-pressed={atsOnly} variant={atsOnly ? "contained" : "outlined"} onClick={() => setAtsOnly(true)}>{t("cvEditorAtsTemplates")}</Button>
<Button size="small" aria-pressed={!atsOnly} variant={!atsOnly ? "contained" : "outlined"} onClick={() => setAtsOnly(false)}>{t("cvEditorAllTemplates")}</Button>
{atsOnly && <Typography variant="caption" color="text.secondary">{t("cvEditorAtsTemplateHelp")}</Typography>}
</Stack>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 1.25 }}>
@@ -31,16 +32,16 @@ export default function CvTemplateGallery({ themes, selectedId, onSelect, previe
const active = theme.id === selectedId;
const locked = theme.available === false;
return (
<Paper key={theme.id} variant="outlined" role="button" tabIndex={locked ? -1 : 0} aria-disabled={locked} aria-pressed={active}
<Paper key={theme.id} variant="outlined" role="button" tabIndex={locked ? -1 : 0} aria-label={theme.name} aria-describedby={`${id}-${theme.id}-category ${showDescriptions ? `${id}-${theme.id}-description ` : ""}${id}-${theme.id}-features`} aria-disabled={locked} aria-pressed={active}
onClick={() => { if (!locked) onSelect(theme); }}
onKeyDown={(event) => { if (!locked && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); onSelect(theme); } }}
sx={{ p: 1, cursor: locked ? "not-allowed" : "pointer", opacity: locked ? 0.6 : 1, outline: "none", position: "relative", borderColor: active ? "primary.main" : "divider", borderWidth: active ? 2 : 1, transition: "border-color 120ms ease, transform 120ms ease", "&:hover": locked ? undefined : { borderColor: "primary.main", transform: "translateY(-1px)" }, "&:focus-visible": { outline: "3px solid", outlineColor: "primary.main", outlineOffset: 2 } }}>
sx={{ p: 1, cursor: locked ? "not-allowed" : "pointer", position: "relative", borderColor: active ? "primary.main" : "divider", borderWidth: active ? 2 : 1, "&:hover": locked ? undefined : { borderColor: "primary.main" } }}>
{active && <CheckCircleIcon aria-label={t("cvEditorSelectedTemplate")} color="primary" sx={{ position: "absolute", zIndex: 2, top: 6, right: 6, bgcolor: "background.paper", borderRadius: "50%" }} />}
<Box sx={{ mb: 1 }}><CvTemplateThumbnail theme={theme} height={previewHeight} /></Box>
<Typography variant="body2" sx={{ fontWeight: 750 }}>{theme.name}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>{theme.category}</Typography>
{showDescriptions && <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.35 }}>{theme.description}</Typography>}
<Stack direction="row" spacing={0.5} alignItems="center" flexWrap="wrap" useFlexGap sx={{ mt: 0.75 }}>
<Typography id={`${id}-${theme.id}-category`} variant="caption" color="text.secondary" sx={{ display: "block" }}>{theme.category}</Typography>
{showDescriptions && <Typography id={`${id}-${theme.id}-description`} variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.35 }}>{theme.description}</Typography>}
<Stack id={`${id}-${theme.id}-features`} direction="row" spacing={0.5} alignItems="center" flexWrap="wrap" useFlexGap sx={{ mt: 0.75 }}>
{theme.atsFriendly && <Chip size="small" label={t("cvEditorAtsFriendly")} color="success" variant="outlined" sx={{ height: 20, fontSize: 10 }} />}
{theme.requiresPro && <Chip size="small" label={locked ? t("cvEditorPro") : t("cvEditorProUnlocked")} color="secondary" variant="outlined" sx={{ height: 20, fontSize: 10 }} />}
<Stack direction="row" spacing={0.35} sx={{ ml: "auto" }} aria-hidden>