feat(categories): basic dnd kanban board

This commit is contained in:
Paul 2021-10-27 23:25:08 +01:00
parent 314ecee95c
commit da47348273
3 changed files with 217 additions and 18 deletions

View file

@ -82,6 +82,7 @@
"@types/node": "^15.12.4", "@types/node": "^15.12.4",
"@types/preact-i18n": "^2.3.0", "@types/preact-i18n": "^2.3.0",
"@types/prismjs": "^1.16.5", "@types/prismjs": "^1.16.5",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-helmet": "^6.1.1", "@types/react-helmet": "^6.1.1",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-scroll": "^1.8.2", "@types/react-scroll": "^1.8.2",

View file

@ -1,5 +1,6 @@
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import { Category } from "revolt-api/types/Servers"; import { Category } from "revolt-api/types/Servers";
import { Server } from "revolt.js/dist/maps/Servers"; import { Server } from "revolt.js/dist/maps/Servers";
import styled from "styled-components"; import styled from "styled-components";
@ -28,10 +29,25 @@ const KanbanEntry = styled.div`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 4px;
margin: 4px; margin: 4px;
height: 40px; height: 40px;
padding: 8px;
flex-shrink: 0; flex-shrink: 0;
font-size: 0.9em;
background: var(--primary-background); background: var(--primary-background);
img {
flex-shrink: 0;
}
span {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
`; `;
const KanbanList = styled.div` const KanbanList = styled.div`
@ -42,15 +58,33 @@ const KanbanList = styled.div`
overflow-y: auto; overflow-y: auto;
flex-direction: column; flex-direction: column;
background: var(--secondary-background); background: var(--secondary-background);
> [data-rbd-droppable-id] {
min-height: 24px;
}
`;
const KanbanListTitle = styled.div`
height: 42px;
display: grid;
place-items: center;
`; `;
const KanbanBoard = styled.div` const KanbanBoard = styled.div`
gap: 8px; gap: 8px;
display: flex; display: flex;
overflow-x: scroll;
flex-direction: row; flex-direction: row;
`; `;
const FullSize = styled.div`
flex-grow: 1;
> * {
height: 100%;
overflow-x: scroll;
}
`;
interface Props { interface Props {
server: Server; server: Server;
} }
@ -61,24 +95,181 @@ export const Categories = observer(({ server }: Props) => {
); );
return ( return (
<DragDropContext
onDragEnd={(target) => {
const { destination, source, draggableId, type } = target;
if (
!destination ||
(destination.droppableId === source.droppableId &&
destination.index === source.index)
) {
return;
}
if (type === "column") {
// Remove from array.
const cat = categories.find((x) => x.id === draggableId);
const arr = categories.filter((x) => x.id !== draggableId);
// Insert at new position.
arr.splice(destination.index, 0, cat!);
setCategories(arr);
} else {
setCategories(
categories.map((category) => {
if (category.id === destination.droppableId) {
const channels = category.channels.filter(
(x) => x !== draggableId,
);
channels.splice(
destination.index,
0,
draggableId,
);
return {
...category,
channels,
};
} else if (category.id === source.droppableId) {
return {
...category,
channels: category.channels.filter(
(x) => x !== draggableId,
),
};
}
return category;
}),
);
}
}}>
<FullSize>
<Droppable
droppableId="categories"
direction="horizontal"
type="column">
{(provided) =>
(
<div
ref={provided.innerRef}
{...provided.droppableProps}>
<KanbanBoard> <KanbanBoard>
{categories.map((category, i) => ( {categories.map((category, index) => (
<KanbanList key={category.id}> <Draggable
<h3>{category.title}</h3> key={category.id}
{category.channels.map((x) => { draggableId={category.id}
const channel = server.client.channels.get(x); index={index}>
if (!channel) return null; {(provided) =>
(
<div
{...(provided.draggableProps as any)}
ref={provided.innerRef}>
<KanbanList
key={category.id}>
<KanbanListTitle
{...(provided.dragHandleProps as any)}>
<span>
{
category.title
}
</span>
</KanbanListTitle>
<Droppable
droppableId={
category.id
}
key={
category.id
}>
{(provided) =>
(
<div
ref={
provided.innerRef
}
{...provided.droppableProps}>
{category.channels.map(
(
x,
index,
) => {
const channel =
server.client.channels.get(
x,
);
if (
!channel
)
return null;
return ( return (
<KanbanEntry key={x}> <Draggable
<ChannelIcon target={channel} size={24} />{" "} key={
{channel.name} x
}
draggableId={
x
}
index={
index
}>
{(
provided,
) =>
(
<div
{...(provided.draggableProps as any)}
{...provided.dragHandleProps}
ref={
provided.innerRef
}>
<KanbanEntry>
<ChannelIcon
target={
channel
}
size={
24
}
/>
<span>
{
channel.name
}
</span>
</KanbanEntry> </KanbanEntry>
</div>
) as any
}
</Draggable>
); );
})} },
)}
{
provided.placeholder
}
</div>
) as any
}
</Droppable>
</KanbanList> </KanbanList>
</div>
) as any
}
</Draggable>
))} ))}
{provided.placeholder}
</KanbanBoard> </KanbanBoard>
</div>
) as any
}
</Droppable>
</FullSize>
</DragDropContext>
); );
}); });

View file

@ -1398,6 +1398,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/react-beautiful-dnd@^13.1.2":
version "13.1.2"
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130"
integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==
dependencies:
"@types/react" "*"
"@types/react-helmet@^6.1.1": "@types/react-helmet@^6.1.1":
version "6.1.2" version "6.1.2"
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.2.tgz#e9d7d16b29e4ec5716711c52c35c3cec45819eac" resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.2.tgz#e9d7d16b29e4ec5716711c52c35c3cec45819eac"