From d9c922557c3c35cccc6ad6cb6383f2ae11d485a4 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Sat, 7 Sep 2024 02:35:35 +0200 Subject: [PATCH 1/3] working grid --- package.json | 2 + pnpm-lock.yaml | 65 ++++++- .../molecules/DropGridItem/index.tsx | 35 ++++ src/pages/dnd.tsx | 153 +++++++++++++++++ src/pages/index.tsx | 158 ++++++++---------- 5 files changed, 317 insertions(+), 96 deletions(-) create mode 100644 src/components/molecules/DropGridItem/index.tsx create mode 100644 src/pages/dnd.tsx diff --git a/package.json b/package.json index 554d946..e8ef8f8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "lint": "next lint" }, "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", "@shadow-panda/style-context": "^0.7.1", "next": "14.2.8", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfa0769..d74b6f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@dnd-kit/core': + specifier: ^6.1.0 + version: 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': + specifier: ^8.0.0 + version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@shadow-panda/style-context': specifier: ^0.7.1 version: 0.7.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -85,6 +91,28 @@ packages: peerDependencies: postcss-selector-parser: ^6.0.13 + '@dnd-kit/accessibility@3.1.0': + resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.1.0': + resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@8.0.0': + resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==} + peerDependencies: + '@dnd-kit/core': ^6.1.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@esbuild/aix-ppc64@0.20.2': resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} @@ -2041,6 +2069,31 @@ snapshots: dependencies: postcss-selector-parser: 6.1.1 + '@dnd-kit/accessibility@3.1.0(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.7.0 + + '@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.0(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.7.0 + + '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.7.0 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.7.0 + '@esbuild/aix-ppc64@0.20.2': optional: true @@ -2998,7 +3051,7 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0) eslint-plugin-react: 7.35.2(eslint@8.57.0) @@ -3018,13 +3071,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -3037,14 +3090,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -3059,7 +3112,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 diff --git a/src/components/molecules/DropGridItem/index.tsx b/src/components/molecules/DropGridItem/index.tsx new file mode 100644 index 0000000..c5db1fd --- /dev/null +++ b/src/components/molecules/DropGridItem/index.tsx @@ -0,0 +1,35 @@ +import { ReactNode } from "react"; +import { css } from "styled-system/css"; +import { Box } from "styled-system/jsx"; +import { useDroppable } from "@dnd-kit/core"; + +export const DroppableGridItem = (props: { children: ReactNode }) => { + const { isOver, setNodeRef } = useDroppable({ + id: "droppable", + }); + const style = { + color: isOver ? "green" : undefined, + }; + + return ( +
+ {props.children} + +
+ ); +}; diff --git a/src/pages/dnd.tsx b/src/pages/dnd.tsx new file mode 100644 index 0000000..2754500 --- /dev/null +++ b/src/pages/dnd.tsx @@ -0,0 +1,153 @@ +import { CardAtlas } from "@/components/atoms/mock-cards/atlas"; +import { Box, Grid } from "styled-system/jsx"; +import { grid } from "styled-system/patterns"; +import { + DndContext, + DragEndEvent, + useDraggable, + useDroppable, + closestCenter, +} from "@dnd-kit/core"; +import { + SortableContext, + rectSortingStrategy, + arrayMove, +} from "@dnd-kit/sortable"; +import { ReactNode, useState } from "react"; + +const nav = `100px`; +const footer = `50px`; +const body = `calc(100vh - ${nav} - ${footer})`; + +export default function Home() { + const [gridItems, setGridItems] = useState( + Array.from({ length: 20 }, (_, gridIndex) => [ + { id: `card-${gridIndex}-0` }, // Each droppable starts with a single card + ]), + ); + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event; + + if (over) { + const originIndex = parseInt(active.data.current?.gridIndex, 10); + const destinationIndex = parseInt(over.id.split("-")[1], 10); + + // Remove card from the origin area + const updatedGrid = [...gridItems]; + const [movedCard] = updatedGrid[originIndex].splice( + active.data.current.index, + 1, + ); + + // Place card in the destination area + updatedGrid[destinationIndex].push(movedCard); + + setGridItems(updatedGrid); + } + } + + return ( +
+ + +
+

Experiment arranging a Sorcery Grid

+

+ While hovering over a card, click{" "} + + ALT + {" "} + key +

+
+
+ {gridItems.map((cards, gridIndex) => ( + + `${gridIndex}-${i}`)} + strategy={rectSortingStrategy} + > + {cards.map((card, cardIndex) => ( + + + + ))} + + + ))} +
+ +
footer
+
+
+
+ ); +} + +const Drag = (props: { + children: ReactNode; + gridIndex: number; + index: number; +}) => { + const { attributes, listeners, setNodeRef, transform } = useDraggable({ + id: `${props.gridIndex}-${props.index}`, + data: { + gridIndex: props.gridIndex, + index: props.index, + }, + }); + const style = transform + ? { + transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, + } + : undefined; + + return ( +
+ {props.children} +
+ ); +}; + +const DroppableGridItem = (props: { children: ReactNode; id: string }) => { + const { setNodeRef } = useDroppable({ + id: props.id, + }); + + return ( +
+ {props.children} +
+ ); +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 82378fe..557bc27 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,107 +1,85 @@ import { CardAtlas } from "@/components/atoms/mock-cards/atlas"; -import { CardImage } from "@/components/atoms/mock-cards/card"; -import { css } from "styled-system/css"; +import { DroppableGridItem } from "@/components/molecules/DropGridItem"; import { Box, Grid } from "styled-system/jsx"; import { grid } from "styled-system/patterns"; +import { DndContext, DragEndEvent, useDraggable } from "@dnd-kit/core"; +import { ReactNode, useState } from "react"; const nav = `100px`; const footer = `50px`; const body = `calc(100vh - ${nav} - ${footer})`; export default function Home() { - return ( -
- -
-

Experiment arranging a Sorcery Grid

-

- While hovering over a card, click{" "} - - ALT - {" "} - key -

-
-
- - - - -
+ const [isDropped, setIsDropped] = useState(false); + const draggableMarkup = Drag me; -
footer
-
-
- ); -} + function handleDragEnd(event: DragEndEvent) { + if (event.over && event.over.id === "droppable") { + setIsDropped(true); + } + } -const Row = (props: { swap?: boolean }) => ( - <> - {Array.from({ length: 5 }).map((_, i) => ( - <> - {i % 2 === (props.swap ? 0 : 1) ? ( -
- - {/* */} - - - + return ( +
+ + +
+

Experiment arranging a Sorcery Grid

+

+ While hovering over a card, click{" "} + + ALT + {" "} + key +

- ) : (
- - + {Array.from({ length: 20 }).map((_, i) => ( + + + + + + ))}
- )} - - ))} - -); + +
footer
+
+
+
+ ); +} + +const Drag = (props: { children: ReactNode }) => { + const { attributes, listeners, setNodeRef, transform } = useDraggable({ + id: "draggable", + }); + const style = transform + ? { + transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, + } + : undefined; + + return ( +
+ {props.children} +
+ ); +}; From dd31224d3fd7836811c5093d5b8b50195f43b73a Mon Sep 17 00:00:00 2001 From: jollygrin Date: Sat, 7 Sep 2024 02:40:38 +0200 Subject: [PATCH 2/3] prepare --- src/pages/dnd.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/dnd.tsx b/src/pages/dnd.tsx index 2754500..2bd49ce 100644 --- a/src/pages/dnd.tsx +++ b/src/pages/dnd.tsx @@ -1,5 +1,5 @@ import { CardAtlas } from "@/components/atoms/mock-cards/atlas"; -import { Box, Grid } from "styled-system/jsx"; +import { Box, Grid, HStack } from "styled-system/jsx"; import { grid } from "styled-system/patterns"; import { DndContext, @@ -102,13 +102,17 @@ export default function Home() { ))}
-
footer
+ ); } +const TrayFooter = () => { + return place hand here; +}; + const Drag = (props: { children: ReactNode; gridIndex: number; From ca5e0f129ee0b158fea794a71b241408ca9dcfd6 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Sat, 7 Sep 2024 03:00:24 +0200 Subject: [PATCH 3/3] hand page --- src/pages/hand.tsx | 228 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/pages/hand.tsx diff --git a/src/pages/hand.tsx b/src/pages/hand.tsx new file mode 100644 index 0000000..903c533 --- /dev/null +++ b/src/pages/hand.tsx @@ -0,0 +1,228 @@ +//@ts-nocheck +import { CardAtlas } from "@/components/atoms/mock-cards/atlas"; +import { Box, Grid } from "styled-system/jsx"; +import { grid } from "styled-system/patterns"; +import { + DndContext, + DragEndEvent, + useDraggable, + useDroppable, + closestCenter, +} from "@dnd-kit/core"; +import { + SortableContext, + rectSortingStrategy, + arrayMove, +} from "@dnd-kit/sortable"; +import { ReactNode, useState } from "react"; + +const nav = `100px`; +const footer = `50px`; +const body = `calc(100vh - ${nav} - ${footer})`; + +export default function Home() { + // Initialize gridItems with cards + const [gridItems, setGridItems] = useState( + Array.from({ length: 20 }, () => []), + ); + + // Initialize hand tray with some cards + const [handItems, setHandItems] = useState([ + { id: "hand-card-1" }, + { id: "hand-card-2" }, + { id: "hand-card-3" }, + { id: "hand-card-4" }, + { id: "hand-card-5" }, + ]); + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event; + + if (over) { + const originId = active.id; + const destinationId = over.id; + + const originGridIndex = active.data.current?.gridIndex; + const originHandIndex = active.data.current?.handIndex; + const destinationGridIndex = destinationId?.startsWith("grid-") + ? parseInt(destinationId?.split("-")[1], 10) + : null; + const destinationHandIndex = destinationId?.startsWith("hand-") + ? parseInt(destinationId.split("-")[1], 10) + : null; + + const updatedGridItems = [...gridItems]; // Create a shallow copy of gridItems + const updatedHandItems = [...handItems]; // Create a shallow copy of handItems + + // Handle moving between grid items + if (originGridIndex !== null && destinationGridIndex !== null) { + // Ensure both origin and destination grid arrays exist + if (!updatedGridItems[originGridIndex]) { + updatedGridItems[originGridIndex] = []; + } + if (!updatedGridItems[destinationGridIndex]) { + updatedGridItems[destinationGridIndex] = []; + } + + // Move card within grid + const [movedCard] = updatedGridItems[originGridIndex].splice( + active.data.current.index, + 1, + ); + updatedGridItems[destinationGridIndex].push(movedCard); // Add card to destination grid area + } + + // Handle moving from grid to hand + else if (originGridIndex !== null && destinationHandIndex === null) { + // Moving from grid to hand + if (!updatedGridItems[originGridIndex]) { + updatedGridItems[originGridIndex] = []; + } + + const [movedCard] = updatedGridItems[originGridIndex].splice( + active.data.current.index, + 1, + ); + updatedHandItems.push(movedCard); // Add card back to hand + } + + // Handle moving from hand to grid + else if (originHandIndex !== null && destinationGridIndex !== null) { + // Moving from hand to grid + if (!updatedGridItems[destinationGridIndex]) { + updatedGridItems[destinationGridIndex] = []; + } + + const [movedCard] = updatedHandItems.splice(originHandIndex, 1); // Remove from hand + updatedGridItems[destinationGridIndex].push(movedCard); // Add card to destination grid area + } + + // Update the state to reflect the changes + setGridItems(updatedGridItems); // Update grid state + setHandItems(updatedHandItems); // Update hand state (without refilling it) + } + } + + return ( +
+ + +
+

Experiment arranging a Sorcery Grid

+

+ While hovering over a card, click{" "} + + ALT + {" "} + key +

+
+
+ {gridItems.map((cards, gridIndex) => ( + + card?.id)} + strategy={rectSortingStrategy} + > + {cards.map((card, cardIndex) => ( + + + + ))} + + + ))} +
+ + {/* Hand tray */} +
+ {handItems.map((card, handIndex) => ( + + + + ))} +
+
+
+
+ ); +} + +const Drag = (props: { + children: ReactNode; + gridIndex?: number; + handIndex?: number; + index: number; +}) => { + const { attributes, listeners, setNodeRef, transform } = useDraggable({ + id: + props.gridIndex !== undefined + ? `grid-${props.gridIndex}-${props.index}` + : `hand-${props.handIndex}`, + data: { + gridIndex: props.gridIndex, + handIndex: props.handIndex, + index: props.index, + }, + }); + const style = { + ...(transform + ? { transform: `translate3d(${transform.x}px, ${transform.y}px, 0)` } + : {}), + width: "100%", // Ensure cards are visible + height: "100%", // Ensure cards are visible + }; + + return ( +
+ {props.children} +
+ ); +}; + +const DroppableGridItem = (props: { children: ReactNode; id: string }) => { + const { setNodeRef } = useDroppable({ + id: props.id, + }); + + return ( +
+ {props.children} +
+ ); +};