feat: theme config

This commit is contained in:
siyue
2024-10-30 21:46:23 +08:00
parent 32163e5524
commit e8a8159140
4 changed files with 168 additions and 37 deletions
@@ -1,10 +1,11 @@
"use client";
import React from "react";
import React, { useMemo } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { useResumeStore } from "@/store/useResumeStore";
import { cn } from "@/lib/utils";
import { throttle } from "lodash";
import { THEME_COLORS } from "@/types/resume";
const getFontFamilyClass = (fontFamily: string) => {
switch (fontFamily) {
@@ -26,7 +27,8 @@ export function PreviewPanel() {
menuSections,
globalSettings,
projects,
draggingProjectId
draggingProjectId,
colorTheme
} = useResumeStore();
const previewRef = React.useRef<HTMLDivElement>(null);
const [scrollBehavior, setScrollBehavior] =
@@ -36,6 +38,18 @@ export function PreviewPanel() {
globalSettings?.fontFamily || "sans"
);
// 获取当前主题色
const currentThemeColor = useMemo(() => {
return colorTheme || THEME_COLORS[0];
}, [colorTheme]);
// 标题样式的公共配置
const sectionTitleStyles = {
fontSize: `${globalSettings?.headerSize || 18}px`,
borderColor: currentThemeColor, // 使用主题色作为下边框颜色
color: currentThemeColor // 使用主题色作为文字颜色
};
// 处理自动滚动
const handleScroll = React.useCallback(
throttle((offset: number) => {
@@ -101,9 +115,7 @@ export function PreviewPanel() {
>
<h3
className="text-lg font-semibold border-b border-gray-200 pb-2"
style={{
fontSize: `${globalSettings?.headerSize || 18}px`
}}
style={sectionTitleStyles}
>
</h3>
@@ -180,7 +192,7 @@ export function PreviewPanel() {
layout
className={cn("text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{project.role}
@@ -190,7 +202,7 @@ export function PreviewPanel() {
layout
className={cn("text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{project.date}
@@ -203,7 +215,7 @@ export function PreviewPanel() {
layout
className={cn("whitespace-pre-wrap", "text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -220,7 +232,7 @@ export function PreviewPanel() {
layout
className={cn("font-medium", "text-gray-800")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
@@ -229,7 +241,7 @@ export function PreviewPanel() {
layout
className={cn("whitespace-pre-wrap", "text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -245,7 +257,7 @@ export function PreviewPanel() {
layout
className={cn("font-medium", "text-gray-800")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
@@ -254,7 +266,7 @@ export function PreviewPanel() {
layout
className={cn("whitespace-pre-wrap", "text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -270,7 +282,7 @@ export function PreviewPanel() {
layout
className={cn("font-medium", "text-gray-800")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
@@ -279,7 +291,7 @@ export function PreviewPanel() {
layout
className={cn("whitespace-pre-wrap", "text-gray-600")}
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -293,8 +305,6 @@ export function PreviewPanel() {
</motion.div>
);
// ... 其他 section 渲染函数 ...
const renderSection = (sectionId: string) => {
switch (sectionId) {
case "basic":
@@ -302,16 +312,14 @@ export function PreviewPanel() {
<motion.div layout className="space-y-2">
<h3
className="text-lg font-semibold border-b border-gray-200 pb-2"
style={{
fontSize: `${globalSettings?.headerSize || 18}px`
}}
style={sectionTitleStyles}
>
</h3>
<p
className="text-gray-600 whitespace-pre-wrap"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -331,9 +339,7 @@ export function PreviewPanel() {
>
<h3
className="text-lg font-semibold border-b border-gray-200 pb-2"
style={{
fontSize: `${globalSettings?.headerSize || 18}px`
}}
style={sectionTitleStyles}
>
</h3>
@@ -358,7 +364,7 @@ export function PreviewPanel() {
<p
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{edu.degree}
@@ -367,7 +373,7 @@ export function PreviewPanel() {
<span
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{edu.date}
@@ -376,7 +382,7 @@ export function PreviewPanel() {
<p
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -398,9 +404,7 @@ export function PreviewPanel() {
>
<h3
className="text-lg font-semibold border-b border-gray-200 pb-2"
style={{
fontSize: `${globalSettings?.headerSize || 18}px`
}}
style={sectionTitleStyles}
>
</h3>
@@ -425,7 +429,7 @@ export function PreviewPanel() {
<p
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{exp.position}
@@ -434,7 +438,7 @@ export function PreviewPanel() {
<span
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`
fontSize: `${globalSettings?.baseFontSize || 14}px`
}}
>
{exp.date}
@@ -443,7 +447,7 @@ export function PreviewPanel() {
<p
className="text-gray-600"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
lineHeight: globalSettings?.lineHeight || 1.6
}}
>
@@ -455,7 +459,6 @@ export function PreviewPanel() {
);
case "projects":
return renderProjects();
// ... 其他 case
default:
return null;
}
@@ -512,7 +515,7 @@ export function PreviewPanel() {
layout="position"
className="flex justify-center items-center space-x-4 flex-wrap"
style={{
fontSize: `${globalSettings?.contentFontSize || 14}px`,
fontSize: `${globalSettings?.baseFontSize || 14}px`,
color: "rgb(75, 85, 99)"
}}
>
@@ -7,7 +7,9 @@ import {
EyeOff,
Layout,
Type,
SpaceIcon
SpaceIcon,
Palette,
Plus
} from "lucide-react";
import { useResumeStore } from "@/store/useResumeStore";
import { getThemeConfig } from "@/theme/themeConfig";
@@ -22,6 +24,9 @@ import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { THEME_COLORS } from "@/types/resume";
import debounce from "lodash/debounce";
import { useMemo } from "react";
const fontOptions = [
{ value: "sans", label: "无衬线体" },
@@ -79,9 +84,19 @@ export function SidePanel() {
toggleSectionVisibility,
setActiveSection,
globalSettings,
updateGlobalSettings
updateGlobalSettings,
colorTheme,
setColorTheme
} = useResumeStore();
const debouncedSetColor = useMemo(
() =>
debounce((value) => {
setColorTheme(value);
}, 100),
[]
);
return (
<motion.div
className={cn(
@@ -168,6 +183,62 @@ export function SidePanel() {
</Reorder.Group>
</SettingCard>
{/* 主题色设置 */}
<SettingCard icon={Palette} title="主题色">
<div className="space-y-4">
<div className="grid grid-cols-6 gap-2">
{THEME_COLORS.map((presetTheme) => (
<button
key={presetTheme}
className={cn(
"relative group aspect-square rounded-lg overflow-hidden border-2 transition-all duration-200",
colorTheme === presetTheme
? "border-black dark:border-white"
: theme === "dark"
? "border-neutral-800 hover:border-neutral-700"
: "border-gray-100 hover:border-gray-200"
)}
onClick={() => setColorTheme(presetTheme)}
>
{/* 颜色展示 */}
<div
className="absolute inset-0"
style={{ backgroundColor: presetTheme }}
/>
{/* 选中指示器 */}
{colorTheme === presetTheme && (
<motion.div
layoutId="theme-selected"
className="absolute inset-0 flex items-center justify-center bg-black/20 dark:bg-white/20"
initial={false}
transition={{
type: "spring",
bounce: 0.2,
duration: 0.6
}}
>
<div className="w-2 h-2 rounded-full bg-white dark:bg-black" />
</motion.div>
)}
</button>
))}
</div>
<div className="flex flex-col gap-4">
<div className="text-sm text-gray-600 dark:text-gray-400">
</div>
<input
type="color"
onChange={(e) => debouncedSetColor(e.target.value)}
className="w-[40px] h-[40px] rounded-lg cursor-pointer overflow-hidden hover:scale-105 transition-transform"
/>
</div>
</div>
</SettingCard>
{/* 排版设置 */}
<SettingCard icon={Type} title="排版">
<div className="space-y-6">
+16 -1
View File
@@ -7,7 +7,8 @@ import {
Education,
Experience,
GlobalSettings,
Project
Project,
ResumeTheme
} from "../types/resume";
interface ResumeStore {
@@ -29,6 +30,11 @@ interface ResumeStore {
theme: "light" | "dark";
activeSection: string;
colorTheme: string; // 当前使用的主题色 ID
// 新增 Actions
setColorTheme: (colorTheme: string) => void;
// Actions
updateBasicInfo: (data: Partial<BasicInfo>) => void;
updateEducation: (id: string, data: Partial<Education>) => void;
@@ -93,6 +99,10 @@ const initialState = {
{ id: "projects", title: "项目经历", icon: "🚀", enabled: true, order: 4 }
],
theme: "light" as const,
// 主题色
colorTheme: "#2563eb", // 默认使用经典蓝主题
activeSection: "basic",
projects: [
{
@@ -128,6 +138,11 @@ export const useResumeStore = create<ResumeStore>()(
persist(
(set) => ({
...initialState,
setColorTheme: (colorTheme) => {
console.log(colorTheme, "colorTheme");
set({ colorTheme });
},
setDraggingProjectId: (id) => set({ draggingProjectId: id }),
updateBasicInfo: (data) =>
+42
View File
@@ -68,3 +68,45 @@ export type GlobalSettings = {
pagePadding: number;
paragraphSpacing: number;
};
export interface ThemeColors {
primary: string; // 主色
secondary: string; // 次要色
text: {
primary: string; // 主要文字颜色
secondary: string; // 次要文字颜色
accent: string; // 强调文字颜色
};
background: {
primary: string; // 主要背景色
secondary: string; // 次要背景色
accent: string; // 强调背景色
};
border: string; // 边框颜色
divider: string; // 分割线颜色
}
export interface ResumeTheme {
id: string;
name: string;
color: string;
}
export const THEME_COLORS = [
"#2563eb", // 经典蓝
"#059669", // 翡翠绿
"#7c3aed", // 优雅紫
"#e11d48", // 玫瑰红
"#d97706", // 琥珀金
"#0891b2", // 青碧蓝
"#4f46e5", // 靛青蓝
"#0d9488", // 青蓝绿
"#0284c7", // 天际蓝
"#6d28d9", // 雅致紫
"#c026d3", // 绯紫红
"#db2777", // 粉红色
"#ea580c", // 活力橙
"#65a30d", // 青柠绿
"#475569", // 岩石灰
"#dc2626" // 中国红
];