diff --git a/package.json b/package.json index fd3b7b9..e5ecfdf 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "puppeteer": "^23.9.0", "puppeteer-core": "^23.9.0", "react": "^18", + "react-colorful": "^5.6.1", "react-day-picker": "^8.10.1", "react-dom": "^18", "react-resizable-panels": "^2.0.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83fec35..49e6512 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: react: specifier: ^18 version: 18.3.1 + react-colorful: + specifier: ^5.6.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-day-picker: specifier: ^8.10.1 version: 8.10.1(date-fns@3.6.0)(react@18.3.1) @@ -4014,6 +4017,12 @@ packages: resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} engines: {node: '>= 0.8'} + react-colorful@5.6.1: + resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + react-day-picker@8.10.1: resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} peerDependencies: @@ -8689,6 +8698,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-day-picker@8.10.1(date-fns@3.6.0)(react@18.3.1): dependencies: date-fns: 3.6.0 diff --git a/src/components/editor/SidePanel.tsx b/src/components/editor/SidePanel.tsx index 295e250..3327014 100644 --- a/src/components/editor/SidePanel.tsx +++ b/src/components/editor/SidePanel.tsx @@ -20,6 +20,7 @@ import LayoutSetting from "./layout/LayoutSetting"; import { useResumeStore } from "@/store/useResumeStore"; import { cn } from "@/lib/utils"; import { THEME_COLORS } from "@/types/resume"; +import { ColorPicker } from "@/components/ui/color-picker"; const fontOptions = [ { value: "sans", label: "无衬线体" }, @@ -98,7 +99,7 @@ export function SidePanel() { const debouncedSetColor = useMemo( () => - debounce((value) => { + debounce((value: string) => { setThemeColor(value); }, 100), [] @@ -201,11 +202,9 @@ export function SidePanel() {
{t("theme.custom")}
- debouncedSetColor(e.target.value)} - className="w-[40px] h-[40px] rounded-lg cursor-pointer overflow-hidden hover:scale-105 transition-transform" + onChange={(value) => debouncedSetColor(value)} /> diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx new file mode 100644 index 0000000..0c8c497 --- /dev/null +++ b/src/components/ui/color-picker.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { forwardRef, useMemo, useState } from "react"; +import { HexColorPicker } from "react-colorful"; +import { cn } from "@/lib/utils"; +import type { ButtonProps } from "@/components/ui/button"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Input } from "@/components/ui/input"; +import { useForwardedRef } from "../../lib/use-forwarded-ref"; + +interface ColorPickerProps + extends Omit { + value: string; + onChange: (value: string) => void; + onBlur?: () => void; +} + +const ColorPicker = forwardRef( + ( + { disabled, value, onChange, onBlur, name, className, ...props }, + forwardedRef + ) => { + const ref = useForwardedRef(forwardedRef); + const [open, setOpen] = useState(false); + + const parsedValue = useMemo(() => { + return value || "#FFFFFF"; + }, [value]); + + return ( + + + + + + +
+ # + { + onChange(e.currentTarget.value.startsWith("#") ? e.currentTarget.value : `#${e.currentTarget.value}`); + }} + value={parsedValue.replace("#", "")} + className="h-8" + /> +
+
+
+ ); + } +); +ColorPicker.displayName = "ColorPicker"; + +export { ColorPicker }; diff --git a/src/lib/use-forwarded-ref.ts b/src/lib/use-forwarded-ref.ts new file mode 100644 index 0000000..2805030 --- /dev/null +++ b/src/lib/use-forwarded-ref.ts @@ -0,0 +1,17 @@ +import { useRef, useEffect } from "react"; +import type { ForwardedRef } from "react"; + +export function useForwardedRef(ref: ForwardedRef) { + const innerRef = useRef(null); + + useEffect(() => { + if (!ref) return; + if (typeof ref === "function") { + ref(innerRef.current); + } else { + ref.current = innerRef.current; + } + }); + + return innerRef; +}