From a23383fe4deff5aed5fdbe1ecb9c0e630dba2642 Mon Sep 17 00:00:00 2001 From: siyue Date: Tue, 29 Oct 2024 23:12:10 +0800 Subject: [PATCH] feat: resizeable --- apps/fronted/package.json | 1 + .../app/workbench/compoents/CustomHandle.tsx | 48 ++++++ .../workbench/compoents/Editor/RichText.tsx | 5 +- apps/fronted/src/app/workbench/page.tsx | 52 ++++++- .../src/components/editor/EditPanel.tsx | 2 +- .../components/editor/project/ProjectItem.tsx | 134 +++++++++++++---- .../src/components/ui/alert-dialog.tsx | 141 ++++++++++++++++++ apps/fronted/src/components/ui/button.tsx | 34 ++--- pnpm-lock.yaml | 61 ++++++++ 9 files changed, 423 insertions(+), 55 deletions(-) create mode 100644 apps/fronted/src/app/workbench/compoents/CustomHandle.tsx create mode 100644 apps/fronted/src/components/ui/alert-dialog.tsx diff --git a/apps/fronted/package.json b/apps/fronted/package.json index ffb6850..6224d41 100644 --- a/apps/fronted/package.json +++ b/apps/fronted/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-select": "^2.1.2", diff --git a/apps/fronted/src/app/workbench/compoents/CustomHandle.tsx b/apps/fronted/src/app/workbench/compoents/CustomHandle.tsx new file mode 100644 index 0000000..a8e1faa --- /dev/null +++ b/apps/fronted/src/app/workbench/compoents/CustomHandle.tsx @@ -0,0 +1,48 @@ +import { GripVertical } from "lucide-react"; +import { ResizableHandle } from "@/components/ui/resizable"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; + +interface CustomHandleProps { + withHandle?: boolean; + className?: string; +} + +const CustomHandle = ({ withHandle = true, className }: CustomHandleProps) => { + const [isActive, setIsActive] = useState(false); + + return ( + setIsActive(true)} + onMouseUp={() => setIsActive(false)} + onTouchStart={() => setIsActive(true)} + onTouchEnd={() => setIsActive(false)} + > + {withHandle && ( +
+ +
+ )} +
+ ); +}; + +export default CustomHandle; diff --git a/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx b/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx index cd19109..933b957 100644 --- a/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx +++ b/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx @@ -135,7 +135,7 @@ const MenuButton = ({ ); }; -const TextColorButton = ({ editor, tooltip }) => { +const TextColorButton = ({ editor }) => { const [activeColor, setActiveColor] = React.useState(null); React.useEffect(() => { @@ -411,7 +411,8 @@ const RichTextEditor = ({ ] ) } - } + }, + immediatelyRender: false }); if (!editor) { diff --git a/apps/fronted/src/app/workbench/page.tsx b/apps/fronted/src/app/workbench/page.tsx index a8c7b81..7265667 100644 --- a/apps/fronted/src/app/workbench/page.tsx +++ b/apps/fronted/src/app/workbench/page.tsx @@ -2,14 +2,21 @@ import { useRef, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; +import { Eye, Edit2, Menu } from "lucide-react"; import { useResumeStore } from "@/store/useResumeStore"; import { EditorHeader } from "@/components/editor/EditorHeader"; import { SidePanel } from "@/components/editor/SidePanel"; import { EditPanel } from "@/components/editor/EditPanel"; import { PreviewPanel } from "@/components/editor/PreviewPanel"; import { getThemeConfig } from "@/theme/themeConfig"; -import { Eye, Edit2, Menu } from "lucide-react"; import { useScrollbarTheme } from "@/hooks/useScrollBarTheme"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup +} from "@/components/ui/resizable"; +import { cn } from "@/lib/utils"; +import CustomHandle from "./compoents/CustomHandle"; export default function Home() { const theme = useResumeStore((state) => state.theme); @@ -19,8 +26,11 @@ export default function Home() { // 移动端状态管理 const [showSidebar, setShowSidebar] = useState(false); const [isPreviewMode, setIsPreviewMode] = useState(false); + useScrollbarTheme(); + const [defaultLayout] = useState([15, 40, 50]); + // 移动端切换按钮 const MobileNav = () => (
@@ -75,15 +85,41 @@ export default function Home() { > - {/* 桌面端布局 */} -
- - - +
+ {/* 桌面端布局 */} + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+
- {/* 移动端布局 */} -
+
{isPreviewMode ? ( void; } +interface DeleteConfirmDialogProps { + projectName: string; + onDelete: () => void; +} + +const DeleteConfirmDialog: React.FC = ({ + projectName, + onDelete +}) => { + const theme = useResumeStore((state) => state.theme); + + return ( + + + + + e.stopPropagation()} // 添加这行 + > + + + 确认删除项目 + + + 您确定要删除项目 {projectName} 吗?此操作无法撤销。 + + + + + 取消 + + void }) => { + e.preventDefault(); + onDelete(); + }} + className={cn( + theme === "dark" + ? "bg-red-600 hover:bg-red-700 text-white" + : "bg-red-600 hover:bg-red-700 text-white" + )} + > + 删除 + + + + + ); +}; + const ProjectEditor: React.FC = ({ project, onSave, @@ -86,23 +187,11 @@ const ProjectEditor: React.FC = ({ ); }; -// ProjectEditor 组件用于编辑单个项目 -interface Project { - id: string; - name: string; - role: string; - date: string; - description: string; - technologies: string; - responsibilities: string; - achievements: string; -} - const ProjectItem = ({ project }: { project: Project }) => { const dragControls = useDragControls(); const [expandedId, setExpandedId] = useState(null); - const { theme, updateProjects, deleteProject } = useResumeStore(); + return ( {
- + /> , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/apps/fronted/src/components/ui/button.tsx b/apps/fronted/src/components/ui/button.tsx index 6ecff44..36496a2 100644 --- a/apps/fronted/src/components/ui/button.tsx +++ b/apps/fronted/src/components/ui/button.tsx @@ -1,11 +1,11 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { @@ -17,40 +17,40 @@ const buttonVariants = cva( secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline" + link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", - icon: "h-10 w-10" - } + icon: "h-10 w-10", + }, }, defaultVariants: { variant: "default", - size: "default" - } + size: "default", + }, } -); +) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean; + asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; + const Comp = asChild ? Slot : "button" return ( - ); + ) } -); -Button.displayName = "Button"; +) +Button.displayName = "Button" -export { Button, buttonVariants }; +export { Button, buttonVariants } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3817dc..7d9a41d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: '@radix-ui/react-accordion': specifier: ^1.2.1 version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-label': specifier: ^2.1.0 version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) @@ -1473,6 +1476,31 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@types/react': 18.3.2 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} peerDependencies: @@ -1596,6 +1624,39 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.2)(react@18.3.1) + '@types/react': 18.3.2 + '@types/react-dom': 18.3.0 + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.2)(react@18.3.1) + dev: false + /@radix-ui/react-direction@1.1.0(@types/react@18.3.2)(react@18.3.1): resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: