mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-07-03 14:07:11 +02:00
feat: preview click to section
This commit is contained in:
@@ -52,26 +52,6 @@ export function EditorHeader({ isMobile }: EditorHeaderProps) {
|
||||
<span className="text-lg font-semibold">Magic Resume</span>
|
||||
<div className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" />
|
||||
</motion.div>
|
||||
|
||||
{/* 在移动端隐藏导航按钮 */}
|
||||
<div className="hidden md:flex items-center space-x-2">
|
||||
{visibleSections?.map((section) => (
|
||||
<motion.button
|
||||
key={section.id}
|
||||
className={`px-4 py-1.5 rounded-lg text-sm flex items-center space-x-2 shrink-0 ${
|
||||
activeSection === section.id
|
||||
? "bg-indigo-500/10 text-indigo-500"
|
||||
: themeConfig.hover
|
||||
}`}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => setActiveSection(section.id)}
|
||||
>
|
||||
<span>{section.icon}</span>
|
||||
<span>{section.title}</span>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GlobalSettings,
|
||||
} from "@/types/resume";
|
||||
import { ResumeTemplate } from "@/types/template";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
|
||||
interface BaseInfoProps {
|
||||
basic: BasicInfo | undefined;
|
||||
@@ -22,6 +23,7 @@ const BaseInfo = ({
|
||||
layout,
|
||||
template,
|
||||
}: BaseInfoProps) => {
|
||||
const { setActiveSection } = useResumeStore();
|
||||
const useIconMode = globalSettings?.useIconMode ?? false;
|
||||
|
||||
const getIcon = (iconName: string | undefined) => {
|
||||
@@ -271,11 +273,14 @@ const BaseInfo = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
if (layout === "between") {
|
||||
return BetweenContent;
|
||||
}
|
||||
const containerClass =
|
||||
"hover:cursor-pointer hover:bg-gray-100 rounded-md transition-all duration-300 ease-in-out hover:shadow-md";
|
||||
|
||||
return CenterContent;
|
||||
return (
|
||||
<div className={containerClass} onClick={() => setActiveSection("basic")}>
|
||||
{layout === "between" ? BetweenContent : CenterContent}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseInfo;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { GlobalSettings, CustomItem } from "@/types/resume";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
|
||||
interface CustomSectionProps {
|
||||
sectionId: string;
|
||||
@@ -11,20 +12,26 @@ interface CustomSectionProps {
|
||||
}
|
||||
|
||||
const CustomSection = ({
|
||||
sectionId,
|
||||
title,
|
||||
items,
|
||||
globalSettings,
|
||||
}: CustomSectionProps) => {
|
||||
const { setActiveSection } = useResumeStore();
|
||||
const visibleItems = items.filter((item) => {
|
||||
return item.visible && (item.title || item.description);
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="hover:cursor-pointer hover:bg-gray-100 rounded-md transition-all duration-300 ease-in-out hover:shadow-md"
|
||||
layout
|
||||
style={{
|
||||
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
setActiveSection(sectionId);
|
||||
}}
|
||||
>
|
||||
<SectionTitle
|
||||
title={title}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Education, GlobalSettings } from "@/types/resume";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
interface EducationSectionProps {
|
||||
education: Education[];
|
||||
globalSettings: GlobalSettings | undefined;
|
||||
@@ -11,12 +12,23 @@ const EducationSection = ({
|
||||
education,
|
||||
globalSettings,
|
||||
}: EducationSectionProps) => {
|
||||
const { setActiveSection } = useResumeStore();
|
||||
const visibleEducation = education?.filter((edu) => edu.visible);
|
||||
return (
|
||||
<motion.div
|
||||
className="hover:cursor-pointer
|
||||
hover:bg-gray-100
|
||||
rounded-md
|
||||
transition-all
|
||||
duration-300
|
||||
ease-in-out
|
||||
hover:shadow-md"
|
||||
style={{
|
||||
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
setActiveSection("education");
|
||||
}}
|
||||
>
|
||||
<SectionTitle
|
||||
type="education"
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Experience, GlobalSettings } from "@/types/resume";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
|
||||
interface ExperienceSectionProps {
|
||||
experiences?: Experience[];
|
||||
@@ -50,18 +51,22 @@ const ExperienceSection: React.FC<ExperienceSectionProps> = ({
|
||||
experiences,
|
||||
globalSettings,
|
||||
}) => {
|
||||
if (!experiences?.length) return null;
|
||||
const { setActiveSection } = useResumeStore();
|
||||
|
||||
const visibleExperiences = experiences.filter(
|
||||
const visibleExperiences = experiences?.filter(
|
||||
(experience) => experience.visible
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="hover:cursor-pointer hover:bg-gray-100 rounded-md transition-all duration-300 ease-in-out hover:shadow-md"
|
||||
layout
|
||||
style={{
|
||||
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
setActiveSection("experience");
|
||||
}}
|
||||
>
|
||||
<SectionTitle type="experience" globalSettings={globalSettings} />
|
||||
<div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { Project, GlobalSettings } from "@/types/resume";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
|
||||
interface ProjectItemProps {
|
||||
project: Project;
|
||||
@@ -57,16 +58,20 @@ const ProjectSection: React.FC<ProjectSectionProps> = ({
|
||||
projects,
|
||||
globalSettings,
|
||||
}) => {
|
||||
if (!projects?.length) return null;
|
||||
const { setActiveSection } = useResumeStore();
|
||||
|
||||
const visibleProjects = projects.filter((project) => project.visible);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="hover:cursor-pointer hover:bg-gray-100 rounded-md transition-all duration-300 ease-in-out hover:shadow-md"
|
||||
layout
|
||||
style={{
|
||||
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
setActiveSection("projects");
|
||||
}}
|
||||
>
|
||||
<SectionTitle
|
||||
type="projects"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { GlobalSettings } from "@/types/resume";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
|
||||
interface SkillSectionProps {
|
||||
skill: string | undefined;
|
||||
@@ -9,16 +10,18 @@ interface SkillSectionProps {
|
||||
}
|
||||
|
||||
const SkillSection = ({ skill, globalSettings }: SkillSectionProps) => {
|
||||
if (!skill) {
|
||||
return null;
|
||||
}
|
||||
const { setActiveSection } = useResumeStore();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
className="hover:cursor-pointer hover:bg-gray-100 rounded-md transition-all duration-300 ease-in-out hover:shadow-md"
|
||||
style={{
|
||||
marginTop: `${globalSettings?.sectionSpacing || 24}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
setActiveSection("skills");
|
||||
}}
|
||||
>
|
||||
<SectionTitle type="skills" globalSettings={globalSettings} />
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user