Skip to content

Commit f05c506

Browse files
committed
feat: add first version of tabComponent
1 parent 9acdbde commit f05c506

File tree

10 files changed

+450
-50
lines changed

10 files changed

+450
-50
lines changed

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "dist/index.js",
66
"scripts": {
77
"test": "jest --coverage",
8-
"dev": "tsc --skipLibCheck && start-storybook -s ./public -p 6006",
8+
"dev": "start-storybook -s ./public -p 6006",
99
"build-storybook": "build-storybook",
1010
"check-types": "tsc --skipLibCheck",
1111
"build": "tsc --project tsconfig.build.json",
@@ -27,11 +27,15 @@
2727
"@types/react": "^16.9.35",
2828
"@types/react-dom": "^16.9.9",
2929
"dt-utils": "^1.0.1",
30+
"immutability-helper": "^3.1.1",
3031
"loadsh": "^0.0.4",
3132
"monaco-editor": "^0.21.2",
3233
"rc-collapse": "^2.0.1",
3334
"rc-tree": "^3.10.0",
3435
"react": "^16.13.1",
36+
"react-dnd": "^9.3.4",
37+
"react-dnd-html5-backend": "^9.3.4",
38+
"rc-tabs": "11.7.0",
3539
"react-dom": "^16.13.1",
3640
"react-split-pane": "^0.1.92",
3741
"reflect-metadata": "^0.1.13",

src/components/tabs/Tab.tsx

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as React from 'react'
2+
import { useRef } from 'react';
3+
import { findDOMNode } from 'react-dom';
4+
import { DragSourceMonitor, DropTargetMonitor, useDrag, useDrop } from 'react-dnd';
5+
6+
interface ITabProps {
7+
active?: string;
8+
content?: React.ReactNode;
9+
index?: number;
10+
id?: number | string;
11+
name?: any;
12+
moveTab: (dragIndex?: number, hoverIndex?: number | string) => void;
13+
}
14+
15+
const WrapTabNode: React.FC<ITabProps> = (props) => {
16+
const { id, index, moveTab, children } = props
17+
const ref = useRef<HTMLDivElement>(null)
18+
19+
const [, drag] = useDrag({
20+
collect: (monitor: DragSourceMonitor) => ({
21+
isDragging: monitor.isDragging(),
22+
}),
23+
item: { type: 'DND_NODE', id, index }
24+
});
25+
26+
const [, drop] = useDrop({
27+
accept: 'DND_NODE',
28+
hover (item: { type: string; index: number }, monitor: DropTargetMonitor) {
29+
debugger
30+
if (!ref.current) return
31+
let hoverIndex
32+
const component = ref.current
33+
const dragIndex = monitor.getItem().index
34+
hoverIndex = index
35+
// Don't replace items with themselves
36+
if (dragIndex === hoverIndex) {
37+
return;
38+
}
39+
const hoverBoundingRect = (findDOMNode(component) as Element)?.getBoundingClientRect();
40+
const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
41+
const clientOffset = monitor.getClientOffset();
42+
const hoverClientX = (clientOffset as { x: number; y: number; }).x - hoverBoundingRect.left;
43+
// 往下拖动
44+
if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) {
45+
return;
46+
}
47+
// 往上拖动
48+
if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) {
49+
return;
50+
}
51+
moveTab(dragIndex, hoverIndex);
52+
monitor.getItem().index = hoverIndex;
53+
}
54+
})
55+
drag(drop(ref))
56+
57+
return (
58+
<div ref={ref} >
59+
{children}
60+
</div>
61+
)
62+
}
63+
64+
export default WrapTabNode
65+

src/components/tabs/index.tsx

+60-18
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,75 @@
11
import * as React from 'react';
2+
import { useCallback } from 'react'
3+
import update from 'immutability-helper';
4+
import { DndProvider } from 'react-dnd';
5+
import HTML5Backend from 'react-dnd-html5-backend';
26

7+
import Tabs, { TabPane } from 'rc-tabs';
8+
9+
import WrapTabNode from './Tab';
310
import { prefixClaName } from 'mo/common/className';
11+
import './style.scss'
412

5-
export interface ITab<T = any, K = any> {
13+
export interface ITab {
14+
active?: string;
615
id?: number;
716
name?: string;
817
mode?: string;
9-
data?: T;
10-
options?: K;
18+
data?: [];
1119
value?: string;
12-
renderPane?: () => React.ReactElement;
20+
renderPane?: string | React.ReactNode;
1321
}
14-
1522
interface ITabsProps {
1623
data: ITab[];
17-
onClose?: (item: ITab, index: number) => void;
24+
closeTab?: (index: number) => void;
25+
changeTab?: (tabs: ITab[]) => void;
26+
selectTab: (index: number) => void;
27+
children: React.ReactNode | JSX.Element
1828
}
1929

20-
const Tabs: React.FunctionComponent<ITabsProps> = (props: ITabsProps) => {
21-
const { data, onClose } = props;
22-
const tabs = data.map((tab: ITab, index: number) => {
23-
return (
24-
<a key={tab.id}>
25-
{tab.name}{' '}
26-
<button onClick={(e) => onClose!(tab, index)}>Close</button>
27-
</a>
28-
);
29-
});
30-
return <div className={prefixClaName('tabs')}>{tabs}</div>;
30+
const DraggleTabs: React.FC<ITabsProps> = (props: ITabsProps) => {
31+
32+
const { data, changeTab, selectTab } = props;
33+
34+
const moveTab = useCallback((dragIndex, hoverIndex) => {
35+
const dragTab = data[dragIndex]
36+
changeTab?.(update(data, {
37+
$splice: [[dragIndex, 1], [ hoverIndex, 0, dragTab]],
38+
}))
39+
}, [data])
40+
41+
const onTabClick = key => {
42+
console.log(`onTabClick ${key}`)
43+
selectTab(key)
44+
}
45+
46+
const renderTabBar = (props, DefaultTabBar) => {
47+
return ( <DefaultTabBar {...props}>
48+
{node => {
49+
return (<WrapTabNode key={node.key} index={node.key} moveTab={moveTab}>{node}
50+
</WrapTabNode>)
51+
}}
52+
</DefaultTabBar>
53+
)
54+
}
55+
56+
return (
57+
<div className={prefixClaName('tabs-container')}>
58+
<DndProvider backend={HTML5Backend}>
59+
<Tabs
60+
renderTabBar={renderTabBar}
61+
onChange={onTabClick}
62+
editable={{ showAdd: false, onEdit: () => { console.log(1)} }}
63+
>
64+
{data?.map(({ active, id, name }: ITab, index) => {
65+
return (
66+
<TabPane tab={`${name}`} key={index}/>
67+
)
68+
})}
69+
</Tabs>
70+
</DndProvider>
71+
</div>
72+
);
3173
};
3274

33-
export default Tabs;
75+
export default DraggleTabs

src/components/tabs/style.scss

+76-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,77 @@
1-
.scroll {
2-
pointer-events: none;
3-
width: 10px;
1+
.mo-tabs-container {
2+
display: flex;
3+
overflow: hidden;
4+
font-size: 14px;
5+
box-sizing: border-box;
6+
.rc-tabs-nav {
7+
display: flex;
8+
align-items: center;
9+
.rc-tabs-nav-wrap {
10+
position: relative;
11+
display: inline-block;
12+
display: -webkit-box;
13+
display: -ms-flexbox;
14+
display: flex;
15+
-webkit-box-flex: 1;
16+
-ms-flex: auto;
17+
flex: auto;
18+
-ms-flex-item-align: stretch;
19+
align-self: stretch;
20+
overflow: hidden;
21+
white-space: nowrap;
22+
}
23+
.rc-tabs-nav-operations {
24+
display: -webkit-box;
25+
display: -ms-flexbox;
26+
display: flex;
27+
-ms-flex-item-align: stretch;
28+
align-self: stretch;
29+
}
30+
}
31+
}
32+
.rc-tabs-nav-list {
33+
height: 35px;
34+
background-color: #001f27;
35+
border-color: #002b36;
36+
display: flex;
37+
flex-flow: row nowrap;
38+
justify-content: flex-start;
39+
align-items: center;
40+
overflow: hidden;
41+
}
42+
43+
.rc-tabs-tab {
44+
position: relative;
45+
display: -webkit-inline-box;
46+
display: -ms-inline-flexbox;
47+
display: inline-flex;
48+
-webkit-box-align: center;
49+
-ms-flex-align: center;
50+
align-items: center;
51+
margin: 0 32px 0 0;
52+
padding: 12px 0;
53+
font-size: 14px;
54+
background: transparent;
55+
border: 0;
56+
outline: none;
57+
cursor: pointer;
58+
&:hover {
59+
.rc-tabs-tab-remove {
60+
visibility: visible;
61+
}
62+
}
63+
}
64+
.rc-tabs-tab-btn {
65+
outline: none;
66+
}
67+
.rc-tabs-tab-remove {
68+
margin-right: -4px;
69+
margin-left: 8px;
70+
color: #999999;
71+
font-size: 16px;
72+
background: transparent;
73+
border: none;
74+
outline: none;
75+
cursor: pointer;
76+
visibility: hidden;
477
}

src/extensions/explore/explore.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const Explorer: React.FunctionComponent<IExplorerProps> = (
2121
const id = Math.random() * 10 + 1;
2222
const tabData = {
2323
id: id,
24-
name: 'test-tab1',
24+
name: `test-tab${id.toFixed(2)}`,
2525
value: 'just test tab data',
2626
};
2727
console.log('open editor:', tabData);

src/extensions/explore/index.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { activityBarService, IActivityBarItem, sidebarService } from 'mo';
2+
import { activityBarService, IActivityBarItem, sidebarService, editorService } from 'mo';
33

44
import { Explorer } from './explore';
55
import { ExtensionService } from 'mo/services/extensionService';
@@ -19,7 +19,12 @@ function init(extensionCtx: ExtensionService) {
1919
selected: exploreActiveItem.id,
2020
data: [...state.data, exploreActiveItem],
2121
});
22-
22+
editorService.changeTab((data ) => {
23+
console.log(data)
24+
})
25+
editorService.selectTab(tab =>{
26+
console.log(`selected tabs${tab}`)
27+
})
2328
const explorePane = {
2429
id: 'explore',
2530
title: 'EXPLORER',

src/model/workbench/editor.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
/* eslint-disable no-invalid-this */
2+
import 'reflect-metadata';
3+
import { EventBus } from 'mo/common/event';
24
import { observable } from 'mo/common/observable';
35
import { ITab } from 'mo/components/tabs';
46
import { container, inject, injectable } from 'tsyringe';
57

8+
export enum EditorEvent {
9+
CloseTab = 'editor.close',
10+
ChangeTab = 'editor.changeTab',
11+
OpenTab = 'editor.openTab',
12+
SelectTab = 'editor.selectTab'
13+
}
614
export interface IEditor {
715
current: IEditorGroup | undefined;
816
groups: IEditorGroup[];
917
closeAll?: () => void;
1018
onClose?: () => void;
1119
render?: () => React.ReactNode;
20+
changeTab: (tabs: ITab[], group?: number) => void;
21+
selectTab: (tab: ITab) => void;
1222
}
1323

1424
export interface IEditorGroup<E = any> {
@@ -64,6 +74,16 @@ export class EditorModel implements IEditor {
6474
}
6575

6676
public render!: () => React.ReactNode;
77+
78+
public readonly selectTab = (tab: ITab) => {
79+
EventBus.emit(EditorEvent.ChangeTab, tab);
80+
}
81+
public readonly changeTab = (
82+
updateTabs: ITab[],
83+
groupId?: number
84+
) => {
85+
EventBus.emit(EditorEvent.ChangeTab, updateTabs, groupId);
86+
}
6787
}
6888

6989
container.register('CurrentEditorGroup', { useValue: '' });

0 commit comments

Comments
 (0)