feat: swiper for boards on desktop

This commit is contained in:
2025-08-16 19:57:22 +04:00
parent a4bcd62189
commit 2e9ed02722
5 changed files with 116 additions and 68 deletions

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { Group, ScrollArea } from "@mantine/core"; import { Group } from "@mantine/core";
import Board from "@/app/deals/components/desktop/Board/Board"; import Board from "@/app/deals/components/desktop/Board/Board";
import CreateBoardButton from "@/app/deals/components/desktop/CreateBoardButton/CreateBoardButton"; import CreateBoardButton from "@/app/deals/components/desktop/CreateBoardButton/CreateBoardButton";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
@ -24,15 +24,11 @@ const Boards = () => {
}; };
return ( return (
<ScrollArea
offsetScrollbars={"x"}
scrollbars={"x"}
scrollbarSize={0}
w={"100%"}>
<Group <Group
pr={"xs"}
wrap={"nowrap"} wrap={"nowrap"}
gap={0}> gap={"md"}
pr={"sm"}
style={{ maxWidth: "100%", overflow: "hidden" }}>
<SortableDnd <SortableDnd
initialItems={boards} initialItems={boards}
renderItem={renderBoard} renderItem={renderBoard}
@ -41,14 +37,15 @@ const Boards = () => {
containerStyle={{ containerStyle={{
flexWrap: "nowrap", flexWrap: "nowrap",
gap: "var(--mantine-spacing-md)", gap: "var(--mantine-spacing-md)",
margin: "var(--mantine-spacing-md)", paddingBlock: "var(--mantine-spacing-md)",
paddingLeft: "var(--mantine-spacing-md)",
}} }}
dragHandleStyle={{ cursor: "pointer" }} dragHandleStyle={{ cursor: "pointer" }}
disabled={isMobile} disabled={isMobile}
swiperEnabled
/> />
<CreateBoardButton /> <CreateBoardButton />
</Group> </Group>
</ScrollArea>
); );
}; };

View File

@ -28,6 +28,7 @@ const Header = () => {
wrap={"nowrap"} wrap={"nowrap"}
pr={"md"}> pr={"md"}>
<Boards /> <Boards />
<Group wrap={"nowrap"}>
<ColorSchemeToggle /> <ColorSchemeToggle />
<ProjectSelect <ProjectSelect
data={projects} data={projects}
@ -35,6 +36,7 @@ const Header = () => {
onChange={value => value && setSelectedProject(value)} onChange={value => value && setSelectedProject(value)}
/> />
</Group> </Group>
</Group>
); );
}; };

View File

@ -13,7 +13,7 @@
} }
} }
.swiperContainer :global(.swiper-scrollbar-drag) { .swiper-container :global(.swiper-scrollbar-drag) {
@mixin dark { @mixin dark {
background-color: var(--mantine-color-dark-9); background-color: var(--mantine-color-dark-9);
} }
@ -22,6 +22,6 @@
} }
} }
.swiperContainer :global(.swiper-slide) { .swiper-container :global(.swiper-slide) {
padding-bottom: 20px; padding-bottom: 20px;
} }

View File

@ -101,7 +101,7 @@ const FunnelDnd = <
return ( return (
<Swiper <Swiper
ref={swiperRef} ref={swiperRef}
className={classes.swiperContainer} className={classes["swiper-container"]}
slidesPerView={1.2} slidesPerView={1.2}
modules={[Pagination]} modules={[Pagination]}
freeMode={{ enabled: false }} freeMode={{ enabled: false }}
@ -118,7 +118,7 @@ const FunnelDnd = <
return ( return (
<Swiper <Swiper
ref={swiperRef} ref={swiperRef}
className={classes.swiperContainer} className={classes["swiper-container"]}
modules={[Scrollbar, Mousewheel, FreeMode]} modules={[Scrollbar, Mousewheel, FreeMode]}
spaceBetween={15} spaceBetween={15}
slidesPerView={"auto"} slidesPerView={"auto"}

View File

@ -11,6 +11,8 @@ import { Active, DndContext, DragEndEvent } from "@dnd-kit/core";
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers"; import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
import { SortableContext } from "@dnd-kit/sortable"; import { SortableContext } from "@dnd-kit/sortable";
import { LexoRank } from "lexorank"; import { LexoRank } from "lexorank";
import { FreeMode, Mousewheel, Scrollbar } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import { Box, Flex } from "@mantine/core"; import { Box, Flex } from "@mantine/core";
import useDndSensors from "@/app/deals/hooks/useSensors"; import useDndSensors from "@/app/deals/hooks/useSensors";
import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay"; import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay";
@ -35,6 +37,7 @@ type Props<T extends BaseItem> = {
containerStyle?: CSSProperties; containerStyle?: CSSProperties;
vertical?: boolean; vertical?: boolean;
disabled?: boolean; disabled?: boolean;
swiperEnabled?: boolean;
}; };
const SortableDnd = <T extends BaseItem>({ const SortableDnd = <T extends BaseItem>({
@ -45,8 +48,9 @@ const SortableDnd = <T extends BaseItem>({
onDragEnd, onDragEnd,
onItemClick, onItemClick,
containerStyle, containerStyle,
vertical, vertical = false,
disabled = false, disabled = false,
swiperEnabled = false,
}: Props<T>) => { }: Props<T>) => {
const [active, setActive] = useState<Active | null>(null); const [active, setActive] = useState<Active | null>(null);
const [items, setItems] = useState<T[]>([]); const [items, setItems] = useState<T[]>([]);
@ -98,16 +102,48 @@ const SortableDnd = <T extends BaseItem>({
setActive(null); setActive(null);
}; };
return ( const renderWithSwiper = () => (
<DndContext <Swiper
modifiers={[restrictToHorizontalAxis]} modules={[Scrollbar, Mousewheel, FreeMode]}
sensors={sensors} spaceBetween={15}
onDragStart={({ active }) => setActive(active)} slidesPerView={"auto"}
onDragEnd={onDragEndLocal} scrollbar={{ hide: false }}
onDragCancel={() => setActive(null)}> mousewheel={{
<SortableContext enabled: true,
items={items} sensitivity: 0.2,
disabled={disabled}> }}
style={containerStyle}
direction={vertical ? "vertical" : "horizontal"}
freeMode={{ enabled: true }}
grabCursor>
{items.map((item, index) => (
<SwiperSlide
style={{ width: "fit-content" }}
key={index}
onClick={e => {
if (!onItemClick) return;
e.stopPropagation();
onItemClick(item);
}}>
<SortableItem
id={item.id}
disabled={disabled}
renderItem={renderDraggable =>
renderItem(item, renderDraggable)
}
renderDraggable={
renderDraggable
? () => renderDraggable(item)
: undefined
}
dragHandleStyle={dragHandleStyle}
/>
</SwiperSlide>
))}
</Swiper>
);
const renderWithFlex = () => (
<Flex <Flex
style={{ style={{
gap: 0, gap: 0,
@ -139,6 +175,19 @@ const SortableDnd = <T extends BaseItem>({
</Box> </Box>
))} ))}
</Flex> </Flex>
);
return (
<DndContext
modifiers={[restrictToHorizontalAxis]}
sensors={sensors}
onDragStart={({ active }) => setActive(active)}
onDragEnd={onDragEndLocal}
onDragCancel={() => setActive(null)}>
<SortableContext
items={items}
disabled={disabled}>
{swiperEnabled ? renderWithSwiper() : renderWithFlex()}
</SortableContext> </SortableContext>
<SortableOverlay> <SortableOverlay>
{activeItem ? renderItem(activeItem, renderDraggable) : null} {activeItem ? renderItem(activeItem, renderDraggable) : null}