perf: 🎨 remove fragment imports, optimize templates

This commit is contained in:
Amruth Pillai
2023-11-06 22:37:32 +01:00
parent 2d35057e57
commit fca61543c5
42 changed files with 742 additions and 661 deletions

View File

@ -0,0 +1,12 @@
import { templatesList } from "@reactive-resume/templates";
import { useMemo } from "react";
export const useTemplate = (templateId?: string) => {
const template = useMemo(() => {
return templatesList.find((template) => template.id === templateId);
}, [templateId]);
if (!template || !template.Component) return null;
return template;
};

View File

@ -1,4 +1,5 @@
export * from "./hooks/use-breakpoint";
export * from "./hooks/use-form-field";
export * from "./hooks/use-password-toggle";
export * from "./hooks/use-template";
export * from "./hooks/use-theme";

View File

@ -2,6 +2,7 @@ import { z } from "zod";
export const customFieldSchema = z.object({
id: z.string().cuid2(),
icon: z.string(),
name: z.string(),
value: z.string(),
});

View File

@ -7,7 +7,7 @@ export const customSectionSchema = itemSchema.extend({
name: z.string(),
description: z.string(),
date: z.string(),
level: z.number().min(0).max(5).default(0),
location: z.string(),
summary: z.string(),
keywords: z.array(z.string()).default([]),
url: urlSchema,
@ -22,7 +22,7 @@ export const defaultCustomSection: CustomSection = {
name: "",
description: "",
date: "",
level: 0,
location: "",
summary: "",
keywords: [],
url: defaultUrl,

View File

@ -33,34 +33,48 @@ export const FrameWrapper = ({ children, style }: Props) => {
setHeight(`${height}px`);
}, []);
const loadFonts = useCallback(
(frame: HTMLIFrameElement) => {
if (font.family === "CMU Serif") {
return webfontloader.load({
classes: false,
custom: {
families: ["CMU Serif"],
urls: ["https://cdn.jsdelivr.net/npm/computer-modern/cmu-serif.min.css"],
},
context: frame.contentWindow!,
fontactive: () => handleResize(frame!),
});
}
webfontloader.load({
classes: false,
google: { families: [fontString] },
context: frame.contentWindow!,
fontactive: () => handleResize(frame!),
});
},
[font, fontString, handleResize],
);
const loadIconFonts = useCallback((frame: HTMLIFrameElement) => {
const document = frame.contentDocument!;
const link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "https://unpkg.com/@phosphor-icons/web@2.0.3/src/regular/style.css";
document.head.appendChild(link);
}, []);
const onLoad = useCallback(() => {
if (!frameRef.current) return;
handleResize(frameRef.current);
if (font.family === "CMU Serif") {
return webfontloader.load({
classes: false,
custom: {
families: ["CMU Serif"],
urls: ["https://cdn.jsdelivr.net/npm/computer-modern/cmu-serif.min.css"],
},
context: frameRef.current.contentWindow!,
fontactive: () => {
handleResize(frameRef.current!);
},
});
}
webfontloader.load({
classes: false,
google: { families: [fontString] },
context: frameRef.current.contentWindow!,
fontactive: () => {
handleResize(frameRef.current!);
},
});
}, [frameRef, font, fontString, handleResize]);
loadFonts(frameRef.current);
loadIconFonts(frameRef.current);
}, [frameRef, handleResize, loadFonts, loadIconFonts]);
useEffect(() => {
onLoad();

View File

@ -41,17 +41,14 @@ export const Shared = css<GlobalStyleProps>`
h1 {
font-size: 2rem;
line-height: calc(var(--line-height) + 1.5rem);
}
h2 {
font-size: 1.5rem;
line-height: calc(var(--line-height) + 1rem);
}
h3 {
font-size: 1rem;
line-height: calc(var(--line-height) + 0rem);
}
/* Paragraphs */
@ -67,7 +64,7 @@ export const Shared = css<GlobalStyleProps>`
small {
font-size: calc(var(--font-size) - 2px);
line-height: calc(var(--line-height) - 0.5rem);
line-height: calc(var(--line-height) - 2px);
}
i,

View File

@ -1 +1,16 @@
export * from "./rhyhorn";
import { TemplateProps } from "../shared";
import { Rhyhorn } from "./rhyhorn";
type Template = {
id: string;
name: string;
Component: (props: TemplateProps) => JSX.Element;
};
export const templatesList: Template[] = [
{
id: "rhyhorn",
name: "Rhyhorn",
Component: Rhyhorn,
},
];

View File

@ -16,7 +16,7 @@ import { References } from "./sections/references";
import { Skills } from "./sections/skills";
import { Summary } from "./sections/summary";
import { Volunteer } from "./sections/volunteer";
import { RhyhornStyles } from "./style";
import { RhyhornWrapper } from "./style";
const sectionMap: Partial<Record<SectionKey, () => React.ReactNode>> = {
summary: Summary,
@ -44,7 +44,7 @@ const getSection = (id: SectionKey) => {
};
export const Rhyhorn = ({ isFirstPage, columns }: TemplateProps) => (
<RhyhornStyles>
<RhyhornWrapper>
{isFirstPage && <Header />}
{/* Main */}
@ -52,5 +52,5 @@ export const Rhyhorn = ({ isFirstPage, columns }: TemplateProps) => (
{/* Sidebar */}
{columns[1].map(getSection)}
</RhyhornStyles>
</RhyhornWrapper>
);

View File

@ -1,7 +1,6 @@
import { Award as IAward } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,24 +11,27 @@ export const Awards = () => {
<SectionBase<IAward>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.title}</h6>
</a>
) : (
<h6>{item.title}</h6>
)}
<h6>{item.title}</h6>
<p>{item.awarder}</p>
</div>
<div>
<h6>{item.date}</h6>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,7 +1,6 @@
import { Certification as ICertification } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,24 +11,27 @@ export const Certifications = () => {
<SectionBase<ICertification>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.name}</h6>
</a>
) : (
<h6>{item.name}</h6>
)}
<h6>{item.name}</h6>
<p>{item.issuer}</p>
</div>
<div>
<h6>{item.date}</h6>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -6,7 +6,6 @@ import {
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -24,30 +23,32 @@ export const CustomSection = ({ id }: Props) => {
<SectionBase<ICustomSection>
section={section}
header={(item: CustomSectionItem) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.name}</h6>
</a>
) : (
<h6>{item.name}</h6>
)}
<h6>{item.name}</h6>
<p>{item.description}</p>
</div>
<div>
<h6>{item.date}</h6>
<div className="rating">
{Array.from({ length: item.level }).map((_, i) => (
<span key={i} />
))}
</div>
<p>{item.location}</p>
</div>
</Fragment>
</>
)}
main={(item: CustomSectionItem) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item: CustomSectionItem) => <small>{item.keywords.join(", ")}</small>}
footer={(item: CustomSectionItem) => (
<>
<small>{item.keywords.join(", ")}</small>
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
</>
)}
/>
);
};

View File

@ -1,7 +1,6 @@
import { Education as IEducation } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,15 +11,9 @@ export const Education = () => {
<SectionBase<IEducation>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.institution}</h6>
</a>
) : (
<h6>{item.institution}</h6>
)}
<h6>{item.institution}</h6>
<p>{item.area}</p>
</div>
@ -31,9 +24,18 @@ export const Education = () => {
{item.score ? ` | ${item.score}` : ""}
</p>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,7 +1,6 @@
import { Experience as IExperience } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,15 +11,9 @@ export const Experience = () => {
<SectionBase<IExperience>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.company}</h6>
</a>
) : (
<h6>{item.company}</h6>
)}
<h6>{item.company}</h6>
<p>{item.position}</p>
</div>
@ -28,9 +21,18 @@ export const Experience = () => {
<h6>{item.date}</h6>
<p>{item.location}</p>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,11 +1,12 @@
import { Picture, useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
export const Header = () => {
const basics = useStore((state) => state.basics);
return (
<div className="header">
{basics.picture.url && !basics.picture.effects.hidden && (
{isUrl(basics.picture.url) && !basics.picture.effects.hidden && (
<Picture
alt={basics.name}
src={basics.picture.url}

View File

@ -1,6 +1,5 @@
import { Interest as IInterest } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -11,14 +10,14 @@ export const Interests = () => {
<SectionBase<IInterest>
section={section}
header={(item) => (
<Fragment>
<>
<div>
<h6>{item.name}</h6>
<p>{item.keywords.join(", ")}</p>
</div>
<div />
</Fragment>
</>
)}
/>
);

View File

@ -1,7 +1,6 @@
import { Language as ILanguage } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { getCEFRLevel } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,14 +11,14 @@ export const Languages = () => {
<SectionBase<ILanguage>
section={section}
header={(item) => (
<Fragment>
<>
<div>
<h6>{item.name}</h6>
<p>{item.fluency || getCEFRLevel(item.fluencyLevel)}</p>
</div>
<div />
</Fragment>
</>
)}
/>
);

View File

@ -1,7 +1,6 @@
import { Profile as IProfile } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import styled from "styled-components";
import { SectionBase } from "../shared/section-base";
@ -18,7 +17,7 @@ export const Profiles = () => {
<SectionBase<IProfile>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{item.icon && (
<i>
@ -42,7 +41,7 @@ export const Profiles = () => {
</div>
<div />
</Fragment>
</>
)}
/>
);

View File

@ -1,7 +1,6 @@
import { Project as IProject } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,25 +11,31 @@ export const Projects = () => {
<SectionBase<IProject>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.name}</h6>
</a>
) : (
<h6>{item.name}</h6>
)}
<h6>{item.name}</h6>
<p>{item.description}</p>
</div>
<div>
<h6>{item.date}</h6>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => <small>{item.keywords.join(", ")}</small>}
footer={(item) => (
<>
<small>{item.keywords.join(", ")}</small>
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
</>
)}
/>
);
};

View File

@ -1,7 +1,6 @@
import { Publication as IPublication } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,24 +11,27 @@ export const Publications = () => {
<SectionBase<IPublication>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.name}</h6>
</a>
) : (
<h6>{item.name}</h6>
)}
<h6>{item.name}</h6>
<p>{item.publisher}</p>
</div>
<div>
<h6>{item.date}</h6>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,7 +1,6 @@
import { Reference as IReference } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,22 +11,25 @@ export const References = () => {
<SectionBase<IReference>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.name}</h6>
</a>
) : (
<h6>{item.name}</h6>
)}
<h6>{item.name}</h6>
<p>{item.description}</p>
</div>
<div />
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,6 +1,5 @@
import { Skill as ISkill } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -11,14 +10,14 @@ export const Skills = () => {
<SectionBase<ISkill>
section={section}
header={(item) => (
<Fragment>
<>
<div>
<h6>{item.name}</h6>
<p>{item.description}</p>
</div>
<div />
</Fragment>
</>
)}
footer={(item) => <small>{item.keywords.join(", ")}</small>}
/>

View File

@ -1,7 +1,5 @@
import { useStore } from "@reactive-resume/templates";
import { Heading } from "../shared/heading";
export const Summary = () => {
const section = useStore((state) => state.sections.summary);
@ -9,7 +7,7 @@ export const Summary = () => {
return (
<section id={section.id} className={`section section__${section.id}`}>
<Heading>{section.name}</Heading>
<h4 className="section__heading">{section.name}</h4>
<main className="section__item-content">
<div

View File

@ -1,7 +1,6 @@
import { Volunteer as IVolunteer } from "@reactive-resume/schema";
import { useStore } from "@reactive-resume/templates";
import { isUrl } from "@reactive-resume/utils";
import { Fragment } from "react";
import { SectionBase } from "../shared/section-base";
@ -12,15 +11,9 @@ export const Volunteer = () => {
<SectionBase<IVolunteer>
section={section}
header={(item) => (
<Fragment>
<>
<div>
{isUrl(item.url.href) ? (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.organization}</h6>
</a>
) : (
<h6>{item.organization}</h6>
)}
<h6>{item.organization}</h6>
<p>{item.position}</p>
</div>
@ -28,9 +21,18 @@ export const Volunteer = () => {
<h6>{item.date}</h6>
<p>{item.location}</p>
</div>
</Fragment>
</>
)}
main={(item) => <div dangerouslySetInnerHTML={{ __html: item.summary }} />}
footer={(item) => (
<div>
{isUrl(item.url.href) && (
<a href={item.url.href} target="_blank" rel="noopener noreferrer nofollow">
<h6>{item.url.label || item.url.href}</h6>
</a>
)}
</div>
)}
/>
);
};

View File

@ -1,10 +0,0 @@
import styled from "styled-components";
export const Heading = styled.h4`
font-size: 0.9rem;
line-height: 1.2rem;
padding-bottom: 2px;
margin-bottom: 6px;
text-transform: uppercase;
border-bottom: 1px solid var(--color-text);
`;

View File

@ -1,8 +1,6 @@
import { Item, SectionItem, SectionWithItem } from "@reactive-resume/schema";
import { ItemGrid } from "@reactive-resume/templates";
import { Heading } from "./heading";
type Props<T extends Item> = {
section: SectionWithItem<T>;
header?: (item: T) => React.ReactNode;
@ -15,7 +13,7 @@ export const SectionBase = <T extends SectionItem>({ section, header, main, foot
return (
<section id={section.id} className={`section section__${section.id}`}>
<Heading>{section.name}</Heading>
<h4 className="section__heading">{section.name}</h4>
<ItemGrid className="section__items" $columns={section.columns}>
{section.items

View File

@ -1,6 +1,6 @@
import styled from "styled-components";
export const RhyhornStyles = styled.div`
export const RhyhornWrapper = styled.div`
display: grid;
row-gap: 16px;
@ -21,13 +21,17 @@ export const RhyhornStyles = styled.div`
line-height: calc(var(--line-height) + 0.5rem);
}
&__headline {
color: var(--color-primary);
}
&__meta {
font-size: 0.875rem;
line-height: var(--line-height);
span {
padding: 0 6px;
border-right: 1px solid var(--color-primary);
border-right: 1px solid currentColor;
&:first-child {
padding-left: 0;
@ -41,6 +45,16 @@ export const RhyhornStyles = styled.div`
}
.section {
&__heading {
font-size: 0.9rem;
line-height: 1.2rem;
padding-bottom: 2px;
margin-bottom: 6px;
text-transform: uppercase;
color: var(--color-primary);
border-bottom: 1px solid var(--color-text);
}
&__item {
display: flex;
flex-direction: column;

View File

@ -10,16 +10,18 @@ export const HoverCardContent = forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 6, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 rounded-md border bg-background p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
<HoverCardPrimitive.Portal>
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 rounded-sm border bg-background p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</HoverCardPrimitive.Portal>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;

View File

@ -312,16 +312,6 @@ export const fonts: Font[] = [
"http://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf",
},
},
{
family: "Material Icons",
category: "monospace",
subsets: ["latin"],
variants: ["regular"],
files: {
regular:
"http://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNZIhI8tIHh.ttf",
},
},
{
family: "Roboto Mono",
category: "monospace",