-
Notifications
You must be signed in to change notification settings - Fork 318
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
Custom outline item names - Feature Request #634
Comments
I believe you have been working on this issue and might have implemented the solution already. In case it helps here is my implementation using the override and puck action. Demo:The main component is coming from packages/core/components/Puck/components/Outline/ file which does not have an useStated array declared to keep record of items opened The second one is packages/core/components/LayerTree // All import statements which basically includes the /lib
// Type declarations for outline overrides
export type OutlineItemRenderProps = {
id: string;
zone: string | undefined;
isSelected: boolean;
isHovering: boolean;
index: number;
};
export type OutlineZonesRenderProps = {
zone: string | undefined;
label: string;
};
const getClassName = getClassNameFactory("LayerTree", styles);
const getClassNameLayer = getClassNameFactory("Layer", styles);
export const LayerTree = ({
data,
config,
zoneContent,
itemSelector,
setItemSelector,
zone,
label,
openList, // Basically supports opening multiple outline items. Its just an useStated array declared in the
setOpen,
render,
}: {
data: Data;
config: Config;
zoneContent: Data["content"];
itemSelector?: ItemSelector | null;
setItemSelector: (item: ItemSelector | null) => void;
zone?: string;
label?: string;
openList: string[];
setOpen: (id: string) => void;
render?: {
outlineItemRender?: (props: OutlineItemRenderProps) => ReactNode;
zoneRender?: (props: OutlineZonesRenderProps) => ReactNode;
};
}) => {
const zones = data.zones || {};
const ctx = useContext(dropZoneContext);
return (
<>
{label ? (
<div className={getClassName("zoneTitle")}>
<div className={getClassName("zoneIcon")}>
<LuLayers size="12" />
</div>{" "}
{label}
{render?.zoneRender ? (
<render.zoneRender zone={zone} label={label} />
) : (
""
)}
</div>
) : (
<div className={getClassName("zoneTitle")}>
<div className={getClassName("zoneIcon")}>
<LuLayers size="12" />
</div>{" "}
{"Container"}
{render?.zoneRender ? (
<render.zoneRender zone={rootDroppableId} label={"Container"} />
) : (
""
)}
</div>
)}
<ul className={getClassName()}>
{zoneContent.length === 0 && (
<div className={getClassName("helper")}>No items</div>
)}
{zoneContent.map((item, i) => {
const isSelected =
itemSelector?.index === i &&
(itemSelector.zone === zone ||
(itemSelector.zone === rootDroppableId && !zone));
const zonesForItem = findZonesForArea(data, item.props.id);
const containsZone = Object.keys(zonesForItem).length > 0;
const {
setHoveringArea = () => {},
setHoveringComponent = () => {},
hoveringComponent,
} = ctx || {};
const selectedItem =
itemSelector && data ? getItem(itemSelector, data) : null;
const isHovering = hoveringComponent === item.props.id;
const isOpen = openList.includes(item.props.id);
const childIsSelected = isChildOfZone(
item,
selectedItem,
ctx ? { data, pathData: ctx.pathData } : undefined
);
return (
<li
className={getClassNameLayer({
isSelected,
isOpen,
isHovering,
containsZone,
childIsSelected,
})}
key={`${item.props.id}_${i}`}
>
<div className={getClassNameLayer("inner")}>
<div
className={getClassNameLayer("innerWrapper")}
onMouseOver={(e) => {
e.stopPropagation();
setHoveringArea(item.props.id);
setHoveringComponent(item.props.id);
}}
onMouseOut={(e) => {
e.stopPropagation();
setHoveringArea(null);
setHoveringComponent(null);
}}
>
<div className={getClassNameLayer("innerContent")}>
{containsZone && (
<IconButton
title={isOpen ? "Collapse" : "Expand"}
onClick={() => {
setOpen(item.props.id);
}}
>
<LuChevronDown
className={getClassNameLayer("chevron")}
size="16"
/>
</IconButton>
)}
<div
onClick={(e) => {
if (isSelected) {
setItemSelector(null);
return;
}
setItemSelector({
index: i,
zone,
});
const id = zoneContent[i].props.id;
const frame = getFrame();
scrollIntoView(
frame?.querySelector(
`[data-rfd-drag-handle-draggable-id="draggable-${id}"]`
) as HTMLElement
);
}}
className={getClassNameLayer("title")}
>
<div className={getClassNameLayer("icon")}>
{item.type === "Text" || item.type === "Heading" ? (
<LuType size="16" />
) : (
<LuLayoutGrid size="16" />
)}
</div>
<div className={getClassNameLayer("name")}>
{config.components[item.type]["label"] ?? item.type}
</div>
</div>
</div>
{render?.outlineItemRender ? (
<div className={getClassNameLayer("innerContent")}>
<render.outlineItemRender
id={item.props.id}
zone={zone}
isSelected={isSelected}
isHovering={isHovering}
index={i}
/>
</div>
) : (
""
)}
</div>
</div>
{containsZone &&
Object.keys(zonesForItem).map((zoneKey, idx) => (
<div key={idx} className={getClassNameLayer("zones")}>
<LayerTree
config={config}
data={data}
zoneContent={zones[zoneKey]}
setItemSelector={setItemSelector}
itemSelector={itemSelector}
zone={zoneKey}
label={getZoneId(zoneKey)[1]}
openList={openList}
setOpen={setOpen}
render={render}
/>
</div>
))}
</li>
);
})}
</ul>
<> There were a few modifications to the style.module.css for this file as well, most of which allow overflow of items and flex row + justify-between. One issue i faced was the the jittering of screen when items revealed on hover were overflowing. To fix this I added overflow: scrollX to the wrapper component which is not elegant but prevents jittering. Only including the chaged css classes /* Original */
.Layer-clickable {
align-items: center;
background: none;
border: 0;
border-radius: 4px;
color: inherit;
cursor: pointer;
display: flex;
font: inherit;
padding-left: 12px;
padding-right: 4px;
width: 100%;
}
.Layer-clickable:focus-visible {
...
}
.Layer--isSelected > .Layer-inner > .Layer-clickable > .Layer-chevron,
.Layer--childIsSelected > .Layer-inner > .Layer-clickable > .Layer-chevron {
transform: scaleY(-1);
}
.Layer--isSelected > .Layer-zones,
.Layer-title,
.LayerTree-zoneTitle {
display: flex;
gap: 8px;
align-items: center;
margin: 8px 4px;
overflow-x: hidden;
} /* Changed */
// Changed the class from .Layer-clickable
.Layer-innerWrapper {
align-items: center;
background: none;
border: 0;
border-radius: 4px;
color: inherit;
display: flex;
gap: 8px;
justify-content: space-between;
font: inherit;
padding-left: 8px;
padding-right: 8px;
width: 100%;
}
// To handle the content inside the outline item
.Layer-innerContent {
align-items: center;
display: flex;
gap: 8px;
align-items: center;
}
.Layer-innerWrapper:focus-visible {
outline: 2px solid var(--puck-color-azure-05);
outline-offset: 2px;
position: relative;
z-index: 1;
}
.Layer--isOpen > .Layer-inner > .Layer-innerWrapper .Layer-chevron,
.Layer--childIsSelected > .Layer-inner > .Layer-innerWrapper .Layer-chevron {
transform: scaleY(-1);
}
.Layer--isOpen > .Layer-zones,
.Layer--childIsSelected > .Layer-zones {
display: block;
}
.Layer-title,
.LayerTree-zoneTitle {
display: flex;
gap: 8px;
align-items: center;
margin: 8px 4px;
overflow-x: visible; // To avoid jttering
}
.Layer-title {
cursor: pointer; // Clickable to open items other than selected
}
.LayerTree-zoneTitle {
cursor: default;
} |
Description
As edits grow in size and complexity, the outline can become quite large. The outline is a great way to help the user understand what is in the edit and where, but it can become confusing as well when you start having multiple instances of the same component at the top level.
Consider the following example:
In that case there aren't many instances, but the user will most likely have multiple groups at top level, and in each group they could have multiple role entities, so when you have 20 or 50 groups each with maybe 10 or 20 role entities this can be more confusing than clarifying.
Proposed solution
This can be easily fixed in a flexible manner if there would be an outline items array in the outline override, or a specific override for each outline item. That way you could do something like this:
Outline item override:
The text was updated successfully, but these errors were encountered: