@opensumi/ide-main-layout 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/lib/browser/accordion/accordion.service.js +1 -1
  2. package/lib/browser/accordion/accordion.service.js.map +1 -1
  3. package/lib/browser/accordion/styles.module.less +4 -2
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/layout.service.d.ts.map +1 -1
  6. package/lib/browser/layout.service.js +6 -6
  7. package/lib/browser/layout.service.js.map +1 -1
  8. package/lib/browser/main-layout.contribution.js +1 -1
  9. package/lib/browser/main-layout.contribution.js.map +1 -1
  10. package/lib/browser/quick-open-view.js +3 -2
  11. package/lib/browser/quick-open-view.js.map +1 -1
  12. package/lib/browser/tabbar/tabbar.service.js.map +1 -1
  13. package/lib/browser/tabbar-handler.js.map +1 -1
  14. package/lib/browser/views-registry.js +7 -7
  15. package/lib/browser/views-registry.js.map +1 -1
  16. package/package.json +10 -9
  17. package/src/browser/accordion/accordion.service.ts +635 -0
  18. package/src/browser/accordion/accordion.view.tsx +102 -0
  19. package/src/browser/accordion/section.view.tsx +181 -0
  20. package/src/browser/accordion/styles.module.less +214 -0
  21. package/src/browser/accordion/titlebar.view.tsx +17 -0
  22. package/src/browser/default-config.ts +35 -0
  23. package/src/browser/index.ts +40 -0
  24. package/src/browser/input/index.tsx +32 -0
  25. package/src/browser/layout.service.ts +515 -0
  26. package/src/browser/main-layout.contribution.ts +447 -0
  27. package/src/browser/quick-open-view.ts +138 -0
  28. package/src/browser/tabbar/bar.view.tsx +289 -0
  29. package/src/browser/tabbar/panel.view.tsx +190 -0
  30. package/src/browser/tabbar/renderer.view.tsx +133 -0
  31. package/src/browser/tabbar/styles.module.less +408 -0
  32. package/src/browser/tabbar/tabbar.service.ts +845 -0
  33. package/src/browser/tabbar-handler.ts +200 -0
  34. package/src/browser/views-registry.ts +157 -0
  35. package/src/browser/welcome.view.tsx +152 -0
  36. package/src/common/index.ts +2 -0
  37. package/src/common/main-layout.definition.ts +122 -0
  38. package/src/index.ts +1 -0
@@ -0,0 +1,289 @@
1
+ import clsx from 'classnames';
2
+ import { observer } from 'mobx-react-lite';
3
+ import React from 'react';
4
+
5
+ import { Badge, Icon } from '@opensumi/ide-components';
6
+ import { ComponentRegistryInfo, useInjectable, KeybindingRegistry, usePreference } from '@opensumi/ide-core-browser';
7
+ import { getIcon } from '@opensumi/ide-core-browser';
8
+ import { InlineMenuBar } from '@opensumi/ide-core-browser/lib/components/actions';
9
+ import { Layout } from '@opensumi/ide-core-browser/lib/components/layout/layout';
10
+ import { VIEW_CONTAINERS } from '@opensumi/ide-core-browser/lib/layout/view-id';
11
+ import { IProgressService } from '@opensumi/ide-core-browser/lib/progress';
12
+
13
+ import { IMainLayoutService } from '../../common';
14
+
15
+ import { TabbarConfig } from './renderer.view';
16
+ import styles from './styles.module.less';
17
+ import { TabbarService, TabbarServiceFactory } from './tabbar.service';
18
+
19
+ function splitVisibleTabs(containers: ComponentRegistryInfo[], tabSize: number, availableSize: number) {
20
+ const visibleCount = Math.floor(availableSize / tabSize);
21
+ if (visibleCount >= containers.length) {
22
+ return [containers, []];
23
+ }
24
+ if (visibleCount <= 1) {
25
+ return [[], containers];
26
+ }
27
+ return [containers.slice(0, visibleCount - 1), containers.slice(visibleCount - 1)];
28
+ }
29
+
30
+ export interface ITabbarViewProps {
31
+ TabView: React.FC<{ component: ComponentRegistryInfo }>;
32
+ forbidCollapse?: boolean;
33
+ // tabbar的尺寸(横向为宽,纵向高),tab折叠后为改尺寸加上panelBorderSize
34
+ barSize?: number;
35
+ // 包含tab的内外边距的总尺寸,用于控制溢出隐藏逻辑
36
+ tabSize: number;
37
+ MoreTabView: React.FC;
38
+ panelBorderSize?: number;
39
+ tabClassName?: string;
40
+ className?: string;
41
+ // tab上预留的位置,用来控制tab过多的显示效果
42
+ margin?: number;
43
+ canHideTabbar?: boolean;
44
+ }
45
+
46
+ export const TabbarViewBase: React.FC<ITabbarViewProps> = observer(
47
+ ({
48
+ TabView,
49
+ MoreTabView,
50
+ forbidCollapse,
51
+ barSize = 48,
52
+ panelBorderSize = 0,
53
+ tabClassName,
54
+ className,
55
+ margin,
56
+ tabSize,
57
+ canHideTabbar,
58
+ }) => {
59
+ const { side, direction, fullSize } = React.useContext(TabbarConfig);
60
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
61
+
62
+ React.useEffect(() => {
63
+ // 内部只关注总的宽度
64
+ tabbarService.barSize = barSize + panelBorderSize;
65
+ }, []);
66
+ const { currentContainerId, handleTabClick } = tabbarService;
67
+
68
+ const hideTabBarWhenHidePanel = usePreference<boolean>('workbench.hideSlotTabBarWhenHidePanel', false);
69
+
70
+ const willHideTabbar = canHideTabbar && hideTabBarWhenHidePanel;
71
+
72
+ if (willHideTabbar && !currentContainerId) {
73
+ // 之所以要用这么偏门的方法,是因为:
74
+ // 我尝试了好几种方案,比如让 tabbar 或其他几个组件返回 null 的话
75
+ // 会导致 SplitPanel 计算 children 的尺寸不正确,或者计算 tabbar 上按钮区域长度不对等等
76
+ // 最后试了这个方法一劳永逸,感觉也挺合适
77
+ tabbarService.resizeHandle?.setSize(0);
78
+ }
79
+
80
+ const [visibleContainers, hideContainers] = splitVisibleTabs(
81
+ tabbarService.visibleContainers.filter((container) => !container.options?.hideTab),
82
+ tabSize,
83
+ fullSize - (margin || 0),
84
+ );
85
+ hideContainers.forEach((componentInfo) => {
86
+ tabbarService.updateTabInMoreKey(componentInfo.options!.containerId, true);
87
+ });
88
+
89
+ return (
90
+ <div className={clsx([styles.tab_bar, className])}>
91
+ <div className={styles.bar_content} style={{ flexDirection: Layout.getTabbarDirection(direction) }}>
92
+ {visibleContainers.map((component) => {
93
+ const containerId = component.options?.containerId;
94
+ if (!containerId) {
95
+ return null;
96
+ }
97
+ tabbarService.updateTabInMoreKey(containerId, false);
98
+ let ref: HTMLLIElement | null;
99
+ return (
100
+ <li
101
+ draggable={true}
102
+ onDragStart={(e) => {
103
+ if (ref) {
104
+ const dragImage = ref.cloneNode(true) as HTMLLIElement;
105
+ dragImage.classList.add(styles.dragging);
106
+ if (tabClassName) {
107
+ dragImage.classList.add(tabClassName);
108
+ }
109
+ document.body.appendChild(dragImage);
110
+ e.persist();
111
+ requestAnimationFrame(() => {
112
+ e.dataTransfer.setDragImage(dragImage, 0, 0);
113
+ document.body.removeChild(dragImage);
114
+ });
115
+ }
116
+ tabbarService.handleDragStart(e, containerId);
117
+ }}
118
+ onDragOver={(e) => {
119
+ e.preventDefault();
120
+ if (ref) {
121
+ ref.classList.add('on-drag-over');
122
+ }
123
+ }}
124
+ onDragLeave={() => {
125
+ if (ref) {
126
+ ref.classList.remove('on-drag-over');
127
+ }
128
+ }}
129
+ onDrop={(e) => {
130
+ if (ref) {
131
+ ref.classList.remove('on-drag-over');
132
+ }
133
+ tabbarService.handleDrop(e, containerId);
134
+ }}
135
+ key={containerId}
136
+ id={containerId}
137
+ onContextMenu={(e) => tabbarService.handleContextMenu(e, containerId)}
138
+ // 如果设置了可隐藏 Tabbar,那么就不允许点击 tab 时隐藏整个 panel 了 通过设置 forbidCollapse 来阻止这个动作
139
+ onClick={(e) => handleTabClick(e, willHideTabbar || forbidCollapse)}
140
+ ref={(el) => (ref = el)}
141
+ className={clsx({ active: currentContainerId === containerId }, tabClassName)}
142
+ >
143
+ <TabView component={component} />
144
+ </li>
145
+ );
146
+ })}
147
+ {hideContainers.length ? (
148
+ <li
149
+ key='tab-more'
150
+ onClick={(e) =>
151
+ tabbarService.showMoreMenu(
152
+ e,
153
+ visibleContainers[visibleContainers.length - 1] &&
154
+ visibleContainers[visibleContainers.length - 1].options?.containerId,
155
+ )
156
+ }
157
+ className={tabClassName}
158
+ >
159
+ <MoreTabView />
160
+ </li>
161
+ ) : null}
162
+ </div>
163
+ </div>
164
+ );
165
+ },
166
+ );
167
+
168
+ export const IconTabView: React.FC<{ component: ComponentRegistryInfo }> = observer(({ component }) => {
169
+ const progressService: IProgressService = useInjectable(IProgressService);
170
+ const keybindingRegistry: KeybindingRegistry = useInjectable(KeybindingRegistry);
171
+ const inProgress = progressService.getIndicator(component.options?.containerId || '')?.progressModel.show;
172
+
173
+ const title = React.useMemo(() => {
174
+ const options = component.options;
175
+ if (options?.activateKeyBinding) {
176
+ return `${options?.title} (${keybindingRegistry.acceleratorForKeyString(options.activateKeyBinding, '+')})`;
177
+ }
178
+ return options?.title;
179
+ }, [component]);
180
+
181
+ return (
182
+ <div className={styles.icon_tab}>
183
+ <div className={clsx(component.options?.iconClass, 'activity-icon')} title={title}></div>
184
+ {inProgress ? (
185
+ <Badge className={styles.tab_badge}>
186
+ <span className={styles.icon_wrapper}>
187
+ <Icon icon='time-circle' />
188
+ </span>
189
+ </Badge>
190
+ ) : (
191
+ component.options?.badge && <Badge className={styles.tab_badge}>{component.options?.badge}</Badge>
192
+ )}
193
+ </div>
194
+ );
195
+ });
196
+
197
+ export const TextTabView: React.FC<{ component: ComponentRegistryInfo }> = observer(({ component }) => (
198
+ <div className={styles.text_tab}>
199
+ <div className={styles.bottom_tab_title}>{component.options?.title?.toUpperCase()}</div>
200
+ {component.options?.badge && <Badge className={styles.tab_badge}>{component.options?.badge}</Badge>}
201
+ </div>
202
+ ));
203
+
204
+ export const IconElipses: React.FC = () => (
205
+ <div className={styles.icon_tab}>
206
+ {/* i18n */}
207
+ <div className={clsx(getIcon('ellipsis'), 'activity-icon')} title='extra tabs'></div>
208
+ </div>
209
+ );
210
+
211
+ export const TextElipses: React.FC = () => (
212
+ <div className={styles.text_tab}>
213
+ <div className={styles.bottom_tab_title}>
214
+ <i className={getIcon('doubleright')}></i>
215
+ </div>
216
+ </div>
217
+ );
218
+
219
+ export const RightTabbarRenderer: React.FC = () => {
220
+ const { side } = React.useContext(TabbarConfig);
221
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
222
+ return (
223
+ <div
224
+ id={VIEW_CONTAINERS.RIGHT_TABBAR}
225
+ className={styles.right_tab_bar}
226
+ onContextMenu={tabbarService.handleContextMenu}
227
+ >
228
+ <TabbarViewBase
229
+ tabSize={48}
230
+ MoreTabView={IconElipses}
231
+ tabClassName={styles.right_tab}
232
+ TabView={IconTabView}
233
+ barSize={48}
234
+ panelBorderSize={1}
235
+ />
236
+ </div>
237
+ );
238
+ };
239
+
240
+ export const LeftTabbarRenderer: React.FC = () => {
241
+ const { side } = React.useContext(TabbarConfig);
242
+ const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
243
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
244
+
245
+ const leftBarInlineMenus = React.useMemo(() => layoutService.getExtraMenu(), [layoutService]);
246
+
247
+ return (
248
+ <div
249
+ id={VIEW_CONTAINERS.LEFT_TABBAR}
250
+ className={styles.left_tab_bar}
251
+ onContextMenu={tabbarService.handleContextMenu}
252
+ >
253
+ <TabbarViewBase
254
+ tabSize={48}
255
+ MoreTabView={IconElipses}
256
+ className={styles.left_tab_content}
257
+ tabClassName={styles.left_tab}
258
+ TabView={IconTabView}
259
+ barSize={48}
260
+ margin={90}
261
+ panelBorderSize={1}
262
+ />
263
+ <InlineMenuBar className={styles.vertical_icons} menus={leftBarInlineMenus} />
264
+ </div>
265
+ );
266
+ };
267
+
268
+ export const BottomTabbarRenderer: React.FC = () => {
269
+ const { side } = React.useContext(TabbarConfig);
270
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
271
+ return (
272
+ <div
273
+ id={VIEW_CONTAINERS.BOTTOM_TABBAR}
274
+ onContextMenu={tabbarService.handleContextMenu}
275
+ className={clsx(styles.bottom_bar_container, 'next_bottom_bar')}
276
+ >
277
+ <TabbarViewBase
278
+ // TODO: 暂时通过预估值来计算是否超出可视范围,实际上需要通过dom尺寸的计算
279
+ tabSize={80}
280
+ MoreTabView={TextElipses}
281
+ tabClassName={styles.bottom_tab}
282
+ TabView={TextTabView}
283
+ barSize={24}
284
+ panelBorderSize={1}
285
+ canHideTabbar
286
+ />
287
+ </div>
288
+ );
289
+ };
@@ -0,0 +1,190 @@
1
+ import clsx from 'classnames';
2
+ import { observer } from 'mobx-react-lite';
3
+ import React from 'react';
4
+
5
+ import { INJECTOR_TOKEN, Injector } from '@opensumi/di';
6
+ import {
7
+ ComponentRegistryInfo,
8
+ useInjectable,
9
+ ComponentRenderer,
10
+ ConfigProvider,
11
+ AppConfig,
12
+ ErrorBoundary,
13
+ useViewState,
14
+ } from '@opensumi/ide-core-browser';
15
+ import { InlineActionBar, InlineMenuBar } from '@opensumi/ide-core-browser/lib/components/actions';
16
+ import { LAYOUT_VIEW_SIZE } from '@opensumi/ide-core-browser/lib/layout/constants';
17
+ import { IMenu } from '@opensumi/ide-core-browser/lib/menu/next';
18
+ import { IProgressService } from '@opensumi/ide-core-browser/lib/progress';
19
+ import { ProgressBar } from '@opensumi/ide-core-browser/lib/progress/progress-bar';
20
+
21
+ import { AccordionServiceFactory, AccordionService } from '../accordion/accordion.service';
22
+ import { AccordionContainer } from '../accordion/accordion.view';
23
+ import { TitleBar } from '../accordion/titlebar.view';
24
+
25
+ import { TabbarConfig } from './renderer.view';
26
+ import styles from './styles.module.less';
27
+ import { TabbarService, TabbarServiceFactory } from './tabbar.service';
28
+
29
+ const NoUpdateBoundary: React.FC<{ visible: boolean; children: React.ReactElement }> = React.memo(
30
+ ({ children }) => children,
31
+ (prev, next) => !(prev.visible || next.visible),
32
+ );
33
+
34
+ const panelVisible = { zIndex: 1, display: 'block' };
35
+ const panelInVisible = { zIndex: -1, display: 'none' };
36
+
37
+ export interface IBaseTabPanelView {
38
+ PanelView: React.FC<{ component: ComponentRegistryInfo; side: string; titleMenu: IMenu }>;
39
+ // tabPanel的尺寸(横向为宽,纵向高)
40
+ id?: string;
41
+ panelSize?: number;
42
+ }
43
+
44
+ export const BaseTabPanelView: React.FC<IBaseTabPanelView> = observer(({ PanelView, panelSize, id }) => {
45
+ const { side } = React.useContext(TabbarConfig);
46
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
47
+ const appConfig: AppConfig = useInjectable(AppConfig);
48
+ const customPanelSize = appConfig.panelSizes && appConfig.panelSizes[side];
49
+ const { currentContainerId } = tabbarService;
50
+
51
+ React.useEffect(() => {
52
+ // panelSize = 384-1-48
53
+ tabbarService.panelSize = customPanelSize || panelSize || 335;
54
+ }, []);
55
+
56
+ return (
57
+ <div
58
+ id={id}
59
+ className={clsx(styles.tab_panel, {
60
+ [styles.tab_panel_hidden]: !currentContainerId || currentContainerId === '',
61
+ })}
62
+ >
63
+ {tabbarService.visibleContainers.map((component) => {
64
+ const containerId = component.options?.containerId;
65
+ if (!containerId) {
66
+ return null;
67
+ }
68
+ const titleMenu = tabbarService.getTitleToolbarMenu(containerId);
69
+ return (
70
+ <div
71
+ key={containerId}
72
+ className={clsx(styles.panel_wrap, containerId) /* @deprecated: query by data-viewlet-id */}
73
+ data-viewlet-id={containerId}
74
+ style={currentContainerId === containerId ? panelVisible : panelInVisible}
75
+ id={id}
76
+ >
77
+ <ErrorBoundary>
78
+ <NoUpdateBoundary visible={currentContainerId === containerId}>
79
+ <PanelView titleMenu={titleMenu} side={side} component={component} />
80
+ </NoUpdateBoundary>
81
+ </ErrorBoundary>
82
+ </div>
83
+ );
84
+ })}
85
+ </div>
86
+ );
87
+ });
88
+
89
+ const ContainerView: React.FC<{
90
+ component: ComponentRegistryInfo;
91
+ side: string;
92
+ titleMenu: IMenu;
93
+ }> = ({ component, titleMenu, side }) => {
94
+ const ref = React.useRef<HTMLElement | null>();
95
+ const containerRef = React.useRef<HTMLDivElement | null>(null);
96
+ const configContext = useInjectable<AppConfig>(AppConfig);
97
+ const { title, titleComponent, component: CustomComponent, containerId } = component.options || {};
98
+ const injector: Injector = useInjectable(INJECTOR_TOKEN);
99
+ const handleContextMenu = (e: React.MouseEvent) => {
100
+ const accordionService: AccordionService = injector.get(AccordionServiceFactory)(containerId);
101
+ accordionService.handleContextMenu(e);
102
+ };
103
+ if (!containerId) {
104
+ return null;
105
+ }
106
+ const progressService: IProgressService = useInjectable(IProgressService);
107
+ const indicator = progressService.getIndicator(containerId);
108
+ if (!indicator) {
109
+ return null;
110
+ }
111
+ const viewState = useViewState(side, containerRef);
112
+
113
+ return (
114
+ <div ref={containerRef} className={styles.view_container}>
115
+ {!CustomComponent && (
116
+ <div onContextMenu={handleContextMenu} className={styles.panel_titlebar}>
117
+ {!title ? null : <TitleBar title={title} menubar={<InlineActionBar menus={titleMenu} />} />}
118
+ {titleComponent && (
119
+ <div className={styles.panel_component}>
120
+ <ConfigProvider value={configContext}>
121
+ <ComponentRenderer Component={titleComponent} initialProps={component.options?.titleProps} />
122
+ </ConfigProvider>
123
+ </div>
124
+ )}
125
+ </div>
126
+ )}
127
+ <div className={styles.container_wrap} ref={(ele) => (ref.current = ele)}>
128
+ <ProgressBar progressModel={indicator.progressModel} />
129
+ {CustomComponent ? (
130
+ <ConfigProvider value={configContext}>
131
+ <ComponentRenderer
132
+ initialProps={{ viewState, ...component.options?.initialProps }}
133
+ Component={CustomComponent}
134
+ />
135
+ </ConfigProvider>
136
+ ) : (
137
+ <AccordionContainer
138
+ views={component.views}
139
+ minSize={component.options!.miniSize}
140
+ containerId={component.options!.containerId}
141
+ />
142
+ )}
143
+ </div>
144
+ </div>
145
+ );
146
+ };
147
+
148
+ const BottomPanelView: React.FC<{
149
+ component: ComponentRegistryInfo;
150
+ side: string;
151
+ titleMenu: IMenu;
152
+ }> = ({ component, titleMenu, side }) => {
153
+ const contentRef = React.useRef<HTMLDivElement | null>();
154
+ const titleComponent = component.options && component.options.titleComponent;
155
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
156
+ // 注入自定义视图 or 通过views注入视图
157
+ const progressService: IProgressService = useInjectable(IProgressService);
158
+ const indicator = progressService.getIndicator(component.options!.containerId)!;
159
+ const viewState = useViewState(side, contentRef);
160
+
161
+ return (
162
+ <div className={styles.panel_container}>
163
+ <div className={styles.panel_title_bar} style={{ height: LAYOUT_VIEW_SIZE.PANEL_TITLEBAR_HEIGHT }}>
164
+ <h1>{component.options?.title?.toUpperCase()}</h1>
165
+ <div className={styles.title_component_container}>
166
+ {titleComponent && (
167
+ <ComponentRenderer Component={titleComponent} initialProps={component.options?.titleProps} />
168
+ )}
169
+ </div>
170
+ <div className={styles.panel_toolbar_container}>
171
+ {titleMenu && <InlineActionBar menus={titleMenu} />}
172
+ <InlineMenuBar menus={tabbarService.commonTitleMenu} moreAtFirst />
173
+ </div>
174
+ </div>
175
+ <div ref={(ele) => (contentRef.current = ele)} className={styles.panel_wrapper}>
176
+ <ProgressBar progressModel={indicator.progressModel} />
177
+ <ComponentRenderer
178
+ initialProps={{ viewState, ...component.options?.initialProps }}
179
+ Component={component.options?.component ? component.options.component : component.views[0].component!}
180
+ />
181
+ </div>
182
+ </div>
183
+ );
184
+ };
185
+
186
+ export const RightTabPanelRenderer: React.FC = () => <BaseTabPanelView PanelView={ContainerView} />;
187
+
188
+ export const LeftTabPanelRenderer: React.FC = () => <BaseTabPanelView PanelView={ContainerView} />;
189
+
190
+ export const BottomTabPanelRenderer: React.FC = () => <BaseTabPanelView PanelView={BottomPanelView} panelSize={280} />;
@@ -0,0 +1,133 @@
1
+ import clsx from 'classnames';
2
+ import React from 'react';
3
+
4
+ import { ComponentRegistryInfo, useInjectable, IEventBus, ResizeEvent } from '@opensumi/ide-core-browser';
5
+ import { PanelContext } from '@opensumi/ide-core-browser/lib/components';
6
+ import { Layout } from '@opensumi/ide-core-browser/lib/components/layout/layout';
7
+ import { VIEW_CONTAINERS } from '@opensumi/ide-core-browser/lib/layout/view-id';
8
+
9
+ import { RightTabbarRenderer, LeftTabbarRenderer, BottomTabbarRenderer } from './bar.view';
10
+ import { RightTabPanelRenderer, LeftTabPanelRenderer, BottomTabPanelRenderer } from './panel.view';
11
+ import styles from './styles.module.less';
12
+ import { TabbarServiceFactory, TabbarService } from './tabbar.service';
13
+
14
+ export const TabbarConfig = React.createContext<{
15
+ side: string;
16
+ direction: Layout.direction;
17
+ fullSize: number;
18
+ }>({
19
+ side: 'left',
20
+ direction: 'left-to-right',
21
+ fullSize: 0,
22
+ });
23
+
24
+ export const TabRendererBase: React.FC<{
25
+ side: string;
26
+ id?: string;
27
+ className?: string;
28
+ components: ComponentRegistryInfo[];
29
+ direction?: Layout.direction;
30
+ TabbarView: React.FC;
31
+ TabpanelView: React.FC;
32
+ // @deprecated
33
+ noAccordion?: boolean;
34
+ }> = ({ id, className, components, direction = 'left-to-right', TabbarView, side, TabpanelView }) => {
35
+ const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(side);
36
+ const eventBus = useInjectable<IEventBus>(IEventBus);
37
+ const resizeHandle = React.useContext(PanelContext);
38
+ const rootRef = React.useRef<HTMLDivElement>(null);
39
+ const [fullSize, setFullSize] = React.useState(0);
40
+ React.useLayoutEffect(() => {
41
+ tabbarService.registerResizeHandle(resizeHandle);
42
+ components.forEach((component) => {
43
+ tabbarService.registerContainer(component.options!.containerId, component);
44
+ });
45
+ tabbarService.updatePanelVisibility();
46
+ tabbarService.viewReady.resolve();
47
+ }, []);
48
+ React.useEffect(() => {
49
+ if (rootRef.current) {
50
+ setFullSize(rootRef.current[Layout.getDomSizeProperty(direction)]);
51
+ let lastFrame: number | null;
52
+ eventBus.on(ResizeEvent, (e) => {
53
+ if (e.payload.slotLocation === side) {
54
+ if (lastFrame) {
55
+ window.cancelAnimationFrame(lastFrame);
56
+ }
57
+ lastFrame = window.requestAnimationFrame(() => {
58
+ setFullSize(rootRef.current![Layout.getDomSizeProperty(direction)]);
59
+ });
60
+ }
61
+ });
62
+ }
63
+ }, []);
64
+
65
+ return (
66
+ <div
67
+ ref={rootRef}
68
+ id={id}
69
+ className={clsx(styles.tab_container, className)}
70
+ style={{ flexDirection: Layout.getFlexDirection(direction) }}
71
+ >
72
+ <TabbarConfig.Provider value={{ side, direction, fullSize }}>
73
+ <TabbarView />
74
+ <TabpanelView />
75
+ </TabbarConfig.Provider>
76
+ </div>
77
+ );
78
+ };
79
+
80
+ export const RightTabRenderer = ({
81
+ className,
82
+ components,
83
+ }: {
84
+ className: string;
85
+ components: ComponentRegistryInfo[];
86
+ }) => (
87
+ <TabRendererBase
88
+ side='right'
89
+ direction='right-to-left'
90
+ id={VIEW_CONTAINERS.RIGHT_TABBAR}
91
+ className={clsx(className, 'right-slot')}
92
+ components={components}
93
+ TabbarView={RightTabbarRenderer}
94
+ TabpanelView={RightTabPanelRenderer}
95
+ />
96
+ );
97
+
98
+ export const LeftTabRenderer = ({
99
+ className,
100
+ components,
101
+ }: {
102
+ className: string;
103
+ components: ComponentRegistryInfo[];
104
+ }) => (
105
+ <TabRendererBase
106
+ side='left'
107
+ direction='left-to-right'
108
+ id={VIEW_CONTAINERS.LEFT_TABBAR_PANEL}
109
+ className={clsx(className, 'left-slot')}
110
+ components={components}
111
+ TabbarView={LeftTabbarRenderer}
112
+ TabpanelView={LeftTabPanelRenderer}
113
+ />
114
+ );
115
+
116
+ export const BottomTabRenderer = ({
117
+ className,
118
+ components,
119
+ }: {
120
+ className: string;
121
+ components: ComponentRegistryInfo[];
122
+ }) => (
123
+ <TabRendererBase
124
+ side='bottom'
125
+ id={VIEW_CONTAINERS.BOTTOM_TABBAR_PANEL}
126
+ direction='bottom-to-top'
127
+ className={clsx(className, 'bottom-slot')}
128
+ components={components}
129
+ TabbarView={BottomTabbarRenderer}
130
+ TabpanelView={BottomTabPanelRenderer}
131
+ noAccordion={true}
132
+ />
133
+ );