From 9fb9e794dbee9d4197a09df947ce4811d7059ec8 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Sat, 9 Aug 2025 15:51:23 +0400 Subject: [PATCH] feat: boards dnd editor for mobile --- src/app/deals/components/Board/Board.tsx | 7 +-- src/app/deals/components/Board/BoardMenu.tsx | 7 +-- .../DealContainer/DealContainer.tsx | 6 +-- .../ProjectBoardsEditorDrawer.tsx | 35 ++++++++++---- .../components/BoardMobile.tsx | 46 +++++++++++++++++++ ...ButtonMobile.tsx => CreateBoardButton.tsx} | 6 +-- src/components/dnd/FunnelDnd/FunnelDnd.tsx | 22 +++++---- .../dnd/SortableDnd/SortableDnd.tsx | 29 ++++++++---- .../dnd/SortableItem/SortableItem.tsx | 30 ++++++++---- 9 files changed, 139 insertions(+), 49 deletions(-) create mode 100644 src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx rename src/app/deals/drawers/ProjectBoardsEditorDrawer/components/{CreateBoardButtonMobile.tsx => CreateBoardButton.tsx} (85%) diff --git a/src/app/deals/components/Board/Board.tsx b/src/app/deals/components/Board/Board.tsx index feee2e1..14e7e78 100644 --- a/src/app/deals/components/Board/Board.tsx +++ b/src/app/deals/components/Board/Board.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from "react"; +import React, { CSSProperties, FC, useState } from "react"; import { Box, Group, Text } from "@mantine/core"; import BoardMenu from "@/app/deals/components/Board/BoardMenu"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; @@ -7,9 +7,10 @@ import { BoardSchema } from "@/lib/client"; type Props = { board: BoardSchema; + containerStyle?: CSSProperties; }; -const Board: FC = ({ board }) => { +const Board: FC = ({ board, containerStyle }) => { const [isHovered, setIsHovered] = useState(false); const { onUpdateBoard } = useBoardsContext(); @@ -20,7 +21,7 @@ const Board: FC = ({ board }) => { py={"xs"} justify={"space-between"} wrap={"nowrap"} - style={{ borderWidth: 1 }} + style={{ ...containerStyle, borderWidth: 1 }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> void; + isHovered?: boolean; + menuIconSize?: number; }; -const BoardMenu: FC = ({ isHovered, board, startEditing }) => { +const BoardMenu: FC = ({ board, startEditing, isHovered = true, menuIconSize = 16 }) => { const { selectedBoard, onDeleteBoard } = useBoardsContext(); return ( @@ -28,7 +29,7 @@ const BoardMenu: FC = ({ isHovered, board, startEditing }) => { cursor: "pointer", }} onClick={e => e.stopPropagation()}> - + diff --git a/src/app/deals/components/DealContainer/DealContainer.tsx b/src/app/deals/components/DealContainer/DealContainer.tsx index 07a0262..f980161 100644 --- a/src/app/deals/components/DealContainer/DealContainer.tsx +++ b/src/app/deals/components/DealContainer/DealContainer.tsx @@ -17,9 +17,9 @@ const DealContainer: FC = ({ deal, disabled = false }) => { - {dealBody} - + id={deal.id} + renderItem={() => dealBody} + /> ); }; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx index f673846..98ccd95 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { FC } from "react"; -import { IconChevronLeft } from "@tabler/icons-react"; -import { Box, Center, Drawer, Group, rem, Text } from "@mantine/core"; -import Board from "@/app/deals/components/Board/Board"; +import React, { FC, ReactNode } from "react"; +import { IconChevronLeft, IconGripVertical } from "@tabler/icons-react"; +import { Box, Center, Divider, Drawer, Group, rem, Text } from "@mantine/core"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -import CreateBoardButtonMobile from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButtonMobile"; +import BoardMobile from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile"; +import CreateBoardButton from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton"; import SortableDnd from "@/components/dnd/SortableDnd"; import { BoardSchema } from "@/lib/client"; @@ -20,8 +20,22 @@ const ProjectBoardsEditorDrawer: FC = () => { const { selectedProject } = useProjectsContext(); const onClose = () => setIsEditorDrawerOpened(false); - const renderBoard = (board: BoardSchema) => { - return ; + const renderDraggable = () => ( + + + + ); + + const renderBoard = ( + board: BoardSchema, + renderDraggable?: (item: BoardSchema) => ReactNode + ) => { + return ( + + {renderDraggable && renderDraggable(board)} + + + ); }; const onDragEnd = (itemId: number, newLexorank: string) => { @@ -55,13 +69,16 @@ const ProjectBoardsEditorDrawer: FC = () => { + - + ); }; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx new file mode 100644 index 0000000..dff1995 --- /dev/null +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx @@ -0,0 +1,46 @@ +import React, { FC } from "react"; +import { Box, Group, Text } from "@mantine/core"; +import { modals } from "@mantine/modals"; +import BoardMenu from "@/app/deals/components/Board/BoardMenu"; +import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; +import { BoardSchema } from "@/lib/client"; + +type Props = { + board: BoardSchema; +}; + +const BoardMobile: FC = ({ board }) => { + const { onUpdateBoard } = useBoardsContext(); + + const startEditing = () => { + modals.openContextModal({ + modal: "enterNameModal", + title: "Редактирование доски", + withCloseButton: true, + innerProps: { + onComplete: name => onUpdateBoard(board.id, { name }), + defaultValue: board.name, + }, + }); + }; + + return ( + + + {board.name} + + + + ); +}; + +export default BoardMobile; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButtonMobile.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx similarity index 85% rename from src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButtonMobile.tsx rename to src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx index f80dd96..f53c162 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButtonMobile.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx @@ -3,7 +3,7 @@ import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -const CreateBoardButtonMobile = () => { +const CreateBoardButton = () => { const { onCreateBoard } = useBoardsContext(); const onStartCreating = () => { @@ -18,7 +18,7 @@ const CreateBoardButtonMobile = () => { }; return ( - + Создать доску @@ -27,4 +27,4 @@ const CreateBoardButtonMobile = () => { ); }; -export default CreateBoardButtonMobile; +export default CreateBoardButton; diff --git a/src/components/dnd/FunnelDnd/FunnelDnd.tsx b/src/components/dnd/FunnelDnd/FunnelDnd.tsx index 014f7f8..3718d62 100644 --- a/src/components/dnd/FunnelDnd/FunnelDnd.tsx +++ b/src/components/dnd/FunnelDnd/FunnelDnd.tsx @@ -84,16 +84,18 @@ const FunnelDnd = < - {renderContainer( - container, - - )} - + disabled={disabled} + renderItem={() => + renderContainer( + container, + + ) + } + /> ); })} = { initialItems: T[]; - renderItem: (item: T) => ReactNode; + renderItem: ( + item: T, + renderDraggable?: (item: T) => ReactNode + ) => ReactNode; + renderDraggable?: (item: T) => ReactNode; // if not passed - the whole item renders as draggable + dragHandleStyle?: CSSProperties; onDragEnd: (itemId: number, newLexorank: string) => void; onItemClick?: (item: T) => void; containerStyle?: CSSProperties; - itemStyle?: CSSProperties; vertical?: boolean; disabled?: boolean; }; @@ -35,10 +39,11 @@ type Props = { const SortableDnd = ({ initialItems, renderItem, + renderDraggable, + dragHandleStyle, onDragEnd, onItemClick, containerStyle, - itemStyle, vertical, disabled = false, }: Props) => { @@ -120,18 +125,24 @@ const SortableDnd = ({ onItemClick(item); }}> - {renderItem(item)} - + disabled={disabled} + renderItem={renderDraggable => + renderItem(item, renderDraggable) + } + renderDraggable={ + renderDraggable + ? () => renderDraggable(item) + : undefined + } + dragHandleStyle={dragHandleStyle} + /> ))} - {activeItem ? renderItem(activeItem) : null} + {activeItem ? renderItem(activeItem, renderDraggable) : null} ); diff --git a/src/components/dnd/SortableItem/SortableItem.tsx b/src/components/dnd/SortableItem/SortableItem.tsx index 84d6a3e..430b786 100644 --- a/src/components/dnd/SortableItem/SortableItem.tsx +++ b/src/components/dnd/SortableItem/SortableItem.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties, PropsWithChildren, useMemo } from "react"; +import React, { CSSProperties, ReactNode, useMemo } from "react"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import DragHandle from "./DragHandle"; @@ -6,18 +6,19 @@ import SortableItemContext from "./SortableItemContext"; type Props = { id: number | string; - itemStyle?: CSSProperties; - dragHandleStyle?: CSSProperties; + renderItem: (renderDraggable?: () => ReactNode) => ReactNode; + renderDraggable?: () => ReactNode; // if not passed - the whole item renders as draggable disabled?: boolean; + dragHandleStyle?: CSSProperties; }; const SortableItem = ({ - children, - itemStyle, - id, + renderItem, dragHandleStyle, + renderDraggable, + id, disabled, -}: PropsWithChildren) => { +}: Props) => { const { attributes, isDragging, @@ -41,15 +42,26 @@ const SortableItem = ({ opacity: isDragging ? 0.4 : undefined, transform: CSS.Translate.toString(transform), transition, - ...itemStyle, }; + const renderDragHandle = () => ( + + {renderDraggable && renderDraggable()} + + ); + return (
- {children} + {renderDraggable ? ( + renderItem(renderDragHandle) + ) : ( + + {renderItem()} + + )}
);