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

Update Tree to auto-expand to selected nodes #185

Merged
merged 3 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .yarn/versions/98f2b3c0.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
releases:
"@essex/components": patch
essex-toolkit-stories: patch
38 changes: 34 additions & 4 deletions packages/components/src/Tree/Tree.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ interface Expansion {
is: (key: string) => boolean
on: (key: string) => void
}
export function useExpansion(): Expansion {
const [expandMap, setExpandMap] = useState<Record<string, boolean>>({})
export function useExpansion(items: TreeItem[], selectedKey?: string): Expansion {
const defaultExpansion = useMemo(() => forceExpansionToSelected(items, selectedKey), [items, selectedKey])
const [expandMap, setExpandMap] = useState<Record<string, boolean>>(defaultExpansion)
return useMemo(
() => ({
is: (key: string) => expandMap[key],
on: (key: string) =>
on: (key: string) =>
setExpandMap((prev) => ({
...prev,
[key]: !prev[key],
Expand All @@ -39,7 +40,7 @@ export function useTreeItemGroups(
onItemClick?: (item: TreeItem) => void,
onItemExpandClick?: (item: TreeItem) => void,
): TreeItemGroup[] {
const expansion = useExpansion()
const expansion = useExpansion(items, selectedKey)
return useMemo(() => {
const collected = collectItemsByGroup(items)
return [
Expand Down Expand Up @@ -120,3 +121,32 @@ function makeDetailedItems(
return base
})
}


// Construct a default expansion map by rolling up an expand for any parents of selected or expanded children to the top of the tree.
// This is only used to initialize the expansion, so that user interactions are not overridden.
function forceExpansionToSelected(items: TreeItem[], selectedKey?: string): Record<string, boolean> {
const collect = (children: TreeItem[], parentKey?: string): string[] => {
return children.map(child => {
if (child.children) {
const childKeys = collect(child.children, child.key)
if (childKeys && childKeys.length > 0) {
natoverse marked this conversation as resolved.
Show resolved Hide resolved
return [child.key, ...childKeys]
}
}
if (child.expanded) {
return [child.key]
}
// if a child is selected, we actually want the _parent_ to be expanded, not the child itself
if (child.selected || child.key === selectedKey) {
return [parentKey]
}
return []
})
.flatMap(key => key) as string[]
}
return collect(items).reduce((acc, cur) => {
acc[cur] = true
return acc
}, {} as Record<string, boolean>)
}
59 changes: 45 additions & 14 deletions packages/components/src/Tree/Tree.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const boxStyle: React.CSSProperties = {
}

const Template: ComponentStory<typeof Tree> = (args: TreeProps) => {
const [selected, setSelected] = useState<string | undefined>()
const [selected, setSelected] = useState<string | undefined>(args.selectedKey)

return (
<div style={containerStyle}>
Expand All @@ -177,6 +177,7 @@ const Template: ComponentStory<typeof Tree> = (args: TreeProps) => {
{...args}
selectedKey={selected}
onItemClick={(item) => setSelected(item.key)}
onItemExpandClick={(item) => console.log('expand clicked', item)}
/>
</div>
</div>
Expand Down Expand Up @@ -262,31 +263,61 @@ Customized.args = {
},
}

export const ItemProps = Template.bind({})
export const NestedSelections = Template.bind({})
// add in a few custom click, selection, etc. props to test full item overrides
ItemProps.args = {
NestedSelections.args = {
selectedKey: 'item-1.1.1',
items: [
{
key: 'item-1',
text: 'Item 1 (selected, onClick)',
selected: true,
onClick: (item) => console.log('click override', item),
text: 'Item 1 (default expands to selected child)',
children: [
{
key: 'item-1.1',
text: 'Item 1.1 (expanded, onExpand)',
expanded: true,
onExpand: (item) => console.log('expand override', item),
text: 'Item 1.1',
children: [
{
key: 'item-1.1.1 ',
text: 'Item 1.1.1 (selected)',
selected: true,
key: 'item-1.1.1',
text: 'Item 1.1.1 (default selectedKey)',
children: [
{
key: 'item-1.1.1.1',
text: 'Item 1.1.1.1',
},
],
},
],
},
],
},
{
key: 'item-2',
text: 'Item 2 (default expands to expanded child)',
children: [
{
key: 'item-2.1',
text: 'Item 2.1',
children: [
{
key: 'item-2.1.1',
text: 'Item 2.1.1 (static expanded prop)',
expanded: true,
children: [
{
key: 'item-2.1.1.1',
text: 'Item 2.1.1.1',
},
],
},
],
},
],
},
{
key: 'item-3',
text: 'Item 3 (onClick override)',
onClick: (item) => console.log('click override', item),
},
],
}

Expand Down Expand Up @@ -326,10 +357,10 @@ CustomRenderers.args = {
onRenderGroupHeader: (props, defaultRenderer) => {
return (
<>
{props.group.key === 'group-1' ? (
{props?.group.key === 'group-1' ? (
<div style={groupStyle}>{`${props.group.text} (custom)`}</div>
) : (
defaultRenderer(props)
defaultRenderer?.(props)
)}
</>
)
Expand Down