Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {
type ItemDropTarget,
$propValuesByInstanceSelectorWithMemoryProps,
} from "~/shared/nano-states";
import type { InstanceSelector } from "~/shared/tree-utils";
import { isDescendantOrSelf, type InstanceSelector } from "~/shared/tree-utils";
import { serverSyncStore } from "~/shared/sync/sync-stores";
import { reparentInstance, toggleInstanceShow } from "~/shared/instance-utils";
import { emitCommand } from "~/builder/shared/commands";
Expand Down Expand Up @@ -661,6 +661,10 @@ export const NavigatorTree = () => {
getInstanceKey(item.selector)
);
const show = Boolean(propValues?.get(showAttribute) ?? true);
const isSelectedDescendantItem =
selectedInstanceSelector !== undefined &&
item.selector.join() !== selectedInstanceSelector.join() &&
isDescendantOrSelf(item.selector, selectedInstanceSelector);

// Hook memory prop
const isAnimationSelected =
Expand Down Expand Up @@ -733,6 +737,7 @@ export const NavigatorTree = () => {
<TreeNode
level={level}
isSelected={selectedKey === key}
isSelectedDescendant={isSelectedDescendantItem}
isHighlighted={hoveredKey === key || dropTargetKey === key}
isExpanded={item.isExpanded}
isActionVisible={isAnimating}
Expand Down
6 changes: 6 additions & 0 deletions apps/builder/app/shared/tree-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,10 @@ test("is descendant or self", () => {
expect(isDescendantOrSelf(["1", "2", "3"], ["1", "2", "3"])).toBe(true);
expect(isDescendantOrSelf(["0", "1", "2", "3"], ["1", "2", "3"])).toBe(true);
expect(isDescendantOrSelf(["1", "2", "3"], ["0", "1", "2", "3"])).toBe(false);
expect(
isDescendantOrSelf(
["item-child", "collection:entry-1", "collection", "body", "page-root"],
["collection", "body", "page-root"]
)
).toBe(true);
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/design-system/src/components/tree.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,16 @@ export const Tree = () => {
With prefix and action visible
</TreeNodeLabel>
</TreeNode>
<TreeNode
level={2}
isSelected={false}
isSelectedDescendant
isExpanded={undefined}
buttonProps={{}}
action={<SmallIconButton tabIndex={-1} icon={<EllipsesIcon />} />}
>
<TreeNodeLabel>Selected descendant node</TreeNodeLabel>
</TreeNode>
<TreeNode
level={1}
tabbable
Expand Down
58 changes: 32 additions & 26 deletions packages/design-system/src/components/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,26 @@ const treeNodeLevel = "--tree-node-level";
const treeNodeOutline = "--tree-node-outline";
const treeNodeBackgroundColor = "--tree-node-background-color";
const treeActionOpacity = "--tree-action-opacity";
const treeDepthBarsVisibility = "--tree-depth-bars-visibility";
const treeDepthBarsColor = "--tree-depth-bars-color";

type TreeSelectionState = "none" | "selected" | "selected-descendant";

const getTreeSelectionState = ({
isSelected,
isSelectedDescendant,
}: {
isSelected: boolean;
isSelectedDescendant: boolean;
}): TreeSelectionState => {
if (isSelected) {
return "selected";
}

if (isSelectedDescendant) {
return "selected-descendant";
}

return "none";
};

const ITEM_PADDING_LEFT = 8;
// extra padding on the right to make sure scrollbar doesn't obscure anything
Expand All @@ -44,11 +62,6 @@ const TreeContainer = ({ children }: { children: ReactNode }) => {
const focusManager = useFocusManager();
return (
<Box
css={{
"&:hover": {
[treeDepthBarsVisibility]: "visible",
},
}}
onKeyDown={(event) => {
if (event.defaultPrevented) {
return;
Expand Down Expand Up @@ -100,29 +113,16 @@ const NodeContainer = styled("div", {
backgroundColor: `var(${treeNodeBackgroundColor})`,
[treeActionOpacity]: 1,
},
"&:has([aria-selected=true])": {
'&[data-selection-state="selected-descendant"]': {
[treeNodeBackgroundColor]: theme.colors.backgroundItemCurrentChild,
backgroundColor: `var(${treeNodeBackgroundColor})`,
},
'&[data-selection-state="selected"]': {
[treeNodeBackgroundColor]: theme.colors.backgroundItemCurrent,
backgroundColor: `var(${treeNodeBackgroundColor})`,
[treeDepthBarsColor]: theme.colors.borderItemChildLineCurrent,
},
});

const DepthBars = styled("div", {
visibility: `var(${treeDepthBarsVisibility}, hidden)`,
position: "absolute",
top: 0,
left: 0,
width: `calc((var(${treeNodeLevel}) - 1) * ${BARS_GAP}px)`,
height: "100%",
backgroundImage: `repeating-linear-gradient(
to right,
transparent,
transparent ${BARS_GAP - 1}px,
var(${treeDepthBarsColor}, ${theme.colors.borderItemChildLine}) ${BARS_GAP - 1}px,
var(${treeDepthBarsColor}, ${theme.colors.borderItemChildLine}) ${BARS_GAP}px
)`,
});

const NodeButton = styled("button", {
all: "unset",
boxSizing: "border-box",
Expand Down Expand Up @@ -404,6 +404,7 @@ export const TreeNode = ({
level,
tabbable,
isSelected,
isSelectedDescendant = false,
isHighlighted,
isExpanded,
isActionVisible,
Expand All @@ -416,6 +417,7 @@ export const TreeNode = ({
level: number;
tabbable?: boolean;
isSelected: boolean;
isSelectedDescendant?: boolean;
isHighlighted?: boolean;
isExpanded?: undefined | boolean;
isActionVisible?: boolean;
Expand All @@ -426,6 +428,10 @@ export const TreeNode = ({
children: ReactNode;
}) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const selectionState = getTreeSelectionState({
isSelected,
isSelectedDescendant,
});
// scroll the selected button into view when selected from canvas.
useEffect(() => {
if (isSelected) {
Expand Down Expand Up @@ -463,13 +469,13 @@ export const TreeNode = ({
return (
<NodeContainer
{...nodeProps}
data-selection-state={selectionState}
css={{
[treeNodeLevel]: level,
...(isActionVisible && { [treeActionOpacity]: 1 }),
}}
onKeyDown={handleKeydown}
>
<DepthBars />
<NodeButton
{...buttonProps}
ref={buttonRef}
Expand Down
Loading