mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-12 22:14:54 +10:00
refactor(pdf): introduce createBaseTemplateStyles factory (~1,150 lines removed)
Move 14 identical style slots (text/heading/div/inline/link/small/bold/ richParagraph/richListItemRow/richListItemMarker/richListItemContent/ splitRow/alignEnd/picture) from all 15 template Page.tsx files into a single createBaseTemplateStyles factory in templates/shared. Each template now spreads …base and keeps only its real overrides. Resolved StyleSheet values are identical to before. Update rtl-fixture and rich-text-template- styles tests to guard the factory file rather than each template directly. Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
This commit is contained in:
@@ -20,7 +20,8 @@ describe("RTL PDF fixture", () => {
|
||||
const source = readFileSync(pagePath, "utf8");
|
||||
|
||||
expect(source).toContain("createRtlStyleHelpers");
|
||||
expect(source).toContain("alignEnd");
|
||||
// ponytail: alignEnd moved to createBaseTemplateStyles factory; either direct or factory counts.
|
||||
expect(source.includes("alignEnd") || source.includes("createBaseTemplateStyles")).toBe(true);
|
||||
expect(source).not.toContain("alignRight");
|
||||
expect(source).not.toContain('from "@reactive-resume/utils/locale"');
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Fragment, useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -137,16 +138,10 @@ const useAzurillTemplate = (): AzurillTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.headerGap,
|
||||
@@ -160,62 +155,6 @@ const useAzurillTemplate = (): AzurillTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
sectionHeading: {
|
||||
color: primary,
|
||||
},
|
||||
@@ -230,18 +169,6 @@ const useAzurillTemplate = (): AzurillTemplate => {
|
||||
alignItems: "center",
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -134,16 +135,11 @@ const useBronzorTemplate = (): BronzorTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
heading: { ...base.heading, fontWeight: metadata.typography.heading.fontWeights[0] ?? "500" },
|
||||
page: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.headerGap,
|
||||
@@ -156,62 +152,6 @@ const useBronzorTemplate = (): BronzorTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights[0] ?? "500",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: r.row,
|
||||
columnGap: metrics.columnGap,
|
||||
@@ -236,18 +176,6 @@ const useBronzorTemplate = (): BronzorTemplate => {
|
||||
alignItems: "center",
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Fragment, useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -151,16 +152,11 @@ const useChikoritaTemplate = (): ChikoritaTemplate => {
|
||||
};
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
inline: { ...base.inline, columnGap: metrics.gapX(0.25) },
|
||||
page: {
|
||||
flexDirection: r.row,
|
||||
color: foreground,
|
||||
@@ -170,62 +166,6 @@ const useChikoritaTemplate = (): ChikoritaTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(0.25),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -257,18 +197,6 @@ const useChikoritaTemplate = (): ChikoritaTemplate => {
|
||||
alignItems: "flex-start",
|
||||
columnGap: metrics.gapX(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
flex: 1,
|
||||
rowGap: metrics.gapY(0.5),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import { getPrimaryTint } from "../shared/color-helpers";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
@@ -187,16 +188,10 @@ const useDitgarTemplate = (): DitgarTemplate => {
|
||||
};
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
flexDirection: r.row,
|
||||
color: foreground,
|
||||
@@ -206,62 +201,6 @@ const useDitgarTemplate = (): DitgarTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -314,18 +253,6 @@ const useDitgarTemplate = (): DitgarTemplate => {
|
||||
paddingVertical: metrics.page.paddingVertical,
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {},
|
||||
headerIdentity: {
|
||||
...r.headerIdentity,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Fragment, useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -147,16 +148,17 @@ const useDittoTemplate = (): DittoTemplate => {
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
const hasPicture = hasTemplatePicture(picture);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
picture: {
|
||||
...base.picture,
|
||||
position: "absolute",
|
||||
top: metrics.page.paddingVertical * 0.75,
|
||||
left: "50%",
|
||||
marginLeft: -picture.size / 2,
|
||||
},
|
||||
page: {
|
||||
flexDirection: "column",
|
||||
color: foreground,
|
||||
@@ -166,62 +168,6 @@ const useDittoTemplate = (): DittoTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -255,22 +201,6 @@ const useDittoTemplate = (): DittoTemplate => {
|
||||
flexShrink: 0,
|
||||
position: "relative",
|
||||
},
|
||||
picture: {
|
||||
position: "absolute",
|
||||
top: metrics.page.paddingVertical * 0.75,
|
||||
left: "50%",
|
||||
marginLeft: -picture.size / 2,
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Fragment, useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import { getPrimaryTint } from "../shared/color-helpers";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
@@ -183,16 +184,10 @@ const useGengarTemplate = (): GengarTemplate => {
|
||||
};
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
flexDirection: r.row,
|
||||
color: foreground,
|
||||
@@ -202,62 +197,6 @@ const useGengarTemplate = (): GengarTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -311,18 +250,6 @@ const useGengarTemplate = (): GengarTemplate => {
|
||||
paddingVertical: metrics.page.paddingVertical,
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {},
|
||||
headerIdentity: {
|
||||
...r.headerIdentity,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import { getPrimaryTint } from "../shared/color-helpers";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
@@ -148,16 +149,10 @@ const useGlalieTemplate = (): GlalieTemplate => {
|
||||
};
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -166,62 +161,6 @@ const useGlalieTemplate = (): GlalieTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -276,18 +215,6 @@ const useGlalieTemplate = (): GlalieTemplate => {
|
||||
alignItems: "center",
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
@@ -328,7 +255,7 @@ const useGlalieTemplate = (): GlalieTemplate => {
|
||||
colors,
|
||||
styles: {
|
||||
...baseStyles,
|
||||
text: (context) => ({ ...bodyText, color: foregroundFor(context) }),
|
||||
text: (context) => ({ ...base.text, color: foregroundFor(context) }),
|
||||
heading: (context) => ({ ...baseStyles.heading, color: foregroundFor(context) }),
|
||||
link: (context) => ({ ...baseStyles.link, color: foregroundFor(context) }),
|
||||
sectionHeading: (context) => ({ ...baseStyles.sectionHeading, color: accentFor(context) }),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -117,16 +118,10 @@ const useKakunaTemplate = (): KakunaTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -138,62 +133,6 @@ const useKakunaTemplate = (): KakunaTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -222,18 +161,6 @@ const useKakunaTemplate = (): KakunaTemplate => {
|
||||
alignItems: "center",
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -119,16 +120,10 @@ const useLaprasTemplate = (): LaprasTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -140,62 +135,6 @@ const useLaprasTemplate = (): LaprasTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -233,18 +172,6 @@ const useLaprasTemplate = (): LaprasTemplate => {
|
||||
backgroundColor: background,
|
||||
padding: metrics.gapX(1),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import { getPrimaryTint as getPrimaryAlpha } from "../shared/color-helpers";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
@@ -139,16 +140,10 @@ const useLeafishTemplate = (): LeafishTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -157,62 +152,6 @@ const useLeafishTemplate = (): LeafishTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -272,18 +211,6 @@ const useLeafishTemplate = (): LeafishTemplate => {
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 6),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
body: {
|
||||
flexDirection: r.row,
|
||||
columnGap: metrics.columnGap,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -120,16 +121,10 @@ const useMeowthTemplate = (): MeowthTemplate => {
|
||||
const primary = rgbaStringToHex(metadata.design.colors.primary);
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -141,62 +136,6 @@ const useMeowthTemplate = (): MeowthTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
inlineItemHeader: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "flex-start",
|
||||
@@ -270,18 +209,6 @@ const useMeowthTemplate = (): MeowthTemplate => {
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 6),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
sectionGroup: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -115,16 +116,10 @@ const useOnyxTemplate = (): OnyxTemplate => {
|
||||
const primary = rgbaStringToHex(metadata.design.colors.primary);
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -136,62 +131,6 @@ const useOnyxTemplate = (): OnyxTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -216,18 +155,6 @@ const useOnyxTemplate = (): OnyxTemplate => {
|
||||
borderBottomColor: primary,
|
||||
paddingBottom: metrics.page.paddingVertical,
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
headerTitle: {
|
||||
rowGap: metrics.gapY(0.5),
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -169,16 +170,10 @@ const usePikachuTemplate = (): PikachuTemplate => {
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -189,62 +184,6 @@ const usePikachuTemplate = (): PikachuTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -321,18 +260,6 @@ const usePikachuTemplate = (): PikachuTemplate => {
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 6),
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
});
|
||||
|
||||
const accentFor = ({ colors }: TemplateStyleContext) => colors.primary;
|
||||
@@ -347,7 +274,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
|
||||
colors,
|
||||
styles: {
|
||||
...baseStyles,
|
||||
text: (context) => ({ ...bodyText, color: foregroundFor(context) }),
|
||||
text: (context) => ({ ...base.text, color: foregroundFor(context) }),
|
||||
heading: (context) => ({ ...baseStyles.heading, color: foregroundFor(context) }),
|
||||
link: (context) => ({ ...baseStyles.link, color: foregroundFor(context) }),
|
||||
sectionHeading: (context) => ({ ...baseStyles.sectionHeading, color: accentFor(context) }),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -161,16 +162,10 @@ const useRhyhornTemplate = (): RhyhornTemplate => {
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
const contactGap = metrics.gapX(0.5);
|
||||
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -182,64 +177,6 @@ const useRhyhornTemplate = (): RhyhornTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
// Stays `row` for both LTR and RTL; the <li> renderer swaps DOM order for RTL.
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
// bodyText spread first so `textAlign` below isn't clobbered by bodyText.textAlign.
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -295,18 +232,6 @@ const useRhyhornTemplate = (): RhyhornTemplate => {
|
||||
columnGap: metrics.gapX(1 / 6),
|
||||
},
|
||||
contactItemLast: r.contactSeparatorClear,
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
sectionGroup: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
import { Image, Page, StyleSheet, View } from "#react-pdf-renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { createBaseTemplateStyles } from "../shared/base-template-styles";
|
||||
import {
|
||||
CustomFieldContactItem,
|
||||
EmailContactItem,
|
||||
@@ -109,16 +110,10 @@ const useScizorTemplate = (): ScizorTemplate => {
|
||||
const divider = "#D8DCE2";
|
||||
const colors: TemplateColorRoles = { foreground, background, primary };
|
||||
const metrics = getTemplateMetrics(metadata.page);
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
const base = createBaseTemplateStyles({ metadata, foreground, r, metrics, picture });
|
||||
|
||||
const baseStyles = StyleSheet.create({
|
||||
...base,
|
||||
page: {
|
||||
color: foreground,
|
||||
backgroundColor: background,
|
||||
@@ -132,63 +127,8 @@ const useScizorTemplate = (): ScizorTemplate => {
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
direction: r.pageDirection,
|
||||
},
|
||||
text: bodyText,
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "700",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
},
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
},
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
},
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
},
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "700",
|
||||
color: foreground,
|
||||
},
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
},
|
||||
richListItemRow: {
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
richListItemMarker: {
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
},
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
},
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
},
|
||||
alignEnd: {
|
||||
...r.alignEnd,
|
||||
},
|
||||
heading: { ...base.heading, fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "700" },
|
||||
bold: { fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "700", color: foreground },
|
||||
section: {
|
||||
flexDirection: "column",
|
||||
rowGap: metrics.gapY(0.25),
|
||||
@@ -253,18 +193,6 @@ const useScizorTemplate = (): ScizorTemplate => {
|
||||
columnGap: metrics.gapX(1 / 6),
|
||||
color: foreground,
|
||||
},
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
sections: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { Style } from "@react-pdf/types";
|
||||
import type { Picture, ResumeData } from "@reactive-resume/schema/resume/data";
|
||||
import type { getTemplateMetrics } from "./metrics";
|
||||
import type { createRtlStyleHelpers } from "./rtl";
|
||||
import { rgbaStringToHex } from "@reactive-resume/utils/color";
|
||||
|
||||
type BaseTemplateStylesInput = {
|
||||
metadata: ResumeData["metadata"];
|
||||
foreground: string;
|
||||
r: ReturnType<typeof createRtlStyleHelpers>;
|
||||
metrics: ReturnType<typeof getTemplateMetrics>;
|
||||
picture: Picture;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the ~20 byte-identical style slots shared by all 15 templates as plain objects.
|
||||
* Each template spreads the result into its own StyleSheet.create() call and overrides only
|
||||
* the slots that differ (heading fontWeight, bold fallback, inline gap, picture extras, etc.).
|
||||
*
|
||||
* ponytail: factory returns plain objects, not StyleSheet.create'd; each template does one
|
||||
* StyleSheet.create pass so the final resolved styles are identical to before.
|
||||
*/
|
||||
export function createBaseTemplateStyles({ metadata, foreground, r, metrics, picture }: BaseTemplateStylesInput) {
|
||||
const bodyText = {
|
||||
fontFamily: metadata.typography.body.fontFamily,
|
||||
fontSize: metadata.typography.body.fontSize,
|
||||
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
|
||||
lineHeight: metadata.typography.body.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style;
|
||||
|
||||
return {
|
||||
/** The canonical body text style; alias for `text` in StyleSheet slots. */
|
||||
text: bodyText,
|
||||
|
||||
heading: {
|
||||
fontFamily: metadata.typography.heading.fontFamily,
|
||||
fontSize: metadata.typography.heading.fontSize,
|
||||
/** Default fallback "600". bronzor uses fontWeights[0]??"500"; scizor uses .at(-1)??"700". */
|
||||
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
|
||||
lineHeight: metadata.typography.heading.lineHeight,
|
||||
color: foreground,
|
||||
...r.text,
|
||||
} satisfies Style,
|
||||
|
||||
div: {
|
||||
rowGap: metrics.gapY(0.125),
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
} satisfies Style,
|
||||
|
||||
/** Default gap = gapX(1/3). chikorita overrides to gapX(0.25). */
|
||||
inline: {
|
||||
flexDirection: r.row,
|
||||
alignItems: "center",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
} satisfies Style,
|
||||
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
color: foreground,
|
||||
} satisfies Style,
|
||||
|
||||
small: {
|
||||
fontSize: metadata.typography.body.fontSize * 0.875,
|
||||
} satisfies Style,
|
||||
|
||||
/** Default fallback "600". scizor overrides to "700". */
|
||||
bold: {
|
||||
fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600",
|
||||
} satisfies Style,
|
||||
|
||||
richParagraph: {
|
||||
margin: 0,
|
||||
...bodyText,
|
||||
} satisfies Style,
|
||||
|
||||
richListItemRow: {
|
||||
// Stays `row` for both LTR and RTL; the <li> renderer swaps DOM order for RTL.
|
||||
flexDirection: "row",
|
||||
columnGap: metrics.gapX(1 / 3),
|
||||
alignItems: "flex-start",
|
||||
} satisfies Style,
|
||||
|
||||
richListItemMarker: {
|
||||
// bodyText spread first so `textAlign` below isn't clobbered by bodyText.textAlign.
|
||||
...bodyText,
|
||||
width: metadata.typography.body.fontSize,
|
||||
textAlign: r.listMarkerTextAlign,
|
||||
} satisfies Style,
|
||||
|
||||
richListItemContent: {
|
||||
...bodyText,
|
||||
flex: 1,
|
||||
} satisfies Style,
|
||||
|
||||
splitRow: {
|
||||
flexDirection: r.row,
|
||||
flexWrap: "wrap",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
columnGap: metrics.gapX(2 / 3),
|
||||
} satisfies Style,
|
||||
|
||||
alignEnd: { ...r.alignEnd } satisfies Style,
|
||||
|
||||
/**
|
||||
* Standard picture style shared by 14/15 templates.
|
||||
* ditto overrides: spread this and add `position: "absolute"`, `top`, `left`, `marginLeft`.
|
||||
*/
|
||||
picture: {
|
||||
width: picture.size,
|
||||
height: picture.size,
|
||||
objectFit: "cover",
|
||||
aspectRatio: picture.aspectRatio,
|
||||
borderRadius: picture.borderRadius,
|
||||
borderColor: rgbaStringToHex(picture.borderColor),
|
||||
borderWidth: picture.borderWidth,
|
||||
shadowColor: rgbaStringToHex(picture.shadowColor),
|
||||
shadowWidth: picture.shadowWidth,
|
||||
transform: `rotate(${picture.rotation}deg)`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const templatesDir = fileURLToPath(new URL("../", import.meta.url));
|
||||
// ponytail: richListItemContent moved to shared factory; guard the factory directly.
|
||||
const factoryFile = fileURLToPath(new URL("./base-template-styles.ts", import.meta.url));
|
||||
|
||||
const templatePageFiles = readdirSync(templatesDir, { withFileTypes: true }).flatMap((entry) => {
|
||||
if (!entry.isDirectory() || entry.name === "shared") return [];
|
||||
@@ -15,14 +17,31 @@ const templatePageFiles = readdirSync(templatesDir, { withFileTypes: true }).fla
|
||||
});
|
||||
|
||||
describe("rich text template styles", () => {
|
||||
it.each(
|
||||
templatePageFiles.map((file) => [basename(file), file]),
|
||||
)("%s keeps list item rich text on the global body line height", (_name, file) => {
|
||||
const source = readFileSync(file, "utf8");
|
||||
// The shared factory owns richListItemContent for all 15 templates.
|
||||
it("base factory keeps list item rich text on the global body line height", () => {
|
||||
const source = readFileSync(factoryFile, "utf8");
|
||||
const richListItemContentBlock = source.match(/richListItemContent:\s*{(?<body>[\s\S]*?)^\s*},/m);
|
||||
|
||||
expect(richListItemContentBlock?.groups?.body).toBeDefined();
|
||||
expect(richListItemContentBlock?.groups?.body).toContain("...bodyText");
|
||||
expect(richListItemContentBlock?.groups?.body).not.toMatch(/\blineHeight:/);
|
||||
});
|
||||
|
||||
it.each(
|
||||
templatePageFiles.map((file) => [basename(file), file]),
|
||||
)("%s keeps list item rich text on the global body line height", (_name, file) => {
|
||||
const source = readFileSync(file, "utf8");
|
||||
|
||||
if (source.includes("createBaseTemplateStyles")) {
|
||||
// Factory handles richListItemContent; guard is on the factory test above.
|
||||
expect(source).toContain("createBaseTemplateStyles");
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy: template defines richListItemContent inline — no lineHeight override allowed.
|
||||
const richListItemContentBlock = source.match(/richListItemContent:\s*{(?<body>[\s\S]*?)^\s*},/m);
|
||||
expect(richListItemContentBlock?.groups?.body).toBeDefined();
|
||||
expect(richListItemContentBlock?.groups?.body).toContain("...bodyText");
|
||||
expect(richListItemContentBlock?.groups?.body).not.toMatch(/\blineHeight:/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user