feat: add semantic CSS stylesheets (#3274)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Amruth Pillai
2026-07-30 12:39:15 +02:00
committed by GitHub
co-authored by Cursor Agent
parent 4ac19f81b3
commit d2ffbf9618
320 changed files with 78393 additions and 2915 deletions
@@ -4,8 +4,11 @@ import type { TemplatePageProps } from "../../document";
import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types";
import { useMemo } from "react";
import { rgbaStringToHex } from "@reactive-resume/utils/color";
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
import { Page, StyleSheet, View } from "#react-pdf-renderer";
import { useRender } from "../../context";
import { resolvedPdfFlowProps } from "../../semantic/adapter";
import { useRenderedSectionIds, useResolvedNode, useSemanticNodeVisible } from "../../semantic/context";
import { semanticNodeKeys } from "../../semantic/node-keys";
import { createBaseTemplateStyles } from "../shared/base-template-styles";
import {
CustomFieldContactItem,
@@ -14,11 +17,19 @@ import {
PhoneContactItem,
WebsiteContactItem,
} from "../shared/contact-item";
import { TemplateProvider } from "../shared/context";
import { TemplateProvider, useTemplatePageNodeKey } from "../shared/context";
import { filterSections } from "../shared/filtering";
import { getTemplateMetrics } from "../shared/metrics";
import { hasTemplatePicture } from "../shared/picture";
import { Heading, Text } from "../shared/primitives";
import {
Heading,
SemanticContactListView,
SemanticHeaderPicture,
SemanticHeaderView,
SemanticRegionView,
semanticTemplatePartNodeKey,
Text,
} from "../shared/primitives";
import { createRtlStyleHelpers } from "../shared/rtl";
import { Section } from "../shared/sections";
import { composeStyles, headerNameLineHeight } from "../shared/styles";
@@ -46,31 +57,62 @@ type RhyhornHeaderProps = {
styles: RhyhornStyles;
};
export const RhyhornPage = ({ page, pageSize, pageMinHeightStyle, showHeader }: TemplatePageProps) => {
type RhyhornContactItemProps = {
nodeKey: string | undefined;
last: boolean;
styles: RhyhornStyles;
children: ReactNode;
};
const RhyhornContactItem = ({ nodeKey, last, styles, children }: RhyhornContactItemProps) => {
const resolved = useResolvedNode(nodeKey);
const visible = useSemanticNodeVisible(nodeKey);
if (!visible) return null;
return (
<View
{...resolvedPdfFlowProps(resolved)}
style={composeStyles(styles.contactItem, last ? styles.contactItemLast : undefined, resolved.style)}
>
{children}
</View>
);
};
export const RhyhornPage = ({ page, pageSize, pageMinHeightStyle, showHeader, pageNumber }: TemplatePageProps) => {
const data = useRender();
const pageNodeKey = semanticNodeKeys.page(pageNumber);
const { style: semanticPageStyle, size: semanticPageSize, ...semanticPageProps } = useResolvedNode(pageNodeKey);
const { metadata } = data;
const { colors, styles } = useRhyhornTemplate();
const metrics = getTemplateMetrics(metadata.page);
const mainSections = filterSections(page.main, data);
const sidebarSections = filterSections(page.sidebar, data);
const mainSections = useRenderedSectionIds(pageNodeKey, filterSections(page.main, data));
const sidebarSections = useRenderedSectionIds(pageNodeKey, filterSections(page.sidebar, data));
return (
<Page size={pageSize} style={composeStyles(styles.page, pageMinHeightStyle)}>
<TemplateProvider styles={styles} colors={colors}>
<Page
{...semanticPageProps}
size={semanticPageSize ?? pageSize}
style={composeStyles(styles.page, pageMinHeightStyle, semanticPageStyle)}
>
<TemplateProvider pageNodeKey={pageNodeKey} styles={styles} colors={colors}>
{showHeader && <Header styles={styles} />}
<View style={composeStyles(styles.sectionGroup, { rowGap: metrics.sectionGap })}>
<SemanticRegionView region="main" style={composeStyles(styles.sectionGroup, { rowGap: metrics.sectionGap })}>
{mainSections.map((section) => (
<Section key={section} section={section} placement="main" />
))}
</View>
</SemanticRegionView>
{!page.fullWidth && (
<View style={composeStyles(styles.sectionGroup, { rowGap: metrics.sectionGap })}>
<SemanticRegionView
region="sidebar"
style={composeStyles(styles.sectionGroup, { rowGap: metrics.sectionGap })}
>
{sidebarSections.map((section) => (
<Section key={section} section={section} placement="sidebar" />
))}
</View>
</SemanticRegionView>
)}
</TemplateProvider>
</Page>
@@ -80,69 +122,111 @@ export const RhyhornPage = ({ page, pageSize, pageMinHeightStyle, showHeader }:
const Header = ({ styles }: RhyhornHeaderProps) => {
const { basics, picture } = useRender();
const hasPicture = hasTemplatePicture(picture);
const pageNodeKey = useTemplatePageNodeKey();
const headerNodeKey = semanticNodeKeys.header(semanticNodeKeys.region(pageNodeKey, "header"));
const contactListNodeKey = headerNodeKey ? semanticNodeKeys.contactList(headerNodeKey) : undefined;
const contactNodeKey = (name: string, id?: string) =>
contactListNodeKey ? semanticNodeKeys.contactItem(contactListNodeKey, name, id) : undefined;
const contentNodeKey = (name: string, id?: string) =>
semanticTemplatePartNodeKey(contactNodeKey(name, id), "contact-item-content");
const contactItems: {
id: string;
nodeKey: string | undefined;
content: ReactNode;
}[] = [];
if (basics.email) {
contactItems.push({
id: "email",
content: <EmailContactItem email={basics.email} style={styles.contactItemContent} />,
nodeKey: contactNodeKey("email"),
content: (
<EmailContactItem
email={basics.email}
primitiveNodeKey={contentNodeKey("email")}
style={styles.contactItemContent}
/>
),
});
}
if (basics.phone) {
contactItems.push({
id: "phone",
content: <PhoneContactItem phone={basics.phone} style={styles.contactItemContent} />,
nodeKey: contactNodeKey("phone"),
content: (
<PhoneContactItem
phone={basics.phone}
primitiveNodeKey={contentNodeKey("phone")}
style={styles.contactItemContent}
/>
),
});
}
if (basics.location) {
contactItems.push({
id: "location",
content: <LocationContactItem location={basics.location} style={styles.contactItemContent} />,
nodeKey: contactNodeKey("location"),
content: (
<LocationContactItem
location={basics.location}
primitiveNodeKey={contentNodeKey("location")}
style={styles.contactItemContent}
/>
),
});
}
if (basics.website.url) {
contactItems.push({
id: "website",
content: <WebsiteContactItem website={basics.website} style={styles.contactItemContent} />,
nodeKey: contactNodeKey("website"),
content: (
<WebsiteContactItem
website={basics.website}
primitiveNodeKey={contentNodeKey("website")}
style={styles.contactItemContent}
/>
),
});
}
contactItems.push(
...basics.customFields.map((field) => ({
id: `custom-${field.id}`,
content: <CustomFieldContactItem field={field} style={styles.contactItemContent} />,
nodeKey: contactNodeKey("custom", field.id),
content: (
<CustomFieldContactItem
field={field}
primitiveNodeKey={contentNodeKey("custom", field.id)}
style={styles.contactItemContent}
/>
),
})),
);
return (
<View style={styles.header}>
<SemanticHeaderView style={styles.header}>
<View style={styles.headerTitle}>
<View style={styles.headerIdentity}>
<Heading style={styles.headerName}>{basics.name}</Heading>
<Text>{basics.headline}</Text>
</View>
<View style={styles.contactList}>
<SemanticContactListView style={styles.contactList}>
{contactItems.map((item, index) => (
<View
<RhyhornContactItem
key={item.id}
style={composeStyles(
styles.contactItem,
index === contactItems.length - 1 ? styles.contactItemLast : undefined,
)}
nodeKey={item.nodeKey}
last={index === contactItems.length - 1}
styles={styles}
>
{item.content}
</View>
</RhyhornContactItem>
))}
</View>
</SemanticContactListView>
</View>
{hasPicture && <Image src={picture.url} style={styles.picture} />}
</View>
{hasPicture && <SemanticHeaderPicture src={picture.url} style={styles.picture} />}
</SemanticHeaderView>
);
};
@@ -0,0 +1,32 @@
import type { TemplateSemanticManifest } from "../../semantic/template-manifest";
export const rhyhornSemanticManifest = {
template: "rhyhorn",
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
parts: [
{
name: "contact-item-content",
key: "contact-item-content",
owner: { kind: "contact-item", key: "contact-item" },
binding: {
type: "primitive",
primitive: { ownerRole: "structured-link", present: "Link", absent: "View" },
source: "existing",
},
route: { parent: "owner", at: "start", take: "all" },
},
{
name: "contact-item-last",
key: "contact-item-last",
owner: { kind: "contact-item", key: "contact-item", position: "last" },
binding: { type: "alias", canonicalKind: "contact-item", token: "contact-item-last" },
},
],
canonicalBindings: [{ kind: "contact-item", binding: { type: "primitive", primitive: "View", source: "existing" } }],
} as const satisfies TemplateSemanticManifest;