@workbench-kit/react 0.0.1-prototype.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 (51) hide show
  1. package/package.json +62 -0
  2. package/src/index.ts +43 -0
  3. package/src/layout/Panel.tsx +27 -0
  4. package/src/layout/SideBarViewFrame.tsx +328 -0
  5. package/src/modal/ConfirmDialog.tsx +53 -0
  6. package/src/modal/Modal.tsx +110 -0
  7. package/src/overlay/ContextMenu.tsx +145 -0
  8. package/src/primitives/Badge.tsx +12 -0
  9. package/src/primitives/Button.tsx +14 -0
  10. package/src/primitives/Checkbox.tsx +15 -0
  11. package/src/primitives/EmptyState.tsx +26 -0
  12. package/src/primitives/Field.tsx +38 -0
  13. package/src/primitives/IconButton.tsx +32 -0
  14. package/src/primitives/Select.tsx +12 -0
  15. package/src/primitives/TextInput.tsx +24 -0
  16. package/src/primitives/Toolbar.tsx +8 -0
  17. package/src/styles.css +1762 -0
  18. package/src/utils/cx.ts +3 -0
  19. package/src/workbench/ActivityBar.tsx +52 -0
  20. package/src/workbench/SplitView.tsx +136 -0
  21. package/src/workbench/StatusBar.tsx +123 -0
  22. package/src/workbench/WorkbenchShell.tsx +70 -0
  23. package/src/workbench/WorkbenchStandaloneShell.tsx +291 -0
  24. package/src/workbench/chat/ChatComposer.tsx +148 -0
  25. package/src/workbench/chat/ChatMessageItem.tsx +35 -0
  26. package/src/workbench/chat/ChatMessageList.tsx +49 -0
  27. package/src/workbench/chat/ChatPanel.tsx +55 -0
  28. package/src/workbench/chat/index.ts +9 -0
  29. package/src/workbench/chat/types.ts +10 -0
  30. package/src/workbench/commands.ts +490 -0
  31. package/src/workbench/index.ts +98 -0
  32. package/src/workbench/settings/WorkbenchSettingsModal.tsx +188 -0
  33. package/src/workbench/settings/WorkbenchSettingsNav.tsx +41 -0
  34. package/src/workbench/settings/WorkbenchSettingsSection.tsx +30 -0
  35. package/src/workbench/settings/index.ts +7 -0
  36. package/src/workbench/settings/types.ts +16 -0
  37. package/src/workbench/shellState.ts +203 -0
  38. package/src/workbench/standalone.ts +94 -0
  39. package/src/workbench/workspace/WorkspaceEditor.tsx +226 -0
  40. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +353 -0
  41. package/src/workbench/workspace/WorkspaceExplorer.tsx +524 -0
  42. package/src/workbench/workspace/WorkspaceFileIcon.tsx +149 -0
  43. package/src/workbench/workspace/WorkspaceHighlightedText.tsx +22 -0
  44. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -0
  45. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -0
  46. package/src/workbench/workspace/index.ts +98 -0
  47. package/src/workbench/workspace/path.ts +10 -0
  48. package/src/workbench/workspace/search.ts +6 -0
  49. package/src/workbench/workspace/tree.ts +1 -0
  50. package/src/workbench/workspace/types.ts +10 -0
  51. package/src/workbench/workspace/useVirtualWorkspace.ts +98 -0
@@ -0,0 +1,3 @@
1
+ export function cx(...classNames: Array<false | null | string | undefined>): string {
2
+ return classNames.filter(Boolean).join(' ');
3
+ }
@@ -0,0 +1,52 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export interface ActivityBarItem {
5
+ active?: boolean;
6
+ disabled?: boolean;
7
+ icon: ReactNode;
8
+ id: string;
9
+ label: string;
10
+ title?: string;
11
+ }
12
+
13
+ export interface ActivityBarProps extends Omit<ComponentPropsWithoutRef<'nav'>, 'children'> {
14
+ items: ActivityBarItem[];
15
+ onItemActivate?: (item: ActivityBarItem) => void;
16
+ secondaryItems?: ActivityBarItem[];
17
+ }
18
+
19
+ export function ActivityBar({
20
+ 'aria-label': ariaLabel = 'Activity bar',
21
+ className,
22
+ items,
23
+ onItemActivate,
24
+ secondaryItems = [],
25
+ ...props
26
+ }: ActivityBarProps) {
27
+ const renderItem = (item: ActivityBarItem) => (
28
+ <button
29
+ key={item.id}
30
+ type="button"
31
+ aria-label={item.label}
32
+ aria-pressed={item.active}
33
+ className={cx(
34
+ 'ui-workbench-activity-bar__item',
35
+ item.active && 'ui-workbench-activity-bar__item--active',
36
+ )}
37
+ disabled={item.disabled}
38
+ title={item.title ?? item.label}
39
+ onClick={() => onItemActivate?.(item)}
40
+ >
41
+ <span className="ui-workbench-activity-bar__icon">{item.icon}</span>
42
+ </button>
43
+ );
44
+
45
+ return (
46
+ <nav aria-label={ariaLabel} className={cx('ui-workbench-activity-bar', className)} {...props}>
47
+ {items.map(renderItem)}
48
+ {secondaryItems.length ? <span className="ui-workbench-activity-bar__spacer" /> : null}
49
+ {secondaryItems.map(renderItem)}
50
+ </nav>
51
+ );
52
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ useRef,
3
+ useState,
4
+ type CSSProperties,
5
+ type KeyboardEvent,
6
+ type PointerEvent,
7
+ type ReactNode,
8
+ } from 'react';
9
+ import { cx } from '../utils/cx';
10
+
11
+ export interface SplitViewProps {
12
+ className?: string;
13
+ defaultPrimarySizePercent?: number;
14
+ keyboardStepPercent?: number;
15
+ maxPrimarySizePercent?: number;
16
+ minPrimarySizePercent?: number;
17
+ onPrimarySizePercentChange?: (primarySizePercent: number) => void;
18
+ primary: ReactNode;
19
+ primarySizePercent?: number;
20
+ secondary: ReactNode;
21
+ }
22
+
23
+ export function SplitView({
24
+ className,
25
+ defaultPrimarySizePercent = 40,
26
+ keyboardStepPercent = 5,
27
+ maxPrimarySizePercent = 85,
28
+ minPrimarySizePercent = 15,
29
+ onPrimarySizePercentChange,
30
+ primary,
31
+ primarySizePercent: controlledPrimarySizePercent,
32
+ secondary,
33
+ }: SplitViewProps) {
34
+ const [uncontrolledPrimarySizePercent, setUncontrolledPrimarySizePercent] =
35
+ useState(defaultPrimarySizePercent);
36
+ const containerRef = useRef<HTMLDivElement>(null);
37
+ const dragging = useRef(false);
38
+ const isControlled = controlledPrimarySizePercent !== undefined;
39
+
40
+ const clampPrimarySize = (value: number) =>
41
+ Math.max(minPrimarySizePercent, Math.min(maxPrimarySizePercent, value));
42
+
43
+ const primarySizePercent = clampPrimarySize(
44
+ controlledPrimarySizePercent ?? uncontrolledPrimarySizePercent,
45
+ );
46
+
47
+ const commitPrimarySize = (value: number) => {
48
+ const nextValue = clampPrimarySize(value);
49
+ if (!isControlled) {
50
+ setUncontrolledPrimarySizePercent(nextValue);
51
+ }
52
+ onPrimarySizePercentChange?.(nextValue);
53
+ };
54
+
55
+ const updatePrimarySize = (clientX: number) => {
56
+ if (!containerRef.current) return;
57
+ const rect = containerRef.current.getBoundingClientRect();
58
+ const pct = ((clientX - rect.left) / rect.width) * 100;
59
+ commitPrimarySize(pct);
60
+ };
61
+
62
+ const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {
63
+ event.preventDefault();
64
+ event.currentTarget.setPointerCapture(event.pointerId);
65
+ dragging.current = true;
66
+ event.currentTarget.classList.add('is-dragging');
67
+ };
68
+
69
+ const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {
70
+ if (!dragging.current) return;
71
+ updatePrimarySize(event.clientX);
72
+ };
73
+
74
+ const onPointerUp = (event: PointerEvent<HTMLDivElement>) => {
75
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
76
+ event.currentTarget.releasePointerCapture(event.pointerId);
77
+ }
78
+ dragging.current = false;
79
+ event.currentTarget.classList.remove('is-dragging');
80
+ };
81
+
82
+ const onSeparatorKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
83
+ if (event.key === 'ArrowLeft') {
84
+ event.preventDefault();
85
+ commitPrimarySize(primarySizePercent - keyboardStepPercent);
86
+ return;
87
+ }
88
+
89
+ if (event.key === 'ArrowRight') {
90
+ event.preventDefault();
91
+ commitPrimarySize(primarySizePercent + keyboardStepPercent);
92
+ return;
93
+ }
94
+
95
+ if (event.key === 'Home') {
96
+ event.preventDefault();
97
+ commitPrimarySize(minPrimarySizePercent);
98
+ return;
99
+ }
100
+
101
+ if (event.key === 'End') {
102
+ event.preventDefault();
103
+ commitPrimarySize(maxPrimarySizePercent);
104
+ }
105
+ };
106
+
107
+ return (
108
+ <div
109
+ ref={containerRef}
110
+ className={cx('ui-workbench-split-view', className)}
111
+ style={
112
+ {
113
+ '--ui-workbench-split-primary-size': `${primarySizePercent}%`,
114
+ } as CSSProperties
115
+ }
116
+ >
117
+ <div className="ui-workbench-split-view__primary">{primary}</div>
118
+ <div
119
+ aria-orientation="vertical"
120
+ aria-valuemax={maxPrimarySizePercent}
121
+ aria-valuemin={minPrimarySizePercent}
122
+ aria-valuenow={Math.round(primarySizePercent)}
123
+ className="ui-workbench-split-view__separator"
124
+ role="separator"
125
+ tabIndex={0}
126
+ onKeyDown={onSeparatorKeyDown}
127
+ onPointerDown={onPointerDown}
128
+ onPointerMove={onPointerMove}
129
+ onPointerUp={onPointerUp}
130
+ >
131
+ <div className="ui-workbench-split-view__handle" />
132
+ </div>
133
+ <div className="ui-workbench-split-view__secondary">{secondary}</div>
134
+ </div>
135
+ );
136
+ }
@@ -0,0 +1,123 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export type StatusBarSectionAlign = 'end' | 'start';
5
+
6
+ export interface StatusBarItemModel {
7
+ active?: boolean;
8
+ ariaLabel?: string;
9
+ disabled?: boolean;
10
+ hidden?: boolean;
11
+ icon?: ReactNode;
12
+ id: string;
13
+ label: ReactNode;
14
+ title?: string;
15
+ }
16
+
17
+ export interface StatusBarSectionModel {
18
+ align?: StatusBarSectionAlign;
19
+ hidden?: boolean;
20
+ id: string;
21
+ items: StatusBarItemModel[];
22
+ }
23
+
24
+ export interface StatusBarProps extends ComponentPropsWithRef<'footer'> {
25
+ compact?: boolean;
26
+ onItemActivate?: (item: StatusBarItemModel) => void;
27
+ sections?: StatusBarSectionModel[];
28
+ }
29
+
30
+ export function StatusBar({
31
+ 'aria-label': ariaLabel = 'Status bar',
32
+ children,
33
+ className,
34
+ compact,
35
+ onItemActivate,
36
+ sections,
37
+ ...props
38
+ }: StatusBarProps) {
39
+ const content =
40
+ children ??
41
+ sections
42
+ ?.filter((section) => !section.hidden)
43
+ .map((section) => (
44
+ <StatusBarSection key={section.id} align={section.align}>
45
+ {section.items
46
+ .filter((item) => !item.hidden)
47
+ .map((item) => (
48
+ <StatusBarItem
49
+ key={item.id}
50
+ active={item.active}
51
+ aria-label={item.ariaLabel}
52
+ disabled={item.disabled}
53
+ icon={item.icon}
54
+ title={item.title}
55
+ onClick={() => onItemActivate?.(item)}
56
+ >
57
+ {item.label}
58
+ </StatusBarItem>
59
+ ))}
60
+ </StatusBarSection>
61
+ ));
62
+
63
+ return (
64
+ <footer
65
+ aria-label={ariaLabel}
66
+ className={cx(
67
+ 'ui-workbench-status-bar',
68
+ compact && 'ui-workbench-status-bar--compact',
69
+ className,
70
+ )}
71
+ {...props}
72
+ >
73
+ {content}
74
+ </footer>
75
+ );
76
+ }
77
+
78
+ export interface StatusBarSectionProps extends ComponentPropsWithRef<'div'> {
79
+ align?: StatusBarSectionAlign;
80
+ }
81
+
82
+ export function StatusBarSection({ align = 'start', className, ...props }: StatusBarSectionProps) {
83
+ return (
84
+ <div
85
+ className={cx(
86
+ 'ui-workbench-status-bar__section',
87
+ align === 'end' && 'ui-workbench-status-bar__section--end',
88
+ className,
89
+ )}
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ export interface StatusBarItemProps extends ComponentPropsWithRef<'button'> {
96
+ active?: boolean;
97
+ icon?: ReactNode;
98
+ }
99
+
100
+ export function StatusBarItem({
101
+ active,
102
+ children,
103
+ className,
104
+ icon,
105
+ type = 'button',
106
+ ...props
107
+ }: StatusBarItemProps) {
108
+ return (
109
+ <button
110
+ type={type}
111
+ className={cx(
112
+ 'ui-workbench-status-bar__item',
113
+ active && 'ui-workbench-status-bar__item--active',
114
+ className,
115
+ )}
116
+ data-active={active ? 'true' : undefined}
117
+ {...props}
118
+ >
119
+ {icon ? <span className="ui-workbench-status-bar__icon">{icon}</span> : null}
120
+ <span className="ui-workbench-status-bar__label">{children}</span>
121
+ </button>
122
+ );
123
+ }
@@ -0,0 +1,70 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+ import { ActivityBar, type ActivityBarProps, type ActivityBarItem } from './ActivityBar';
3
+ import { SplitView } from './SplitView';
4
+ import { StatusBar, type StatusBarItemModel, type StatusBarSectionModel } from './StatusBar';
5
+
6
+ export interface WorkbenchShellProps {
7
+ activityBar: Omit<ActivityBarProps, 'items'> & {
8
+ items: ActivityBarItem[];
9
+ };
10
+ compactStatus?: boolean;
11
+ onStatusItemActivate?: (item: StatusBarItemModel) => void;
12
+ primarySidebar?: {
13
+ isVisible: boolean;
14
+ node: ReactNode;
15
+ onSizePercentChange?: (sizePercent: number) => void;
16
+ primarySizePercent?: number;
17
+ minPrimarySizePercent?: number;
18
+ maxPrimarySizePercent?: number;
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ };
22
+ rootClassName?: string;
23
+ rootStyle?: CSSProperties;
24
+ secondaryArea: ReactNode;
25
+ statusSections: StatusBarSectionModel[];
26
+ overlays?: ReactNode;
27
+ theme?: string;
28
+ }
29
+
30
+ export function WorkbenchShell({
31
+ activityBar,
32
+ compactStatus = true,
33
+ onStatusItemActivate,
34
+ overlays,
35
+ primarySidebar,
36
+ rootClassName,
37
+ rootStyle,
38
+ secondaryArea,
39
+ statusSections,
40
+ theme,
41
+ }: WorkbenchShellProps) {
42
+ const body = primarySidebar?.isVisible ? (
43
+ <SplitView
44
+ className={primarySidebar?.className}
45
+ minPrimarySizePercent={primarySidebar?.minPrimarySizePercent}
46
+ maxPrimarySizePercent={primarySidebar?.maxPrimarySizePercent}
47
+ onPrimarySizePercentChange={primarySidebar?.onSizePercentChange}
48
+ primary={primarySidebar.node}
49
+ primarySizePercent={primarySidebar.primarySizePercent}
50
+ secondary={secondaryArea}
51
+ />
52
+ ) : (
53
+ secondaryArea
54
+ );
55
+
56
+ return (
57
+ <div className={rootClassName} data-theme={theme} style={rootStyle}>
58
+ <div className="ide-body">
59
+ <ActivityBar {...activityBar} />
60
+ {body}
61
+ </div>
62
+ <StatusBar
63
+ compact={compactStatus}
64
+ sections={statusSections}
65
+ onItemActivate={onStatusItemActivate}
66
+ />
67
+ {overlays}
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,291 @@
1
+ import { useMemo } from 'react';
2
+ import type { CSSProperties, MouseEvent, ReactNode } from 'react';
3
+ import type { StatusBarItemModel, StatusBarSectionModel } from './StatusBar';
4
+ import type { WorkbenchShellProps } from './WorkbenchShell';
5
+ import { WorkbenchShell } from './WorkbenchShell';
6
+ import type { WorkbenchShellCommandContext } from './commands';
7
+ import { useWorkbenchShellState } from './shellState';
8
+ import type {
9
+ WorkbenchActivityChangeEvent,
10
+ WorkbenchActivityDescriptor,
11
+ WorkbenchStandaloneBootstrap,
12
+ WorkbenchStandaloneBootstrapEvent,
13
+ WorkbenchTheme,
14
+ } from './standalone';
15
+
16
+ const DEFAULT_THEME: WorkbenchTheme = 'dark';
17
+ const DEFAULT_ACTIVITY_ID = 'explorer';
18
+
19
+ export interface WorkbenchStandaloneShellContext<
20
+ TActivityId extends string = string,
21
+ TTheme extends WorkbenchTheme = WorkbenchTheme,
22
+ > {
23
+ activityId: TActivityId;
24
+ commandContext: WorkbenchShellCommandContext<TActivityId>;
25
+ showActivity: (activityId: TActivityId) => void;
26
+ activateActivity: (activityId: TActivityId) => void;
27
+ setTheme: (theme: TTheme) => void;
28
+ setPrimarySidebarSizePercent: (sizePercent: number) => void;
29
+ setPrimarySidebarVisible: (isVisible: boolean) => void;
30
+ togglePrimarySidebar: () => void;
31
+ openSettings: () => void;
32
+ closeSettings: () => void;
33
+ setSettingsCategoryId: (settingsCategoryId: string) => void;
34
+ setSettingsScopeId: (settingsScopeId: string) => void;
35
+ setSettingsSearchValue: (settingsSearchValue: string) => void;
36
+ }
37
+
38
+ export interface WorkbenchStandaloneShellProps<
39
+ TActivityId extends string = string,
40
+ TTheme extends WorkbenchTheme = WorkbenchTheme,
41
+ > {
42
+ bootstrap: WorkbenchStandaloneBootstrap<TActivityId>;
43
+ activityBar?: Omit<
44
+ WorkbenchShellProps['activityBar'],
45
+ 'items' | 'secondaryItems' | 'onContextMenu' | 'onItemActivate'
46
+ >;
47
+ compactStatus?: boolean;
48
+ includeSettings?: boolean;
49
+ maxPrimarySidebarSizePercent?: number;
50
+ minPrimarySidebarSizePercent?: number;
51
+ onActivityActivate?: (
52
+ event: WorkbenchActivityChangeEvent<TActivityId>,
53
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
54
+ ) => void;
55
+ onActivityBarContextMenu?: (
56
+ event: MouseEvent<HTMLElement>,
57
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
58
+ ) => void;
59
+ onActivityBarItemActivate?: (
60
+ itemId: TActivityId | string,
61
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
62
+ ) => void;
63
+ onStatusItemActivate?: (
64
+ item: StatusBarItemModel,
65
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
66
+ ) => void;
67
+ onEvent?: (event: WorkbenchStandaloneBootstrapEvent<TActivityId>) => void;
68
+ renderPrimarySidebar: (
69
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
70
+ ) => ReactNode;
71
+ renderOverlays?: (context: WorkbenchStandaloneShellContext<TActivityId, TTheme>) => ReactNode;
72
+ renderSecondaryArea: (context: WorkbenchStandaloneShellContext<TActivityId, TTheme>) => ReactNode;
73
+ rootClassName?: string;
74
+ rootStyle?: CSSProperties;
75
+ settingsItemIcon?: ReactNode;
76
+ settingsItemId?: string;
77
+ settingsItemLabel?: string;
78
+ primarySidebarClassName?: string;
79
+ primarySidebarStyle?: CSSProperties;
80
+ getStatusSections?: (
81
+ context: WorkbenchStandaloneShellContext<TActivityId, TTheme>,
82
+ ) => StatusBarSectionModel[];
83
+ }
84
+
85
+ function toWorkbenchActivityItems<TActivityId extends string>(
86
+ activities: WorkbenchActivityDescriptor<TActivityId>[],
87
+ ): Array<{ icon: ReactNode; id: TActivityId; label: string }> {
88
+ return activities.map((activity) => ({
89
+ id: activity.id,
90
+ label: activity.label,
91
+ icon:
92
+ activity.iconNode ?? (activity.icon ? <i className={`codicon ${activity.icon}`} /> : null),
93
+ }));
94
+ }
95
+
96
+ export function WorkbenchStandaloneShell<
97
+ TActivityId extends string = string,
98
+ TTheme extends WorkbenchTheme = WorkbenchTheme,
99
+ >({
100
+ bootstrap,
101
+ activityBar,
102
+ compactStatus = true,
103
+ includeSettings = true,
104
+ maxPrimarySidebarSizePercent,
105
+ minPrimarySidebarSizePercent,
106
+ onActivityActivate,
107
+ onActivityBarContextMenu,
108
+ onActivityBarItemActivate,
109
+ onStatusItemActivate,
110
+ onEvent,
111
+ renderPrimarySidebar,
112
+ renderOverlays,
113
+ renderSecondaryArea,
114
+ rootClassName,
115
+ rootStyle,
116
+ settingsItemIcon,
117
+ settingsItemId = 'settings',
118
+ settingsItemLabel = 'Settings',
119
+ primarySidebarClassName,
120
+ primarySidebarStyle,
121
+ getStatusSections,
122
+ }: WorkbenchStandaloneShellProps<TActivityId, TTheme>) {
123
+ const { contract, initialState } = bootstrap;
124
+ const activityIds = useMemo(
125
+ () => new Set(contract.activities.map((activity) => activity.id)),
126
+ [contract.activities],
127
+ );
128
+
129
+ const shell = useWorkbenchShellState<TActivityId, TTheme>({
130
+ activeActivityId:
131
+ initialState?.activeActivityId ??
132
+ contract.activities[0]?.id ??
133
+ (DEFAULT_ACTIVITY_ID as TActivityId),
134
+ isPrimarySidebarVisible: initialState?.isPrimarySidebarVisible ?? true,
135
+ isSettingsOpen: initialState?.isSettingsOpen ?? false,
136
+ primarySidebarSizePercent: initialState?.primarySidebarSizePercent ?? 24,
137
+ settingsCategoryId: initialState?.settingsCategoryId ?? '',
138
+ settingsScopeId: initialState?.settingsScopeId ?? '',
139
+ settingsSearchValue: initialState?.settingsSearchValue ?? '',
140
+ theme: (initialState?.theme ?? contract.initialTheme ?? DEFAULT_THEME) as TTheme,
141
+ });
142
+ const {
143
+ isPrimarySidebarVisible,
144
+ isSettingsOpen,
145
+ primarySidebarSizePercent,
146
+ theme,
147
+ activeActivityId,
148
+ } = shell.state;
149
+
150
+ const emitEvent = (event: WorkbenchStandaloneBootstrapEvent<TActivityId>) => {
151
+ onEvent?.(event);
152
+ };
153
+ const showActivity = (activityId: TActivityId) => {
154
+ shell.showActivity(activityId);
155
+ emitEvent({ type: 'activity-change', payload: { nextActivityId: activityId } });
156
+ onActivityActivate?.(
157
+ { nextActivityId: activityId },
158
+ createContext({
159
+ activityId,
160
+ shell,
161
+ }),
162
+ );
163
+ };
164
+ const activateActivity = (activityId: TActivityId) => {
165
+ shell.activateActivity(activityId);
166
+ emitEvent({ type: 'activity-change', payload: { nextActivityId: activityId } });
167
+ onActivityActivate?.(
168
+ { nextActivityId: activityId },
169
+ createContext({
170
+ activityId,
171
+ shell,
172
+ }),
173
+ );
174
+ };
175
+ const createContext = ({
176
+ activityId,
177
+ shell,
178
+ }: {
179
+ activityId: TActivityId;
180
+ shell: ReturnType<typeof useWorkbenchShellState<TActivityId, TTheme>>;
181
+ }): WorkbenchStandaloneShellContext<TActivityId, TTheme> => ({
182
+ activityId,
183
+ commandContext: {
184
+ isPrimarySidebarVisible,
185
+ openSettings: shell.openSettings,
186
+ showActivity: shell.showActivity,
187
+ togglePrimarySidebar: shell.togglePrimarySidebar,
188
+ },
189
+ showActivity,
190
+ activateActivity,
191
+ setTheme: shell.setTheme,
192
+ setPrimarySidebarSizePercent: shell.setPrimarySidebarSizePercent,
193
+ setPrimarySidebarVisible: shell.setPrimarySidebarVisible,
194
+ togglePrimarySidebar: shell.togglePrimarySidebar,
195
+ openSettings: shell.openSettings,
196
+ closeSettings: shell.closeSettings,
197
+ setSettingsCategoryId: shell.setSettingsCategoryId,
198
+ setSettingsScopeId: shell.setSettingsScopeId,
199
+ setSettingsSearchValue: shell.setSettingsSearchValue,
200
+ });
201
+
202
+ const context = useMemo(
203
+ () => createContext({ activityId: activeActivityId, shell }),
204
+ [shell, activeActivityId, isPrimarySidebarVisible],
205
+ );
206
+
207
+ const statusSections = useMemo(
208
+ () =>
209
+ getStatusSections?.(context) ??
210
+ contract.statusSections.map((section) => ({
211
+ ...section,
212
+ items: section.items.map((item) => ({ ...item })),
213
+ })),
214
+ [contract.statusSections, context, getStatusSections],
215
+ );
216
+
217
+ const activityItems = useMemo(
218
+ () => toWorkbenchActivityItems(contract.activities),
219
+ [contract.activities],
220
+ );
221
+ const activityBarItems = useMemo(
222
+ () =>
223
+ activityItems.map((item) => ({
224
+ ...item,
225
+ active: item.id === activeActivityId,
226
+ })),
227
+ [activityItems, activeActivityId],
228
+ );
229
+
230
+ const hasSettingsSection = includeSettings && settingsItemId !== '';
231
+
232
+ const primarySidebarNode = renderPrimarySidebar(context);
233
+ const secondaryArea = renderSecondaryArea(context);
234
+ const overlays = renderOverlays?.(context);
235
+
236
+ return (
237
+ <WorkbenchShell
238
+ activityBar={{
239
+ ...activityBar,
240
+ items: activityBarItems,
241
+ secondaryItems: hasSettingsSection
242
+ ? [
243
+ {
244
+ id: settingsItemId,
245
+ icon: settingsItemIcon ?? <i className="codicon codicon-settings-gear" />,
246
+ label: settingsItemLabel,
247
+ active: isSettingsOpen,
248
+ },
249
+ ]
250
+ : [],
251
+ onContextMenu: (event) => onActivityBarContextMenu?.(event, context),
252
+ onItemActivate: (item) => {
253
+ if (item.id === settingsItemId) {
254
+ shell.openSettings();
255
+ emitEvent({
256
+ type: 'status-message',
257
+ message: `Settings ${isSettingsOpen ? 'opened' : 'closed'}`,
258
+ });
259
+ return;
260
+ }
261
+
262
+ if (activityIds.has(item.id as TActivityId)) {
263
+ activateActivity(item.id as TActivityId);
264
+ onActivityBarItemActivate?.(item.id, context);
265
+ return;
266
+ }
267
+
268
+ onActivityBarItemActivate?.(item.id, context);
269
+ },
270
+ }}
271
+ compactStatus={compactStatus}
272
+ onStatusItemActivate={(item) => onStatusItemActivate?.(item, context)}
273
+ primarySidebar={{
274
+ className: primarySidebarClassName,
275
+ isVisible: isPrimarySidebarVisible,
276
+ maxPrimarySizePercent: maxPrimarySidebarSizePercent,
277
+ minPrimarySizePercent: minPrimarySidebarSizePercent,
278
+ onSizePercentChange: shell.setPrimarySidebarSizePercent,
279
+ primarySizePercent: primarySidebarSizePercent,
280
+ node: primarySidebarNode,
281
+ style: primarySidebarStyle,
282
+ }}
283
+ rootClassName={rootClassName}
284
+ rootStyle={rootStyle}
285
+ secondaryArea={secondaryArea}
286
+ statusSections={statusSections}
287
+ theme={theme}
288
+ overlays={overlays}
289
+ />
290
+ );
291
+ }