feat: division between mobile and desktop components, boards for mobile

This commit is contained in:
2025-08-13 09:55:27 +04:00
parent 1a98facd72
commit 838c9640a1
26 changed files with 175 additions and 89 deletions

View File

@ -1,50 +0,0 @@
import React, { FC } from "react";
import { Box, Group, Text } from "@mantine/core";
import BoardMenu from "@/app/deals/components/Board/BoardMenu";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput";
import { BoardSchema } from "@/lib/client";
type Props = {
board: BoardSchema;
};
const BoardMobile: FC<Props> = ({ board }) => {
const { selectedBoard } = useBoardsContext();
const { onUpdateBoard } = useBoardsContext();
return (
<Group
px={"md"}
py={"xs"}
bdrs={"lg"}
wrap={"nowrap"}
justify={"space-between"}>
<InPlaceInput
defaultValue={board.name}
onComplete={value => onUpdateBoard(board.id, { name: value })}
inputStyles={{
input: {
height: 25,
minHeight: 25,
},
}}
getChildren={startEditing => (
<>
<Box>
<Text>{board.name}</Text>
</Box>
<BoardMenu
isHovered
board={board}
startEditing={startEditing}
/>
</>
)}
modalTitle={"Редактирование доски"}
/>
</Group>
);
};
export default BoardMobile;

View File

@ -1,7 +1,7 @@
import React, { FC, useState } from "react";
import { Box, Group, Text } from "@mantine/core";
import styles from "@/app/deals/components/Board/Board.module.css";
import BoardMenu from "@/app/deals/components/Board/BoardMenu";
import styles from "@/app/deals/components/desktop/Board/Board.module.css";
import BoardMenu from "@/app/deals/components/shared/BoardMenu/BoardMenu";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import SmallPageBlock from "@/components/layout/SmallPageBlock/SmallPageBlock";
import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput";

View File

@ -2,9 +2,8 @@
import React from "react";
import { Group, ScrollArea } from "@mantine/core";
import Board from "@/app/deals/components/Board/Board";
import BoardMobile from "@/app/deals/components/BoardMobile/BoardMobile";
import CreateBoardButton from "@/app/deals/components/CreateBoardButton/CreateBoardButton";
import Board from "@/app/deals/components/desktop/Board/Board";
import CreateBoardButton from "@/app/deals/components/desktop/CreateBoardButton/CreateBoardButton";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import SortableDnd from "@/components/dnd/SortableDnd";
import useIsMobile from "@/hooks/useIsMobile";
@ -16,10 +15,6 @@ const Boards = () => {
const renderBoard = (board: BoardSchema) => <Board board={board} />;
const renderBoardMobile = (board: BoardSchema) => (
<BoardMobile board={board} />
);
const onDragEnd = (itemId: number, newLexorank: string) => {
onUpdateBoard(itemId, { lexorank: newLexorank });
};
@ -39,7 +34,7 @@ const Boards = () => {
gap={0}>
<SortableDnd
initialItems={boards}
renderItem={isMobile ? renderBoardMobile : renderBoard}
renderItem={renderBoard}
onDragEnd={onDragEnd}
onItemClick={selectBoard}
containerStyle={{ flexWrap: "nowrap" }}

View File

@ -0,0 +1,14 @@
.board-mobile {
min-width: 50px;
flex-wrap: nowrap;
gap: 3px;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-bottom: 2px solid gray;
}
.board-mobile-selected {
border: 2px solid gray;
border-bottom: 0;
}

View File

@ -0,0 +1,29 @@
import React, { FC } from "react";
import classNames from "classnames";
import { Box, Text } from "@mantine/core";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { BoardSchema } from "@/lib/client";
import styles from "./BoardMobile.module.css";
type Props = {
board: BoardSchema;
};
const BoardMobile: FC<Props> = ({ board }) => {
const { selectedBoard } = useBoardsContext();
return (
<Box
px={"md"}
py={"xs"}
className={classNames(
styles["board-mobile"],
selectedBoard?.id === board.id &&
styles["board-mobile-selected"]
)}>
<Text style={{ textWrap: "nowrap" }}>{board.name}</Text>
</Box>
);
};
export default BoardMobile;

View File

@ -0,0 +1,50 @@
"use client";
import React from "react";
import { Group, ScrollArea } from "@mantine/core";
import BoardMobile from "@/app/deals/components/mobile/BoardMobile/BoardMobile";
import CreateBoardButtonMobile from "@/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import SortableDnd from "@/components/dnd/SortableDnd";
import useIsMobile from "@/hooks/useIsMobile";
import { BoardSchema } from "@/lib/client";
const BoardsMobile = () => {
const { boards, setSelectedBoard, onUpdateBoard } = useBoardsContext();
const isMobile = useIsMobile();
const renderBoard = (board: BoardSchema) => <BoardMobile board={board} />;
const onDragEnd = (itemId: number, newLexorank: string) => {
onUpdateBoard(itemId, { lexorank: newLexorank });
};
const selectBoard = (board: BoardSchema) => {
setSelectedBoard(board);
};
return (
<ScrollArea
offsetScrollbars={"x"}
scrollbars={"x"}
scrollbarSize={0}
w={"100vw"}>
<Group
wrap={"nowrap"}
gap={0}>
<SortableDnd
initialItems={boards}
renderItem={renderBoard}
onDragEnd={onDragEnd}
onItemClick={selectBoard}
containerStyle={{ flexWrap: "nowrap" }}
dragHandleStyle={{ cursor: "pointer" }}
disabled={isMobile}
/>
<CreateBoardButtonMobile />
</Group>
</ScrollArea>
);
};
export default BoardsMobile;

View File

@ -0,0 +1,12 @@
.create-button {
padding: 11px 10px 12px 10px;
border-bottom: 2px solid gray;
}
.spacer {
height: 46px;
padding: 11px 10px 12px 10px;
border-bottom: 2px solid gray;
width: 100%;
}

View File

@ -0,0 +1,35 @@
import { IconPlus } from "@tabler/icons-react";
import { Box } from "@mantine/core";
import styles from "@/app/deals/components/mobile/CreateBoardButtonMobile/CreateBoardButtonMobile.module.css";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput";
const CreateBoardButtonMobile = () => {
const { onCreateBoard } = useBoardsContext();
return (
<>
<InPlaceInput
placeholder={"Название доски"}
onComplete={onCreateBoard}
getChildren={startEditing => (
<Box
onClick={startEditing}
className={styles["create-button"]}>
<IconPlus size={22} />
</Box>
)}
modalTitle={"Создание доски"}
inputStyles={{
wrapper: {
marginLeft: 15,
marginRight: 15,
},
}}
/>
<Box className={styles.spacer} />
</>
);
};
export default CreateBoardButtonMobile;

View File

@ -2,7 +2,6 @@ import React, { FC } from "react";
import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
import { Box, Group, Menu, Text } from "@mantine/core";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import useIsMobile from "@/hooks/useIsMobile";
import { BoardSchema } from "@/lib/client";
type Props = {
@ -19,7 +18,6 @@ const BoardMenu: FC<Props> = ({
menuIconSize = 16,
}) => {
const { selectedBoard, onDeleteBoard } = useBoardsContext();
const isMobile = useIsMobile();
return (
<Menu>
@ -27,10 +25,7 @@ const BoardMenu: FC<Props> = ({
<Box
style={{
opacity:
!isMobile &&
(isHovered || selectedBoard?.id === board.id)
? 1
: 0,
isHovered || selectedBoard?.id === board.id ? 1 : 0,
cursor: "pointer",
}}
onClick={e => e.stopPropagation()}>

View File

@ -1,6 +1,6 @@
import React, { FC, useMemo } from "react";
import { Box } from "@mantine/core";
import DealCard from "@/app/deals/components/DealCard/DealCard";
import DealCard from "@/app/deals/components/shared/DealCard/DealCard";
import SortableItem from "@/components/dnd/SortableItem";
import { DealSchema } from "@/lib/client";

View File

@ -2,10 +2,10 @@
import React, { FC, ReactNode } from "react";
import { Group, ScrollArea } from "@mantine/core";
import CreateStatusButton from "@/app/deals/components/CreateStatusButton/CreateStatusButton";
import DealCard from "@/app/deals/components/DealCard/DealCard";
import DealContainer from "@/app/deals/components/DealContainer/DealContainer";
import StatusColumnWrapper from "@/app/deals/components/StatusColumnWrapper/StatusColumnWrapper";
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";
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";

View File

@ -2,7 +2,8 @@
import { IconChevronLeft, IconSettings } from "@tabler/icons-react";
import { Box, Group, Stack, Text } from "@mantine/core";
import Boards from "@/app/deals/components/Boards/Boards";
import Boards from "@/app/deals/components/desktop/Boards/Boards";
import BoardsMobile from "@/app/deals/components/mobile/BoardsMobile/BoardsMobile";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect";
@ -20,7 +21,8 @@ const Header = () => {
<Group
w={"100%"}
justify={"space-between"}
wrap={"nowrap"}>
wrap={"nowrap"}
pr={"md"}>
<Boards />
<ColorSchemeToggle />
<ProjectSelect
@ -35,9 +37,7 @@ const Header = () => {
const getMobileHeader = () => {
return (
<Stack>
<Group
justify={"space-between"}
w={"100%"}>
<Group justify={"space-between"}>
<Box p={"md"}>
<IconChevronLeft />
</Box>
@ -48,7 +48,7 @@ const Header = () => {
<IconSettings />
</Box>
</Group>
<Boards />
<BoardsMobile />
</Stack>
);
};

View File

@ -1,10 +1,10 @@
import React, { ReactNode } from "react";
import { Box, Group, Text } from "@mantine/core";
import styles from "@/app/deals/components/StatusColumnWrapper/StatusColumnWrapper.module.css";
import StatusMenu from "@/app/deals/components/StatusColumnWrapper/StatusMenu";
import StatusMenu from "@/app/deals/components/shared/StatusMenu/StatusMenu";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput";
import { StatusSchema } from "@/lib/client";
import styles from "./StatusColumnWrapper.module.css";
type Props = {
status: StatusSchema;

View File

@ -1,7 +1,7 @@
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 BoardMenu from "@/app/deals/components/shared/BoardMenu/BoardMenu";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import { StatusSchema } from "@/lib/client";

View File

@ -1,7 +1,7 @@
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 BoardMenu from "@/app/deals/components/shared/BoardMenu/BoardMenu";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { BoardSchema } from "@/lib/client";

View File

@ -1,5 +1,5 @@
import Funnel from "@/app/deals/components/Funnel/Funnel";
import Header from "@/app/deals/components/Header/Header";
import Funnel from "@/app/deals/components/shared/Funnel/Funnel";
import Header from "@/app/deals/components/shared/Header/Header";
import { BoardsContextProvider } from "@/app/deals/contexts/BoardsContext";
import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext";
import { StatusesContextProvider } from "@/app/deals/contexts/StatusesContext";
@ -16,7 +16,7 @@ export default function DealsPage() {
<PageContainer>
<PageBlock
transparent
style={{ padding: 0, paddingRight: 15 }}>
style={{ padding: 0 }}>
<Header />
</PageBlock>
<PageBlock className={"mobile-padding-height"}>

View File

@ -13,7 +13,7 @@ import {
SortableContext,
} from "@dnd-kit/sortable";
import { Group, ScrollArea } from "@mantine/core";
import CreateStatusButton from "@/app/deals/components/CreateStatusButton/CreateStatusButton";
import CreateStatusButton from "@/app/deals/components/shared/CreateStatusButton/CreateStatusButton";
import useDndSensors from "@/app/deals/hooks/useSensors";
import FunnelColumn from "@/components/dnd/FunnelDnd/FunnelColumn";
import FunnelOverlay from "@/components/dnd/FunnelDnd/FunnelOverlay";

View File

@ -23,7 +23,7 @@ const DragHandle = ({ id, children, style, disabled }: Props) => {
style={{
width: "100wv",
cursor: disabled ? "default" : "grab",
touchAction: "none",
touchAction: disabled ? "auto" : "none",
...style,
}}
ref={setNodeRef}>

View File

@ -1,15 +1,21 @@
.container {
background-color: white;
@mixin dark {
background-color: var(--mantine-color-dark-8);
}
@mixin light {
background-color: white;
}
@media (min-width: 48em) {
padding: rem(35);
border-radius: rem(20);
@mixin dark {
box-shadow: 5px 5px 30px 1px var(--mantine-color-dark-6);
}
@mixin light {
box-shadow: 5px 5px 24px rgba(0, 0, 0, 0.16);
}
@media (min-width: 48em) {
padding: rem(35);
border-radius: rem(20);
}
}