@noya-app/noya-designsystem 0.1.82 → 0.1.84

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.
@@ -1,14 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { memoGeneric } from "@noya-app/react-utils";
4
- import React, { useMemo, useRef, useState } from "react";
5
- import { Button } from "../Button";
6
- import { DividerVertical } from "../Divider";
7
- import { Drawer } from "../Drawer";
4
+ import React, { useMemo, useState } from "react";
5
+ import { cssVarNames } from "../../theme";
6
+ import { Divider } from "../Divider";
8
7
  import { isSelectableMenuItem, MenuItem } from "../internal/Menu";
9
- import { EDITOR_PANEL_GROUP_ID, RIGHT_SIDEBAR_ID } from "./constants";
10
- import { LayoutProps, PanelLayoutState, SidebarRef } from "./types";
11
- import { VerticalTabMenu } from "./VerticalTabMenu";
8
+ import { ToolbarMenu } from "../Toolbar";
9
+ import { EDITOR_PANEL_GROUP_ID } from "./constants";
10
+ import { LayoutProps, PanelLayoutState } from "./types";
12
11
 
13
12
  export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
14
13
  LeftTab extends string = string,
@@ -25,16 +24,46 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
25
24
  rightTabValue,
26
25
  onChangeRightTab,
27
26
  onChangeLeftTab,
28
- compactDrawerMenu,
29
27
  }: LayoutProps<LeftTab, RightTab>) {
30
- const portalContainer = useRef<HTMLDivElement>(null);
31
-
32
28
  const [{ leftSidebarCollapsed, rightSidebarCollapsed }, setLayoutState] =
33
29
  useState<PanelLayoutState>({
34
30
  leftSidebarCollapsed: true,
35
31
  rightSidebarCollapsed: true,
36
32
  });
37
33
 
34
+ // Expose expand/collapse methods via refs
35
+ React.useImperativeHandle(
36
+ leftSidebarRef,
37
+ () => ({
38
+ expand: () =>
39
+ setLayoutState((prev) => ({
40
+ ...prev,
41
+ leftSidebarCollapsed: false,
42
+ rightSidebarCollapsed: true,
43
+ })),
44
+ collapse: () =>
45
+ setLayoutState((prev) => ({ ...prev, leftSidebarCollapsed: true })),
46
+ isExpanded: () => !leftSidebarCollapsed,
47
+ }),
48
+ [leftSidebarCollapsed]
49
+ );
50
+
51
+ React.useImperativeHandle(
52
+ rightSidebarRef,
53
+ () => ({
54
+ expand: () =>
55
+ setLayoutState((prev) => ({
56
+ ...prev,
57
+ rightSidebarCollapsed: false,
58
+ leftSidebarCollapsed: true,
59
+ })),
60
+ collapse: () =>
61
+ setLayoutState((prev) => ({ ...prev, rightSidebarCollapsed: true })),
62
+ isExpanded: () => !rightSidebarCollapsed,
63
+ }),
64
+ [rightSidebarCollapsed]
65
+ );
66
+
38
67
  const allTabItems = useMemo(
39
68
  () =>
40
69
  [
@@ -44,15 +73,12 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
44
73
  [leftTabItems, rightTabItems, leftPanel, rightPanel]
45
74
  );
46
75
 
47
- const hasTabs = allTabItems.length > 0;
48
-
49
- // Determine if we should use the compact top-row menu variant.
50
76
  const allSelectableTabItems = useMemo(
51
77
  () => allTabItems.filter(isSelectableMenuItem),
52
78
  [allTabItems]
53
79
  );
54
- const useCompactInToolbar =
55
- !!compactDrawerMenu && allSelectableTabItems.length <= 1;
80
+
81
+ const hasTabs = allSelectableTabItems.length > 0;
56
82
 
57
83
  const leftSelectableTabItems = useMemo(
58
84
  () => (leftTabItems ?? []).filter(isSelectableMenuItem),
@@ -64,165 +90,118 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
64
90
  [rightTabItems]
65
91
  );
66
92
 
67
- // Shared helpers for expanding/collapsing drawers
68
- const toggleSidebar = (ref: React.RefObject<SidebarRef | null>) => {
69
- if (ref.current?.isExpanded()) {
70
- ref.current?.collapse();
71
- } else {
72
- ref.current?.expand();
73
- }
74
- };
75
-
76
- const ensureExpanded = (ref: React.RefObject<SidebarRef | null>) => {
77
- if (!ref.current?.isExpanded()) {
78
- ref.current?.expand();
79
- }
80
- };
81
-
82
93
  const handleSelectTab = (value: LeftTab | RightTab) => {
83
- if (leftSelectableTabItems.some((item) => item.value === value)) {
84
- if (value === leftTabValue) {
85
- toggleSidebar(leftSidebarRef);
94
+ const isLeftItem = leftSelectableTabItems.some(
95
+ (item) => item.value === value
96
+ );
97
+ const isRightItem = rightSelectableTabItems.some(
98
+ (item) => item.value === value
99
+ );
100
+
101
+ if (isLeftItem) {
102
+ if (value === leftTabValue && !leftSidebarCollapsed) {
103
+ // Toggle off - clicking same tab that's already open
104
+ setLayoutState((prev) => ({ ...prev, leftSidebarCollapsed: true }));
86
105
  } else {
87
- ensureExpanded(leftSidebarRef);
106
+ // Open this tab, close other
107
+ setLayoutState({
108
+ leftSidebarCollapsed: false,
109
+ rightSidebarCollapsed: true,
110
+ });
88
111
  onChangeLeftTab?.(value as LeftTab);
89
112
  }
90
- } else if (rightSelectableTabItems.some((item) => item.value === value)) {
91
- if (value === rightTabValue) {
92
- toggleSidebar(rightSidebarRef);
113
+ } else if (isRightItem) {
114
+ if (value === rightTabValue && !rightSidebarCollapsed) {
115
+ // Toggle off - clicking same tab that's already open
116
+ setLayoutState((prev) => ({ ...prev, rightSidebarCollapsed: true }));
93
117
  } else {
94
- ensureExpanded(rightSidebarRef);
118
+ // Open this tab, close other
119
+ setLayoutState({
120
+ leftSidebarCollapsed: true,
121
+ rightSidebarCollapsed: false,
122
+ });
95
123
  onChangeRightTab?.(value as RightTab);
96
124
  }
97
125
  }
98
126
  };
99
127
 
100
- const { activeValue, isSidebarCollapsed } =
101
- leftSelectableTabItems.length > 0 && !leftSidebarCollapsed
102
- ? { activeValue: leftTabValue, isSidebarCollapsed: leftSidebarCollapsed }
103
- : rightSelectableTabItems.length > 0 && !rightSidebarCollapsed
104
- ? {
105
- activeValue: rightTabValue,
106
- isSidebarCollapsed: rightSidebarCollapsed,
107
- }
108
- : { activeValue: undefined, isSidebarCollapsed: true };
128
+ // Determine which panel is currently showing
129
+ const activeDrawerPanel = !leftSidebarCollapsed
130
+ ? leftPanel
131
+ : !rightSidebarCollapsed
132
+ ? rightPanel
133
+ : null;
134
+
135
+ const isDrawerOpen = activeDrawerPanel !== null;
136
+
137
+ // Determine active tab value for highlighting
138
+ const activeTabValue = !leftSidebarCollapsed
139
+ ? leftTabValue
140
+ : !rightSidebarCollapsed
141
+ ? rightTabValue
142
+ : undefined;
143
+
144
+ // Create toolbar menu items with checked state
145
+ const toolbarMenuItems = useMemo(
146
+ () =>
147
+ allSelectableTabItems.map((item) => ({
148
+ ...item,
149
+ checked: activeTabValue === item.value,
150
+ })),
151
+ [allSelectableTabItems, activeTabValue]
152
+ );
109
153
 
110
154
  return (
111
155
  <div
112
- ref={portalContainer}
113
156
  id={EDITOR_PANEL_GROUP_ID}
114
- className="n-flex n-flex-1 n-relative focus:n-outline-none"
157
+ className="n-flex n-flex-col n-flex-1 n-relative focus:n-outline-none"
115
158
  >
116
- {hasTabs && !useCompactInToolbar && (
159
+ {/* Horizontal toggle bar - secondary toolbar row */}
160
+ {hasTabs && (
117
161
  <>
118
- <VerticalTabMenu
119
- tooltipSide="right"
120
- items={allTabItems}
121
- activeValue={activeValue}
122
- isSidebarCollapsed={isSidebarCollapsed}
123
- onChange={(value) => handleSelectTab(value as LeftTab | RightTab)}
124
- />
125
- <DividerVertical className="n-h-full" />
162
+ <div
163
+ className="n-flex n-flex-row n-items-center n-gap-1 n-px-2 n-py-1.5 n-bg-sidebar-background n-flex-none"
164
+ style={{
165
+ [cssVarNames.colors.inputBackground]: "transparent",
166
+ [cssVarNames.colors.buttonBackground]: "transparent",
167
+ }}
168
+ >
169
+ <ToolbarMenu
170
+ items={toolbarMenuItems}
171
+ onSelectMenuItem={handleSelectTab}
172
+ />
173
+ </div>
174
+ <Divider />
126
175
  </>
127
176
  )}
128
177
 
129
- {useCompactInToolbar && (
130
- <div className="n-absolute n-top-0 n-left-0 n-h-[calc(var(--n-toolbar-height)+1px)] n-w-[calc(var(--n-toolbar-height)+1px)] n-bg-sidebar-background n-border-r n-border-b n-border-divider-strong n-z-10 n-flex n-items-center n-justify-center">
131
- {allSelectableTabItems[0] && (
132
- <Button
133
- variant="none"
134
- className="n-rounded"
135
- style={{
136
- width: "var(--n-input-height)",
137
- height: "var(--n-input-height)",
138
- }}
139
- icon={allSelectableTabItems[0].icon}
140
- tooltip={
141
- allSelectableTabItems[0].tooltip ??
142
- allSelectableTabItems[0].title
178
+ {/* Main content area with drawer overlay */}
179
+ <div className="n-flex-1 n-flex n-flex-col n-relative n-min-h-0">
180
+ {/* Drawer panel - positioned absolutely over content */}
181
+ {isDrawerOpen && (
182
+ <div className="n-absolute n-inset-0 n-z-20 n-flex n-flex-col">
183
+ {/* Drawer content */}
184
+ <div className="n-flex-none n-max-h-[50vh] n-overflow-auto n-bg-sidebar-background n-border-b n-border-divider-strong n-shadow-lg">
185
+ {activeDrawerPanel}
186
+ </div>
187
+ {/* Backdrop - click to close */}
188
+ <div
189
+ className="n-flex-1 n-bg-black/30"
190
+ onClick={() =>
191
+ setLayoutState({
192
+ leftSidebarCollapsed: true,
193
+ rightSidebarCollapsed: true,
194
+ })
143
195
  }
144
- active={(() => {
145
- const value = allSelectableTabItems[0].value as
146
- | LeftTab
147
- | RightTab;
148
- const isLeftItem = leftSelectableTabItems.some(
149
- (i) => i.value === value
150
- );
151
- const isRightItem = rightSelectableTabItems.some(
152
- (i) => i.value === value
153
- );
154
- return isLeftItem
155
- ? !leftSidebarCollapsed
156
- : isRightItem
157
- ? !rightSidebarCollapsed
158
- : false;
159
- })()}
160
- onClick={() => {
161
- const value = allSelectableTabItems[0].value as
162
- | LeftTab
163
- | RightTab;
164
- handleSelectTab(value);
165
- }}
166
196
  />
167
- )}
168
- </div>
169
- )}
197
+ </div>
198
+ )}
170
199
 
171
- <div className="n-flex-1 n-flex n-relative">{centerPanel}</div>
172
-
173
- {leftPanel && (
174
- <Drawer
175
- modal={false}
176
- ref={leftSidebarRef}
177
- className="n-flex-1"
178
- style={
179
- useCompactInToolbar
180
- ? undefined
181
- : { left: 48, maxWidth: "calc(100% - 48px)" }
182
- }
183
- overlayStyle={useCompactInToolbar ? undefined : { left: 48 }}
184
- open={!leftSidebarCollapsed}
185
- positioning="absolute"
186
- side="left"
187
- trigger={null}
188
- portalContainer={portalContainer.current}
189
- onOpenChange={(open) => {
190
- setLayoutState({
191
- leftSidebarCollapsed: !open,
192
- rightSidebarCollapsed: rightSidebarCollapsed,
193
- });
194
- }}
195
- >
196
- {leftPanel}
197
- </Drawer>
198
- )}
199
- {rightPanel && (
200
- <Drawer
201
- id={RIGHT_SIDEBAR_ID}
202
- modal={false}
203
- ref={rightSidebarRef}
204
- className="n-flex-1"
205
- style={
206
- useCompactInToolbar
207
- ? undefined
208
- : { left: 48, maxWidth: "calc(100% - 48px)" }
209
- }
210
- overlayStyle={useCompactInToolbar ? undefined : { left: 48 }}
211
- open={!rightSidebarCollapsed}
212
- positioning="absolute"
213
- side="left"
214
- trigger={null}
215
- portalContainer={portalContainer.current}
216
- onOpenChange={(open) => {
217
- setLayoutState({
218
- leftSidebarCollapsed: leftSidebarCollapsed,
219
- rightSidebarCollapsed: !open,
220
- });
221
- }}
222
- >
223
- {rightPanel}
224
- </Drawer>
225
- )}
200
+ {/* Center content */}
201
+ <div className="n-flex-1 n-flex n-relative n-min-h-0">
202
+ {centerPanel}
203
+ </div>
204
+ </div>
226
205
  </div>
227
206
  );
228
207
  });
@@ -0,0 +1,43 @@
1
+ import { memoGeneric } from "@noya-app/react-utils";
2
+ import React from "react";
3
+ import { isSelectableMenuItem, MenuItem } from "../internal/Menu";
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ type HorizontalTabBarProps<TabValue extends string> = {
7
+ items: MenuItem<TabValue>[];
8
+ activeValue: TabValue | undefined;
9
+ onChange: (value: TabValue) => void;
10
+ };
11
+
12
+ export const HorizontalTabBar = memoGeneric(function HorizontalTabBar<
13
+ TabValue extends string,
14
+ >({ items, activeValue, onChange }: HorizontalTabBarProps<TabValue>) {
15
+ const selectableItems = items.filter(isSelectableMenuItem);
16
+
17
+ // Don't render if there's nothing to switch between
18
+ if (selectableItems.length <= 1) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <div className="n-flex n-flex-col n-bg-sidebar-background n-flex-none">
24
+ <div className="n-flex n-flex-row n-px-3 n-py-3">
25
+ <SegmentedControl
26
+ enableCompactMode
27
+ className="n-flex-1"
28
+ value={activeValue}
29
+ onValueChange={onChange}
30
+ variant="default"
31
+ items={selectableItems.map((item) => ({
32
+ value: item.value,
33
+ title: item.title,
34
+ icon: item.icon,
35
+ tooltip: item.tooltip,
36
+ disabled: item.disabled,
37
+ }))}
38
+ />
39
+ </div>
40
+ {/* <Divider /> */}
41
+ </div>
42
+ );
43
+ });
@@ -9,15 +9,14 @@ import {
9
9
  PanelGroup,
10
10
  PanelResizeHandle,
11
11
  } from "../resizablePanels";
12
- import { DividerVertical } from "../Divider";
13
12
  import {
14
13
  CONTENT_AREA_ID,
15
14
  EDITOR_PANEL_GROUP_ID,
16
15
  LEFT_SIDEBAR_ID,
17
16
  RIGHT_SIDEBAR_ID,
18
17
  } from "./constants";
18
+ import { HorizontalTabBar } from "./HorizontalTabBar";
19
19
  import { LayoutProps } from "./types";
20
- import { VerticalTabMenu } from "./VerticalTabMenu";
21
20
 
22
21
  export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
23
22
  LeftTab extends string,
@@ -37,17 +36,21 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
37
36
  rightTabValue,
38
37
  onChangeLeftTab,
39
38
  onChangeRightTab,
40
- compactDrawerMenu,
39
+ onLeftSidebarStateChange,
40
+ onRightSidebarStateChange,
41
+ shouldAutoSave = true,
41
42
  }: LayoutProps<LeftTab, RightTab>) {
42
43
  const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
44
+ const containerRef = useRef<HTMLDivElement>(null);
43
45
 
44
46
  const autoSaveId = useMemo(() => {
47
+ if (!shouldAutoSave) return undefined;
45
48
  return autoSavePrefix
46
- ? `${autoSavePrefix}--v3--${EDITOR_PANEL_GROUP_ID}`
49
+ ? `${autoSavePrefix}--v5--${EDITOR_PANEL_GROUP_ID}`
47
50
  : Math.random().toString(36).substring(2, 15);
48
- }, [autoSavePrefix]);
51
+ }, [autoSavePrefix, shouldAutoSave]);
49
52
 
50
- // Track collapsed state based on layout changes
53
+ // Track collapsed state for rendering purposes (PanelGroup owns the source of truth)
51
54
  const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(
52
55
  leftSidebarOptions.defaultCollapsed ?? false
53
56
  );
@@ -55,20 +58,72 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
55
58
  rightSidebarOptions.defaultCollapsed ?? false
56
59
  );
57
60
 
61
+ // Track previous sidebar state to avoid redundant callbacks
62
+ const prevLeftSidebarState = useRef<{ width: number; collapsed: boolean } | null>(null);
63
+ const prevRightSidebarState = useRef<{ width: number; collapsed: boolean } | null>(null);
64
+
58
65
  const handleLayoutChange = useCallback(
59
66
  (sizes: number[]) => {
60
67
  // Update collapsed state based on sizes
61
68
  const leftIndex = leftPanel ? 0 : -1;
62
69
  const rightIndex = rightPanel ? (leftPanel ? 2 : 1) : -1;
63
70
 
71
+ // Get container width for pixel calculation using ref
72
+ const containerWidth = containerRef.current?.clientWidth ?? 0;
73
+
74
+ // Detect if sizes are in pixels or percentages by checking if they sum to ~100 or ~containerWidth
75
+ const sizesSum = sizes.reduce((a, b) => a + b, 0);
76
+
77
+ // Skip if sizesSum is too small compared to containerWidth - this indicates incomplete data
78
+ // during initial render (react-resizable-panels may report partial sizes)
79
+ if (containerWidth > 0 && sizesSum < containerWidth * 0.5 && sizesSum > 100) {
80
+ return;
81
+ }
82
+
83
+ const isPixels = containerWidth > 0 && Math.abs(sizesSum - containerWidth) < Math.abs(sizesSum - 100);
84
+
64
85
  if (leftIndex >= 0) {
65
- setLeftSidebarCollapsed(sizes[leftIndex] === 0);
86
+ const collapsed = sizes[leftIndex] === 0;
87
+ setLeftSidebarCollapsed(collapsed);
88
+
89
+ // Calculate width in pixels - handle both percentage (0-100) and pixel formats
90
+ const widthPixels = isPixels
91
+ ? Math.round(sizes[leftIndex])
92
+ : containerWidth > 0
93
+ ? Math.round((sizes[leftIndex] / 100) * containerWidth)
94
+ : 0;
95
+
96
+ // Only call callback if state actually changed
97
+ if (
98
+ prevLeftSidebarState.current?.width !== widthPixels ||
99
+ prevLeftSidebarState.current?.collapsed !== collapsed
100
+ ) {
101
+ prevLeftSidebarState.current = { width: widthPixels, collapsed };
102
+ onLeftSidebarStateChange?.({ width: widthPixels, collapsed });
103
+ }
66
104
  }
67
105
  if (rightIndex >= 0) {
68
- setRightSidebarCollapsed(sizes[rightIndex] === 0);
106
+ const collapsed = sizes[rightIndex] === 0;
107
+ setRightSidebarCollapsed(collapsed);
108
+
109
+ // Calculate width in pixels - handle both percentage (0-100) and pixel formats
110
+ const widthPixels = isPixels
111
+ ? Math.round(sizes[rightIndex])
112
+ : containerWidth > 0
113
+ ? Math.round((sizes[rightIndex] / 100) * containerWidth)
114
+ : 0;
115
+
116
+ // Only call callback if state actually changed
117
+ if (
118
+ prevRightSidebarState.current?.width !== widthPixels ||
119
+ prevRightSidebarState.current?.collapsed !== collapsed
120
+ ) {
121
+ prevRightSidebarState.current = { width: widthPixels, collapsed };
122
+ onRightSidebarStateChange?.({ width: widthPixels, collapsed });
123
+ }
69
124
  }
70
125
  },
71
- [leftPanel, rightPanel]
126
+ [leftPanel, rightPanel, onLeftSidebarStateChange, onRightSidebarStateChange]
72
127
  );
73
128
 
74
129
  const handleLeftTabChange = useCallback(
@@ -107,39 +162,23 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
107
162
  [onChangeRightTab, rightSidebarRef, rightTabValue]
108
163
  );
109
164
 
110
- const showLeftTabs =
111
- !!leftSidebarOptions.showTabs &&
112
- !!leftTabItems &&
113
- // When compact drawer UI is enabled, only show the rail when the
114
- // sidebar is collapsed (for large screens/panel layout).
115
- (!compactDrawerMenu || leftSidebarCollapsed);
165
+ const showLeftTabs = !!leftSidebarOptions.showTabs && !!leftTabItems;
116
166
 
117
167
  return (
118
- <PanelGroup
119
- ref={panelGroupRef}
120
- id={EDITOR_PANEL_GROUP_ID}
121
- className="n-flex-1"
122
- direction="horizontal"
123
- autoSaveId={autoSaveId}
124
- onLayout={handleLayoutChange}
125
- >
168
+ <div ref={containerRef} className="n-flex n-flex-1">
169
+ <PanelGroup
170
+ ref={panelGroupRef}
171
+ id={EDITOR_PANEL_GROUP_ID}
172
+ className="n-flex-1"
173
+ direction="horizontal"
174
+ autoSaveId={autoSaveId}
175
+ onLayout={handleLayoutChange}
176
+ >
126
177
  {leftPanel && (
127
178
  <>
128
- {showLeftTabs && (
129
- <VerticalTabMenu
130
- tooltipSide="right"
131
- items={leftTabItems}
132
- activeValue={leftTabValue}
133
- isSidebarCollapsed={leftSidebarCollapsed}
134
- onChange={handleLeftTabChange}
135
- />
136
- )}
137
- {showLeftTabs && !leftSidebarCollapsed && (
138
- <DividerVertical className="n-h-full" />
139
- )}
140
179
  <Panel
141
180
  id={LEFT_SIDEBAR_ID}
142
- className="n-relative"
181
+ className="n-relative n-workspace-sidebar"
143
182
  order={1}
144
183
  ref={leftSidebarRef as React.RefObject<ImperativePanelHandle>}
145
184
  defaultSize={leftSidebarOptions.initialSize}
@@ -153,9 +192,23 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
153
192
  collapsible
154
193
  defaultCollapsed={leftSidebarOptions.defaultCollapsed}
155
194
  >
156
- {leftSidebarCollapsed ? null : leftPanel}
195
+ {leftSidebarCollapsed ? null : (
196
+ <div className="n-flex n-flex-col n-h-full">
197
+ {showLeftTabs && (
198
+ <HorizontalTabBar
199
+ items={leftTabItems}
200
+ activeValue={leftTabValue}
201
+ onChange={handleLeftTabChange}
202
+ />
203
+ )}
204
+ <div className="n-flex-1 n-relative n-min-h-0">{leftPanel}</div>
205
+ </div>
206
+ )}
157
207
  </Panel>
158
- <PanelResizeHandle index={0} className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
208
+ <PanelResizeHandle
209
+ index={0}
210
+ className={`n-cursor-col-resize n-w-px n-h-full ${leftSidebarCollapsed ? "" : "n-bg-divider"}`}
211
+ />
159
212
  </>
160
213
  )}
161
214
  <Panel
@@ -167,10 +220,13 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
167
220
  </Panel>
168
221
  {rightPanel && (
169
222
  <>
170
- <PanelResizeHandle index={1} className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
223
+ <PanelResizeHandle
224
+ index={1}
225
+ className={`n-cursor-col-resize n-w-px n-h-full ${rightSidebarCollapsed ? "" : "n-bg-divider"}`}
226
+ />
171
227
  <Panel
172
228
  id={RIGHT_SIDEBAR_ID}
173
- className="n-relative"
229
+ className="n-relative n-workspace-sidebar"
174
230
  order={3}
175
231
  ref={rightSidebarRef as React.RefObject<ImperativePanelHandle>}
176
232
  minSize={rightSidebarOptions.minSize}
@@ -184,22 +240,24 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
184
240
  collapsible
185
241
  defaultCollapsed={rightSidebarOptions.defaultCollapsed}
186
242
  >
187
- {rightSidebarCollapsed ? null : rightPanel}
243
+ {rightSidebarCollapsed ? null : (
244
+ <div className="n-flex n-flex-col n-h-full">
245
+ {rightSidebarOptions.showTabs && rightTabItems && (
246
+ <HorizontalTabBar
247
+ items={rightTabItems}
248
+ activeValue={rightTabValue}
249
+ onChange={handleRightTabChange}
250
+ />
251
+ )}
252
+ <div className="n-flex-1 n-relative n-min-h-0">
253
+ {rightPanel}
254
+ </div>
255
+ </div>
256
+ )}
188
257
  </Panel>
189
- {rightSidebarOptions.showTabs &&
190
- rightTabItems &&
191
- !rightSidebarCollapsed && <DividerVertical className="n-h-full" />}
192
- {rightSidebarOptions.showTabs && rightTabItems && (
193
- <VerticalTabMenu
194
- tooltipSide="left"
195
- items={rightTabItems}
196
- activeValue={rightTabValue}
197
- isSidebarCollapsed={rightSidebarCollapsed}
198
- onChange={handleRightTabChange}
199
- />
200
- )}
201
258
  </>
202
259
  )}
203
- </PanelGroup>
260
+ </PanelGroup>
261
+ </div>
204
262
  );
205
263
  });