mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-26 09:54:43 +10:00
feat: add section heading icons to PDF templates (#3127)
* feat: add section heading icons to PDF templates Add customizable Phosphor icons before section titles in PDF output. Users can toggle visibility globally via a new "Hide section heading icons" switch (independent of item-level icons) and customize individual section icons through the builder sidebar icon picker. - Add `icon` field to `baseSectionSchema` and `summarySchema` - Add `hideSectionIcons` to `pageSchema` (defaults to true for backward compat) - Implement `SectionHeadingIcon` component with heading font-size scaling - Support "none" sentinel for per-section icon hiding - Fallback to sensible defaults (briefcase, graduation-cap, etc.) for legacy data - Add icon picker to builder sidebar sections and custom section dialogs Closes #2632 * test: add unit tests for section heading icons - Add tests for getResumeSectionIcon() covering built-in sections, summary, custom sections, "none" sentinel, and default fallbacks - Add schema tests for baseSectionSchema icon field, summarySchema icon, and pageSchema hideSectionIcons default behavior * refactor: minor updates to icon display * Update apps/web/locales/es-ES.po Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Amruth Pillai <im.amruth@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import type { SectionType } from "@reactive-resume/schema/resume/data";
|
||||
import type { LeftSidebarSection } from "@/libs/resume/section";
|
||||
import { CaretDownIcon } from "@phosphor-icons/react";
|
||||
import { getDefaultSectionIconName } from "@reactive-resume/schema/resume/section-icons";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@reactive-resume/ui/components/accordion";
|
||||
import { Button } from "@reactive-resume/ui/components/button";
|
||||
import { cn } from "@reactive-resume/utils/style";
|
||||
import { useCurrentResume } from "@/features/resume/builder/draft";
|
||||
import { IconPicker } from "@/components/input/icon-picker";
|
||||
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
|
||||
import { getSectionIcon, getSectionTitle } from "@/libs/resume/section";
|
||||
import { useSectionStore } from "../../../-store/section";
|
||||
import { SectionDropdownMenu } from "./section-menu";
|
||||
@@ -15,6 +17,7 @@ type Props = React.ComponentProps<typeof AccordionContent> & {
|
||||
|
||||
export function SectionBase({ type, className, ...props }: Props) {
|
||||
const resume = useCurrentResume();
|
||||
const updateResumeData = useUpdateResumeData();
|
||||
const data = resume.data;
|
||||
const section =
|
||||
type === "basics"
|
||||
@@ -28,9 +31,27 @@ export function SectionBase({ type, className, ...props }: Props) {
|
||||
: data.sections[type];
|
||||
|
||||
const isHidden = "hidden" in section && section.hidden;
|
||||
const hasSectionIcon = !["picture", "basics", "custom"].includes(type);
|
||||
const rawIcon = "icon" in section && typeof section.icon === "string" ? section.icon : "";
|
||||
const fallbackIcon = hasSectionIcon ? getDefaultSectionIconName(type as "summary" | SectionType) : "";
|
||||
const sectionIcon = rawIcon === "none" ? "" : rawIcon || fallbackIcon;
|
||||
|
||||
const collapsed = useSectionStore((state) => state.sections[type]?.collapsed ?? false);
|
||||
const toggleCollapsed = useSectionStore((state) => state.toggleCollapsed);
|
||||
|
||||
const onIconChange = (icon: string) => {
|
||||
// Store "none" when user explicitly picks the empty/prohibit option
|
||||
const valueToStore = icon === "" ? "none" : icon;
|
||||
|
||||
updateResumeData((draft) => {
|
||||
if (type === "summary") {
|
||||
draft.summary.icon = valueToStore;
|
||||
} else if (type !== "basics" && type !== "picture" && type !== "custom") {
|
||||
draft.sections[type as SectionType].icon = valueToStore;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
id={`sidebar-${type}`}
|
||||
@@ -49,8 +70,12 @@ export function SectionBase({ type, className, ...props }: Props) {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex flex-1 items-center gap-x-4">
|
||||
{getSectionIcon(type)}
|
||||
<div className={cn("flex flex-1 items-center gap-x-4", hasSectionIcon && "gap-x-2")}>
|
||||
{hasSectionIcon ? (
|
||||
<IconPicker value={sectionIcon} onChange={onIconChange} size="icon" variant="ghost" />
|
||||
) : (
|
||||
getSectionIcon(type)
|
||||
)}
|
||||
<h2 className="line-clamp-1 font-semibold text-2xl tracking-tight">
|
||||
{("title" in section && section.title) || getSectionTitle(type)}
|
||||
</h2>
|
||||
|
||||
@@ -261,7 +261,7 @@ function PageSectionForm() {
|
||||
<form.Field name="hideIcons">
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-2"
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
@@ -276,7 +276,31 @@ function PageSectionForm() {
|
||||
}
|
||||
/>
|
||||
<FormLabel>
|
||||
<Trans>Hide all icons on the resume</Trans>
|
||||
<Trans>Hide Icons</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="hideSectionIcons">
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
render={
|
||||
<Switch
|
||||
checked={field.state.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.handleChange(checked);
|
||||
handleAutoSave("hideSectionIcons", checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel>
|
||||
<Trans>Hide Section Icons</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user