Skip to content

Commit

Permalink
Merge pull request #4222 from matuzalemsteles/issue-4210
Browse files Browse the repository at this point in the history
feat(@clayui/core): adds functionality for expandable nodes and nested nodes
  • Loading branch information
matuzalemsteles authored Sep 13, 2021
2 parents 268c54a + e6b065c commit 2206cdb
Show file tree
Hide file tree
Showing 10 changed files with 864 additions and 79 deletions.
6 changes: 4 additions & 2 deletions packages/clay-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
"@clayui/button": "^3.32.0",
"@clayui/drop-down": "^3.35.3",
"@clayui/icon": "^3.32.0",
"@clayui/layout": "^3.32.0",
"@clayui/modal": "^3.35.3",
"@clayui/provider": "^3.35.3",
"@clayui/layout": "^3.32.0",
"classnames": "^2.2.6"
"@clayui/shared": "^3.35.3",
"classnames": "^2.2.6",
"react-transition-group": "^4.4.1"
},
"peerDependencies": {
"@clayui/css": "3.x",
Expand Down
70 changes: 70 additions & 0 deletions packages/clay-core/src/tree-view/Collection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* SPDX-FileCopyrightText: © 2021 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

import React from 'react';

import {ItemContextProvider, useItem} from './useItem';

export type ChildrenFunction<T> = (item: T) => React.ReactElement;

export interface ICollectionProps<T> {
children: React.ReactNode | ChildrenFunction<T>;
items?: Array<T>;
}

function getKey(index: number, key?: React.Key | null, parentKey?: React.Key) {
if (
key != null &&
(!String(key).startsWith('.') || String(key).startsWith('.$'))
) {
return key;
}

return parentKey ? `${parentKey}.${index}` : `$.${index}`;
}

export function Collection<T extends Record<any, any>>({
children,
items,
}: ICollectionProps<T>) {
const {key: parentKey} = useItem();

return (
<>
{typeof children === 'function' && items
? items.map((item, index) => {
const child = children(item);

const key = getKey(
index,
item.id ?? child.key,
parentKey
);

return (
<ItemContextProvider
key={key}
value={{...item, key}}
>
{child}
</ItemContextProvider>
);
})
: React.Children.toArray(children).map((child, index) => {
if (!React.isValidElement(child)) {
return null;
}

const key = getKey(index, child.key, parentKey);

return (
<ItemContextProvider key={key} value={{key}}>
{child}
</ItemContextProvider>
);
})}
</>
);
}
58 changes: 35 additions & 23 deletions packages/clay-core/src/tree-view/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@
import classNames from 'classnames';
import React from 'react';

import {ChildrenFunction, Collection, ICollectionProps} from './Collection';
import {TreeViewGroup} from './TreeViewGroup';
import {TreeViewItem, TreeViewItemStack} from './TreeViewItem';
import {TreeViewContext} from './context';
import type {IExpandable, IMultipleSelection} from './useTree';

type ChildrenFunction<T> = (item: T) => React.ReactElement;
import {Icons, TreeViewContext} from './context';
import {ITreeProps, useTree} from './useTree';

interface ITreeViewProps<T>
extends React.HTMLAttributes<HTMLUListElement>,
IMultipleSelection,
IExpandable {
children: React.ReactNode | ChildrenFunction<T>;
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'>,
ITreeProps,
ICollectionProps<T> {
displayType?: 'light' | 'dark';
items?: Array<T>;
expanderIcons?: Icons;
nestedKey?: string;
showExpanderOnHover?: boolean;
}

Expand All @@ -35,10 +34,34 @@ export function TreeView<T>({
children,
className,
displayType = 'light',
expandedKeys,
expanderIcons,
items,
nestedKey,
onExpandedChange,
onSelectionChange,
selectedKeys,
showExpanderOnHover = true,
...otherProps
}: ITreeViewProps<T>) {
const state = useTree({
expandedKeys,
onExpandedChange,
onSelectionChange,
selectedKeys,
});

const context = {
childrenRoot:
typeof children === 'function'
? (children as ChildrenFunction<Object>)
: undefined,
expanderIcons,
nestedKey,
showExpanderOnHover,
...state,
};

return (
<ul
{...otherProps}
Expand All @@ -48,24 +71,13 @@ export function TreeView<T>({
})}
role="tree"
>
<TreeViewContext.Provider value={{showExpanderOnHover}}>
{items
? items.map((item, index) => {
if (typeof children === 'function') {
return React.cloneElement(
children(item) as React.ReactElement,
{key: index}
);
}

return null;
})
: children}
<TreeViewContext.Provider value={context}>
<Collection<T> items={items}>{children}</Collection>
</TreeViewContext.Provider>
</ul>
);
}

TreeView.Item = TreeViewItem;
TreeView.Group = TreeViewGroup;
TreeView.Item = TreeViewItem;
TreeView.ItemStack = TreeViewItemStack;
67 changes: 43 additions & 24 deletions packages/clay-core/src/tree-view/TreeViewGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,58 @@
* SPDX-License-Identifier: BSD-3-Clause
*/

import {setElementFullHeight} from '@clayui/shared';
import classNames from 'classnames';
import React from 'react';
import {CSSTransition} from 'react-transition-group';

type ChildrenFunction<T> = (item: T) => React.ReactElement;

type TreeViewItemProps<T> = {
children: React.ReactNode | ChildrenFunction<T>;
items?: Array<T>;
};
import {Collection, ICollectionProps} from './Collection';
import {useTreeViewContext} from './context';
import {useItem} from './useItem';

export function TreeViewGroup<T>(
props: TreeViewItemProps<T>
props: ICollectionProps<T>
): JSX.Element & {
displayName: string;
};

export function TreeViewGroup<T>({children, items}: TreeViewItemProps<T>) {
return (
<div className="collapse show">
<ul className="treeview-group" role="group">
{items
? items.map((item, index) => {
if (typeof children === 'function') {
return React.cloneElement(
children(item) as React.ReactElement,
{key: index}
);
}
export function TreeViewGroup<T extends Record<any, any>>({
children,
items,
}: ICollectionProps<T>) {
const {expandedKeys} = useTreeViewContext();

return null;
})
: children}
</ul>
</div>
const item = useItem();

return (
<CSSTransition
className={classNames('collapse', {
show: expandedKeys.has(item.key),
})}
classNames={{
enter: 'collapsing',
enterActive: 'show',
enterDone: 'show',
exit: 'show',
exitActive: 'collapsing',
}}
id={item.key}
in={expandedKeys.has(item.key)}
onEnter={(el: HTMLElement) =>
el.setAttribute('style', 'height: 0px')
}
onEntered={(el: HTMLElement) => el.removeAttribute('style')}
onEntering={(el: HTMLElement) => setElementFullHeight(el)}
onExit={(el) => setElementFullHeight(el)}
onExiting={(el) => el.setAttribute('style', 'height: 0px')}
timeout={250}
>
<div>
<ul className="treeview-group" role="group">
<Collection<T> items={items}>{children}</Collection>
</ul>
</div>
</CSSTransition>
);
}

Expand Down
Loading

0 comments on commit 2206cdb

Please sign in to comment.