Skip to content

Commit

Permalink
BACKLOG-11062: Add some forwardRef and customizable component on Tree… (
Browse files Browse the repository at this point in the history
#318)

* BACKLOG-11062: Add some forwardRef and customizable component on TreeView

* Missing key
  • Loading branch information
Thomas Draier authored Oct 11, 2022
1 parent c1bf5d3 commit f92ac02
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 110 deletions.
24 changes: 14 additions & 10 deletions src/components/Table/TableRow/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import clsx from 'clsx';
import {TableRowProps} from './TableRow.types';
import './TableRow.scss';

export const TableRow: React.FC<TableRowProps> = ({
className,
component = 'tr',
hasMultipleLines = false,
isSelected = false,
isHighlighted = false,
children,
...props
}) => React.createElement(
const TableRowForwardRef: React.ForwardRefRenderFunction<HTMLElement, TableRowProps> = (
{
className,
component = 'tr',
hasMultipleLines = false,
isSelected = false,
isHighlighted = false,
children,
...props
}, ref) => React.createElement(
component,
{
className: clsx(
Expand All @@ -24,9 +25,12 @@ export const TableRow: React.FC<TableRowProps> = ({
isHighlighted && 'moonstone-TableRow-highlighted',
className
),
...props
...props,
ref
},
children
);

export const TableRow = React.forwardRef(TableRowForwardRef);

TableRow.displayName = 'TableRow';
39 changes: 21 additions & 18 deletions src/components/Table/table-cells/TableBodyCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ import {capitalize} from '~/utils/helpers';
import {TableCell} from './TableCell';
import './TableCell.scss';

export const TableBodyCell: React.FC<TableCellProps> = ({
component = 'td',
textAlign = 'left',
verticalAlign = 'center',
className,
iconStart,
iconEnd,
isExpandableColumn,
width,
row,
cell,
children,
isScrollable,
...props
}) => {
const TableBodyCellForwardRef: React.ForwardRefRenderFunction<HTMLDivElement, TableCellProps> = (
{
component = 'td',
textAlign = 'left',
verticalAlign = 'center',
className,
iconStart,
iconEnd,
isExpandableColumn,
width,
row,
cell,
children,
isScrollable,
...props
}, ref) => {
const leftMarginBuffer = 20; // Px
const leftMarginIndentDepth = row?.depth * 20; // Px
const scrollableClass = isScrollable ? 'moonstone-tableCellContent' : '';
Expand All @@ -38,7 +39,7 @@ export const TableBodyCell: React.FC<TableCellProps> = ({
// which the cells show the chevron icon to expand and collapse sub-rows (isExpandableColumn)
if (isExpandableColumn && row?.canExpand) {
return (
<TableCell {...row?.getToggleRowExpandedProps({style: {marginLeft: `${leftMarginIndentDepth}px`}})}>
<TableCell ref={ref} {...row?.getToggleRowExpandedProps({style: {marginLeft: `${leftMarginIndentDepth}px`}})}>
{row?.isExpanded ?
<ChevronDown className="moonstone-marginRightNano"/> :
<ChevronRight className="moonstone-marginRightNano"/>}
Expand All @@ -54,15 +55,15 @@ export const TableBodyCell: React.FC<TableCellProps> = ({
// the chevron icons for expand/collapse
if (isExpandableColumn && !row?.canExpand) {
return (
<TableCell style={{marginLeft: `${leftMarginIndentDepth + leftMarginBuffer}px`}}>
<TableCell ref={ref} style={{marginLeft: `${leftMarginIndentDepth + leftMarginBuffer}px`}}>
{renderCellContent()}
</TableCell>
);
}

// These are just the normal cells in the other columns which don't display anything with
// relation to the row expansion feature
return <TableCell>{renderCellContent()}</TableCell>;
return <TableCell ref={ref}>{renderCellContent()}</TableCell>;
};

return (
Expand All @@ -84,4 +85,6 @@ export const TableBodyCell: React.FC<TableCellProps> = ({
);
};

export const TableBodyCell = React.forwardRef(TableBodyCellForwardRef);

TableBodyCell.displayName = 'TableBodyCell';
33 changes: 18 additions & 15 deletions src/components/Table/table-cells/TableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ import clsx from 'clsx';
import {TableCellProps} from './TableCell.types';
import './TableCell.scss';

export const TableCell: React.FC<Partial<TableCellProps>> = ({
iconStart,
iconEnd,
className,
children,
...props
}) => {
const TableCellForwardRef: React.ForwardRefRenderFunction<HTMLDivElement, TableCellProps> = (
{
iconStart,
iconEnd,
className,
children,
...props
}, ref) => {
return (
<div
className={clsx(
'moonstone-TableCell',
'flexRow_nowrap',
'alignCenter',
className
)}
{...props}
<div ref={ref}
className={clsx(
'moonstone-TableCell',
'flexRow_nowrap',
'alignCenter',
className
)}
{...props}
>

{iconStart && (
Expand All @@ -42,4 +43,6 @@ export const TableCell: React.FC<Partial<TableCellProps>> = ({
);
};

export const TableCell = React.forwardRef(TableCellForwardRef);

TableCell.displayName = 'FoundationTableCell';
119 changes: 58 additions & 61 deletions src/components/TreeView/ControlledTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,24 @@ const displayIconOrLoading = (icon: React.ReactElement, isLoading: boolean) => {
return displayIcon(i, 'default', 'moonstone-treeView_itemIconEnd');
};

export const ControlledTreeView: React.FC<ControlledTreeViewProps> = ({
data,
openedItems = [],
selectedItems = [],
onClickItem = () => undefined,
onDoubleClickItem = () => undefined,
onContextMenuItem = () => undefined,
onOpenItem = () => undefined,
onCloseItem = () => undefined,
isReversed = false,
...props
}) => {
const ControlledTreeViewForwardRef: React.ForwardRefRenderFunction<HTMLUListElement, ControlledTreeViewProps> = (
{
data,
openedItems = [],
selectedItems = [],
onClickItem = () => undefined,
onDoubleClickItem = () => undefined,
onContextMenuItem = () => undefined,
onOpenItem = () => undefined,
onCloseItem = () => undefined,
isReversed = false,
component = 'ul',
itemComponent = 'li',
...props
}, ref) => {
const isFlatData = data.filter(item => item.children && item.children.length > 0).length === 0;

function generateLevelJSX(nodeData: TreeViewData[], deep: number, parentHasIconStart: boolean) {
function generateLevelJSX(nodeData: TreeViewData[], deep: number, parentHasIconStart: boolean): React.ReactNode[] {
return nodeData.map(node => {
const hasChild = Boolean(node.hasChildren || (node.children && node.children.length !== 0));
const hasIconStart = Boolean(node.iconStart);
Expand Down Expand Up @@ -92,61 +95,55 @@ export const ControlledTreeView: React.FC<ControlledTreeViewProps> = ({
}
);

// TreeItem has child
return (
<React.Fragment key={`${deep}-${node.id}`}>
<li role="treeitem"
aria-expanded={isOpen}
{...node.treeItemProps}
return [
React.createElement(
itemComponent,
{role: 'treeitem', 'aria-expanded': isOpen, key: `${deep}-${node.id}`, ...node.treeItemProps},
<div
className={cssTreeViewItem}
style={{
paddingLeft: `calc((var(--spacing-medium) + var(--spacing-nano)) * ${deep} + var(--spacing-medium))`
}}
>
<div
className={cssTreeViewItem}
style={{
paddingLeft: `calc((var(--spacing-medium) + var(--spacing-nano)) * ${deep} + var(--spacing-medium))`
}}
>
{/* Icon arrow */}
{isClosable && hasChild && (
<div
className={clsx('flexRow', 'alignCenter', 'moonstone-treeView_itemToggle')}
onClick={toggleNode}
>
{isOpen ? <ChevronDown/> : <ChevronRight/>}
</div>
)}
{!isFlatData && !hasChild &&
<div className={clsx('flexRow', 'alignCenter', 'moonstone-treeView_itemToggle')}/>}

{/* TreeViewItem */}
{/* Icon arrow */}
{isClosable && hasChild && (
<div
className={clsx('flexRow_nowrap', 'alignCenter', 'flexFluid', 'moonstone-treeView_itemLabel', node.className)}
onClick={node.isDisabled ? undefined : handleNodeClick}
onDoubleClick={handleNodeDoubleClick}
onContextMenu={handleNodeContextMenu}
className={clsx('flexRow', 'alignCenter', 'moonstone-treeView_itemToggle')}
onClick={toggleNode}
>
{displayIcon(node.iconStart, 'small', 'moonstone-treeView_itemIconStart', parentHasIconStart)}
<Typography isNowrap
className={clsx('flexFluid')}
component="span"
variant="body"
{...node.typographyOptions}
>
{node.label}
</Typography>
{displayIconOrLoading(node.iconEnd, isLoading)}
{isOpen ? <ChevronDown/> : <ChevronRight/>}
</div>
)}
{!isFlatData && !hasChild &&
<div className={clsx('flexRow', 'alignCenter', 'moonstone-treeView_itemToggle')}/>}

{/* TreeViewItem */}
<div
className={clsx('flexRow_nowrap', 'alignCenter', 'flexFluid', 'moonstone-treeView_itemLabel', node.className)}
onClick={node.isDisabled ? undefined : handleNodeClick}
onDoubleClick={handleNodeDoubleClick}
onContextMenu={handleNodeContextMenu}
>
{displayIcon(node.iconStart, 'small', 'moonstone-treeView_itemIconStart', parentHasIconStart)}
<Typography isNowrap
className={clsx('flexFluid')}
component="span"
variant="body"
{...node.typographyOptions}
>
{node.label}
</Typography>
{displayIconOrLoading(node.iconEnd, isLoading)}
</div>
</li>
{isOpen && node.children && generateLevelJSX(node.children, isClosable ? (deep + 1) : deep, hasIconStart)}
</React.Fragment>
);
</div>
),
...((isOpen && node.children) ? generateLevelJSX(node.children, isClosable ? (deep + 1) : deep, hasIconStart) : [])
];
});
}

// TreeView component
return (
<ul role="tree" {...props}>
{generateLevelJSX(data, 0, false)}
</ul>
);
return React.createElement(component, {ref, role: 'tree', ...props}, generateLevelJSX(data, 0, false));
};

export const ControlledTreeView = React.forwardRef(ControlledTreeViewForwardRef);
13 changes: 12 additions & 1 deletion src/components/TreeView/ControlledTreeView.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,19 @@ export type ControlledTreeViewProps = {
onContextMenuItem?: (node: TreeViewData, e?: React.MouseEvent) => void;

/**
* Reverse color usefull for context with dark background
* Reverse color useful for context with dark background
*/
isReversed?: boolean;

/**
* Component used for the list (ul)
*/
component?: string;

/**
* Component used for every item (li)
*/
itemComponent?: string;

};

6 changes: 4 additions & 2 deletions src/components/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {TreeViewProps} from './TreeView.types';
import {UncontrolledTreeView} from './UncontrolledTreeView';
import {ControlledTreeView} from './ControlledTreeView';

export const TreeView: React.FC<TreeViewProps> = ({openedItems, defaultOpenedItems, onOpenItem, onCloseItem, data, ...others}) => {
const TreeViewForwardRef: React.ForwardRefRenderFunction<HTMLUListElement, TreeViewProps> = ({openedItems, defaultOpenedItems, onOpenItem, onCloseItem, data, ...others}, ref) => {
// If no data render nothing
if (!Array.isArray(data) || data.length < 1) {
return null;
Expand All @@ -13,7 +13,9 @@ export const TreeView: React.FC<TreeViewProps> = ({openedItems, defaultOpenedIte
return <UncontrolledTreeView defaultOpenedItems={defaultOpenedItems} data={data} {...others}/>;
}

return <ControlledTreeView openedItems={openedItems} data={data} onOpenItem={onOpenItem} onCloseItem={onCloseItem} {...others}/>;
return <ControlledTreeView ref={ref} openedItems={openedItems} data={data} onOpenItem={onOpenItem} onCloseItem={onCloseItem} {...others}/>;
};

export const TreeView = React.forwardRef(TreeViewForwardRef);

TreeView.displayName = 'TreeView';
10 changes: 9 additions & 1 deletion src/components/TreeView/TreeView.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,16 @@ export type TreeViewProps = {
*/
onContextMenuItem?: (node: TreeViewData, e?: React.MouseEvent) => void;
/**
* Reverse color usefull for context with dark background
* Reverse color useful for context with dark background
*/
isReversed?: boolean;
/**
* Component used for the list (ul)
*/
component?: string;
/**
* Component used for every item (li)
*/
itemComponent?: string;
};

6 changes: 4 additions & 2 deletions src/components/TreeView/UncontrolledTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {UncontrolledTreeViewProps} from './UncontrolledTreeView.types';
import {TreeViewData} from './TreeView.types';
import {ControlledTreeView} from './ControlledTreeView';

export const UncontrolledTreeView: React.FC<UncontrolledTreeViewProps> = ({defaultOpenedItems = [], ...others}) => {
const UncontrolledTreeViewForwardRef: React.ForwardRefRenderFunction<HTMLUListElement, UncontrolledTreeViewProps> = ({defaultOpenedItems = [], ...others}, ref) => {
const [openedItems, setOpenedItems] = useState(defaultOpenedItems);

const onOpenItem = (node: TreeViewData) => {
Expand All @@ -14,5 +14,7 @@ export const UncontrolledTreeView: React.FC<UncontrolledTreeViewProps> = ({defau
setOpenedItems(prevOpenedItems => prevOpenedItems.filter(item => item !== node.id));
};

return <ControlledTreeView openedItems={openedItems} onOpenItem={onOpenItem} onCloseItem={onCloseItem} {...others}/>;
return <ControlledTreeView ref={ref} openedItems={openedItems} onOpenItem={onOpenItem} onCloseItem={onCloseItem} {...others}/>;
};

export const UncontrolledTreeView = React.forwardRef(UncontrolledTreeViewForwardRef);
11 changes: 11 additions & 0 deletions src/components/TreeView/UncontrolledTreeView.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,16 @@ export type UncontrolledTreeViewProps = {
* Reverse color useful for context with dark background
*/
isReversed?: boolean;

/**
* Component used for the list (ul)
*/
component?: string;

/**
* Component used for every item (li)
*/
itemComponent?: string;

};

0 comments on commit f92ac02

Please # to comment.