handleLayerHover(null)}
+ >
+
+ {layers.length} layer{layers.length === 1 ? "" : "s"}
+
+
+ {visibleLayers.map((layer) => {
+ const selected = layer.key === selectedKey;
+ const isCollapsed = collapsed[layer.key] ?? false;
+ const hasChildren = layer.childCount > 0;
+ const isCompHost = isCompositionHost(layer.element);
+
+ return (
+
handleSelectLayer(layer)}
+ onPointerEnter={() => handleLayerHover(layer)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ handleSelectLayer(layer);
+ }
+ }}
+ className={`group flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left transition-colors ${
+ selected
+ ? "bg-studio-accent/14 text-studio-accent"
+ : "text-neutral-300 hover:bg-white/[0.04] hover:text-neutral-100"
+ }`}
+ style={{ paddingLeft: 8 + layer.depth * 16 }}
+ >
+ {hasChildren ? (
+
+ ) : (
+
+ )}
+
+ {getTagBadge(layer.tagName)}
+
+
{layer.label}
+ {hasChildren && (
+
{layer.childCount}
+ )}
+
+ );
+ })}
+
+
+ );
+});
+
+function getVisibleLayers(
+ layers: DomEditLayerItem[],
+ collapsed: CollapsedState,
+): DomEditLayerItem[] {
+ if (Object.keys(collapsed).length === 0) return layers;
+
+ const result: DomEditLayerItem[] = [];
+ let skipDepth = -1;
+
+ for (const layer of layers) {
+ if (skipDepth >= 0 && layer.depth > skipDepth) continue;
+ skipDepth = -1;
+
+ result.push(layer);
+
+ if (collapsed[layer.key] && layer.childCount > 0) {
+ skipDepth = layer.depth;
+ }
+ }
+
+ return result;
+}
diff --git a/packages/studio/src/components/editor/propertyPanelPrimitives.tsx b/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
index 436e4c9b8..6b766310a 100644
--- a/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
+++ b/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
@@ -334,24 +334,43 @@ export function Section({
icon,
children,
accessory,
+ defaultCollapsed = false,
}: {
title: string;
icon: ReactNode;
children: ReactNode;
accessory?: ReactNode;
+ defaultCollapsed?: boolean;
}) {
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
+
return (
-