Skip to content

Commit

Permalink
feat: develop explorer panels (#174)
Browse files Browse the repository at this point in the history
* feat: develop explorer panels

* fix: optimize add Panel

* fix: improve collapse interactive behavior

* fix: optimize explorer delete panel

* fix: improve the style of folder tree edit input

* fix: improve explorer onDeletePanel  event

* fix: improve BEM classname & ReturnType of render function

* fix: fix static type check
  • Loading branch information
mortalYoung authored Jun 16, 2021
1 parent fdb50da commit 921ecc5
Show file tree
Hide file tree
Showing 29 changed files with 685 additions and 353 deletions.
1 change: 1 addition & 0 deletions src/common/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const ID_MENU_BAR = 'menuBar';
export const ID_SIDE_BAR = 'sidebar';
export const ID_EXPLORER = 'explorer';
export const ID_STATUS_BAR = 'statusBar';
export const ID_FOLDER_TREE = 'folderTree';
4 changes: 4 additions & 0 deletions src/common/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export default {
error(...args) {
console.error(...args);
},

warn(...args) {
console.warn(...args);
},
};
150 changes: 89 additions & 61 deletions src/components/collapse/index.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,125 @@
import * as React from 'react';
import { useState } from 'react';
import Logger from 'mo/common/logger';
import RcCollapse, { Panel as CollapsePanel } from 'rc-collapse';
import { Toolbar } from 'mo/components/toolbar';
import { Icon } from 'mo/components/icon';
import {
prefixClaName,
classNames,
getBEMElement,
getBEMModifier,
} from 'mo/common/className';
import { IActionBarItemProps } from 'mo/components/actionBar';
import { prefixClaName, classNames, getBEMElement } from 'mo/common/className';

export interface IExpandProps {
isActive?: boolean;
}
export interface ICollapseProps<T = any> {
data?: T;
type RenderFunctionProps = (data: DataBaseProps) => React.ReactNode;

interface DataBaseProps {
id: React.Key;
name: string;
className?: string;
onCollapseChange?: (keys) => void;
onCollapseToolbar?: (item) => void;
hidden?: boolean;
toolbar?: IActionBarItemProps[];
renderPanel?: RenderFunctionProps;

[key: string]: any;
}

interface IState {
activePanelKeys: React.Key[];
export interface ICollapseProps {
data?: Partial<DataBaseProps>[];
className?: string;
onCollapseChange?: (keys: React.Key[]) => void;
onToolbarClick?: (
item: IActionBarItemProps,
parentPanel: DataBaseProps
) => void;
}

const defaultCollapseClassName = prefixClaName('collapse');
export const contentPaddingClassName = getBEMModifier(
getBEMElement(defaultCollapseClassName, 'content'),
'padding'
const toolbarCollapseClassName = getBEMElement(
defaultCollapseClassName,
'toolbar'
);

const initState = {
activePanelKeys: [],
};

export function Collapse(props: ICollapseProps) {
const [state, setState] = useState<IState>(initState);
const [activePanelKeys, setActivePanelKeys] = useState<React.Key[]>([]);

const {
className,
data = [],
onCollapseChange,
onCollapseToolbar,
onToolbarClick,
...restProps
} = props;
const onChangeCallback = (key: React.Key[]) => {

const handleChangeCallback = (key: React.Key[]) => {
onCollapseChange?.(key);
setState((state: IState) => ({ ...state, activePanelKeys: key }));
setActivePanelKeys(key || []);
};
const onClick = (e, item) => {

const handleToolbarClick = (
e: React.MouseEvent,
item: IActionBarItemProps,
panel: DataBaseProps
) => {
e.stopPropagation();
onCollapseToolbar?.(item);
console.log('onClick:', e, item);
onToolbarClick?.(item, panel);
};
const render = (render) => {

const renderPanels = (
data: DataBaseProps,
render?: RenderFunctionProps
) => {
if (render) {
return render();
} else {
return (
<span className={contentPaddingClassName}>
Cannot provide...
</span>
);
return render(data);
}
return null;
};
const { activePanelKeys } = state;

const filterData = data.filter((panel) => panel.id) as DataBaseProps[];
if (filterData.length < data.length) {
Logger.warn(new SyntaxError('collapse data must have id'));
}

return (
<div className={classNames(defaultCollapseClassName, className)}>
<RcCollapse
{...restProps}
onChange={(activeKeys: React.Key[]) => {
onChangeCallback(activeKeys);
}}
expandIcon={({ isActive }: IExpandProps) => (
onChange={handleChangeCallback}
expandIcon={({ isActive }: { isActive: boolean }) => (
<Icon type={isActive ? 'chevron-down' : 'chevron-right'} />
)}
{...restProps}
>
{data.map((panel) => (
<CollapsePanel
key={panel.id}
header={panel.name}
className={panel.className}
extra={
activePanelKeys?.includes(panel.id) && (
<Toolbar
key={panel.id}
data={panel.toolbar}
onClick={onClick}
/>
)
}
>
{render(panel.renderPanel)}
</CollapsePanel>
))}
{filterData
.filter((p) => !p.hidden)
.map((panel) => {
const content = renderPanels(panel, panel.renderPanel);
return (
<CollapsePanel
tabIndex={-1}
key={panel.id}
panelKey={panel.id}
header={panel.name}
className={classNames(
panel.className,
content === null && 'empty'
)}
extra={
activePanelKeys.includes(panel.id) && (
<Toolbar
className={toolbarCollapseClassName}
key={panel.id}
data={panel.toolbar || []}
onClick={(e, item) =>
handleToolbarClick(
e,
item,
panel
)
}
/>
)
}
>
{content}
</CollapsePanel>
);
})}
</RcCollapse>
</div>
);
Expand Down
63 changes: 53 additions & 10 deletions src/components/collapse/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,66 @@

#{$collapse} {
font-size: 13px;
height: 100%;

&__content--padding {
padding: 10px;
}

#{$rcCollapse} {
background-color: inherit;
display: flex;
flex-direction: column;
height: 100%;

&-anim-active {
transition: height 0.2s ease-out;
}

&-item {
border-top: 0;
border-bottom: 1px solid var(--sideBarSectionHeader-border);

&-active {
overflow: hidden;

&:not(.empty) {
flex: 1;
}

&:hover {
#{$collapse}__toolbar {
opacity: 1;
}
}
}

&:last-child {
border-bottom-color: transparent;
#{$rcCollapse}-content {
border-radius: 0 0 3px 3px;
}
}

// empty content do not unfold
&.empty {
#{$rcCollapse}-content {
height: 0;
min-height: 0;
}
}
}

&-header {
align-items: center;
border: 1px solid transparent;
cursor: pointer;
display: flex;
font-size: 11px;
font-weight: bold;
height: 22px;
outline: none;
padding: 1px 2px;
user-select: none;

&-collapsable-only {
cursor: default;
Expand All @@ -41,34 +70,48 @@
&-text {
cursor: pointer;
}

&:focus {
border-color: var(--list-focusOutline);

#{$collapse}__toolbar {
opacity: 1;
}
}
}

&-extra {
margin: 0 0 0 auto;

#{$collapse}__toolbar {
opacity: 0;
}

#{$actionBar}__label.codicon {
color: var(--activityBar-inactiveForeground);
height: inherit;
line-height: inherit;

&:hover {
color: var(--activityBar-activeBorder);
}
}
}

&-content {
// transition do not support a non-specific value like auto、100% and so on
// TODO: so set a specific value just for height transition
height: 500px;
min-height: calc(100% - 24px);
overflow: auto;

&-box {
font-size: 12px;
margin-bottom: 8px;
height: 100%;
}

&-inactive {
display: none;
}
}

// test
.samplefolder {
#{$rcCollapse}-content {
height: 600px;
height: 0;
}
}
}
Expand Down
21 changes: 12 additions & 9 deletions src/components/tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import RcTree, { TreeNode as RcTreeNode, TreeProps } from 'rc-tree';
import { Icon } from 'mo/components/icon';
import { prefixClaName, classNames } from 'mo/common/className';
import { DataNode } from 'rc-tree/lib/interface';
import { FileTypes } from 'mo/model';

type Key = number | string;
export interface ITreeNodeItemProps {
disabled?: boolean;
icon?: React.ReactNode;
Expand All @@ -19,7 +19,7 @@ export interface ITreeNodeItemProps {

export interface ITreeProps extends Partial<TreeProps> {
data?: ITreeNodeItemProps[];
onSelectFile?: (file: ITreeNodeItemProps, isUpdate?) => void;
onSelectNode?: (file: ITreeNodeItemProps, isUpdate?) => void;
renderTitle?: (
node: ITreeNodeItemProps,
index: number,
Expand All @@ -35,10 +35,10 @@ const TreeView = ({
onDropTree,
onRightClick,
renderTitle, // custom title
onSelectFile,
onSelectNode,
...restProps
}: ITreeProps) => {
const [selectedKeys, setKeys] = React.useState<Key[]>([]);
const [selectedKeys, setKeys] = React.useState<React.Key[]>([]);
const treeRef = React.useRef<RcTree>(null);

const onDrop = (info) => {
Expand Down Expand Up @@ -110,13 +110,18 @@ const TreeView = ({
key = id || `${index}_${indent}`,
icon,
children,
isLeaf: itemIsLeaf,
} = item;
const isLeaf = !item.children?.length;
const isLeaf =
typeof itemIsLeaf === 'boolean'
? itemIsLeaf
: item.fileType === FileTypes.File;
const IconComponent =
typeof icon === 'string' ? <Icon type={icon} /> : icon;
return (
/**
* TODO: antd TreeNode 目前强依赖于 Tree,不好抽离,后续还不支持的话,考虑重写..
* TODO: 由于依赖 rc-tree,无法针对具体的 div 元素添加 tabindex,从而无法做 :focus 的样式
* https://github.com/ant-design/ant-design/issues/4688
* https://github.com/ant-design/ant-design/issues/4853
*/
Expand All @@ -140,10 +145,7 @@ const TreeView = ({
// always select current click node
const currentNodeKey = [node.key];
setKeys(currentNodeKey);
if (node.isLeaf) {
// only leaf node can trigger onselect event
onSelectFile?.(node.data);
} else {
if (!node.isLeaf) {
const expanded = treeRef.current?.state.expandedKeys || [];
if (expanded.includes(node.key)) {
// difference set, remove current node key from expanded collection
Expand All @@ -159,6 +161,7 @@ const TreeView = ({
);
}
}
onSelectNode?.(node.data);
};

return (
Expand Down
Loading

0 comments on commit 921ecc5

Please sign in to comment.