Skip to content

Commit 921ecc5

Browse files
authored
feat: develop explorer panels (#174)
* 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
1 parent fdb50da commit 921ecc5

File tree

29 files changed

+685
-353
lines changed

29 files changed

+685
-353
lines changed

src/common/id.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const ID_MENU_BAR = 'menuBar';
44
export const ID_SIDE_BAR = 'sidebar';
55
export const ID_EXPLORER = 'explorer';
66
export const ID_STATUS_BAR = 'statusBar';
7+
export const ID_FOLDER_TREE = 'folderTree';

src/common/logger.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ export default {
1010
error(...args) {
1111
console.error(...args);
1212
},
13+
14+
warn(...args) {
15+
console.warn(...args);
16+
},
1317
};

src/components/collapse/index.tsx

+89-61
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,125 @@
11
import * as React from 'react';
22
import { useState } from 'react';
3+
import Logger from 'mo/common/logger';
34
import RcCollapse, { Panel as CollapsePanel } from 'rc-collapse';
45
import { Toolbar } from 'mo/components/toolbar';
56
import { Icon } from 'mo/components/icon';
6-
import {
7-
prefixClaName,
8-
classNames,
9-
getBEMElement,
10-
getBEMModifier,
11-
} from 'mo/common/className';
7+
import { IActionBarItemProps } from 'mo/components/actionBar';
8+
import { prefixClaName, classNames, getBEMElement } from 'mo/common/className';
129

13-
export interface IExpandProps {
14-
isActive?: boolean;
15-
}
16-
export interface ICollapseProps<T = any> {
17-
data?: T;
10+
type RenderFunctionProps = (data: DataBaseProps) => React.ReactNode;
11+
12+
interface DataBaseProps {
13+
id: React.Key;
14+
name: string;
1815
className?: string;
19-
onCollapseChange?: (keys) => void;
20-
onCollapseToolbar?: (item) => void;
16+
hidden?: boolean;
17+
toolbar?: IActionBarItemProps[];
18+
renderPanel?: RenderFunctionProps;
19+
20+
[key: string]: any;
2121
}
2222

23-
interface IState {
24-
activePanelKeys: React.Key[];
23+
export interface ICollapseProps {
24+
data?: Partial<DataBaseProps>[];
25+
className?: string;
26+
onCollapseChange?: (keys: React.Key[]) => void;
27+
onToolbarClick?: (
28+
item: IActionBarItemProps,
29+
parentPanel: DataBaseProps
30+
) => void;
2531
}
32+
2633
const defaultCollapseClassName = prefixClaName('collapse');
27-
export const contentPaddingClassName = getBEMModifier(
28-
getBEMElement(defaultCollapseClassName, 'content'),
29-
'padding'
34+
const toolbarCollapseClassName = getBEMElement(
35+
defaultCollapseClassName,
36+
'toolbar'
3037
);
3138

32-
const initState = {
33-
activePanelKeys: [],
34-
};
35-
3639
export function Collapse(props: ICollapseProps) {
37-
const [state, setState] = useState<IState>(initState);
40+
const [activePanelKeys, setActivePanelKeys] = useState<React.Key[]>([]);
41+
3842
const {
3943
className,
4044
data = [],
4145
onCollapseChange,
42-
onCollapseToolbar,
46+
onToolbarClick,
4347
...restProps
4448
} = props;
45-
const onChangeCallback = (key: React.Key[]) => {
49+
50+
const handleChangeCallback = (key: React.Key[]) => {
4651
onCollapseChange?.(key);
47-
setState((state: IState) => ({ ...state, activePanelKeys: key }));
52+
setActivePanelKeys(key || []);
4853
};
49-
const onClick = (e, item) => {
54+
55+
const handleToolbarClick = (
56+
e: React.MouseEvent,
57+
item: IActionBarItemProps,
58+
panel: DataBaseProps
59+
) => {
5060
e.stopPropagation();
51-
onCollapseToolbar?.(item);
52-
console.log('onClick:', e, item);
61+
onToolbarClick?.(item, panel);
5362
};
54-
const render = (render) => {
63+
64+
const renderPanels = (
65+
data: DataBaseProps,
66+
render?: RenderFunctionProps
67+
) => {
5568
if (render) {
56-
return render();
57-
} else {
58-
return (
59-
<span className={contentPaddingClassName}>
60-
Cannot provide...
61-
</span>
62-
);
69+
return render(data);
6370
}
71+
return null;
6472
};
65-
const { activePanelKeys } = state;
73+
74+
const filterData = data.filter((panel) => panel.id) as DataBaseProps[];
75+
if (filterData.length < data.length) {
76+
Logger.warn(new SyntaxError('collapse data must have id'));
77+
}
78+
6679
return (
6780
<div className={classNames(defaultCollapseClassName, className)}>
6881
<RcCollapse
69-
{...restProps}
70-
onChange={(activeKeys: React.Key[]) => {
71-
onChangeCallback(activeKeys);
72-
}}
73-
expandIcon={({ isActive }: IExpandProps) => (
82+
onChange={handleChangeCallback}
83+
expandIcon={({ isActive }: { isActive: boolean }) => (
7484
<Icon type={isActive ? 'chevron-down' : 'chevron-right'} />
7585
)}
86+
{...restProps}
7687
>
77-
{data.map((panel) => (
78-
<CollapsePanel
79-
key={panel.id}
80-
header={panel.name}
81-
className={panel.className}
82-
extra={
83-
activePanelKeys?.includes(panel.id) && (
84-
<Toolbar
85-
key={panel.id}
86-
data={panel.toolbar}
87-
onClick={onClick}
88-
/>
89-
)
90-
}
91-
>
92-
{render(panel.renderPanel)}
93-
</CollapsePanel>
94-
))}
88+
{filterData
89+
.filter((p) => !p.hidden)
90+
.map((panel) => {
91+
const content = renderPanels(panel, panel.renderPanel);
92+
return (
93+
<CollapsePanel
94+
tabIndex={-1}
95+
key={panel.id}
96+
panelKey={panel.id}
97+
header={panel.name}
98+
className={classNames(
99+
panel.className,
100+
content === null && 'empty'
101+
)}
102+
extra={
103+
activePanelKeys.includes(panel.id) && (
104+
<Toolbar
105+
className={toolbarCollapseClassName}
106+
key={panel.id}
107+
data={panel.toolbar || []}
108+
onClick={(e, item) =>
109+
handleToolbarClick(
110+
e,
111+
item,
112+
panel
113+
)
114+
}
115+
/>
116+
)
117+
}
118+
>
119+
{content}
120+
</CollapsePanel>
121+
);
122+
})}
95123
</RcCollapse>
96124
</div>
97125
);

src/components/collapse/style.scss

+53-10
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,66 @@
22

33
#{$collapse} {
44
font-size: 13px;
5+
height: 100%;
56

67
&__content--padding {
78
padding: 10px;
89
}
910

1011
#{$rcCollapse} {
1112
background-color: inherit;
13+
display: flex;
14+
flex-direction: column;
15+
height: 100%;
1216

1317
&-anim-active {
1418
transition: height 0.2s ease-out;
1519
}
1620

1721
&-item {
18-
border-top: 0;
22+
border-bottom: 1px solid var(--sideBarSectionHeader-border);
23+
24+
&-active {
25+
overflow: hidden;
26+
27+
&:not(.empty) {
28+
flex: 1;
29+
}
30+
31+
&:hover {
32+
#{$collapse}__toolbar {
33+
opacity: 1;
34+
}
35+
}
36+
}
1937

2038
&:last-child {
39+
border-bottom-color: transparent;
2140
#{$rcCollapse}-content {
2241
border-radius: 0 0 3px 3px;
2342
}
2443
}
44+
45+
// empty content do not unfold
46+
&.empty {
47+
#{$rcCollapse}-content {
48+
height: 0;
49+
min-height: 0;
50+
}
51+
}
2552
}
2653

2754
&-header {
2855
align-items: center;
56+
border: 1px solid transparent;
2957
cursor: pointer;
3058
display: flex;
3159
font-size: 11px;
3260
font-weight: bold;
3361
height: 22px;
3462
outline: none;
3563
padding: 1px 2px;
64+
user-select: none;
3665

3766
&-collapsable-only {
3867
cursor: default;
@@ -41,34 +70,48 @@
4170
&-text {
4271
cursor: pointer;
4372
}
73+
74+
&:focus {
75+
border-color: var(--list-focusOutline);
76+
77+
#{$collapse}__toolbar {
78+
opacity: 1;
79+
}
80+
}
4481
}
4582

4683
&-extra {
4784
margin: 0 0 0 auto;
4885

86+
#{$collapse}__toolbar {
87+
opacity: 0;
88+
}
89+
4990
#{$actionBar}__label.codicon {
91+
color: var(--activityBar-inactiveForeground);
5092
height: inherit;
5193
line-height: inherit;
94+
95+
&:hover {
96+
color: var(--activityBar-activeBorder);
97+
}
5298
}
5399
}
54100

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

58108
&-box {
59109
font-size: 12px;
60-
margin-bottom: 8px;
110+
height: 100%;
61111
}
62112

63113
&-inactive {
64-
display: none;
65-
}
66-
}
67-
68-
// test
69-
.samplefolder {
70-
#{$rcCollapse}-content {
71-
height: 600px;
114+
height: 0;
72115
}
73116
}
74117
}

src/components/tree/index.tsx

+12-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import RcTree, { TreeNode as RcTreeNode, TreeProps } from 'rc-tree';
33
import { Icon } from 'mo/components/icon';
44
import { prefixClaName, classNames } from 'mo/common/className';
55
import { DataNode } from 'rc-tree/lib/interface';
6+
import { FileTypes } from 'mo/model';
67

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

2020
export interface ITreeProps extends Partial<TreeProps> {
2121
data?: ITreeNodeItemProps[];
22-
onSelectFile?: (file: ITreeNodeItemProps, isUpdate?) => void;
22+
onSelectNode?: (file: ITreeNodeItemProps, isUpdate?) => void;
2323
renderTitle?: (
2424
node: ITreeNodeItemProps,
2525
index: number,
@@ -35,10 +35,10 @@ const TreeView = ({
3535
onDropTree,
3636
onRightClick,
3737
renderTitle, // custom title
38-
onSelectFile,
38+
onSelectNode,
3939
...restProps
4040
}: ITreeProps) => {
41-
const [selectedKeys, setKeys] = React.useState<Key[]>([]);
41+
const [selectedKeys, setKeys] = React.useState<React.Key[]>([]);
4242
const treeRef = React.useRef<RcTree>(null);
4343

4444
const onDrop = (info) => {
@@ -110,13 +110,18 @@ const TreeView = ({
110110
key = id || `${index}_${indent}`,
111111
icon,
112112
children,
113+
isLeaf: itemIsLeaf,
113114
} = item;
114-
const isLeaf = !item.children?.length;
115+
const isLeaf =
116+
typeof itemIsLeaf === 'boolean'
117+
? itemIsLeaf
118+
: item.fileType === FileTypes.File;
115119
const IconComponent =
116120
typeof icon === 'string' ? <Icon type={icon} /> : icon;
117121
return (
118122
/**
119123
* TODO: antd TreeNode 目前强依赖于 Tree,不好抽离,后续还不支持的话,考虑重写..
124+
* TODO: 由于依赖 rc-tree,无法针对具体的 div 元素添加 tabindex,从而无法做 :focus 的样式
120125
* https://github.com/ant-design/ant-design/issues/4688
121126
* https://github.com/ant-design/ant-design/issues/4853
122127
*/
@@ -140,10 +145,7 @@ const TreeView = ({
140145
// always select current click node
141146
const currentNodeKey = [node.key];
142147
setKeys(currentNodeKey);
143-
if (node.isLeaf) {
144-
// only leaf node can trigger onselect event
145-
onSelectFile?.(node.data);
146-
} else {
148+
if (!node.isLeaf) {
147149
const expanded = treeRef.current?.state.expandedKeys || [];
148150
if (expanded.includes(node.key)) {
149151
// difference set, remove current node key from expanded collection
@@ -159,6 +161,7 @@ const TreeView = ({
159161
);
160162
}
161163
}
164+
onSelectNode?.(node.data);
162165
};
163166

164167
return (

0 commit comments

Comments
 (0)