mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-13 16:22:59 +10:00
design nosepass template, add tests, add template previews
This commit is contained in:
@ -8,6 +8,7 @@ type PictureProps = {
|
||||
|
||||
export const Picture = ({ className }: PictureProps) => {
|
||||
const picture = useArtboardStore((state) => state.resume.basics.picture);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
if (!isUrl(picture.url) || picture.effects.hidden) return null;
|
||||
|
||||
@ -15,11 +16,17 @@ export const Picture = ({ className }: PictureProps) => {
|
||||
<img
|
||||
src={picture.url}
|
||||
alt="Profile"
|
||||
className={cn("relative z-20 object-cover", className)}
|
||||
className={cn(
|
||||
"relative z-20 object-cover",
|
||||
picture.effects.border && "border-primary",
|
||||
picture.effects.grayscale && "grayscale",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
maxWidth: `${picture.size}px`,
|
||||
aspectRatio: `${picture.aspectRatio}`,
|
||||
borderRadius: `${picture.borderRadius}px`,
|
||||
borderWidth: `${picture.effects.border ? fontSize / 3 : 0}px`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { SectionKey } from "@reactive-resume/schema";
|
||||
import { pageSizeMap, TemplateKey } from "@reactive-resume/utils";
|
||||
import { pageSizeMap, Template } from "@reactive-resume/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
||||
@ -12,7 +12,7 @@ export const BuilderLayout = () => {
|
||||
const transformRef = useRef<ReactZoomPanPinchRef>(null);
|
||||
const format = useArtboardStore((state) => state.resume.metadata.page.format);
|
||||
const layout = useArtboardStore((state) => state.resume.metadata.layout);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as TemplateKey);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
|
||||
|
||||
const Template = useMemo(() => getTemplate(template), [template]);
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { SectionKey } from "@reactive-resume/schema";
|
||||
import { TemplateKey } from "@reactive-resume/utils";
|
||||
import { Template } from "@reactive-resume/utils";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Page } from "../components/page";
|
||||
@ -8,7 +8,7 @@ import { getTemplate } from "../templates";
|
||||
|
||||
export const PreviewLayout = () => {
|
||||
const layout = useArtboardStore((state) => state.resume.metadata.layout);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as TemplateKey);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
|
||||
|
||||
const Template = useMemo(() => getTemplate(template), [template]);
|
||||
|
||||
|
||||
@ -19,5 +19,5 @@
|
||||
}
|
||||
|
||||
.wysiwyg {
|
||||
@apply prose max-w-none text-current prose-headings:mt-0 prose-headings:mb-2 prose-p:mt-0 prose-p:mb-2 prose-ul:mt-0 prose-ul:mb-2 prose-li:mt-0 prose-li:mb-2 prose-ol:mt-0 prose-ol:mb-2 prose-img:mt-0 prose-img:mb-2 prose-hr:mt-0 prose-hr:mb-2 prose-p:leading-normal;
|
||||
@apply prose max-w-none text-current prose-headings:mt-0 prose-headings:mb-2 prose-p:mt-0 prose-p:mb-2 prose-ul:mt-0 prose-ul:mb-2 prose-li:mt-0 prose-li:mb-2 prose-ol:mt-0 prose-ol:mb-2 prose-img:mt-0 prose-img:mb-2 prose-hr:mt-0 prose-hr:mb-2 prose-p:leading-normal prose-li:leading-normal;
|
||||
}
|
||||
|
||||
@ -79,11 +79,11 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<div className="mb-2 hidden font-bold uppercase text-primary group-[.main]:block">
|
||||
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
|
||||
<h4>{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="mb-2 hidden items-center gap-x-2 text-center font-bold uppercase text-primary group-[.sidebar]:flex">
|
||||
<div className="mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
<h4>{section.name}</h4>
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
@ -162,18 +162,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<div className="mb-2 hidden font-bold uppercase text-primary group-[.main]:block">
|
||||
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
|
||||
<h4>{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mb-2 hidden items-center gap-x-2 text-center font-bold uppercase text-primary group-[.sidebar]:flex">
|
||||
<div className="mx-auto mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
<h4>{section.name}</h4>
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grid gap-3 group-[.sidebar]:mx-auto group-[.sidebar]:text-center"
|
||||
className="grid gap-x-6 gap-y-3 group-[.sidebar]:mx-auto group-[.sidebar]:text-center"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -193,19 +193,19 @@ const Section = <T,>({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="leading-snug">{children?.(item as T)}</div>
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
<div className="absolute left-[-4.5px] top-px hidden h-[8px] w-[8px] rounded-full bg-primary group-[.main]:block" />
|
||||
</div>
|
||||
@ -223,7 +223,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -258,7 +258,7 @@ const Experience = () => {
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -276,7 +276,7 @@ const Education = () => {
|
||||
<div>{item.area}</div>
|
||||
<div>{item.score}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -292,7 +292,7 @@ const Awards = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -308,7 +308,7 @@ const Certifications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -349,7 +349,7 @@ const Publications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -366,7 +366,7 @@ const Volunteer = () => {
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -377,11 +377,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -399,7 +399,7 @@ const Projects = () => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -438,7 +438,7 @@ const Custom = ({ id }: { id: string }) => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -158,7 +158,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="col-span-4 grid gap-3"
|
||||
className="col-span-4 grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -171,18 +171,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -200,7 +200,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -384,11 +384,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Experience,
|
||||
Interest,
|
||||
Language,
|
||||
Profile,
|
||||
Project,
|
||||
Publication,
|
||||
Reference,
|
||||
@ -26,8 +27,6 @@ import { TemplateProps } from "../types/template";
|
||||
|
||||
const Header = () => {
|
||||
const basics = useArtboardStore((state) => state.resume.basics);
|
||||
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3">
|
||||
@ -71,31 +70,6 @@ const Header = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{profiles.visible && profiles.items.length > 0 && (
|
||||
<div className="flex items-center gap-x-3 gap-y-0.5">
|
||||
{profiles.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => (
|
||||
<div key={item.id} className="flex items-center gap-x-2">
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
className="text-sm"
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,7 +83,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -184,10 +158,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -200,18 +174,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -267,6 +241,38 @@ const Education = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Profiles = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Awards = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.awards);
|
||||
|
||||
@ -381,11 +387,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -457,6 +463,8 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
|
||||
@ -101,7 +101,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -173,10 +173,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -193,21 +193,21 @@ const Section = <T,>({
|
||||
className={cn("relative space-y-2 pl-4 group-[.sidebar]:pl-0", className)}
|
||||
>
|
||||
<div className="relative -ml-4 group-[.sidebar]:ml-0">
|
||||
<div className="pl-4 leading-snug group-[.sidebar]:pl-0">
|
||||
<div className="pl-4 group-[.sidebar]:pl-0">
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-y-0 -left-px border-l-[4px] border-primary group-[.sidebar]:hidden" />
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
@ -227,7 +227,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -411,11 +411,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { TemplateKey } from "@reactive-resume/utils";
|
||||
import { Template } from "@reactive-resume/utils";
|
||||
|
||||
import { Azurill } from "./azurill";
|
||||
import { Bronzor } from "./bronzor";
|
||||
import { Chikorita } from "./chikorita";
|
||||
import { Ditto } from "./ditto";
|
||||
import { Kakuna } from "./kakuna";
|
||||
import { Nosepass } from "./nosepass";
|
||||
import { Onyx } from "./onyx";
|
||||
import { Pikachu } from "./pikachu";
|
||||
import { Rhyhorn } from "./rhyhorn";
|
||||
|
||||
export const getTemplate = (template: TemplateKey) => {
|
||||
export const getTemplate = (template: Template) => {
|
||||
switch (template) {
|
||||
case "onyx":
|
||||
return Onyx;
|
||||
@ -27,6 +28,8 @@ export const getTemplate = (template: TemplateKey) => {
|
||||
return Bronzor;
|
||||
case "pikachu":
|
||||
return Pikachu;
|
||||
case "nosepass":
|
||||
return Nosepass;
|
||||
default:
|
||||
return Onyx;
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold uppercase text-primary">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
|
||||
{section.name}
|
||||
</h4>
|
||||
|
||||
@ -179,12 +179,12 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold uppercase text-primary">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
|
||||
{section.name}
|
||||
</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -197,19 +197,19 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">{children?.(item as T)}</div>
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -228,7 +228,7 @@ const Experience = () => {
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -246,7 +246,7 @@ const Education = () => {
|
||||
<div>{item.area}</div>
|
||||
<div>{item.score}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -262,7 +262,7 @@ const Awards = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -278,7 +278,7 @@ const Certifications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -319,7 +319,7 @@ const Publications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -336,7 +336,7 @@ const Volunteer = () => {
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -347,11 +347,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -368,7 +368,7 @@ const Projects = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -407,7 +407,7 @@ const Custom = ({ id }: { id: string }) => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
529
apps/artboard/src/templates/nosepass.tsx
Normal file
529
apps/artboard/src/templates/nosepass.tsx
Normal file
@ -0,0 +1,529 @@
|
||||
import {
|
||||
Award,
|
||||
Certification,
|
||||
CustomSection,
|
||||
CustomSectionGroup,
|
||||
Education,
|
||||
Experience,
|
||||
Interest,
|
||||
Language,
|
||||
Profile,
|
||||
Project,
|
||||
Publication,
|
||||
Reference,
|
||||
SectionKey,
|
||||
SectionWithItem,
|
||||
Skill,
|
||||
URL,
|
||||
Volunteer,
|
||||
} from "@reactive-resume/schema";
|
||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
||||
import get from "lodash.get";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { Picture } from "../components/picture";
|
||||
import { useArtboardStore } from "../store/artboard";
|
||||
import { TemplateProps } from "../types/template";
|
||||
|
||||
const Header = () => {
|
||||
const basics = useArtboardStore((state) => state.resume.basics);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div className="mt-1 space-y-2 text-right">
|
||||
<p className="font-medium text-primary">Personal Information</p>
|
||||
<Picture className="ml-auto" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-3 space-y-2">
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{basics.name}</div>
|
||||
<div className="text-base">{basics.headline}</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 text-sm">
|
||||
{basics.location && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-map-pin text-primary" />
|
||||
<div>{basics.location}</div>
|
||||
</div>
|
||||
)}
|
||||
{basics.phone && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-phone text-primary" />
|
||||
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
|
||||
{basics.phone}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{basics.email && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-at text-primary" />
|
||||
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
|
||||
{basics.email}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
<Link url={basics.url} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-x-3 text-sm">
|
||||
{basics.customFields.map((item) => (
|
||||
<div key={item.id} className="flex items-center gap-x-1.5">
|
||||
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
|
||||
<span className="text-primary">{item.name}</span>
|
||||
<span>{item.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Summary = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.summary);
|
||||
|
||||
if (!section.visible || isEmptyString(section.content)) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid grid-cols-4 gap-x-6">
|
||||
<div className="text-right">
|
||||
<h4 className="font-medium text-primary">{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<div className="relative">
|
||||
<hr className="mt-3 border-primary pb-3" />
|
||||
<div className="absolute bottom-3 right-0 h-3 w-3 bg-primary" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
type LinkProps = {
|
||||
url: URL;
|
||||
icon?: React.ReactNode;
|
||||
label?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Link = ({ url, icon, label, className }: LinkProps) => {
|
||||
if (!isUrl(url.href)) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
{icon ?? <i className="ph ph-bold ph-link text-primary" />}
|
||||
<a
|
||||
href={url.href}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type SectionProps<T> = {
|
||||
section: SectionWithItem<T> | CustomSectionGroup;
|
||||
children?: (item: T) => React.ReactNode;
|
||||
urlKey?: keyof T;
|
||||
dateKey?: keyof T;
|
||||
levelKey?: keyof T;
|
||||
summaryKey?: keyof T;
|
||||
keywordsKey?: keyof T;
|
||||
};
|
||||
|
||||
const Section = <T,>({
|
||||
section,
|
||||
children,
|
||||
urlKey,
|
||||
dateKey,
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className={cn("grid", dateKey !== undefined && "gap-y-4")}>
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div className="text-right">
|
||||
<h4 className="font-medium text-primary">{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<div className="relative">
|
||||
<hr className="mt-3 border-primary" />
|
||||
<div className="absolute bottom-0 right-0 h-3 w-3 bg-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{dateKey !== undefined && (
|
||||
<div className="grid grid-cols-4 gap-x-6 gap-y-4">
|
||||
{section.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => {
|
||||
const url = (urlKey && get(item, urlKey)) as URL | undefined;
|
||||
const date = (dateKey && get(item, dateKey, "")) as string | undefined;
|
||||
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
|
||||
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
|
||||
|
||||
return (
|
||||
<Fragment key={item.id}>
|
||||
<div className="text-right font-medium text-primary">{date}</div>
|
||||
|
||||
<div className="col-span-3 space-y-1">
|
||||
{children?.(item as T)}
|
||||
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dateKey === undefined && (
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div
|
||||
className="col-span-3 col-start-2 grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => {
|
||||
const url = (urlKey && get(item, urlKey)) as URL | undefined;
|
||||
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
|
||||
const keywords = (keywordsKey && get(item, keywordsKey, [])) as
|
||||
| string[]
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
{children?.(item as T)}
|
||||
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const Profiles = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Experience = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.experience);
|
||||
|
||||
return (
|
||||
<Section<Experience> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Education = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.education);
|
||||
|
||||
return (
|
||||
<Section<Education> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.institution}</div>
|
||||
<div>{item.area}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div>{item.score}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Awards = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.awards);
|
||||
|
||||
return (
|
||||
<Section<Award> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Certifications = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.certifications);
|
||||
|
||||
return (
|
||||
<Section<Certification> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Skills = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.skills);
|
||||
|
||||
return (
|
||||
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Interests = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.interests);
|
||||
|
||||
return (
|
||||
<Section<Interest> section={section} keywordsKey="keywords">
|
||||
{(item) => <div className="font-bold">{item.name}</div>}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Publications = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.publications);
|
||||
|
||||
return (
|
||||
<Section<Publication> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Volunteer = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.volunteer);
|
||||
|
||||
return (
|
||||
<Section<Volunteer> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Projects = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.projects);
|
||||
|
||||
return (
|
||||
<Section<Project>
|
||||
section={section}
|
||||
urlKey="url"
|
||||
dateKey="date"
|
||||
summaryKey="summary"
|
||||
keywordsKey="keywords"
|
||||
>
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const References = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.references);
|
||||
|
||||
return (
|
||||
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Custom = ({ id }: { id: string }) => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
|
||||
|
||||
return (
|
||||
<Section<CustomSection>
|
||||
section={section}
|
||||
urlKey="url"
|
||||
dateKey="date"
|
||||
summaryKey="summary"
|
||||
keywordsKey="keywords"
|
||||
>
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
return <Experience />;
|
||||
case "education":
|
||||
return <Education />;
|
||||
case "awards":
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
return <Projects />;
|
||||
case "references":
|
||||
return <References />;
|
||||
default:
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const Nosepass = ({ columns, isFirstPage = false }: TemplateProps) => {
|
||||
const name = useArtboardStore((state) => state.resume.basics.name);
|
||||
|
||||
const [main, sidebar] = columns;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<img alt="Europass Logo" className="h-[48px]" src="https://i.imgur.com/eRK005p.png" />
|
||||
|
||||
<p className="font-medium text-primary">Curriculum Vitae</p>
|
||||
|
||||
<p className="font-medium text-primary">{name}</p>
|
||||
</div>
|
||||
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<div className="space-y-4">
|
||||
{main.map((section) => (
|
||||
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
|
||||
))}
|
||||
|
||||
{sidebar.map((section) => (
|
||||
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -110,7 +110,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
|
||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -182,10 +182,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
|
||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -198,18 +198,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -379,11 +379,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -34,8 +34,8 @@ const Header = () => {
|
||||
className="summary group bg-primary px-6 pb-7 pt-6 text-background"
|
||||
style={{ borderRadius: `calc(${borderRadius}px - 2px)` }}
|
||||
>
|
||||
<div className="col-span-2 space-y-4">
|
||||
<div className="leading-tight">
|
||||
<div className="col-span-2 space-y-2.5">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">{basics.name}</h2>
|
||||
<p>{basics.headline}</p>
|
||||
</div>
|
||||
@ -102,7 +102,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -182,10 +182,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -198,18 +198,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -227,7 +227,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -358,7 +358,7 @@ const Interests = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.interests);
|
||||
|
||||
return (
|
||||
<Section<Interest> section={section} className="space-y-0" keywordsKey="keywords">
|
||||
<Section<Interest> section={section} className="space-y-1" keywordsKey="keywords">
|
||||
{(item) => <div className="font-bold">{item.name}</div>}
|
||||
</Section>
|
||||
);
|
||||
@ -411,11 +411,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -82,7 +82,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -154,10 +154,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -170,18 +170,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -199,7 +199,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -317,7 +317,7 @@ const Skills = () => {
|
||||
return (
|
||||
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
|
||||
{(item) => (
|
||||
<div className="leading-tight">
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
@ -383,11 +383,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user