Skip to content

Commit

Permalink
feat: introduce new outline UI
Browse files Browse the repository at this point in the history
Supports the DropZone API, higlights which item is selected and syncs hover between the DropZone area and the item.
  • Loading branch information
chrisvxd committed Sep 22, 2023
1 parent 732013f commit e32c4ff
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 0 deletions.
147 changes: 147 additions & 0 deletions packages/core/components/LayerTree/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import styles from "./styles.module.css";
import getClassNameFactory from "../../lib/get-class-name-factory";
import { Data } from "../../types/Config";
import { ItemSelector } from "../../lib/get-item";
import { scrollIntoView } from "../../lib/scroll-into-view";
import { ChevronDown, Grid, Layers, Type } from "react-feather";
import { rootDroppableId } from "../../lib/root-droppable-id";
import { useContext } from "react";
import { dropZoneContext } from "../DropZone/context";
import { findDropzonesForArea } from "../../lib/find-dropzones-for-area";
import { getDropzoneId } from "../../lib/get-dropzone-id";

const getClassName = getClassNameFactory("LayerTree", styles);
const getClassNameLayer = getClassNameFactory("Layer", styles);

export const LayerTree = ({
data,
dropzoneContent,
itemSelector,
setItemSelector,
dropzone,
label,
}: {
data: Data;
dropzoneContent: Data["content"];
itemSelector: ItemSelector | null;
setItemSelector: (item: ItemSelector | null) => void;
dropzone?: string;
label?: string;
}) => {
const dropzones = data.dropzones || {};

const ctx = useContext(dropZoneContext);

return (
<>
{label && (
<div className={getClassName("dropzoneTitle")}>
<div className={getClassName("dropzoneIcon")}>
<Layers size="16" />
</div>{" "}
{label}
</div>
)}
<ul className={getClassName()}>
{dropzoneContent.length === 0 && (
<div className={getClassName("helper")}>No items</div>
)}
{dropzoneContent.map((item, i) => {
const isSelected =
itemSelector?.index === i &&
(itemSelector.dropzone === dropzone ||
(itemSelector.dropzone === rootDroppableId && !dropzone));

const dropzonesForItem = findDropzonesForArea(data, item.props.id);
const containsDropzone = Object.keys(dropzonesForItem).length > 0;

const {
setHoveringArea = () => {},
setHoveringComponent = () => {},
hoveringComponent,
} = ctx || {};

const isHovering = hoveringComponent === item.props.id;

return (
<li
className={getClassNameLayer({
isSelected,
isHovering,
containsDropzone,
})}
key={`${item.props.id}_${i}`}
>
<div className={getClassNameLayer("inner")}>
<div
className={getClassNameLayer("clickable")}
onClick={() => {
if (isSelected) {
setItemSelector(null);
return;
}

setItemSelector({
index: i,
dropzone,
});

const id = dropzoneContent[i].props.id;

scrollIntoView(
document.querySelector(
`[data-rbd-drag-handle-draggable-id="draggable-${id}"]`
) as HTMLElement
);
}}
onMouseOver={(e) => {
e.stopPropagation();
setHoveringArea(item.props.id);
setHoveringComponent(item.props.id);
}}
onMouseOut={(e) => {
e.stopPropagation();
setHoveringArea(null);
setHoveringComponent(null);
}}
>
{containsDropzone && (
<div
className={getClassNameLayer("chevron")}
title={isSelected ? "Collapse" : "Expand"}
>
<ChevronDown size="12" />
</div>
)}
<div className={getClassNameLayer("title")}>
<div className={getClassNameLayer("icon")}>
{item.type === "Text" || item.type === "Heading" ? (
<Type size="16" />
) : (
<Grid size="16" />
)}
</div>
{item.type}
</div>
</div>
</div>
{containsDropzone &&
Object.keys(dropzonesForItem).map((dropzoneKey, idx) => (
<div key={idx} className={getClassNameLayer("dropzones")}>
<LayerTree
data={data}
dropzoneContent={dropzones[dropzoneKey]}
setItemSelector={setItemSelector}
itemSelector={itemSelector}
dropzone={dropzoneKey}
label={getDropzoneId(dropzoneKey)[1]}
/>
</div>
))}
</li>
);
})}
</ul>
</>
);
};
103 changes: 103 additions & 0 deletions packages/core/components/LayerTree/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
.LayerTree {
color: var(--puck-color-grey-2);
font-family: var(--puck-font-stack);
font-size: var(--puck-font-size-xxs);
margin: 0;
position: relative;
list-style: none;
padding: 0;
}

.LayerTree-dropzoneTitle {
color: var(--puck-color-grey-4);
font-size: var(--puck-font-size-xxxs);
text-transform: uppercase;
}

.LayerTree-helper {
text-align: center;
color: var(--puck-color-grey-6);
font-family: var(--puck-font-stack);
margin: 8px 4px;
}

.Layer {
position: relative;
border: 1px solid transparent;
}

.Layer-inner {
padding-left: 20px;
padding-right: 8px;
border-radius: 3px;
}

.Layer--containsDropzone > .Layer-inner {
padding-left: 8px;
}

.Layer-clickable {
align-items: center;
display: flex;
}

.Layer-inner:hover {
cursor: pointer;
}

.Layer:not(.Layer--isSelected) > .Layer-inner:hover,
.Layer--isHovering > .Layer-inner {
color: var(--puck-color-blue);
background: var(--puck-color-azure-85);
}

.Layer--isSelected {
background: var(--puck-color-azure-9);
border-color: var(--puck-color-azure-7);
border-radius: 4px;
}

.Layer--isSelected > .Layer-inner {
background: var(--puck-color-azure-85);
font-weight: 600;
}

.Layer--isSelected > .Layer-inner > .Layer-clickable > .Layer-chevron,
.Layer:has(.Layer--isSelected)
> .Layer-inner
> .Layer-clickable
> .Layer-chevron {
transform: scaleY(-1);
}

.Layer-dropzones {
display: none;
margin-left: 20px;
}

.Layer--isSelected > .Layer-dropzones,
.Layer:has(.Layer--isSelected) > .Layer-dropzones {
display: block;
}

.Layer-dropzones > .LayerTree {
margin-left: 16px;
}

.Layer-title,
.LayerTree-dropzoneTitle {
display: flex;
gap: 8px;
align-items: center;
margin: 8px 4px;
}

.Layer-icon {
color: var(--puck-color-rose-6);
margin-top: 4px;
}

.Layer-dropzoneIcon {
color: var(--puck-color-grey-7);
margin-top: 4px;
}
1 change: 1 addition & 0 deletions packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ export function Puck({
key={dropzoneKey}
data={data}
label={dropzoneKey}
dropzone={dropzoneKey}
dropzoneContent={dropzone}
setItemSelector={setItemSelector}
itemSelector={itemSelector}
Expand Down

0 comments on commit e32c4ff

Please sign in to comment.