From 0a13070d9e0b1ee145ef1bd5dfa1ad9018e19f3b Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Sat, 16 Aug 2025 14:59:37 +0400 Subject: [PATCH] feat: swiper --- package.json | 1 + .../CreateBoardButtonMobile.module.css | 4 +- .../deals/components/shared/Funnel/Funnel.tsx | 22 +--- .../StatusColumnWrapper.module.css | 3 - src/app/deals/hooks/useDealsAndStatusesDnd.ts | 30 ++++- src/app/layout.tsx | 3 + .../dnd/FunnelDnd/FunnelDnd.module.css | 18 +-- src/components/dnd/FunnelDnd/FunnelDnd.tsx | 111 +++++++++++------- .../dnd/SortableItem/DragHandle.tsx | 1 + yarn.lock | 8 ++ 10 files changed, 121 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 6dc2890..63d6bef 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-redux": "^9.2.0", "redux-persist": "^6.0.0", "sharp": "^0.34.3", + "swiper": "^11.2.10", "zod": "^4.0.14" }, "devDependencies": { diff --git a/src/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile.module.css b/src/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile.module.css index 643ce99..6e31f0a 100644 --- a/src/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile.module.css +++ b/src/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile.module.css @@ -1,11 +1,11 @@ .create-button { - padding: 10px 10px 11px 10px; + padding: 8px 10px 9px; border-bottom: 2px solid gray; } .spacer { - height: 46px; + height: 43px; border-bottom: 2px solid gray; width: 100%; } diff --git a/src/app/deals/components/shared/Funnel/Funnel.tsx b/src/app/deals/components/shared/Funnel/Funnel.tsx index f8f4894..ef93a27 100644 --- a/src/app/deals/components/shared/Funnel/Funnel.tsx +++ b/src/app/deals/components/shared/Funnel/Funnel.tsx @@ -1,8 +1,6 @@ "use client"; import React, { FC, ReactNode } from "react"; -import { Group, ScrollArea } from "@mantine/core"; -import CreateStatusButton from "@/app/deals/components/shared/CreateStatusButton/CreateStatusButton"; import DealCard from "@/app/deals/components/shared/DealCard/DealCard"; import DealContainer from "@/app/deals/components/shared/DealContainer/DealContainer"; import StatusColumnWrapper from "@/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper"; @@ -26,15 +24,17 @@ const Funnel: FC = () => { handleDragEnd, activeStatus, activeDeal, + swiperRef, } = useDealsAndStatusesDnd(); - const renderFunnelDnd = () => ( + return ( `${status.id}-status`} getItemsByContainer={(status: StatusSchema, items: DealSchema[]) => sortByLexorank( @@ -71,22 +71,6 @@ const Funnel: FC = () => { isCreatingContainerEnabled={!!selectedBoard} /> ); - - if (isMobile) return renderFunnelDnd(); - - return ( - - - {renderFunnelDnd()} - {selectedBoard && } - - - ); }; export default Funnel; diff --git a/src/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper.module.css b/src/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper.module.css index 84de2a7..3ee8022 100644 --- a/src/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper.module.css +++ b/src/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper.module.css @@ -1,8 +1,5 @@ .container { - min-width: 150px; - width: 15vw; - @media (max-width: 48em) { width: 80vw; } diff --git a/src/app/deals/hooks/useDealsAndStatusesDnd.ts b/src/app/deals/hooks/useDealsAndStatusesDnd.ts index d891a31..d7401c9 100644 --- a/src/app/deals/hooks/useDealsAndStatusesDnd.ts +++ b/src/app/deals/hooks/useDealsAndStatusesDnd.ts @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { RefObject, useMemo, useRef, useState } from "react"; import { DragOverEvent, DragStartEvent, Over } from "@dnd-kit/core"; import { useDebouncedCallback } from "@mantine/hooks"; import { useDealsContext } from "@/app/deals/contexts/DealsContext"; @@ -7,6 +7,7 @@ import useGetNewRank from "@/app/deals/hooks/useGetNewRank"; import { getStatusId, isStatusId } from "@/app/deals/utils/statusId"; import { DealSchema, StatusSchema } from "@/lib/client"; import { sortByLexorank } from "@/utils/lexorank"; +import { SwiperRef } from "swiper/swiper-react"; type ReturnType = { sortedStatuses: StatusSchema[]; @@ -15,9 +16,11 @@ type ReturnType = { handleDragEnd: ({ active, over }: DragOverEvent) => void; activeStatus: StatusSchema | null; activeDeal: DealSchema | null; + swiperRef: RefObject; }; const useDealsAndStatusesDnd = (): ReturnType => { + const swiperRef = useRef(null); const [activeDeal, setActiveDeal] = useState(null); const [activeStatus, setActiveStatus] = useState(null); const { statuses, setStatuses, updateStatus } = useStatusesContext(); @@ -43,6 +46,30 @@ const useDealsAndStatusesDnd = (): ReturnType => { if (!over) return; const activeId = active.id as string | number; + // Only perform swiper navigation for deal drag (not status column drag) + if (typeof activeId !== "string") { + const activeStatusLexorank = getStatusByDealId(Number(activeId))?.lexorank; + let overStatusLexorank: string | undefined; + + if (typeof over.id === "string" && isStatusId(over.id)) { + const overStatusId = getStatusId(over.id); + overStatusLexorank = statuses.find(s => s.id === overStatusId)?.lexorank; + } else { + overStatusLexorank = getStatusByDealId(Number(over.id))?.lexorank; + } + + if (activeStatusLexorank && overStatusLexorank && swiperRef.current?.swiper) { + const activeIndex = sortedStatuses.findIndex(s => s.lexorank === activeStatusLexorank); + const overIndex = sortedStatuses.findIndex(s => s.lexorank === overStatusLexorank); + + if (activeIndex > overIndex) { + swiperRef.current.swiper.slidePrev(); + } else if (activeIndex < overIndex) { + swiperRef.current.swiper.slideNext(); + } + } + } + if (typeof activeId === "string" && isStatusId(activeId)) { handleColumnDragOver(activeId, over); return; @@ -242,6 +269,7 @@ const useDealsAndStatusesDnd = (): ReturnType => { }; return { + swiperRef, sortedStatuses, handleDragStart, handleDragOver, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2f1305a..7d6590d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,9 @@ import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; import "@mantine/carousel/styles.css"; +import "swiper/css"; +import "swiper/css/pagination"; +import "swiper/css/scrollbar"; import { ReactNode } from "react"; import { ColorSchemeScript, diff --git a/src/components/dnd/FunnelDnd/FunnelDnd.module.css b/src/components/dnd/FunnelDnd/FunnelDnd.module.css index 21cecd1..124342d 100644 --- a/src/components/dnd/FunnelDnd/FunnelDnd.module.css +++ b/src/components/dnd/FunnelDnd/FunnelDnd.module.css @@ -13,21 +13,11 @@ } } -.indicator { - height: 4px; - @mixin light { - background-color: lightgray; - } +.swiper-scrollbar-drag { @mixin dark { - background-color: var(--mantine-color-dark-6); + color: var(--mantine-color-dark-7); } - - &[data-active] { - @mixin light { - background-color: gray; - } - @mixin dark { - background-color: var(--mantine-color-dark-5); - } + @mixin light { + color: var(--color-light-whitesmoke); } } diff --git a/src/components/dnd/FunnelDnd/FunnelDnd.tsx b/src/components/dnd/FunnelDnd/FunnelDnd.tsx index 5403ef4..b5d1ae7 100644 --- a/src/components/dnd/FunnelDnd/FunnelDnd.tsx +++ b/src/components/dnd/FunnelDnd/FunnelDnd.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { ReactNode } from "react"; +import React, { ReactNode, RefObject } from "react"; import { closestCorners, DndContext, @@ -12,8 +12,8 @@ import { horizontalListSortingStrategy, SortableContext, } from "@dnd-kit/sortable"; -import { Carousel } from "@mantine/carousel"; -import { Group } from "@mantine/core"; +import { FreeMode, Mousewheel, Pagination, Scrollbar } from "swiper/modules"; +import { Swiper, SwiperRef, SwiperSlide } from "swiper/react"; import CreateStatusButton from "@/app/deals/components/shared/CreateStatusButton/CreateStatusButton"; import useDndSensors from "@/app/deals/hooks/useSensors"; import FunnelColumn from "@/components/dnd/FunnelDnd/FunnelColumn"; @@ -21,7 +21,7 @@ import FunnelOverlay from "@/components/dnd/FunnelDnd/FunnelOverlay"; import { BaseDraggable } from "@/components/dnd/types/types"; import useIsMobile from "@/hooks/useIsMobile"; import SortableItem from "../SortableItem"; -import styles from "./FunnelDnd.module.css"; +import "./FunnelDnd.module.css"; type Props = { containers: TContainer[]; @@ -29,6 +29,7 @@ type Props = { onDragStart: (event: DragStartEvent) => void; onDragOver: (event: DragOverEvent) => void; onDragEnd: (event: DragEndEvent) => void; + swiperRef: RefObject; renderContainer: (container: TContainer, children: ReactNode) => ReactNode; renderContainerOverlay: ( container: TContainer, @@ -53,6 +54,7 @@ const FunnelDnd = < onDragStart, onDragOver, onDragEnd, + swiperRef, renderContainer, renderContainerOverlay, renderItem, @@ -71,54 +73,81 @@ const FunnelDnd = < containers.map(container => { const containerItems = getItemsByContainer(container, items); const containerId = getContainerId(container); - const item = ( - - renderContainer( - container, - - ) - } - /> + return ( + + + renderContainer( + container, + + ) + } + /> + ); - if (!isMobile) return item; - return {item}; }); const renderBody = () => { if (isMobile) { return ( - + {renderContainers()} - {isCreatingContainerEnabled && } - + {isCreatingContainerEnabled && ( + + + + )} + ); } return ( - + {renderContainers()} - + {isCreatingContainerEnabled && ( + + + + )} + ); }; diff --git a/src/components/dnd/SortableItem/DragHandle.tsx b/src/components/dnd/SortableItem/DragHandle.tsx index 1afaa35..05f9bd7 100644 --- a/src/components/dnd/SortableItem/DragHandle.tsx +++ b/src/components/dnd/SortableItem/DragHandle.tsx @@ -23,6 +23,7 @@ const DragHandle = ({ id, children, style, disabled }: Props) => { cursor: disabled ? "default" : "grab", ...style, }} + className={disabled ? "" : "swiper-no-swiping"} ref={setNodeRef}> {children} diff --git a/yarn.lock b/yarn.lock index f11fea0..d5fb372 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6179,6 +6179,7 @@ __metadata: storybook-dark-mode: "npm:^4.0.2" stylelint: "npm:^16.20.0" stylelint-config-standard-scss: "npm:^15.0.1" + swiper: "npm:^11.2.10" tailwindcss: "npm:^4.1.11" ts-jest: "npm:^29.4.0" typescript: "npm:5.8.3" @@ -13278,6 +13279,13 @@ __metadata: languageName: node linkType: hard +"swiper@npm:^11.2.10": + version: 11.2.10 + resolution: "swiper@npm:11.2.10" + checksum: 10c0/b7e3a7c79d92ccc62af77744edc376c9aefc771a0abd50c4adf07b5e3450b5da9ca7f84f5809e275ff65e1bc9d4500d930628e84a76fc7f2db403291c69b7fac + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4"