From 07e06a6ed432d195d2b4cc074d8e7d7c058033c0 Mon Sep 17 00:00:00 2001 From: Fynn Fischbach Date: Wed, 28 Jan 2026 11:37:44 +0100 Subject: [PATCH] feat(builder): arrange sidebar builder depending on section type in template #2564 (#2603) * feat: introduce template sidebar type * feat: align sidebar builder depending on template alignment * revert: keep full width toggle for now until we merge SortableLayoutItem on template change. * fix: typo * use string literals instead of enum, modify logic for layout display slightly --------- Co-authored-by: Amruth Pillai --- src/dialogs/resume/template/data.ts | 14 +++++ .../-sidebar/right/sections/layout/pages.tsx | 55 ++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/dialogs/resume/template/data.ts b/src/dialogs/resume/template/data.ts index f6ab14b89..abc39a20e 100644 --- a/src/dialogs/resume/template/data.ts +++ b/src/dialogs/resume/template/data.ts @@ -7,6 +7,7 @@ export type TemplateMetadata = { description: MessageDescriptor; imageUrl: string; tags: string[]; + sidebarPosition: "left" | "right" | "none"; }; export const templates = { @@ -15,77 +16,90 @@ export const templates = { description: msg`Two-column with a bold colored sidebar and skill bars; great for creative or tech roles where visual flair is welcome.`, imageUrl: "/templates/jpg/azurill.jpg", tags: ["Two-column", "Creative", "Tech", "Visual flair"], + sidebarPosition: "left", }, bronzor: { name: "Bronzor", description: msg`Two-column, clean and professional with subtle section dividers; suits corporate, finance, or consulting positions.`, imageUrl: "/templates/jpg/bronzor.jpg", tags: ["Two-column", "Clean", "Professional", "Corporate", "Finance", "Consulting"], + sidebarPosition: "none", }, chikorita: { name: "Chikorita", description: msg`Two-column with a soft header accent and circular profile photo; ideal for marketing, HR, or client-facing roles.`, imageUrl: "/templates/jpg/chikorita.jpg", tags: ["Two-column", "Soft accent", "Marketing", "HR", "Client-facing"], + sidebarPosition: "right", }, ditgar: { name: "Ditgar", description: msg`Two-column with a dark teal sidebar and skills grid; modern feel for developers, data scientists, or technical PMs.`, imageUrl: "/templates/jpg/ditgar.jpg", tags: ["Two-column", "Modern", "Developer", "Data science", "Technical PM", "Dark sidebar"], + sidebarPosition: "left", }, ditto: { name: "Ditto", description: msg`Two-column, minimal and text-dense with no decorative elements; perfect for traditional industries or ATS-heavy applications.`, imageUrl: "/templates/jpg/ditto.jpg", tags: ["Two-column", "ATS friendly", "Minimal", "Text-dense", "Traditional", "No decoration"], + sidebarPosition: "left", }, gengar: { name: "Gengar", description: msg`Two-column with accent colors and clean typography; balanced choice for business analysts or operations roles.`, imageUrl: "/templates/jpg/gengar.jpg", tags: ["Two-column", "Accent colors", "Clean typography", "Business analyst", "Operations"], + sidebarPosition: "left", }, glalie: { name: "Glalie", description: msg`Two-column, minimal with light gray sidebar and subtle icons; professional and understated for legal, finance, or executive roles.`, imageUrl: "/templates/jpg/glalie.jpg", tags: ["Two-column", "Minimal", "Professional", "Legal", "Finance", "Executive", "Understated"], + sidebarPosition: "left", }, kakuna: { name: "Kakuna", description: msg`Single-column with a magenta left border accent; compact and efficient for entry-level or internship applications.`, imageUrl: "/templates/jpg/kakuna.jpg", tags: ["Single-column", "ATS friendly", "Compact", "Efficient", "Entry level", "Internship", "Magenta accent"], + sidebarPosition: "none", }, lapras: { name: "Lapras", description: msg`Single-column; polished and serious for senior or enterprise-level positions.`, imageUrl: "/templates/jpg/lapras.jpg", tags: ["Single-column", "ATS friendly", "Polished", "Senior", "Enterprise"], + sidebarPosition: "none", }, leafish: { name: "Leafish", description: msg`Two-column with a muted color sidebar; earthy and calm, suits sustainability, healthcare, or nonprofit sectors.`, imageUrl: "/templates/jpg/leafish.jpg", tags: ["Two-column", "Muted sidebar", "Earthy", "Calm", "Sustainability", "Healthcare", "Nonprofit"], + sidebarPosition: "right", }, onyx: { name: "Onyx", description: msg`Single-column with a sidebar and clean grid layout; versatile for any professional or technical role.`, imageUrl: "/templates/jpg/onyx.jpg", tags: ["Single-column", "ATS friendly", "Sidebar", "Grid layout", "Versatile", "Professional", "Technical"], + sidebarPosition: "none", }, pikachu: { name: "Pikachu", description: msg`Two-column with a left margin color; simple and approachable for creative, editorial, or junior roles.`, imageUrl: "/templates/jpg/pikachu.jpg", tags: ["Two-column", "Simple", "Creative", "Editorial", "Junior", "Accent colors"], + sidebarPosition: "left", }, rhyhorn: { name: "Rhyhorn", description: msg`Single-column with a minimal top header and lots of whitespace; clean and modern for designers or content creators.`, imageUrl: "/templates/jpg/rhyhorn.jpg", tags: ["Single-column", "ATS friendly", "Minimal", "Clean", "Modern", "Designer", "Content creator", "Whitespace"], + sidebarPosition: "none", }, } as const satisfies Record; diff --git a/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx b/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx index 8817c4cec..39d8cccea 100644 --- a/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx +++ b/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx @@ -19,6 +19,7 @@ import { match } from "ts-pattern"; import { useResumeStore } from "@/components/resume/store/resume"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; +import { templates } from "@/dialogs/resume/template/data"; import type { SectionType } from "@/schema/resume/data"; import { getSectionTitle } from "@/utils/resume/section"; import { cn } from "@/utils/style"; @@ -63,6 +64,9 @@ const createDroppableId = (pageIndex: number, columnId: ColumnId): string => { export function LayoutPages() { const [activeId, setActiveId] = useState(null); + const template = useResumeStore((state) => state.resume.data.metadata.template); + const templateSidebarPosition = templates[template].sidebarPosition; + const layout = useResumeStore((state) => state.resume.data.metadata.layout); const updateResumeData = useResumeStore((state) => state.updateResumeData); @@ -216,6 +220,7 @@ export function LayoutPages() { pageIndex={pageIndex} page={page} canDelete={layout.pages.length > 1} + sidebarPosition={templateSidebarPosition} onDelete={handleDeletePage} onToggleFullWidth={handleToggleFullWidth} /> @@ -236,11 +241,21 @@ type PageContainerProps = { pageIndex: number; page: { fullWidth: boolean; main: string[]; sidebar: string[] }; canDelete: boolean; + sidebarPosition: "left" | "right" | "none"; onDelete: (pageIndex: number) => void; onToggleFullWidth: (pageIndex: number, fullWidth: boolean) => void; }; -function PageContainer({ pageIndex, page, canDelete, onDelete, onToggleFullWidth }: PageContainerProps) { +function PageContainer({ + pageIndex, + page, + canDelete, + sidebarPosition, + onDelete, + onToggleFullWidth, +}: PageContainerProps) { + const isFullWidth = page.fullWidth; + return (
@@ -268,12 +283,27 @@ function PageContainer({ pageIndex, page, canDelete, onDelete, onToggleFullWidth
- - {!page.fullWidth && } + + + {!isFullWidth && ( + + )}
); @@ -283,17 +313,26 @@ type LayoutColumnProps = { pageIndex: number; columnId: ColumnId; items: string[]; + hideLabel?: boolean; disabled?: boolean; + className?: string; }; -function LayoutColumn({ pageIndex, columnId, items, disabled = false }: LayoutColumnProps) { +function LayoutColumn({ + pageIndex, + columnId, + items, + hideLabel = false, + disabled = false, + className, +}: LayoutColumnProps) { const droppableId = createDroppableId(pageIndex, columnId); const { setNodeRef, isOver } = useDroppable({ id: droppableId, disabled }); return ( -
-
{getColumnLabel(columnId)}
+
+ {!hideLabel &&
{getColumnLabel(columnId)}
}