Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new drag-and-drop engine #598

Draft
wants to merge 48 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d521e82
wip
chrisvxd Jun 20, 2024
2a45f1d
wip
chrisvxd Jul 11, 2024
8f31c0c
wip: implementation per clauderic
chrisvxd Jul 12, 2024
e9c4203
wip: eliminate edit wrapper
chrisvxd Jul 17, 2024
ea69bbb
chore: reintroduce drop placeholder styling
chrisvxd Jul 19, 2024
6c1b0a5
wip: better nesting
chrisvxd Jul 22, 2024
c7e3b9f
wip: better nesting by active zones
chrisvxd Jul 27, 2024
6cae9eb
wip: show drag outline whilst dragging, and deselect on drop
chrisvxd Jul 27, 2024
8012146
wip: wipe out more styles on placeholder
chrisvxd Jul 28, 2024
c126144
wip: add more todos
chrisvxd Jul 28, 2024
84016c0
wip: scope placeholder styles to puck-preview
chrisvxd Jul 28, 2024
980eee0
refactor: encapsulate DragDropProvider and DropZoneProvider in DragDr…
chrisvxd Aug 5, 2024
5833095
refactor: add hook for listening to dndkit events
chrisvxd Aug 5, 2024
4b630dc
refactor: fix dragging of new items with dndkit
chrisvxd Aug 5, 2024
ab6462a
refactor: remove todos
chrisvxd Aug 5, 2024
2cbf26a
chore: use itemSelector out of appState
chrisvxd Aug 5, 2024
d5b08ce
refactor: don't dispatch replace action unnecessarily on drag end
chrisvxd Aug 5, 2024
077707d
chore: reintroduce path data
chrisvxd Aug 6, 2024
ee3ad09
chore: abort drag move if dragging over self or descendent
chrisvxd Aug 6, 2024
4f4cbdc
fix: remove margin alignment from Card so it doesn't move around on drag
chrisvxd Aug 14, 2024
7b69078
fix: ensure placeholder renders correctly with inline rendering
chrisvxd Aug 14, 2024
ce73392
fix: don't select text in drawer items on drag
chrisvxd Aug 14, 2024
ee9803a
fix: ensure areas capture drag, instead of parent
chrisvxd Aug 15, 2024
1b39ee3
chore: remove unnecessary margins on DropZone to behave better with i…
chrisvxd Aug 15, 2024
944e643
fix: adjust card CSS when using "card" mode with inline rendering
chrisvxd Aug 15, 2024
6d9a8fa
fix: render DropZones the same in Puck and Render
chrisvxd Aug 15, 2024
fd25155
wip: only enable when user's cursor over a zone
chrisvxd Aug 15, 2024
ec6afee
wip: select item on drop using timeout
chrisvxd Aug 15, 2024
2cb87d6
wip: ensure overlay re-renders whenever data changes, in case data ch…
chrisvxd Aug 15, 2024
b93059b
wip: set isDragging on state and react in heading analyzer
chrisvxd Aug 15, 2024
7f0336d
wip: defer committing insert and move actions until drag finished
chrisvxd Aug 16, 2024
6707f25
wip: replace useDeferred usage with simpler technique using existing …
chrisvxd Aug 16, 2024
fd21d60
wip: fix type issues in reducer tests
chrisvxd Aug 16, 2024
64dc35a
fixup! wip: replace useDeferred usage with simpler technique using ex…
chrisvxd Aug 16, 2024
7231bf2
wip: don't pollute history with new dnd
chrisvxd Aug 16, 2024
753258a
chore: implement dynamic collision algorithm
chrisvxd Aug 24, 2024
6a1e024
docs: revamp Flex component to use DropZone
chrisvxd Aug 24, 2024
81f6017
fix: set a minimum width for DropZones
chrisvxd Aug 24, 2024
e9f2abc
fix: don't set minimum size for DropZone if it contains children
chrisvxd Aug 24, 2024
86831ad
wip: drag over sibling area if it contains zone with items
chrisvxd Aug 24, 2024
0c01ad8
wip: add edge hitboxes so you can drag between flush zones
chrisvxd Aug 24, 2024
d0f0188
wip: always show DropZone min-height as changing height messes with d…
chrisvxd Aug 24, 2024
8d72124
wip: add iframe support to new overlay
chrisvxd Aug 24, 2024
588e2eb
wip
chrisvxd Aug 30, 2024
d1f3f18
wip: update to support latest dnd-kit fork
chrisvxd Sep 25, 2024
46626a4
wip: deprecate pointer-event in favour of NestedDroppablePlugin
chrisvxd Sep 26, 2024
4c6aa10
wip: use depth instead of element contains for performance
chrisvxd Sep 27, 2024
4c7c915
refactor: ensure isDroppableTarget is respected on drag start
chrisvxd Sep 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/demo/app/[...puckPath]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function Client({ path, isEdit }: { path: string; isEdit: boolean }) {
onPublish={async (data: Data) => {
localStorage.setItem(key, JSON.stringify(data));
}}
iframe={{ enabled: false }}
plugins={[headingAnalyzer]}
headerPath={path}
overrides={{
Expand Down
14 changes: 11 additions & 3 deletions apps/demo/config/blocks/Blank/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ const getClassName = getClassNameFactory("Hero", styles);
export type HeroProps = {};

export const Hero: ComponentConfig<HeroProps> = {
fields: {},
fields: {
myDropZone: {
type: "dropzone",
},
},
defaultProps: {},
render: () => {
return <div className={getClassName()}></div>;
render: ({ myDropZone }) => {
return (
<div className={getClassName()}>
<DropZone style={{ display: "flex" }} zone="my-zone" />
</div>
);
},
};
5 changes: 3 additions & 2 deletions apps/demo/config/blocks/Card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ export const Card: ComponentConfig<CardProps> = {
icon: "Feather",
mode: "flat",
},
render: ({ title, icon, description, mode }) => {
inline: true,
render: ({ title, icon, description, mode, puck }) => {
return (
<div className={getClassName({ [mode]: mode })}>
<div className={getClassName({ [mode]: mode })} ref={puck.dragRef}>
<div className={getClassName("icon")}>{icons[icon]}</div>
<div className={getClassName("title")}>{title}</div>
<div className={getClassName("description")}>{description}</div>
Expand Down
8 changes: 3 additions & 5 deletions apps/demo/config/blocks/Card/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
.Card {
display: flex;
grid-row: 1;
flex-direction: column;
align-items: center;
margin-left: auto;
margin-right: auto;
gap: 16px;
width: 100%;
/* width: 100%; */
}

.Card--card {
background: white;
box-shadow: rgba(140, 152, 164, 0.25) 0px 3px 6px 0px;
box-sizing: border-box;
border-radius: 8px;
flex: 1;
max-width: 100%;
margin-left: unset;
margin-right: unset;
padding: 24px;
align-items: flex-start;
width: auto;
Expand Down
57 changes: 29 additions & 28 deletions apps/demo/config/blocks/Flex/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,48 @@ import { Section } from "../../components/Section";
const getClassName = getClassNameFactory("Flex", styles);

export type FlexProps = {
items: { minItemWidth?: number }[];
minItemWidth: number;
direction: "row" | "column";
gap: number;
wrap: "wrap" | "nowrap";
};

export const Flex: ComponentConfig<FlexProps> = {
fields: {
items: {
type: "array",
arrayFields: {
minItemWidth: {
label: "Minimum Item Width",
type: "number",
min: 0,
},
},
getItemSummary: (_, id) => `Item ${id + 1}`,
direction: {
label: "Direction",
type: "radio",
options: [
{ label: "row", value: "row" },
{ label: "column", value: "column" },
],
},
minItemWidth: {
label: "Minimum Item Width",
gap: {
label: "Gap",
type: "number",
min: 0,
},
wrap: {
label: "Wrap",
type: "radio",
options: [
{ label: "true", value: "wrap" },
{ label: "false", value: "nowrap" },
],
},
},
defaultProps: {
items: [{}, {}],
minItemWidth: 356,
direction: "row",
gap: 24,
wrap: "wrap",
},
render: ({ items, minItemWidth }) => {
render: ({ direction, gap, wrap }) => {
return (
<Section>
<div className={getClassName()}>
{items.map((item, idx) => (
<div
key={idx}
className={getClassName("item")}
style={{ minWidth: item.minItemWidth || minItemWidth }}
>
<DropZone zone={`item-${idx}`} />
</div>
))}
</div>
<DropZone
className={getClassName()}
style={{ flexDirection: direction, gap, flexWrap: wrap }}
zone="flex"
/>
</Section>
);
},
Expand Down
3 changes: 0 additions & 3 deletions apps/demo/config/blocks/Flex/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
.Flex {
display: flex;
gap: 24px;
min-height: 0;
min-width: 0;
flex-wrap: wrap;
}

Expand Down
123 changes: 122 additions & 1 deletion apps/demo/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Config, Data } from "@/core";
import { ButtonGroup, ButtonGroupProps } from "./blocks/ButtonGroup";
import { Card, CardProps } from "./blocks/Card";
import { Columns, ColumnsProps } from "./blocks/Columns";
import { Grid, GridProps } from "./blocks/Grid";
import { GridItem, GridItemProps } from "./blocks/GridItem";
import { Hero, HeroProps } from "./blocks/Hero";
import { Heading, HeadingProps } from "./blocks/Heading";
import { Flex, FlexProps } from "./blocks/Flex";
Expand All @@ -18,6 +20,8 @@ export type Props = {
ButtonGroup: ButtonGroupProps;
Card: CardProps;
Columns: ColumnsProps;
Grid: GridProps;
GridItem: GridItemProps;
Hero: HeroProps;
Heading: HeadingProps;
Flex: FlexProps;
Expand All @@ -43,7 +47,7 @@ export const conf: UserConfig = {
},
categories: {
layout: {
components: ["Columns", "Flex", "VerticalSpace"],
components: ["Columns", "Grid", "GridItem", "Flex", "VerticalSpace"],
},
typography: {
components: ["Heading", "Text"],
Expand All @@ -57,6 +61,8 @@ export const conf: UserConfig = {
ButtonGroup,
Card,
Columns,
Grid,
GridItem,
Hero,
Heading,
Flex,
Expand All @@ -68,6 +74,121 @@ export const conf: UserConfig = {
};

export const initialData: Record<string, Data> = {
"/grid": {
root: { props: { title: "Puck Example" } },
content: [
{
type: "Grid",
props: {
distribution: "auto",
id: "Grid-1",
},
},
// {
// type: "Grid",
// props: {
// distribution: "auto",
// id: "Grid-2",
// },
// },
],
zones: {
"Grid-2:grid": [
{
type: "Card",
props: {
title: "Built for content teams",
description:
"Puck enables content teams to make changes to their content without a developer or breaking the UI.",
icon: "pen-tool",
mode: "flat",
id: "Card-0d9077e00e0ad66c34c62ab6986967e1ce04f9e41",
},
},
{
type: "Card",
props: {
title: "Easy to integrate",
description:
"Front-end developers can easily integrate their own components using a familiar React API.",
icon: "git-merge",
mode: "flat",
id: "Card-978bef5d136d4b0d9855f5272429986ceb22e5a61",
},
},
{
type: "Card",
props: {
title: "No vendor lock-in",
description:
"Completely open-source, Puck is designed to be integrated into your existing React application.",
icon: "github",
mode: "flat",
id: "Card-133a61826f0019841aec6f0aec011bf07e6bc6de1",
},
},
],
"Grid-1:grid": [
{
type: "GridItem",
props: {
id: "GridItem-1",
},
},
{
type: "GridItem",
props: {
id: "GridItem-2",
},
},
{
type: "GridItem",
props: {
id: "GridItem-3",
},
},
],
"GridItem-1:items": [
{
type: "Card",
props: {
title: "Built for content teams",
description:
"Puck enables content teams to make changes to their content without a developer or breaking the UI.",
icon: "pen-tool",
mode: "flat",
id: "Card-0d9077e00e0ad66c34c62ab6986967e1ce04f9e4",
},
},
],
"GridItem-2:items": [
{
type: "Card",
props: {
title: "Easy to integrate",
description:
"Front-end developers can easily integrate their own components using a familiar React API.",
icon: "git-merge",
mode: "flat",
id: "Card-978bef5d136d4b0d9855f5272429986ceb22e5a6",
},
},
],
"GridItem-3:items": [
{
type: "Card",
props: {
title: "No vendor lock-in",
description:
"Completely open-source, Puck is designed to be integrated into your existing React application.",
icon: "github",
mode: "flat",
id: "Card-133a61826f0019841aec6f0aec011bf07e6bc6de",
},
},
],
},
},
"/": {
content: [
{
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/config/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from "react";

import { DefaultRootProps } from "@/core";
import { DefaultRootProps, DropZone } from "@/core";
import { Footer } from "./components/Footer";
import { Header } from "./components/Header";

Expand All @@ -13,7 +13,7 @@ function Root({ children, puck }: RootProps) {
return (
<>
<Header editMode={puck.isEditing} />
{children}
<DropZone zone={"default-zone"} disallow={["GridItem"]} />
<Footer>
<Footer.List title="Section">
<Footer.Link href="">Label</Footer.Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { List, Plus, Trash } from "lucide-react";
import { AutoFieldPrivate, FieldPropsInternal } from "../..";
import { IconButton } from "../../../IconButton";
import { reorder, replace } from "../../../../lib";
import { Droppable } from "../../../Droppable";
import { Draggable } from "../../../Draggable";
import { Droppable } from "../../../LegacyDnd/Droppable";
import { Draggable } from "../../../LegacyDnd/Draggable";
import { useCallback, useEffect, useState } from "react";
import { DragIcon } from "../../../DragIcon";
import { ArrayState, ItemWithId } from "../../../../types/Config";
import { useAppContext } from "../../../Puck/context";
import { DragDropContext } from "../../../DragDropContext";
import { DragDropContext } from "../../../LegacyDnd/DragDropContext";

const getClassName = getClassNameFactory("ArrayField", styles);
const getClassNameItem = getClassNameFactory("ArrayFieldItem", styles);
Expand Down
8 changes: 3 additions & 5 deletions packages/core/components/ComponentList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ const getClassName = getClassNameFactory("ComponentList", styles);
const ComponentListItem = ({
name,
label,
index,
}: {
name: string;
label?: string;
index: number;
index?: number; // TODO deprecate
}) => {
const { overrides } = useAppContext();
return (
<Drawer.Item label={label} name={name} index={index}>
<Drawer.Item label={label} name={name}>
{overrides.componentItem}
</Drawer.Item>
);
Expand Down Expand Up @@ -68,15 +67,14 @@ const ComponentList = ({
<div className={getClassName("content")}>
<Drawer droppableId={title}>
{children ||
Object.keys(config.components).map((componentKey, i) => {
Object.keys(config.components).map((componentKey) => {
return (
<ComponentListItem
key={componentKey}
label={
config.components[componentKey]["label"] ?? componentKey
}
name={componentKey}
index={i}
/>
);
})}
Expand Down
Loading