mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 07:12:18 +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:
co-authored by
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Amruth Pillai
parent
1522794733
commit
b932711f08
@@ -23,6 +23,7 @@ import { Children, createContext, isValidElement, use } from "react";
|
||||
import { match } from "ts-pattern";
|
||||
import { useRender } from "../../context";
|
||||
import { View } from "../../renderer";
|
||||
import { getResumeSectionIcon } from "../../section-icon";
|
||||
import { getResumeSectionTitle } from "../../section-title";
|
||||
import { getSectionItemRows, getSectionItemsLayout, shouldUseSectionTimeline } from "./columns";
|
||||
import { getWebsiteDisplayText } from "./contact";
|
||||
@@ -38,7 +39,7 @@ import {
|
||||
import { filterItems, hasVisibleItems, isSectionVisible, isVisibleSummary } from "./filtering";
|
||||
import { LevelDisplay } from "./level-display";
|
||||
import { getTemplateMetrics } from "./metrics";
|
||||
import { Bold, Div, Heading, Icon, Link, Small, Text } from "./primitives";
|
||||
import { Bold, Div, Heading, Icon, Link, SectionHeadingIcon, Small, Text } from "./primitives";
|
||||
import { RichText } from "./rich-text";
|
||||
import { createRtlStyleHelpers } from "./rtl";
|
||||
import { getInlineItemWebsiteUrl, shouldRenderSeparateItemWebsite } from "./section-links";
|
||||
@@ -132,6 +133,35 @@ const SECTION_ITEM_PLACEHOLDER_KEYS = [
|
||||
"placeholder-6",
|
||||
] as const;
|
||||
|
||||
const defaultSectionHeadingContainerStyle = {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
columnGap: 4,
|
||||
} satisfies Style;
|
||||
|
||||
const getSectionHeadingTextStyle = (...styles: StyleInput[]): Style[] =>
|
||||
composeStyles(...styles).map(
|
||||
({
|
||||
borderBottomWidth: _borderBottomWidth,
|
||||
borderLeftWidth: _borderLeftWidth,
|
||||
borderRightWidth: _borderRightWidth,
|
||||
borderTopWidth: _borderTopWidth,
|
||||
borderWidth: _borderWidth,
|
||||
flexGrow: _flexGrow,
|
||||
flexShrink: _flexShrink,
|
||||
marginBottom: _marginBottom,
|
||||
marginLeft: _marginLeft,
|
||||
marginRight: _marginRight,
|
||||
marginTop: _marginTop,
|
||||
paddingBottom: _paddingBottom,
|
||||
paddingLeft: _paddingLeft,
|
||||
paddingRight: _paddingRight,
|
||||
paddingTop: _paddingTop,
|
||||
width: _width,
|
||||
...textStyle
|
||||
}) => textStyle,
|
||||
);
|
||||
|
||||
const useSectionItemsContext = () => use(SectionItemsContext);
|
||||
|
||||
const getChildKey = (child: ReactNode, fallbackIndex: number) => {
|
||||
@@ -154,13 +184,41 @@ const SectionShell = ({ sectionId, title, showHeading = true, children }: Sectio
|
||||
const sectionStyle = useTemplateStyle("section");
|
||||
const sectionRuleStyle = useSectionStyleRule("section");
|
||||
const sectionHeadingStyle = useTemplateStyle("sectionHeading");
|
||||
const sectionHeadingContainerStyle = useTemplateStyle("sectionHeadingContainer");
|
||||
const sectionHeadingRuleStyle = useSectionStyleRule("heading");
|
||||
const sectionTitle = getResumeSectionTitle(data, sectionId, title);
|
||||
const sectionIcon = getResumeSectionIcon(data, sectionId);
|
||||
const showIcon = Boolean(sectionIcon) && !data.metadata.page.hideSectionIcons;
|
||||
|
||||
if (!showIcon) {
|
||||
// No icon: render heading exactly as before (no structural change)
|
||||
return (
|
||||
<View style={composeStyles(sectionStyle, sectionRuleStyle)}>
|
||||
{showHeading && (
|
||||
<Heading style={composeStyles(sectionHeadingStyle, sectionHeadingRuleStyle)}>{sectionTitle}</Heading>
|
||||
)}
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// With icon: wrap in a flex row container that inherits the heading's border/decoration
|
||||
return (
|
||||
<View style={composeStyles(sectionStyle, sectionRuleStyle)}>
|
||||
{showHeading && (
|
||||
<Heading style={composeStyles(sectionHeadingStyle, sectionHeadingRuleStyle)}>{sectionTitle}</Heading>
|
||||
<View
|
||||
style={composeStyles(
|
||||
sectionHeadingStyle,
|
||||
defaultSectionHeadingContainerStyle,
|
||||
sectionHeadingContainerStyle,
|
||||
sectionHeadingRuleStyle,
|
||||
)}
|
||||
>
|
||||
<SectionHeadingIcon name={sectionIcon as IconName} />
|
||||
<Heading style={getSectionHeadingTextStyle(sectionHeadingStyle, sectionHeadingRuleStyle)}>
|
||||
{sectionTitle}
|
||||
</Heading>
|
||||
</View>
|
||||
)}
|
||||
{children}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user