@noya-app/noya-designsystem 0.1.61 → 0.1.63

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 (43) hide show
  1. package/.eslintrc.js +1 -5
  2. package/.turbo/turbo-build.log +10 -10
  3. package/.turbo/turbo-lint.log +0 -1
  4. package/CHANGELOG.md +16 -0
  5. package/dist/index.css +1 -1
  6. package/dist/index.d.mts +147 -69
  7. package/dist/index.d.ts +147 -69
  8. package/dist/index.js +1347 -1063
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +756 -473
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +4 -4
  13. package/src/components/Button.tsx +28 -20
  14. package/src/components/DimensionInput.tsx +1 -1
  15. package/src/components/DraggableMenuButton.tsx +20 -4
  16. package/src/components/DropdownMenu.tsx +31 -5
  17. package/src/components/IconButton.tsx +2 -2
  18. package/src/components/InputField.tsx +5 -5
  19. package/src/components/Label.tsx +0 -2
  20. package/src/components/LabeledField.tsx +36 -34
  21. package/src/components/Popover.tsx +12 -3
  22. package/src/components/SegmentedControl.tsx +1 -1
  23. package/src/components/SelectMenu.tsx +3 -3
  24. package/src/components/SelectionToolbar.tsx +28 -7
  25. package/src/components/Slider.tsx +1 -1
  26. package/src/components/Spacer.tsx +0 -1
  27. package/src/components/Tabs.tsx +46 -0
  28. package/src/components/Toolbar.tsx +106 -11
  29. package/src/components/Tooltip.tsx +8 -8
  30. package/src/components/file-explorer/FileExplorerLayout.tsx +12 -6
  31. package/src/components/internal/Menu.tsx +19 -2
  32. package/src/components/internal/MenuViewport.tsx +4 -3
  33. package/src/components/workspace/DrawerWorkspaceLayout.tsx +7 -3
  34. package/src/components/workspace/PanelWorkspaceLayout.tsx +103 -3
  35. package/src/components/workspace/WorkspaceLayout.tsx +96 -15
  36. package/src/components/workspace/renderPanelChildren.tsx +12 -0
  37. package/src/components/workspace/types.ts +19 -3
  38. package/src/hooks/useLabel.ts +10 -10
  39. package/src/hooks/usePreservePanelSize.tsx +1 -1
  40. package/src/index.css +10 -6
  41. package/src/index.tsx +2 -0
  42. package/src/utils/editableBlockStyles.ts +10 -0
  43. package/src/hooks/useWindowSize.tsx +0 -26
@@ -0,0 +1,46 @@
1
+ import { useControlledOrUncontrolled } from "@noya-app/react-utils";
2
+ import * as React from "react";
3
+ import { DetailedHTMLProps, HTMLAttributes } from "react";
4
+ import { cx } from "../utils/classNames";
5
+ import { Divider } from "./Divider";
6
+ import { isSelectableMenuItem, MenuItem } from "./internal/Menu";
7
+ import { SegmentedControl } from "./SegmentedControl";
8
+
9
+ export type TabsProps<T extends string = string> = {
10
+ value?: T;
11
+ onValueChange?: (value: T) => void;
12
+ items: MenuItem<T>[];
13
+ renderContent: ({ value }: { value: T }) => React.ReactNode;
14
+ } & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
15
+
16
+ export function Tabs<T extends string = string>({
17
+ value,
18
+ onValueChange,
19
+ items,
20
+ renderContent,
21
+ ...props
22
+ }: TabsProps<T>) {
23
+ const selectableItems = items.filter(isSelectableMenuItem);
24
+
25
+ const [activeTab, setActiveTab] = useControlledOrUncontrolled({
26
+ defaultValue: selectableItems.at(0)?.value ?? ("" as T),
27
+ value,
28
+ onChange: onValueChange,
29
+ });
30
+
31
+ return (
32
+ <div {...props} className={cx("flex flex-col", props.className)}>
33
+ <div className="flex flex-row px-3">
34
+ <SegmentedControl
35
+ className="flex-1"
36
+ value={activeTab}
37
+ onValueChange={setActiveTab}
38
+ variant="tabs"
39
+ items={selectableItems}
40
+ />
41
+ </div>
42
+ <Divider />
43
+ {renderContent({ value: activeTab })}
44
+ </div>
45
+ );
46
+ }
@@ -1,5 +1,5 @@
1
1
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
- import React, { ComponentProps } from "react";
2
+ import React, { ComponentProps, ReactNode } from "react";
3
3
 
4
4
  import { memoGeneric } from "@noya-app/react-utils";
5
5
  import { BaseToolbar } from "./BaseToolbar";
@@ -9,17 +9,17 @@ import { DropdownMenu } from "./DropdownMenu";
9
9
  import {
10
10
  getKeyboardShortcutsForMenuItems,
11
11
  isSelectableMenuItem,
12
+ KeyboardShortcut,
12
13
  MenuItem,
14
+ PopoverMenuItem,
13
15
  SelectableMenuItem,
14
16
  SubMenuItem,
15
17
  } from "./internal/Menu";
18
+ import { Popover } from "./Popover";
19
+ import { Spacer } from "./Spacer";
16
20
  import { Tooltip } from "./Tooltip";
17
21
 
18
- const iconButtonStyle = {
19
- height: 27,
20
- width: 27,
21
- minWidth: 27,
22
- };
22
+ const SIDE_OFFSET = 6;
23
23
 
24
24
  export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
25
25
  T extends string,
@@ -55,24 +55,101 @@ export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
55
55
  );
56
56
  });
57
57
 
58
+ export const ToolbarShortcut = ({
59
+ shortcut,
60
+ label,
61
+ }: {
62
+ shortcut: string;
63
+ label?: ReactNode;
64
+ }) => {
65
+ return (
66
+ <div className="flex items-center">
67
+ {label}
68
+ <Spacer.Horizontal size={6} />
69
+ <KeyboardShortcut shortcut={shortcut} />
70
+ </div>
71
+ );
72
+ };
73
+
74
+ export const ToolbarMenuPopover = memoGeneric(function ToolbarMenuPopover({
75
+ item,
76
+ portalContainer,
77
+ }: {
78
+ item: PopoverMenuItem;
79
+ portalContainer?: HTMLElement | null;
80
+ }) {
81
+ const {
82
+ disabled,
83
+ icon,
84
+ title,
85
+ tooltip,
86
+ content: itemContent,
87
+ checked,
88
+ } = item;
89
+ const [open, setOpen] = React.useState(false);
90
+
91
+ const content = (
92
+ <span>
93
+ <Popover
94
+ open={open}
95
+ onOpenChange={setOpen}
96
+ side="top"
97
+ showArrow={false}
98
+ sideOffset={SIDE_OFFSET}
99
+ portalContainer={portalContainer}
100
+ trigger={
101
+ <Button
102
+ disabled={disabled}
103
+ active={checked || open}
104
+ icon={icon}
105
+ iconRight="DropdownChevronIcon"
106
+ >
107
+ {title}
108
+ </Button>
109
+ }
110
+ >
111
+ {typeof itemContent === "function"
112
+ ? itemContent({ close: () => setOpen(false) })
113
+ : itemContent}
114
+ </Popover>
115
+ </span>
116
+ );
117
+
118
+ return tooltip && !open ? (
119
+ <Tooltip
120
+ sideOffset={SIDE_OFFSET}
121
+ side="top"
122
+ content={tooltip}
123
+ portalContainer={portalContainer}
124
+ >
125
+ {content}
126
+ </Tooltip>
127
+ ) : (
128
+ content
129
+ );
130
+ });
131
+
58
132
  export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
59
133
  T extends string,
60
134
  >({
61
135
  item,
62
136
  onSelectMenuItem,
63
137
  as,
138
+ activeValue,
139
+ tooltipSide,
64
140
  }: {
65
141
  item: SelectableMenuItem<T>;
66
142
  onSelectMenuItem?: (value: T) => void;
67
143
  as?: ComponentProps<typeof Button>["as"];
144
+ activeValue?: T;
145
+ tooltipSide?: ComponentProps<typeof Tooltip>["side"];
68
146
  }) {
69
147
  const content = (
70
148
  <Button
71
149
  as={as}
72
150
  disabled={item.disabled}
73
- active={item.checked}
151
+ active={item.checked || activeValue === item.value}
74
152
  icon={item.icon}
75
- style={item.icon && !item.title ? iconButtonStyle : undefined}
76
153
  {...(as === "a" && { href: item.value })}
77
154
  onClick={() => {
78
155
  if (onSelectMenuItem && item.value) {
@@ -85,7 +162,7 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
85
162
  );
86
163
 
87
164
  return item.tooltip ? (
88
- <Tooltip sideOffset={10} content={item.tooltip}>
165
+ <Tooltip sideOffset={SIDE_OFFSET} content={item.tooltip} side={tooltipSide}>
89
166
  {content}
90
167
  </Tooltip>
91
168
  ) : (
@@ -99,23 +176,32 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
99
176
  item,
100
177
  onSelectMenuItem,
101
178
  buttonAs,
179
+ activeValue,
180
+ tooltipSide,
181
+ portalContainer,
102
182
  }: {
103
183
  item: MenuItem<T>;
104
184
  onSelectMenuItem?: (value: T) => void;
105
185
  buttonAs?: ComponentProps<typeof Button>["as"];
186
+ activeValue?: T;
187
+ tooltipSide?: ComponentProps<typeof Tooltip>["side"];
188
+ portalContainer?: HTMLElement | null;
106
189
  }) {
107
190
  if (item.type === "sectionHeader") return null;
108
-
109
191
  if (item.type === "separator") {
110
192
  return <DividerVertical overflow={4} />;
111
193
  }
112
-
194
+ if (item.type === "popover") {
195
+ return <ToolbarMenuPopover item={item} portalContainer={portalContainer} />;
196
+ }
113
197
  if (isSelectableMenuItem(item)) {
114
198
  return (
115
199
  <ToolbarMenuButton
116
200
  item={item}
117
201
  onSelectMenuItem={onSelectMenuItem}
118
202
  as={buttonAs}
203
+ activeValue={activeValue}
204
+ tooltipSide={tooltipSide}
119
205
  />
120
206
  );
121
207
  }
@@ -129,10 +215,16 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
129
215
  items,
130
216
  onSelectMenuItem,
131
217
  buttonAs,
218
+ activeValue,
219
+ tooltipSide,
220
+ portalContainer,
132
221
  }: {
133
222
  items: MenuItem<T>[];
134
223
  onSelectMenuItem?: (value: T) => void;
135
224
  buttonAs?: ComponentProps<typeof Button>["as"];
225
+ activeValue?: T;
226
+ tooltipSide?: ComponentProps<typeof Tooltip>["side"];
227
+ portalContainer?: HTMLElement | null;
136
228
  }) {
137
229
  return (
138
230
  <>
@@ -143,6 +235,9 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
143
235
  item={item}
144
236
  onSelectMenuItem={onSelectMenuItem}
145
237
  buttonAs={buttonAs}
238
+ activeValue={activeValue}
239
+ tooltipSide={tooltipSide}
240
+ portalContainer={portalContainer}
146
241
  />
147
242
  );
148
243
  })}
@@ -1,25 +1,26 @@
1
1
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
2
  import * as React from "react";
3
+ import { ComponentProps } from "react";
3
4
  import {
4
5
  portalScopeProps,
5
6
  usePortalScopeId,
6
7
  } from "../contexts/PortalScopeContext";
7
8
  import { textStyles } from "./Text";
8
9
 
9
- // const Arrow = styled(TooltipPrimitive.Arrow)(({ theme }) => ({
10
- // fill: theme.colors.popover.background,
11
- // }));
12
-
13
10
  interface Props {
14
11
  children: React.ReactNode;
15
12
  content: React.ReactNode;
16
13
  sideOffset?: number;
14
+ side?: ComponentProps<typeof TooltipPrimitive.Content>["side"];
15
+ portalContainer?: HTMLElement | null;
17
16
  }
18
17
 
19
18
  export const Tooltip = React.memo(function Tooltip({
20
19
  children,
21
20
  content,
22
21
  sideOffset = 2,
22
+ side = "bottom",
23
+ portalContainer,
23
24
  }: Props) {
24
25
  const portalScopeId = usePortalScopeId();
25
26
 
@@ -27,17 +28,16 @@ export const Tooltip = React.memo(function Tooltip({
27
28
  <TooltipPrimitive.Provider>
28
29
  <TooltipPrimitive.Root>
29
30
  <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
30
- <TooltipPrimitive.Portal>
31
+ <TooltipPrimitive.Portal container={portalContainer}>
31
32
  <TooltipPrimitive.Content
32
33
  {...portalScopeProps(portalScopeId)}
33
- side="bottom"
34
+ side={side}
34
35
  align="center"
35
36
  sideOffset={sideOffset}
36
37
  collisionPadding={8}
37
- className={`${textStyles.small} text-text rounded-[3px] py-small px-medium bg-popover-background shadow-popover border border-solid border-divider-strong z-menu`}
38
+ className={`${textStyles.small} text-text rounded-sm p-2 bg-popover-background shadow-tooltip z-menu`}
38
39
  >
39
40
  {content}
40
- {/* <Arrow /> */}
41
41
  </TooltipPrimitive.Content>
42
42
  </TooltipPrimitive.Portal>
43
43
  </TooltipPrimitive.Root>
@@ -1,7 +1,8 @@
1
1
  import {
2
+ ActivityIndicator,
2
3
  Banner,
3
4
  BannerProps,
4
- IconButton,
5
+ Button,
5
6
  Section,
6
7
  SectionProps,
7
8
  } from "@noya-app/noya-designsystem";
@@ -28,20 +29,25 @@ export const FileExplorerLayout = ({
28
29
  export const FileExplorerUploadButton = ({
29
30
  showUploadButton,
30
31
  onUpload,
32
+ isUploading,
31
33
  children,
32
34
  }: {
33
35
  showUploadButton?: boolean;
34
36
  onUpload?: () => void;
37
+ isUploading?: boolean;
35
38
  children?: React.ReactNode;
36
39
  }) => (
37
40
  <div className="flex items-center gap-2">
38
41
  {showUploadButton && (
39
- <IconButton
40
- iconName="UploadIcon"
42
+ <Button
43
+ icon={isUploading ? <ActivityIndicator size={15} /> : "UploadIcon"}
41
44
  onClick={onUpload}
42
- aria-label="Upload media"
43
- tooltip="Upload media"
44
- />
45
+ aria-label="Upload file"
46
+ tooltip="Upload file"
47
+ disabled={isUploading}
48
+ >
49
+ Upload
50
+ </Button>
45
51
  )}
46
52
  {children}
47
53
  </div>
@@ -101,16 +101,26 @@ export type SubMenuItem<T extends string> = BaseMenuItem & {
101
101
  id: string;
102
102
  };
103
103
 
104
+ export type PopoverMenuItem = BaseMenuItem & {
105
+ type: "popover";
106
+ id: string;
107
+ content: ReactNode | ((props: { close: () => void }) => ReactNode);
108
+ shortcut?: string;
109
+ tooltip?: ReactNode;
110
+ };
111
+
104
112
  export type NonSelectableMenuItem<T extends string> =
105
113
  | SeparatorItem
106
114
  | SectionHeaderMenuItem
107
- | SubMenuItem<T>;
115
+ | SubMenuItem<T>
116
+ | PopoverMenuItem;
108
117
 
109
118
  export type MenuItem<T extends string> =
110
119
  | SeparatorItem
111
120
  | SelectableMenuItem<T>
112
121
  | SectionHeaderMenuItem
113
- | SubMenuItem<T>;
122
+ | SubMenuItem<T>
123
+ | PopoverMenuItem;
114
124
 
115
125
  type ScoredSelectableMenuItem<T extends string> = SelectableMenuItem<T> &
116
126
  IScoredItem;
@@ -142,6 +152,10 @@ export const isSelectableMenuItemWithScore = (
142
152
  ): item is ScoredSelectableMenuItem<string> =>
143
153
  isSelectableMenuItem(item) && "index" in item && "score" in item;
144
154
 
155
+ export const isPopoverMenuItem = (
156
+ item: MenuItem<string>
157
+ ): item is PopoverMenuItem => item.type === "popover";
158
+
145
159
  export const getMenuItemKey = <T extends string>(
146
160
  item: MenuItem<T>,
147
161
  index: number
@@ -153,6 +167,8 @@ export const getMenuItemKey = <T extends string>(
153
167
  return item.id;
154
168
  case "submenu":
155
169
  return item.id;
170
+ case "popover":
171
+ return item.id;
156
172
  default:
157
173
  return item.value;
158
174
  }
@@ -191,6 +207,7 @@ function getKeyboardShortcuts<T extends string>(
191
207
  ): [string, T][] {
192
208
  if (item.type === "separator") return [];
193
209
  if (item.type === "sectionHeader") return [];
210
+ if (item.type === "popover") return [];
194
211
 
195
212
  if (item.type === "submenu") return item.items.flatMap(getKeyboardShortcuts);
196
213
 
@@ -7,7 +7,7 @@ import {
7
7
  usePortalScopeId,
8
8
  } from "../../contexts/PortalScopeContext";
9
9
  import { cx } from "../../utils/classNames";
10
- import { MenuItem, SectionHeader, styles } from "./Menu";
10
+ import { isSelectableMenuItem, MenuItem, SectionHeader, styles } from "./Menu";
11
11
  import { SelectItem } from "./SelectItem";
12
12
 
13
13
  type MenuSeparatorComponent = React.FC<{ className?: string }>;
@@ -66,8 +66,7 @@ export const MenuViewport = <T extends string>({
66
66
  const portalScopeId = usePortalScopeId();
67
67
 
68
68
  const hasCheckedItem = items.some(
69
- (item) =>
70
- item.type !== "separator" && item.type !== "sectionHeader" && item.checked
69
+ (item) => isSelectableMenuItem(item) && item.checked
71
70
  );
72
71
 
73
72
  return (
@@ -91,6 +90,8 @@ export const MenuViewport = <T extends string>({
91
90
  indented={hasCheckedItem}
92
91
  />
93
92
  );
93
+ case "popover":
94
+ return null;
94
95
  case "submenu":
95
96
  if (
96
97
  item.items &&
@@ -1,4 +1,5 @@
1
- import React, { memo, useLayoutEffect, useRef, useState } from "react";
1
+ import { memoGeneric } from "@noya-app/react-utils";
2
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
3
  import {
3
4
  EDITOR_PANEL_GROUP_ID,
4
5
  LEFT_SIDEBAR_ID,
@@ -7,7 +8,10 @@ import {
7
8
  import { Drawer } from "../Drawer";
8
9
  import { LayoutProps } from "./types";
9
10
 
10
- export const DrawerWorkspaceLayout = memo(function DrawerWorkspaceLayout({
11
+ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
12
+ LeftTab extends string = string,
13
+ RightTab extends string = string,
14
+ >({
11
15
  leftPanel,
12
16
  leftSidebarRef,
13
17
  rightPanel,
@@ -15,7 +19,7 @@ export const DrawerWorkspaceLayout = memo(function DrawerWorkspaceLayout({
15
19
  centerPanel,
16
20
  internalLayoutState,
17
21
  handleChangeLayoutState,
18
- }: LayoutProps) {
22
+ }: LayoutProps<LeftTab, RightTab>) {
19
23
  const portalContainer = useRef<HTMLDivElement>(null);
20
24
  const [mounted, setMounted] = useState(false);
21
25
 
@@ -1,4 +1,5 @@
1
- import React, { memo, useRef } from "react";
1
+ import { memoGeneric } from "@noya-app/react-utils";
2
+ import React, { ComponentProps, useRef } from "react";
2
3
  import {
3
4
  ImperativePanelGroupHandle,
4
5
  ImperativePanelHandle,
@@ -13,9 +14,17 @@ import {
13
14
  RIGHT_SIDEBAR_ID,
14
15
  usePreservePanelSize,
15
16
  } from "../../hooks/usePreservePanelSize";
17
+ import { cssVarNames } from "../../theme";
18
+ import { DividerVertical } from "../Divider";
19
+ import { MenuItem } from "../internal/Menu";
20
+ import { ToolbarMenu } from "../Toolbar";
21
+ import { Tooltip } from "../Tooltip";
16
22
  import { LayoutProps } from "./types";
17
23
 
18
- export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
24
+ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
25
+ LeftTab extends string,
26
+ RightTab extends string,
27
+ >({
19
28
  leftPanel,
20
29
  rightPanel,
21
30
  centerPanel,
@@ -26,8 +35,14 @@ export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
26
35
  internalLayoutState,
27
36
  leftSidebarRef,
28
37
  rightSidebarRef,
38
+ leftTabItems,
39
+ rightTabItems,
40
+ leftTabValue,
41
+ rightTabValue,
42
+ onChangeLeftTab,
43
+ onChangeRightTab,
29
44
  handleChangeLayoutState,
30
- }: LayoutProps) {
45
+ }: LayoutProps<LeftTab, RightTab>) {
31
46
  const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
32
47
 
33
48
  usePreservePanelSize(panelGroupRef, handleChangeLayoutState);
@@ -46,6 +61,33 @@ export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
46
61
  >
47
62
  {leftPanel && (
48
63
  <>
64
+ {leftTabItems && (
65
+ <VerticalTabMenu
66
+ tooltipSide="right"
67
+ items={leftTabItems}
68
+ activeValue={leftTabValue}
69
+ isSidebarCollapsed={
70
+ internalLayoutState?.leftSidebarCollapsed ?? false
71
+ }
72
+ onChange={(value) => {
73
+ if (value === leftTabValue) {
74
+ if (leftSidebarRef.current?.isExpanded()) {
75
+ leftSidebarRef.current?.collapse();
76
+ } else {
77
+ leftSidebarRef.current?.expand();
78
+ }
79
+ } else {
80
+ if (!leftSidebarRef.current?.isExpanded()) {
81
+ leftSidebarRef.current?.expand();
82
+ }
83
+ onChangeLeftTab?.(value);
84
+ }
85
+ }}
86
+ />
87
+ )}
88
+ {leftTabItems && !internalLayoutState?.leftSidebarCollapsed && (
89
+ <DividerVertical className="h-full" />
90
+ )}
49
91
  <Panel
50
92
  id={LEFT_SIDEBAR_ID}
51
93
  className="relative"
@@ -95,8 +137,66 @@ export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
95
137
  >
96
138
  {internalLayoutState?.rightSidebarCollapsed ? null : rightPanel}
97
139
  </Panel>
140
+ {rightTabItems && !internalLayoutState?.rightSidebarCollapsed && (
141
+ <DividerVertical className="h-full" />
142
+ )}
143
+ {rightTabItems && (
144
+ <VerticalTabMenu
145
+ tooltipSide="left"
146
+ items={rightTabItems}
147
+ activeValue={rightTabValue}
148
+ isSidebarCollapsed={
149
+ internalLayoutState?.rightSidebarCollapsed ?? false
150
+ }
151
+ onChange={(value) => {
152
+ if (value === rightTabValue) {
153
+ if (rightSidebarRef.current?.isExpanded()) {
154
+ rightSidebarRef.current?.collapse();
155
+ } else {
156
+ rightSidebarRef.current?.expand();
157
+ }
158
+ } else {
159
+ if (!rightSidebarRef.current?.isExpanded()) {
160
+ rightSidebarRef.current?.expand();
161
+ }
162
+ onChangeRightTab?.(value);
163
+ }
164
+ }}
165
+ />
166
+ )}
98
167
  </>
99
168
  )}
100
169
  </PanelGroup>
101
170
  );
102
171
  });
172
+
173
+ function VerticalTabMenu<TabValue extends string>({
174
+ items,
175
+ activeValue,
176
+ isSidebarCollapsed,
177
+ onChange,
178
+ tooltipSide,
179
+ }: {
180
+ items: MenuItem<TabValue>[];
181
+ activeValue: TabValue | undefined;
182
+ isSidebarCollapsed: boolean;
183
+ onChange: (value: TabValue) => void;
184
+ tooltipSide?: ComponentProps<typeof Tooltip>["side"];
185
+ }) {
186
+ return (
187
+ <div
188
+ className="w-[47px] h-full bg-sidebar-background flex flex-col items-center py-3 gap-3"
189
+ style={{
190
+ [cssVarNames.colors.inputBackground]: "transparent",
191
+ [cssVarNames.colors.buttonBackground]: "transparent",
192
+ }}
193
+ >
194
+ <ToolbarMenu
195
+ items={items}
196
+ activeValue={isSidebarCollapsed ? undefined : activeValue}
197
+ onSelectMenuItem={onChange}
198
+ tooltipSide={tooltipSide}
199
+ />
200
+ </div>
201
+ );
202
+ }