diff --git a/packages/clay-core/package.json b/packages/clay-core/package.json index 0f62d54217..8903bcaf85 100644 --- a/packages/clay-core/package.json +++ b/packages/clay-core/package.json @@ -30,7 +30,9 @@ "@clayui/drop-down": "^3.35.3", "@clayui/icon": "^3.32.0", "@clayui/modal": "^3.35.3", - "@clayui/provider": "^3.35.3" + "@clayui/provider": "^3.35.3", + "@clayui/layout": "^3.32.0", + "classnames": "^2.2.6" }, "peerDependencies": { "@clayui/css": "3.x", diff --git a/packages/clay-core/src/tree-view/TreeView.tsx b/packages/clay-core/src/tree-view/TreeView.tsx new file mode 100644 index 0000000000..e0f090032e --- /dev/null +++ b/packages/clay-core/src/tree-view/TreeView.tsx @@ -0,0 +1,71 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import classNames from 'classnames'; +import React from 'react'; + +import {TreeViewGroup} from './TreeViewGroup'; +import {TreeViewItem, TreeViewItemStack} from './TreeViewItem'; +import {TreeViewContext} from './context'; +import type {IExpandable, IMultipleSelection} from './useTree'; + +type ChildrenFunction = (item: T) => React.ReactElement; + +interface ITreeViewProps + extends React.HTMLAttributes, + IMultipleSelection, + IExpandable { + children: React.ReactNode | ChildrenFunction; + displayType?: 'light' | 'dark'; + items?: Array; + showExpanderOnHover?: boolean; +} + +export function TreeView( + props: ITreeViewProps +): JSX.Element & { + Item: typeof TreeViewItem; + Group: typeof TreeViewGroup; + ItemStack: typeof TreeViewItemStack; +}; + +export function TreeView({ + children, + className, + displayType = 'light', + items, + showExpanderOnHover = true, + ...otherProps +}: ITreeViewProps) { + return ( +
    + + {items + ? items.map((item, index) => { + if (typeof children === 'function') { + return React.cloneElement( + children(item) as React.ReactElement, + {key: index} + ); + } + + return null; + }) + : children} + +
+ ); +} + +TreeView.Item = TreeViewItem; +TreeView.Group = TreeViewGroup; +TreeView.ItemStack = TreeViewItemStack; diff --git a/packages/clay-core/src/tree-view/TreeViewGroup.tsx b/packages/clay-core/src/tree-view/TreeViewGroup.tsx new file mode 100644 index 0000000000..b0c0bc971b --- /dev/null +++ b/packages/clay-core/src/tree-view/TreeViewGroup.tsx @@ -0,0 +1,42 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import React from 'react'; + +type ChildrenFunction = (item: T) => React.ReactElement; + +type TreeViewItemProps = { + children: React.ReactNode | ChildrenFunction; + items?: Array; +}; + +export function TreeViewGroup( + props: TreeViewItemProps +): JSX.Element & { + displayName: string; +}; + +export function TreeViewGroup({children, items}: TreeViewItemProps) { + return ( +
+
    + {items + ? items.map((item, index) => { + if (typeof children === 'function') { + return React.cloneElement( + children(item) as React.ReactElement, + {key: index} + ); + } + + return null; + }) + : children} +
+
+ ); +} + +TreeViewGroup.displayName = 'ClayTreeViewGroup'; diff --git a/packages/clay-core/src/tree-view/TreeViewItem.tsx b/packages/clay-core/src/tree-view/TreeViewItem.tsx new file mode 100644 index 0000000000..cfce988887 --- /dev/null +++ b/packages/clay-core/src/tree-view/TreeViewItem.tsx @@ -0,0 +1,117 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import Button from '@clayui/button'; +import Icon from '@clayui/icon'; +import Layout from '@clayui/layout'; +import React, {useContext} from 'react'; + +type TreeViewItemProps = { + children: React.ReactNode; +}; + +const SpacingContext = React.createContext(0); + +export function TreeViewItem({children}: TreeViewItemProps) { + const spacing = useContext(SpacingContext); + + const [left, right] = React.Children.toArray(children); + + const group = + // @ts-ignore + right?.type?.displayName === 'ClayTreeViewGroup' ? right : null; + + return ( + +
  • +
    + + {typeof left === 'string' ? ( + + +
    {left}
    +
    +
    + ) : group ? ( + left + ) : ( + + {children} + + )} +
    +
    + {group} +
  • +
    + ); +} + +type TreeViewItemStackProps = { + children: React.ReactNode; + expandable?: boolean; +}; + +export function TreeViewItemStack({ + children, + expandable = true, +}: TreeViewItemStackProps) { + const childrenArray = React.Children.toArray(children); + + return ( + + {expandable && ( + + + + )} + + {React.Children.map(children, (child, index) => { + let content = child; + + if (typeof child === 'string') { + content =
    {child}
    ; + + // @ts-ignore + } else if (child?.type.displayName === 'ClayIcon') { + content =
    {child}
    ; + } + + return ( + + {content} + + ); + })} +
    + ); +} diff --git a/packages/clay-core/src/tree-view/context.ts b/packages/clay-core/src/tree-view/context.ts new file mode 100644 index 0000000000..a226f8820b --- /dev/null +++ b/packages/clay-core/src/tree-view/context.ts @@ -0,0 +1,16 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import React, {useContext} from 'react'; + +export interface ITreeViewContext { + showExpanderOnHover?: boolean; +} + +export const TreeViewContext = React.createContext({}); + +export function useTreeViewContext(): ITreeViewContext { + return useContext(TreeViewContext); +} diff --git a/packages/clay-core/src/tree-view/index.ts b/packages/clay-core/src/tree-view/index.ts new file mode 100644 index 0000000000..879aea88b8 --- /dev/null +++ b/packages/clay-core/src/tree-view/index.ts @@ -0,0 +1,6 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export * from './TreeView'; diff --git a/packages/clay-core/src/tree-view/useTree.ts b/packages/clay-core/src/tree-view/useTree.ts new file mode 100644 index 0000000000..474ca63226 --- /dev/null +++ b/packages/clay-core/src/tree-view/useTree.ts @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export interface IExpandable { + /** + * The currently expanded keys in the collection. + */ + expandedKeys?: Set; + + /** + * Handler that is called when items are expanded or collapsed. + */ + onExpandedChange?: (keys: Set) => void; +} + +export interface IMultipleSelection { + /** + * The currently selected keys in the collection. + */ + selectedKeys?: Set; + + /** + * Handler that is called when the selection changes. + */ + onSelectionChange?: (keys: Set) => void; +} diff --git a/packages/clay-core/stories/TreeView.stories.tsx b/packages/clay-core/stories/TreeView.stories.tsx new file mode 100644 index 0000000000..515e91cf89 --- /dev/null +++ b/packages/clay-core/stories/TreeView.stories.tsx @@ -0,0 +1,152 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import '@clayui/css/lib/css/atlas.css'; + +import '@clayui/css/src/scss/cadmin.scss'; +const spritemap = require('@clayui/css/lib/images/icons/icons.svg'); +import Icon from '@clayui/icon'; +import {Provider} from '@clayui/provider'; +import Sticker from '@clayui/sticker'; +import {storiesOf} from '@storybook/react'; +import React from 'react'; + +import {TreeView} from '../src/tree-view'; + +storiesOf('Components|ClayTreeView', module) + .add('light', () => ( + + + + + + {'Root'} + + + {'Item'} + + + + + )) + .add('dark', () => ( + + + + + + {'Root'} + + + {'Item'} + + + + + )) + .add('dynamic', () => ( + + + {(item) => ( + + + + {item.name} + + + {(item) => ( + {item.name} + )} + + + )} + + + )) + .add('sticker', () => ( + + + + + + {'JH'} + + {'Juan Hidalgo'} + + + + + + {'VV'} + + {'Victor Valle'} + + + + + {'SV'} + + {'Susana Vázquez'} + + + + {'MM'} + + {'Myriam Manso'} + + + + + + {'EY'} + + {'Emily Young'} + + + + + + )); diff --git a/packages/clay-provider/src/Provider.tsx b/packages/clay-provider/src/Provider.tsx index 43849f2f77..173088107a 100644 --- a/packages/clay-provider/src/Provider.tsx +++ b/packages/clay-provider/src/Provider.tsx @@ -33,11 +33,14 @@ Context.displayName = 'ClayProviderContext'; export const Provider = ({ children, spritemap, + theme, ...otherProps }: IProviderProps) => ( - + - {children} + + {theme ?
    {children}
    : children} +
    );