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,31 +24,28 @@ const Boards = () => {
}; };
return ( return (
<ScrollArea <Group
offsetScrollbars={"x"} wrap={"nowrap"}
scrollbars={"x"} gap={"md"}
scrollbarSize={0} pr={"sm"}
w={"100%"}> style={{ maxWidth: "100%", overflow: "hidden" }}>
<Group <SortableDnd
pr={"xs"} initialItems={boards}
wrap={"nowrap"} renderItem={renderBoard}
gap={0}> onDragEnd={onDragEnd}
<SortableDnd onItemClick={selectBoard}
initialItems={boards} containerStyle={{
renderItem={renderBoard} flexWrap: "nowrap",
onDragEnd={onDragEnd} gap: "var(--mantine-spacing-md)",
onItemClick={selectBoard} paddingBlock: "var(--mantine-spacing-md)",
containerStyle={{ paddingLeft: "var(--mantine-spacing-md)",
flexWrap: "nowrap", }}
gap: "var(--mantine-spacing-md)", dragHandleStyle={{ cursor: "pointer" }}
margin: "var(--mantine-spacing-md)", disabled={isMobile}
}} swiperEnabled
dragHandleStyle={{ cursor: "pointer" }} />
disabled={isMobile} <CreateBoardButton />
/> </Group>
<CreateBoardButton />
</Group>
</ScrollArea>
); );
}; };

View File

@ -28,12 +28,14 @@ const Header = () => {
wrap={"nowrap"} wrap={"nowrap"}
pr={"md"}> pr={"md"}>
<Boards /> <Boards />
<ColorSchemeToggle /> <Group wrap={"nowrap"}>
<ProjectSelect <ColorSchemeToggle />
data={projects} <ProjectSelect
value={selectedProject} data={projects}
onChange={value => value && setSelectedProject(value)} value={selectedProject}
/> 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,6 +102,81 @@ const SortableDnd = <T extends BaseItem>({
setActive(null); setActive(null);
}; };
const renderWithSwiper = () => (
<Swiper
modules={[Scrollbar, Mousewheel, FreeMode]}
spaceBetween={15}
slidesPerView={"auto"}
scrollbar={{ hide: false }}
mousewheel={{
enabled: true,
sensitivity: 0.2,
}}
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
style={{
gap: 0,
flexWrap: "nowrap",
flexDirection: vertical ? "column" : "row",
...containerStyle,
}}>
{items.map((item, index) => (
<Box
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}
/>
</Box>
))}
</Flex>
);
return ( return (
<DndContext <DndContext
modifiers={[restrictToHorizontalAxis]} modifiers={[restrictToHorizontalAxis]}
@ -108,37 +187,7 @@ const SortableDnd = <T extends BaseItem>({
<SortableContext <SortableContext
items={items} items={items}
disabled={disabled}> disabled={disabled}>
<Flex {swiperEnabled ? renderWithSwiper() : renderWithFlex()}
style={{
gap: 0,
flexWrap: "nowrap",
flexDirection: vertical ? "column" : "row",
...containerStyle,
}}>
{items.map((item, index) => (
<Box
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}
/>
</Box>
))}
</Flex>
</SortableContext> </SortableContext>
<SortableOverlay> <SortableOverlay>
{activeItem ? renderItem(activeItem, renderDraggable) : null} {activeItem ? renderItem(activeItem, renderDraggable) : null}