mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 09:20:16 +00:00
Initial Move from Azure DevOps to GitHub
This commit is contained in:
162
src/Explorer/Controls/TreeComponent/TreeComponent.test.tsx
Normal file
162
src/Explorer/Controls/TreeComponent/TreeComponent.test.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { TreeComponent, TreeNode, TreeNodeComponent } from "./TreeComponent";
|
||||
|
||||
const buildChildren = (): TreeNode[] => {
|
||||
const grandChild11: TreeNode = {
|
||||
label: "ZgrandChild11"
|
||||
};
|
||||
const grandChild12: TreeNode = {
|
||||
label: "AgrandChild12"
|
||||
};
|
||||
const child1: TreeNode = {
|
||||
label: "Bchild1",
|
||||
children: [grandChild11, grandChild12]
|
||||
};
|
||||
|
||||
const child2: TreeNode = {
|
||||
label: "2child2"
|
||||
};
|
||||
|
||||
return [child1, child2];
|
||||
};
|
||||
|
||||
const buildChildren2 = (): TreeNode[] => {
|
||||
const grandChild11: TreeNode = {
|
||||
label: "ZgrandChild11"
|
||||
};
|
||||
const grandChild12: TreeNode = {
|
||||
label: "AgrandChild12"
|
||||
};
|
||||
|
||||
const child1: TreeNode = {
|
||||
label: "aChild"
|
||||
};
|
||||
|
||||
const child2: TreeNode = {
|
||||
label: "bchild",
|
||||
children: [grandChild11, grandChild12]
|
||||
};
|
||||
|
||||
const child3: TreeNode = {
|
||||
label: "cchild"
|
||||
};
|
||||
|
||||
const child4: TreeNode = {
|
||||
label: "dchild",
|
||||
children: [grandChild11, grandChild12]
|
||||
};
|
||||
|
||||
return [child1, child2, child3, child4];
|
||||
};
|
||||
|
||||
describe("TreeComponent", () => {
|
||||
it("renders a simple tree", () => {
|
||||
const root = {
|
||||
label: "root",
|
||||
children: buildChildren()
|
||||
};
|
||||
|
||||
const props = {
|
||||
rootNode: root,
|
||||
className: "tree"
|
||||
};
|
||||
|
||||
const wrapper = shallow(<TreeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("TreeNodeComponent", () => {
|
||||
it("renders a simple node (sorted children, expanded)", () => {
|
||||
const node: TreeNode = {
|
||||
label: "label",
|
||||
id: "id",
|
||||
children: buildChildren(),
|
||||
contextMenu: [
|
||||
{
|
||||
label: "menuLabel",
|
||||
onClick: undefined,
|
||||
iconSrc: undefined,
|
||||
isDisabled: true
|
||||
}
|
||||
],
|
||||
iconSrc: undefined,
|
||||
isExpanded: true,
|
||||
className: "nodeClassname",
|
||||
isAlphaSorted: true,
|
||||
data: undefined,
|
||||
timestamp: 10,
|
||||
isSelected: undefined,
|
||||
onClick: undefined,
|
||||
onExpanded: undefined,
|
||||
onCollapsed: undefined
|
||||
};
|
||||
|
||||
const props = {
|
||||
node,
|
||||
generation: 12,
|
||||
paddingLeft: 23
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders unsorted children by default", () => {
|
||||
const node: TreeNode = {
|
||||
label: "label",
|
||||
children: buildChildren(),
|
||||
isExpanded: true
|
||||
};
|
||||
const props = {
|
||||
node,
|
||||
generation: 2,
|
||||
paddingLeft: 9
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render children by default", () => {
|
||||
const node: TreeNode = {
|
||||
label: "label",
|
||||
children: buildChildren(),
|
||||
isAlphaSorted: false
|
||||
};
|
||||
const props = {
|
||||
node,
|
||||
generation: 2,
|
||||
paddingLeft: 9
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders sorted children, expanded, leaves and parents separated", () => {
|
||||
const node: TreeNode = {
|
||||
label: "label",
|
||||
id: "id",
|
||||
children: buildChildren2(),
|
||||
contextMenu: [],
|
||||
iconSrc: undefined,
|
||||
isExpanded: true,
|
||||
className: "nodeClassname",
|
||||
isAlphaSorted: true,
|
||||
isLeavesParentsSeparate: true,
|
||||
data: undefined,
|
||||
timestamp: 10,
|
||||
isSelected: undefined,
|
||||
onClick: undefined,
|
||||
onExpanded: undefined,
|
||||
onCollapsed: undefined
|
||||
};
|
||||
|
||||
const props = {
|
||||
node,
|
||||
generation: 12,
|
||||
paddingLeft: 23
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
323
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
Normal file
323
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* Tree component:
|
||||
* - collapsible
|
||||
* - icons prefix
|
||||
* - context menu
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import AnimateHeight from "react-animate-height";
|
||||
import { IconButton } from "office-ui-fabric-react/lib/Button";
|
||||
import {
|
||||
DirectionalHint,
|
||||
IContextualMenuItemProps,
|
||||
IContextualMenuProps
|
||||
} from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
|
||||
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||
|
||||
export interface TreeNodeMenuItem {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
iconSrc?: string;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
label: string;
|
||||
id?: string;
|
||||
children?: TreeNode[];
|
||||
contextMenu?: TreeNodeMenuItem[];
|
||||
iconSrc?: string;
|
||||
isExpanded?: boolean;
|
||||
className?: string;
|
||||
isAlphaSorted?: boolean;
|
||||
data?: any; // Piece of data corresponding to this node
|
||||
timestamp?: number;
|
||||
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
|
||||
isSelected?: () => boolean;
|
||||
onClick?: (isExpanded: boolean) => void; // Only if a leaf, other click will expand/collapse
|
||||
onExpanded?: () => void;
|
||||
onCollapsed?: () => void;
|
||||
onContextMenuOpen?: () => void;
|
||||
}
|
||||
|
||||
export interface TreeComponentProps {
|
||||
rootNode: TreeNode;
|
||||
style?: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export class TreeComponent extends React.Component<TreeComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div style={this.props.style} className={`treeComponent ${this.props.className}`}>
|
||||
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tree node is a react component */
|
||||
interface TreeNodeComponentProps {
|
||||
node: TreeNode;
|
||||
generation: number;
|
||||
paddingLeft: number;
|
||||
}
|
||||
|
||||
interface TreeNodeComponentState {
|
||||
isExpanded: boolean;
|
||||
isMenuShowing: boolean;
|
||||
}
|
||||
export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, TreeNodeComponentState> {
|
||||
private static readonly paddingPerGenerationPx = 16;
|
||||
private static readonly iconOffset = 22;
|
||||
private static readonly transitionDurationMS = 200;
|
||||
private static readonly callbackDelayMS = 100; // avoid calling at the same time as transition to make it smoother
|
||||
private contextMenuRef = React.createRef<HTMLDivElement>();
|
||||
private isExpanded: boolean;
|
||||
|
||||
constructor(props: TreeNodeComponentProps) {
|
||||
super(props);
|
||||
this.isExpanded = props.node.isExpanded;
|
||||
this.state = {
|
||||
isExpanded: props.node.isExpanded,
|
||||
isMenuShowing: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: TreeNodeComponentProps, prevState: TreeNodeComponentState) {
|
||||
// Only call when expand has actually changed
|
||||
if (this.state.isExpanded !== prevState.isExpanded) {
|
||||
if (this.state.isExpanded) {
|
||||
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent.callbackDelayMS);
|
||||
} else {
|
||||
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS);
|
||||
}
|
||||
}
|
||||
if (this.props.node.isExpanded !== this.isExpanded) {
|
||||
this.isExpanded = this.props.node.isExpanded;
|
||||
this.setState({
|
||||
isExpanded: this.props.node.isExpanded
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return this.renderNode(this.props.node, this.props.generation);
|
||||
}
|
||||
|
||||
private static getSortedChildren(treeNode: TreeNode): TreeNode[] {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label);
|
||||
|
||||
let unsortedChildren;
|
||||
if (treeNode.isLeavesParentsSeparate) {
|
||||
// Separate parents and leave
|
||||
const parents: TreeNode[] = treeNode.children.filter(node => node.children);
|
||||
const leaves: TreeNode[] = treeNode.children.filter(node => !node.children);
|
||||
|
||||
if (treeNode.isAlphaSorted) {
|
||||
parents.sort(compareFct);
|
||||
leaves.sort(compareFct);
|
||||
}
|
||||
|
||||
unsortedChildren = parents.concat(leaves);
|
||||
} else {
|
||||
unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children;
|
||||
}
|
||||
|
||||
return unsortedChildren;
|
||||
}
|
||||
|
||||
private static isNodeHeaderBlank(node: TreeNode): boolean {
|
||||
return (node.label === undefined || node.label === null) && !node.contextMenu;
|
||||
}
|
||||
|
||||
private renderNode(node: TreeNode, generation: number): JSX.Element {
|
||||
let paddingLeft = generation * TreeNodeComponent.paddingPerGenerationPx;
|
||||
let additionalOffsetPx = 15;
|
||||
|
||||
if (node.children) {
|
||||
const childrenWithSubChildren = node.children.filter((child: TreeNode) => !!child.children);
|
||||
if (childrenWithSubChildren.length > 0) {
|
||||
additionalOffsetPx = TreeNodeComponent.iconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show as selected if any of the children is selected
|
||||
const showSelected =
|
||||
this.props.node.isSelected &&
|
||||
this.props.node.isSelected() &&
|
||||
!TreeNodeComponent.isAnyDescendantSelected(this.props.node);
|
||||
|
||||
const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft };
|
||||
if (TreeNodeComponent.isNodeHeaderBlank(node)) {
|
||||
headerStyle.height = 0;
|
||||
headerStyle.padding = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
||||
>
|
||||
<div
|
||||
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
||||
style={headerStyle}
|
||||
tabIndex={node.children ? -1 : 0}
|
||||
data-test={node.label}
|
||||
>
|
||||
{this.renderCollapseExpandIcon(node)}
|
||||
{node.iconSrc && <img className="nodeIcon" src={node.iconSrc} alt="" />}
|
||||
{node.label && (
|
||||
<span className="nodeLabel" title={node.label}>
|
||||
{node.label}
|
||||
</span>
|
||||
)}
|
||||
{node.contextMenu && this.renderContextMenuButton(node)}
|
||||
</div>
|
||||
{node.children && (
|
||||
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
||||
<div className="nodeChildren" data-test={node.label}>
|
||||
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
|
||||
<TreeNodeComponent
|
||||
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
||||
node={childNode}
|
||||
generation={generation + 1}
|
||||
paddingLeft={paddingLeft + (!childNode.children && !childNode.iconSrc ? additionalOffsetPx : 0)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</AnimateHeight>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive: is the node or any descendant selected
|
||||
* @param node
|
||||
*/
|
||||
private static isAnyDescendantSelected(node: TreeNode): boolean {
|
||||
return (
|
||||
node.children &&
|
||||
node.children.reduce(
|
||||
(previous: boolean, child: TreeNode) =>
|
||||
previous || (child.isSelected && child.isSelected()) || TreeNodeComponent.isAnyDescendantSelected(child),
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static createClickEvent(): MouseEvent {
|
||||
return new MouseEvent("click", { bubbles: true, view: window, cancelable: true });
|
||||
}
|
||||
|
||||
private onRightClick = (): void => {
|
||||
this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent.createClickEvent());
|
||||
};
|
||||
|
||||
private renderContextMenuButton(node: TreeNode): JSX.Element {
|
||||
const menuItemLabel = "More";
|
||||
return (
|
||||
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
|
||||
<IconButton
|
||||
name="More"
|
||||
className="treeMenuEllipsis"
|
||||
ariaLabel={menuItemLabel}
|
||||
menuIconProps={{
|
||||
iconName: menuItemLabel,
|
||||
styles: { root: { fontSize: "18px", fontWeight: "bold" } }
|
||||
}}
|
||||
menuProps={{
|
||||
coverTarget: true,
|
||||
isBeakVisible: false,
|
||||
directionalHint: DirectionalHint.topAutoEdge,
|
||||
onMenuOpened: (contextualMenu?: IContextualMenuProps) => {
|
||||
this.setState({ isMenuShowing: true });
|
||||
node.onContextMenuOpen && node.onContextMenuOpen();
|
||||
},
|
||||
onMenuDismissed: (contextualMenu?: IContextualMenuProps) => this.setState({ isMenuShowing: false }),
|
||||
contextualMenuItemAs: (props: IContextualMenuItemProps) => (
|
||||
<div
|
||||
data-test={`treeComponentMenuItemContainer`}
|
||||
className="treeComponentMenuItemContainer"
|
||||
onContextMenu={e => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
|
||||
>
|
||||
{props.item.onRenderIcon()}
|
||||
<span className="treeComponentMenuItemLabel">{props.item.text}</span>
|
||||
</div>
|
||||
),
|
||||
items: node.contextMenu.map((menuItem: TreeNodeMenuItem) => ({
|
||||
key: menuItem.label,
|
||||
text: menuItem.label,
|
||||
disabled: menuItem.isDisabled,
|
||||
onClick: menuItem.onClick,
|
||||
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderCollapseExpandIcon(node: TreeNode): JSX.Element {
|
||||
if (!node.children || !node.label) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className="expandCollapseIcon"
|
||||
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
||||
alt={this.state.isExpanded ? "Branch is expanded" : "Branch is collapsed"}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onCollapseExpandIconKeyPress(event, node)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private onNodeClick = (event: React.MouseEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
event.stopPropagation();
|
||||
if (node.children) {
|
||||
const isExpanded = !this.state.isExpanded;
|
||||
// Prevent collapsing if node header is blank
|
||||
if (!(TreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) {
|
||||
this.setState({ isExpanded });
|
||||
}
|
||||
}
|
||||
|
||||
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
|
||||
};
|
||||
|
||||
private onNodeKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
|
||||
}
|
||||
};
|
||||
|
||||
private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
if (node.children) {
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onMoreButtonKeyPress = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TreeComponent renders a simple tree 1`] = `
|
||||
<div
|
||||
className="treeComponent tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={0}
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "Bchild1",
|
||||
},
|
||||
Object {
|
||||
"label": "2child2",
|
||||
},
|
||||
],
|
||||
"label": "root",
|
||||
}
|
||||
}
|
||||
paddingLeft={0}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
<div
|
||||
className=" main2 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
style={
|
||||
Object {
|
||||
"paddingLeft": 9,
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="Branch is collapsed"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src=""
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
className="nodeLabel"
|
||||
title="label"
|
||||
>
|
||||
label
|
||||
</span>
|
||||
</div>
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
"animatingToHeightSpecific": "rah-animating--to-height-specific",
|
||||
"animatingToHeightZero": "rah-animating--to-height-zero",
|
||||
"animatingUp": "rah-animating--up",
|
||||
"static": "rah-static",
|
||||
"staticHeightAuto": "rah-static--height-auto",
|
||||
"staticHeightSpecific": "rah-static--height-specific",
|
||||
"staticHeightZero": "rah-static--height-zero",
|
||||
}
|
||||
}
|
||||
applyInlineTransitions={true}
|
||||
delay={0}
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height={0}
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
key="Bchild1-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "Bchild1",
|
||||
}
|
||||
}
|
||||
paddingLeft={32}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
key="2child2-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
paddingLeft={54}
|
||||
/>
|
||||
</div>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
|
||||
<div
|
||||
className="nodeClassname main12 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
style={
|
||||
Object {
|
||||
"paddingLeft": 23,
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src=""
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
className="nodeLabel"
|
||||
title="label"
|
||||
>
|
||||
label
|
||||
</span>
|
||||
<div
|
||||
onContextMenu={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="More"
|
||||
className="treeMenuEllipsis"
|
||||
menuIconProps={
|
||||
Object {
|
||||
"iconName": "More",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"fontSize": "18px",
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
menuProps={
|
||||
Object {
|
||||
"contextualMenuItemAs": [Function],
|
||||
"coverTarget": true,
|
||||
"directionalHint": 3,
|
||||
"isBeakVisible": false,
|
||||
"items": Array [
|
||||
Object {
|
||||
"disabled": true,
|
||||
"key": "menuLabel",
|
||||
"onClick": undefined,
|
||||
"onRenderIcon": [Function],
|
||||
"text": "menuLabel",
|
||||
},
|
||||
],
|
||||
"onMenuDismissed": [Function],
|
||||
"onMenuOpened": [Function],
|
||||
}
|
||||
}
|
||||
name="More"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
"animatingToHeightSpecific": "rah-animating--to-height-specific",
|
||||
"animatingToHeightZero": "rah-animating--to-height-zero",
|
||||
"animatingUp": "rah-animating--up",
|
||||
"static": "rah-static",
|
||||
"staticHeightAuto": "rah-static--height-auto",
|
||||
"staticHeightSpecific": "rah-static--height-specific",
|
||||
"staticHeightZero": "rah-static--height-zero",
|
||||
}
|
||||
}
|
||||
applyInlineTransitions={true}
|
||||
delay={0}
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="2child2-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
paddingLeft={214}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="Bchild1-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "Bchild1",
|
||||
}
|
||||
}
|
||||
paddingLeft={192}
|
||||
/>
|
||||
</div>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
||||
<div
|
||||
className="nodeClassname main12 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
style={
|
||||
Object {
|
||||
"paddingLeft": 23,
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src=""
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
className="nodeLabel"
|
||||
title="label"
|
||||
>
|
||||
label
|
||||
</span>
|
||||
<div
|
||||
onContextMenu={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="More"
|
||||
className="treeMenuEllipsis"
|
||||
menuIconProps={
|
||||
Object {
|
||||
"iconName": "More",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"fontSize": "18px",
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
menuProps={
|
||||
Object {
|
||||
"contextualMenuItemAs": [Function],
|
||||
"coverTarget": true,
|
||||
"directionalHint": 3,
|
||||
"isBeakVisible": false,
|
||||
"items": Array [],
|
||||
"onMenuDismissed": [Function],
|
||||
"onMenuOpened": [Function],
|
||||
}
|
||||
}
|
||||
name="More"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
"animatingToHeightSpecific": "rah-animating--to-height-specific",
|
||||
"animatingToHeightZero": "rah-animating--to-height-zero",
|
||||
"animatingUp": "rah-animating--up",
|
||||
"static": "rah-static",
|
||||
"staticHeightAuto": "rah-static--height-auto",
|
||||
"staticHeightSpecific": "rah-static--height-specific",
|
||||
"staticHeightZero": "rah-static--height-zero",
|
||||
}
|
||||
}
|
||||
applyInlineTransitions={true}
|
||||
delay={0}
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="bchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "bchild",
|
||||
}
|
||||
}
|
||||
paddingLeft={192}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="dchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "dchild",
|
||||
}
|
||||
}
|
||||
paddingLeft={192}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="aChild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"label": "aChild",
|
||||
}
|
||||
}
|
||||
paddingLeft={214}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
key="cchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"label": "cchild",
|
||||
}
|
||||
}
|
||||
paddingLeft={214}
|
||||
/>
|
||||
</div>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
<div
|
||||
className=" main2 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
style={
|
||||
Object {
|
||||
"paddingLeft": 9,
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src=""
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
className="nodeLabel"
|
||||
title="label"
|
||||
>
|
||||
label
|
||||
</span>
|
||||
</div>
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
"animatingToHeightSpecific": "rah-animating--to-height-specific",
|
||||
"animatingToHeightZero": "rah-animating--to-height-zero",
|
||||
"animatingUp": "rah-animating--up",
|
||||
"static": "rah-static",
|
||||
"staticHeightAuto": "rah-static--height-auto",
|
||||
"staticHeightSpecific": "rah-static--height-specific",
|
||||
"staticHeightZero": "rah-static--height-zero",
|
||||
}
|
||||
}
|
||||
applyInlineTransitions={true}
|
||||
delay={0}
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
key="Bchild1-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "Bchild1",
|
||||
}
|
||||
}
|
||||
paddingLeft={32}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
key="2child2-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
paddingLeft={54}
|
||||
/>
|
||||
</div>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
`;
|
||||
80
src/Explorer/Controls/TreeComponent/treeComponent.less
Normal file
80
src/Explorer/Controls/TreeComponent/treeComponent.less
Normal file
@@ -0,0 +1,80 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.treeComponent {
|
||||
.nodeItem {
|
||||
&:focus {
|
||||
outline: 1px dashed @AccentMedium;
|
||||
}
|
||||
|
||||
.treeNodeHeader {
|
||||
padding: @SmallSpace 2px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
background-color: @AccentLight;
|
||||
|
||||
.treeMenuEllipsis {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.showingMenu {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.treeMenuEllipsis {
|
||||
max-height: 17px;
|
||||
padding-right: 6px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
float: right;
|
||||
padding-left: 6px;
|
||||
opacity: 0;
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.nodeIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-top;
|
||||
margin-right: @SmallSpace;
|
||||
}
|
||||
|
||||
.expandCollapseIcon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
vertical-align: middle;
|
||||
margin: 2px @DefaultSpace 2px @SmallSpace;
|
||||
}
|
||||
|
||||
.nodeLabel {
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
& > .treeNodeHeader {
|
||||
background-color: @AccentExtra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.treeComponentMenuItemContainer {
|
||||
font-size: @mediumFontSize;
|
||||
|
||||
.treeComponentMenuItemLabel {
|
||||
margin-left: @SmallSpace;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user