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:
JamesGoslings
2026-06-01 14:02:42 +02:00
committed by GitHub
co-authored by coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Amruth Pillai
parent 1522794733
commit b932711f08
81 changed files with 1153 additions and 306 deletions
@@ -13,3 +13,26 @@ describe("ExperienceSection", () => {
expect(source).not.toContain("item.roles.length > 0 && <Text>{item.period}</Text>");
});
});
describe("SectionShell", () => {
it("keeps section and heading style rules when section heading icons are hidden", () => {
expect(source).toContain("<View style={composeStyles(sectionStyle, sectionRuleStyle)}>");
expect(source).toContain("<Heading style={composeStyles(sectionHeadingStyle, sectionHeadingRuleStyle)}>");
});
it("wires the section heading container style slot into the icon row", () => {
expect(source).toContain('useTemplateStyle("sectionHeadingContainer")');
expect(source).toContain("sectionHeadingContainerStyle");
});
it("top-aligns heading icon rows and does not use unsupported auto width resets", () => {
const headingContainerBlock = source.match(
/const defaultSectionHeadingContainerStyle = {(?<body>[\s\S]*?)} satisfies Style;/,
);
expect(headingContainerBlock?.groups?.body).toContain('alignItems: "flex-start"');
expect(source).toContain("getSectionHeadingTextStyle(sectionHeadingStyle, sectionHeadingRuleStyle)");
expect(source).toContain("width: _width");
expect(source).not.toContain('width: "auto"');
});
});