mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-25 17:34:52 +10:00
chore: lint using react-doctor, update translations, dynamic imports
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { Style } from "@react-pdf/types";
|
||||
import type { CustomField } from "@reactive-resume/schema/resume/data";
|
||||
import type { IconName } from "phosphor-icons-react-pdf/dynamic";
|
||||
import { View } from "@react-pdf/renderer";
|
||||
import { View } from "../../renderer";
|
||||
import { getCustomFieldLinkUrl, getWebsiteDisplayText } from "./contact";
|
||||
import { Icon, Link, Text } from "./primitives";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
TemplateStyleSlot,
|
||||
TemplateStyleSlots,
|
||||
} from "./types";
|
||||
import { createContext, useContext } from "react";
|
||||
import { createContext, use } from "react";
|
||||
|
||||
type TemplateContextValue = {
|
||||
styles: TemplateStyleSlots;
|
||||
@@ -50,11 +50,14 @@ type TemplateStyleContextValue = {
|
||||
colors: TemplateColorRoles;
|
||||
};
|
||||
|
||||
const EMPTY_FEATURE_STYLES: TemplateFeatureStyleSlots = {};
|
||||
const EMPTY_FEATURES: TemplateFeatures = {};
|
||||
|
||||
export const TemplateProvider = ({
|
||||
styles,
|
||||
featureStyles = {},
|
||||
featureStyles = EMPTY_FEATURE_STYLES,
|
||||
colors,
|
||||
features = {},
|
||||
features = EMPTY_FEATURES,
|
||||
children,
|
||||
}: TemplateProviderProps) => {
|
||||
return (
|
||||
@@ -73,7 +76,7 @@ export const TemplatePlacementProvider = ({
|
||||
};
|
||||
|
||||
const useTemplateContext = () => {
|
||||
const context = useContext(TemplateContext);
|
||||
const context = use(TemplateContext);
|
||||
|
||||
if (!context) throw new Error("useTemplateContext must be called inside a <TemplateProvider>.");
|
||||
|
||||
@@ -86,7 +89,7 @@ export const useTemplateFeature = (feature: keyof TemplateFeatures): boolean =>
|
||||
return features[feature] ?? false;
|
||||
};
|
||||
|
||||
export const useTemplatePlacement = () => useContext(TemplatePlacementContext);
|
||||
export const useTemplatePlacement = () => use(TemplatePlacementContext);
|
||||
|
||||
const useTemplateStyleContext = (): TemplateStyleContextValue => {
|
||||
const { colors } = useTemplateContext();
|
||||
|
||||
@@ -81,9 +81,14 @@ const filterExperienceRoles = <T extends HiddenItem>(item: T, sectionType?: stri
|
||||
};
|
||||
|
||||
export const filterItems = <T extends HiddenItem>(items: T[], sectionType?: string): T[] => {
|
||||
return items
|
||||
.filter((item) => !item.hidden && hasValidPrimaryTitle(item, sectionType))
|
||||
.map((item) => filterExperienceRoles(item, sectionType));
|
||||
const visibleItems: T[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item.hidden || !hasValidPrimaryTitle(item, sectionType)) continue;
|
||||
visibleItems.push(filterExperienceRoles(item, sectionType));
|
||||
}
|
||||
|
||||
return visibleItems;
|
||||
};
|
||||
|
||||
export const hasVisibleItems = (section: ItemSectionLike, sectionType?: string): boolean => {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { Style } from "@react-pdf/types";
|
||||
import type { IconName } from "phosphor-icons-react-pdf/dynamic";
|
||||
import { View } from "@react-pdf/renderer";
|
||||
import { useRender } from "../../context";
|
||||
import { View } from "../../renderer";
|
||||
import { useTemplateIconSlot, useTemplateStyle } from "./context";
|
||||
import { getTemplateMetrics } from "./metrics";
|
||||
import { Icon } from "./primitives";
|
||||
import { composeStyles } from "./styles";
|
||||
|
||||
const LEVEL_ITEM_KEYS = ["level-1", "level-2", "level-3", "level-4", "level-5"] as const;
|
||||
|
||||
export const LevelDisplay = ({ level }: { level: number }) => {
|
||||
const data = useRender();
|
||||
const levelDesign = data.metadata.design.level;
|
||||
@@ -40,13 +42,13 @@ export const LevelDisplay = ({ level }: { level: number }) => {
|
||||
levelContainerStyle,
|
||||
)}
|
||||
>
|
||||
{Array.from({ length: 5 }).map((_, index) => {
|
||||
{LEVEL_ITEM_KEYS.map((itemKey, index) => {
|
||||
const isActive = index < level;
|
||||
|
||||
if (levelDesign.type === "icon") {
|
||||
return (
|
||||
<Icon
|
||||
key={index}
|
||||
key={itemKey}
|
||||
size={iconSize + 4}
|
||||
name={levelDesign.icon as IconName}
|
||||
style={{ opacity: isActive ? 1 : 0.35 }}
|
||||
@@ -57,7 +59,7 @@ export const LevelDisplay = ({ level }: { level: number }) => {
|
||||
if (levelDesign.type === "progress-bar") {
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
key={itemKey}
|
||||
style={composeStyles(
|
||||
{
|
||||
flex: 1,
|
||||
@@ -92,7 +94,7 @@ export const LevelDisplay = ({ level }: { level: number }) => {
|
||||
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
key={itemKey}
|
||||
style={composeStyles(
|
||||
{
|
||||
width,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Style } from "@react-pdf/types";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { StyleInput } from "./styles";
|
||||
import { Link as PdfLink, Text as PdfText, View } from "@react-pdf/renderer";
|
||||
import { Icon as PhosphorIcon } from "phosphor-icons-react-pdf/dynamic";
|
||||
import { Link as PdfLink, Text as PdfText, View } from "../../renderer";
|
||||
import { useTemplateIconSlot, useTemplateStyle } from "./context";
|
||||
import { composeLinkStyles, composeStyles } from "./styles";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ReactElement } from "react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Text as PdfText } from "@react-pdf/renderer";
|
||||
import { renderHtml } from "react-pdf-html";
|
||||
import { Text as PdfText } from "../../renderer";
|
||||
import { normalizeRichTextHtml, richTextMarkClassName } from "./rich-text-html";
|
||||
|
||||
type PdfElement = ReactElement<{ children?: unknown; element?: { tag: string } }>;
|
||||
|
||||
@@ -29,12 +29,19 @@ const getTagName = (node: Node) => node.rawTagName.toLowerCase();
|
||||
const hasBlockDescendant = (node: Node): boolean =>
|
||||
node.childNodes.some((child) => child.nodeType === NodeType.ELEMENT_NODE && !isInlineNode(child));
|
||||
|
||||
const mergeClassNames = (...classNames: (string | undefined)[]): string =>
|
||||
classNames
|
||||
.flatMap((className) => className?.split(/\s+/) ?? [])
|
||||
.filter(Boolean)
|
||||
.filter((className, index, classNames) => classNames.indexOf(className) === index)
|
||||
.join(" ");
|
||||
const mergeClassNames = (...classNames: (string | undefined)[]): string => {
|
||||
const uniqueClassNames = new Set<string>();
|
||||
|
||||
for (const className of classNames) {
|
||||
if (!className) continue;
|
||||
|
||||
for (const part of className.split(/\s+/)) {
|
||||
if (part) uniqueClassNames.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return [...uniqueClassNames].join(" ");
|
||||
};
|
||||
|
||||
const normalizeMarkElements = (root: ReturnType<typeof parse>) => {
|
||||
for (const mark of root.querySelectorAll("mark")) {
|
||||
|
||||
@@ -83,13 +83,29 @@ const getRootElement = (element: RichTextSpacingElement): RichTextSpacingElement
|
||||
return root;
|
||||
};
|
||||
|
||||
const getTopLevelFlowElements = (root: RichTextSpacingElement): RichTextSpacingElement[] =>
|
||||
(root.childNodes ?? []).filter(isElementNode).flatMap((child) => {
|
||||
if (isRichTextTag(child, "p")) return [child];
|
||||
if (!isRichTextTag(child, "ul", "ol")) return [];
|
||||
const getTopLevelFlowElements = (root: RichTextSpacingElement): RichTextSpacingElement[] => {
|
||||
const flowElements: RichTextSpacingElement[] = [];
|
||||
|
||||
return (child.childNodes ?? []).filter(isElementNode).filter((listChild) => isRichTextTag(listChild, "li"));
|
||||
});
|
||||
for (const childNode of root.childNodes ?? []) {
|
||||
if (!isElementNode(childNode)) continue;
|
||||
const child = childNode;
|
||||
|
||||
if (isRichTextTag(child, "p")) {
|
||||
flowElements.push(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isRichTextTag(child, "ul", "ol")) continue;
|
||||
|
||||
for (const listChildNode of child.childNodes ?? []) {
|
||||
if (isElementNode(listChildNode) && isRichTextTag(listChildNode, "li")) {
|
||||
flowElements.push(listChildNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flowElements;
|
||||
};
|
||||
|
||||
export const createRichTextProseSpacing = (bodyLineHeight: number | undefined): RichTextProseSpacing => {
|
||||
if (bodyLineHeight === undefined) return { paragraph: {}, listItem: {} };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Style } from "@react-pdf/types";
|
||||
import { Text as PdfText, View } from "@react-pdf/renderer";
|
||||
import { Html } from "react-pdf-html";
|
||||
import { Text as PdfText, View } from "../../renderer";
|
||||
import { useTemplateStyle } from "./context";
|
||||
import { safeTextStyle } from "./primitives";
|
||||
import { normalizeRichTextHtml, richTextMarkClassName } from "./rich-text-html";
|
||||
|
||||
@@ -19,10 +19,10 @@ import type { IconName } from "phosphor-icons-react-pdf/dynamic";
|
||||
import type { ReactNode } from "react";
|
||||
import type { StyleInput, TemplatePlacement } from "./styles";
|
||||
import type { CustomItemSection, ItemSection } from "./types";
|
||||
import { View } from "@react-pdf/renderer";
|
||||
import { Children, createContext, useContext } from "react";
|
||||
import { Children, createContext, isValidElement, use } from "react";
|
||||
import { match } from "ts-pattern";
|
||||
import { useRender } from "../../context";
|
||||
import { View } from "../../renderer";
|
||||
import { getResumeSectionTitle } from "../../section-title";
|
||||
import { getSectionItemRows, getSectionItemsLayout, shouldUseSectionTimeline } from "./columns";
|
||||
import { getWebsiteDisplayText } from "./contact";
|
||||
@@ -48,8 +48,25 @@ type SectionItemsContextValue = {
|
||||
};
|
||||
|
||||
const SectionItemsContext = createContext<SectionItemsContextValue>({ itemStyle: undefined, useTimeline: false });
|
||||
const SECTION_ITEM_PLACEHOLDER_KEYS = [
|
||||
"placeholder-1",
|
||||
"placeholder-2",
|
||||
"placeholder-3",
|
||||
"placeholder-4",
|
||||
"placeholder-5",
|
||||
"placeholder-6",
|
||||
] as const;
|
||||
|
||||
const useSectionItemsContext = () => useContext(SectionItemsContext);
|
||||
const useSectionItemsContext = () => use(SectionItemsContext);
|
||||
|
||||
const getChildKey = (child: ReactNode, fallbackIndex: number) => {
|
||||
return isValidElement(child) && child.key !== null ? String(child.key) : `child-${fallbackIndex}`;
|
||||
};
|
||||
|
||||
const getRowKey = (row: ReactNode[], rowIndex: number) => {
|
||||
const childKeys = row.map(getChildKey).join("|");
|
||||
return childKeys || `row-${rowIndex}`;
|
||||
};
|
||||
|
||||
const getVisibleItems = <T extends { hidden: boolean }>(section: ItemSection<T>, sectionType?: string): T[] => {
|
||||
if (!hasVisibleItems(section, sectionType)) return [];
|
||||
@@ -109,10 +126,10 @@ const SectionItems = ({ children, columns = 1 }: { children: ReactNode; columns?
|
||||
<SectionItemsContext.Provider value={context}>
|
||||
<View style={composeStyles(layout.containerStyle, sectionItemsStyle)}>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<View key={rowIndex} style={composeStyles(layout.rowStyle)}>
|
||||
<View key={getRowKey(row, rowIndex)} style={composeStyles(layout.rowStyle)}>
|
||||
{row}
|
||||
{Array.from({ length: layout.columns - row.length }, (_, index) => (
|
||||
<View key={`placeholder-${index}`} style={composeStyles(layout.itemStyle)} />
|
||||
{SECTION_ITEM_PLACEHOLDER_KEYS.slice(0, layout.columns - row.length).map((placeholderKey) => (
|
||||
<View key={placeholderKey} style={composeStyles(layout.itemStyle)} />
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user