perf: preview section

This commit is contained in:
JOYCEQL
2024-12-17 12:16:35 +08:00
committed by qingchen
parent 55df8f0538
commit c9dfd68139
9 changed files with 221 additions and 196 deletions
@@ -1,7 +1,7 @@
"use client";
import { motion } from "framer-motion";
import { GlobalSettings, CustomItem } from "@/types/resume";
import { AnimatePresence, motion } from "framer-motion";
import SectionTitle from "./SectionTitle";
import { GlobalSettings, CustomItem } from "@/types/resume";
interface CustomSectionProps {
sectionId: string;
@@ -31,58 +31,61 @@ const CustomSection = ({
type="custom"
globalSettings={globalSettings}
/>
{visibleItems.map((item) => (
<div
key={item.id}
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<div className="flex justify-between items-start">
<div className="space-y-1">
<div>
<h4
className="font-semibold text-gray-800"
style={{
fontSize: `${globalSettings?.subheaderSize || 16}px`,
}}
>
{item.title}
</h4>
<motion.div
layout
className={"font-medium text-baseFont"}
<AnimatePresence mode="popLayout">
{visibleItems.map((item) => (
<motion.div
key={item.id}
layout="position"
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<div className="flex justify-between items-start">
<div className="space-y-1">
<div>
<h4
className="font-semibold text-gray-800"
style={{
fontSize: `${globalSettings?.subheaderSize || 16}px`,
}}
>
{item.title}
</h4>
<motion.div
layout
className={"font-medium text-baseFont"}
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
{item.subtitle}
</motion.div>
</div>
</div>
{item.dateRange && (
<span
className="text-baseFont shrink-0 ml-4"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
{item.subtitle}
</motion.div>
</div>
{item.dateRange}
</span>
)}
</div>
{item.dateRange && (
<span
className="text-baseFont shrink-0 ml-4"
{item.description && (
<div
className="text-baseFont mt-1"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
>
{item.dateRange}
</span>
dangerouslySetInnerHTML={{ __html: item.description }}
/>
)}
</div>
{item.description && (
<div
className="text-baseFont mt-1"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
dangerouslySetInnerHTML={{ __html: item.description }}
/>
)}
</div>
))}
</motion.div>
))}
</AnimatePresence>
</motion.div>
);
};
@@ -1,5 +1,5 @@
"use client";
import { motion } from "framer-motion";
import { AnimatePresence, motion } from "framer-motion";
import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
interface EducationSectionProps {
@@ -14,7 +14,6 @@ const EducationSection = ({
const visibleEducation = education?.filter((edu) => edu.visible);
return (
<motion.div
layout
style={{
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
}}
@@ -23,68 +22,70 @@ const EducationSection = ({
type="education"
globalSettings={globalSettings}
></SectionTitle>
{visibleEducation?.map((edu) => (
<div
key={edu.id}
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<div className="flex justify-between items-start">
<div className="space-y-1">
<div className="flex items-center gap-2">
<h4
className="font-semibold text-gray-800"
style={{
fontSize: `${globalSettings?.subheaderSize || 16}px`,
}}
>
{edu.school}
</h4>
{edu.location && (
<span
className="text-baseFont"
<AnimatePresence mode="popLayout">
{visibleEducation?.map((edu) => (
<motion.div
layout="position"
key={edu.id}
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<div className="flex justify-between items-start">
<div className="space-y-1">
<div className="flex items-center gap-2">
<h4
className="font-semibold text-gray-800"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
fontSize: `${globalSettings?.subheaderSize || 16}px`,
}}
>
· {edu.location}
</span>
)}
{edu.school}
</h4>
{edu.location && (
<span
className="text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
· {edu.location}
</span>
)}
</div>
<p
className="font-medium text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
{[edu.major, edu.degree].filter(Boolean).join(" · ")}
{edu.gpa && ` · GPA ${edu.gpa}`}
</p>
</div>
<p
className="font-medium text-baseFont"
<span
className="text-baseFont shrink-0 ml-4"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
suppressHydrationWarning
>
{[edu.major, edu.degree].filter(Boolean).join(" · ")}
{edu.gpa && ` · GPA ${edu.gpa}`}
</p>
{`${new Date(edu.startDate).toLocaleDateString()} - ${new Date(edu.endDate).toLocaleDateString()}`}
</span>
</div>
<span
className="text-baseFont shrink-0 ml-4"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
suppressHydrationWarning
>
{`${new Date(edu.startDate).toLocaleDateString()} - ${new Date(edu.endDate).toLocaleDateString()}`}
</span>
</div>
{edu.description && (
<div
className="text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
dangerouslySetInnerHTML={{ __html: edu.description }}
/>
)}
</div>
))}
{edu.description && (
<div
className="text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
dangerouslySetInnerHTML={{ __html: edu.description }}
/>
)}
</motion.div>
))}
</AnimatePresence>
</motion.div>
);
};
@@ -1,16 +1,59 @@
import { motion } from "framer-motion";
"use client";
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Experience, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
interface ExperienceSectionProps {
experience: Experience[];
globalSettings: GlobalSettings | undefined;
experiences?: Experience[];
globalSettings?: GlobalSettings;
}
const ExperienceSection = ({
experience,
interface ExperienceItemProps {
experience: Experience;
globalSettings?: GlobalSettings;
}
const ExperienceItem: React.FC<ExperienceItemProps> = React.forwardRef(
({ experience, globalSettings }, ref) => {
return (
<motion.div
style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}
layout
>
<motion.div
layout="position"
className="flex items-center justify-between"
>
<div className="font-medium text-baseFont">{experience.company}</div>
{experience.date && <div>{experience.date}</div>}
</motion.div>
{experience.position && (
<motion.div layout="position" className="font-medium text-baseFont">
{experience.position}
</motion.div>
)}
{experience.details && (
<motion.div
layout="position"
dangerouslySetInnerHTML={{ __html: experience.details }}
></motion.div>
)}
</motion.div>
);
}
);
const ExperienceSection: React.FC<ExperienceSectionProps> = ({
experiences,
globalSettings,
}: ExperienceSectionProps) => {
}) => {
if (!experiences?.length) return null;
const visibleExperiences = experiences.filter(
(experience) => experience.visible
);
return (
<motion.div
layout
@@ -19,54 +62,17 @@ const ExperienceSection = ({
}}
>
<SectionTitle type="experience" globalSettings={globalSettings} />
{experience?.map(
(exp) =>
exp.visible && (
<div
key={exp.id}
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<div className="flex justify-between items-start">
<div>
<h4
className="font-semibold text-gray-800"
style={{
fontSize: `${globalSettings?.subheaderSize || 16}px`,
}}
>
{exp.company}
</h4>
<p
className="font-medium text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
{exp.position}
</p>
</div>
<span
className="text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
{exp.date}
</span>
</div>
<div
className="text-baseFont"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
dangerouslySetInnerHTML={{ __html: exp.details }}
/>
</div>
)
)}
<div>
<AnimatePresence mode="popLayout">
{visibleExperiences.map((experience) => (
<ExperienceItem
key={experience.id}
experience={experience}
globalSettings={globalSettings}
/>
))}
</AnimatePresence>
</div>
</motion.div>
);
};
@@ -1,5 +1,7 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { motion, AnimatePresence } from "framer-motion";
import SectionTitle from "./SectionTitle";
import { Project, GlobalSettings } from "@/types/resume";
@@ -14,6 +16,8 @@ const ProjectSection: React.FC<ProjectSectionProps> = ({
}) => {
if (!projects?.length) return null;
const visibleProjects = projects.filter((project) => project.visible);
return (
<motion.div
layout
@@ -25,14 +29,16 @@ const ProjectSection: React.FC<ProjectSectionProps> = ({
type="projects"
globalSettings={globalSettings}
></SectionTitle>
<div className="space-y-4">
{projects.map((project) => (
<ProjectItem
key={project.id}
project={project}
globalSettings={globalSettings}
/>
))}
<div>
<AnimatePresence mode="popLayout">
{visibleProjects.map((project) => (
<ProjectItem
key={project.id}
project={project}
globalSettings={globalSettings}
/>
))}
</AnimatePresence>
</div>
</motion.div>
);
@@ -43,33 +49,40 @@ interface ProjectItemProps {
globalSettings?: GlobalSettings;
}
const ProjectItem: React.FC<ProjectItemProps> = ({
project,
globalSettings,
}) => {
return (
<div
className="relative"
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
}}
>
<div className="flex items-center justify-between">
<h3 className="font-medium">{project.name}</h3>
<div>{project.date}</div>
</div>
{project.role && (
<div className="font-medium text-baseFont">{project.role}</div>
)}
{project.description && (
<div
className=" mt-2 whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: project.description }}
></div>
)}
</div>
);
};
const ProjectItem: React.FC<ProjectItemProps> = React.forwardRef(
({ project, globalSettings }, ref) => {
return (
<motion.div
layout
style={{
marginTop: `${globalSettings?.paragraphSpacing}px`,
}}
>
<motion.div
layout="position"
className="flex items-center justify-between"
>
<h3 className="font-medium">{project.name}</h3>
{project.date && <div>{project.date}</div>}
</motion.div>
{project.role && (
<motion.div layout="position" className="font-medium text-baseFont">
{project.role}
</motion.div>
)}
{project.description && (
<motion.div
layout="position"
style={{
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6,
}}
dangerouslySetInnerHTML={{ __html: project.description }}
></motion.div>
)}
</motion.div>
);
}
);
export default ProjectSection;
@@ -1,3 +1,4 @@
"use client";
import { useMemo } from "react";
import { GlobalSettings } from "@/types/resume";
import { useResumeStore } from "@/store/useResumeStore";
@@ -1,6 +1,7 @@
"use client";
import { motion } from "framer-motion";
import { GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import { GlobalSettings } from "@/types/resume";
interface SkillSectionProps {
skill: string | undefined;
@@ -36,7 +36,7 @@ const ClassicTemplate: React.FC<ClassicTemplateProps> = ({
case "experience":
return (
<ExperienceSection
experience={data.experience}
experiences={data.experience}
globalSettings={data.globalSettings}
/>
);
@@ -35,7 +35,7 @@ const LeftRightTemplate: React.FC<LeftRightTemplateProps> = ({
case "experience":
return (
<ExperienceSection
experience={data.experience}
experiences={data.experience}
globalSettings={data.globalSettings}
/>
);
@@ -33,7 +33,7 @@ const ModernTemplate: React.FC<ModernTemplateProps> = ({ data, template }) => {
case "experience":
return (
<ExperienceSection
experience={data.experience}
experiences={data.experience}
globalSettings={data.globalSettings}
/>
);
@@ -92,7 +92,7 @@ const ModernTemplate: React.FC<ModernTemplateProps> = ({ data, template }) => {
</div>
<div
className="col-span-2 p-4 space-y-6"
className="col-span-2 p-4"
style={{
backgroundColor: colorScheme.background,
color: colorScheme.text,