mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
* 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 <im.amruth@gmail.com>
This commit is contained in:
co-authored by
Amruth Pillai
parent
89102d6612
commit
07e06a6ed4
@@ -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<Template, TemplateMetadata>;
|
||||
|
||||
@@ -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<string | null>(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 (
|
||||
<div className="space-y-3 rounded-md border border-dashed bg-background/40">
|
||||
<div className="flex items-center justify-between bg-secondary/50 px-4 py-3">
|
||||
@@ -268,12 +283,27 @@ function PageContainer({ pageIndex, page, canDelete, onDelete, onToggleFullWidth
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"grid w-full gap-x-4 gap-y-2 p-4 pt-0 font-medium",
|
||||
page.fullWidth ? "grid-cols-1" : "@md:grid-cols-2",
|
||||
"grid w-full @md:grid-cols-2 gap-x-4 gap-y-2 p-4 pt-0 font-medium",
|
||||
sidebarPosition === "none" && "@md:grid-cols-1",
|
||||
)}
|
||||
>
|
||||
<LayoutColumn pageIndex={pageIndex} columnId="main" items={page.main} disabled={false} />
|
||||
{!page.fullWidth && <LayoutColumn pageIndex={pageIndex} columnId="sidebar" items={page.sidebar} />}
|
||||
<LayoutColumn
|
||||
pageIndex={pageIndex}
|
||||
columnId="main"
|
||||
items={page.main}
|
||||
disabled={false}
|
||||
className={cn(sidebarPosition === "left" ? "order-2" : "order-1")}
|
||||
/>
|
||||
|
||||
{!isFullWidth && (
|
||||
<LayoutColumn
|
||||
pageIndex={pageIndex}
|
||||
columnId="sidebar"
|
||||
items={page.sidebar}
|
||||
hideLabel={sidebarPosition === "none"}
|
||||
className={cn(sidebarPosition === "left" ? "order-1" : "order-2")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -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 (
|
||||
<SortableContext id={droppableId} items={items} strategy={verticalListSortingStrategy}>
|
||||
<div className={cn("space-y-1.5", disabled && "opacity-50")}>
|
||||
<div className="@md:row-start-1 ps-4 font-medium text-xs">{getColumnLabel(columnId)}</div>
|
||||
<div className={cn("space-y-1.5", disabled && "opacity-50", className)}>
|
||||
{!hideLabel && <div className="@md:row-start-1 ps-4 font-medium text-xs">{getColumnLabel(columnId)}</div>}
|
||||
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
|
||||
Reference in New Issue
Block a user