@noya-app/noya-designsystem 0.1.45 → 0.1.47

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 (39) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +15 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +1149 -1064
  5. package/dist/index.d.ts +1149 -1064
  6. package/dist/index.js +10161 -9662
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +10465 -9972
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -3
  11. package/src/__tests__/combobox.test.ts +137 -89
  12. package/src/components/Breadcrumbs.tsx +29 -0
  13. package/src/components/Collection.tsx +74 -0
  14. package/src/components/Combobox.tsx +69 -52
  15. package/src/components/ComboboxMenu.tsx +37 -19
  16. package/src/components/Dialog.tsx +11 -19
  17. package/src/components/DropdownMenu.tsx +0 -1
  18. package/src/components/EditableText.tsx +203 -0
  19. package/src/components/Grid.tsx +243 -0
  20. package/src/components/GridView.tsx +86 -96
  21. package/src/components/List.tsx +268 -0
  22. package/src/components/ListView.tsx +10 -9
  23. package/src/components/SearchCompletionMenu.tsx +15 -31
  24. package/src/components/SegmentedControl.tsx +7 -4
  25. package/src/components/SelectMenu.tsx +9 -5
  26. package/src/components/Switch.tsx +1 -1
  27. package/src/components/Toolbar.tsx +5 -4
  28. package/src/components/TreeView.tsx +1 -0
  29. package/src/components/WorkspaceLayout.tsx +28 -4
  30. package/src/components/internal/Menu.tsx +55 -14
  31. package/src/components/internal/MenuViewport.tsx +78 -77
  32. package/src/contexts/DialogContext.tsx +53 -7
  33. package/src/index.css +2 -1
  34. package/src/index.tsx +6 -4
  35. package/src/utils/combobox.ts +115 -103
  36. package/src/utils/createSectionedMenu.ts +3 -9
  37. package/src/utils/fuzzyScorer.ts +11 -8
  38. package/src/utils/selection.ts +16 -4
  39. package/src/components/SidebarList.tsx +0 -252
@@ -0,0 +1,268 @@
1
+ import {
2
+ forwardRefGeneric,
3
+ memoGeneric,
4
+ useFileDropTarget,
5
+ } from "@noya-app/react-utils";
6
+ import React, { useImperativeHandle, useRef, useState } from "react";
7
+ import { cssVars } from "../theme";
8
+ import { cx } from "../utils/classNames";
9
+ import { updateSelection } from "../utils/selection";
10
+ import { CollectionProps, CollectionRef } from "./Collection";
11
+ import { ListView } from "./ListView";
12
+ import { TreeView } from "./TreeView";
13
+
14
+ const cssVarOverrides = {
15
+ "--icon-selected": cssVars.colors.primary,
16
+ };
17
+
18
+ const emptyArray: string[] = [];
19
+
20
+ export const List = memoGeneric(
21
+ forwardRefGeneric(function List<T, M extends string = string>(
22
+ props: CollectionProps<T, M> & { ref?: React.ForwardedRef<CollectionRef> },
23
+ ref: React.ForwardedRef<CollectionRef>
24
+ ) {
25
+ const {
26
+ className,
27
+ items,
28
+ getId,
29
+ getName,
30
+ expandable,
31
+ getExpanded,
32
+ getRenamable,
33
+ getDepth,
34
+ setExpanded,
35
+ menuItems,
36
+ onSelectMenuItem,
37
+ onRename,
38
+ renderThumbnail,
39
+ renderAction,
40
+ renderDetail,
41
+ onSelectionChange,
42
+ itemRoleDescription = "clickable item",
43
+ detailPosition = "end",
44
+ size = "medium",
45
+ testHoveredId,
46
+ testRenamingId,
47
+ scrollable = false,
48
+ acceptsDrop,
49
+ onMoveItem,
50
+ onFilesDrop,
51
+ onDoubleClickItem,
52
+ renamable,
53
+ selectedIds = emptyArray,
54
+ } = props;
55
+
56
+ const [internalHoveredId, setHoveredId] = useState<string>();
57
+ const [internalRenamingId, setRenamingId] = useState<string>();
58
+
59
+ const hoveredId = internalHoveredId ?? testHoveredId;
60
+ const renamingId = testRenamingId ?? internalRenamingId;
61
+
62
+ const handleSelect = (itemId: string, event?: ListView.ClickInfo) => {
63
+ if (!onSelectionChange) return;
64
+
65
+ setRenamingId(undefined);
66
+ const newSelectedIds = updateSelection(
67
+ items.map(getId),
68
+ selectedIds,
69
+ itemId,
70
+ event
71
+ );
72
+ onSelectionChange(newSelectedIds, event);
73
+ };
74
+
75
+ const handleMenuAction = (action: M) => {
76
+ if (!onSelectMenuItem) return;
77
+
78
+ const selectedItems = items.filter((item) =>
79
+ selectedIds.includes(getId(item))
80
+ );
81
+ onSelectMenuItem(action, selectedItems);
82
+ };
83
+
84
+ const ViewComponent = expandable ? TreeView : ListView;
85
+
86
+ const fileDropTargetRef = useRef<HTMLDivElement>(null);
87
+ const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
88
+ fileDropTargetRef,
89
+ onFilesDrop
90
+ );
91
+
92
+ useImperativeHandle(ref, () => ({
93
+ editName: (id: string) => {
94
+ setRenamingId(id);
95
+ },
96
+ }));
97
+
98
+ return (
99
+ <ViewComponent.Root
100
+ variant="bare"
101
+ className={cx(className, isDropTargetActive && "bg-primary-pastel")}
102
+ containerRef={fileDropTargetRef}
103
+ scrollable={scrollable}
104
+ data={items}
105
+ keyExtractor={getId}
106
+ role="listbox"
107
+ aria-orientation="vertical"
108
+ aria-multiselectable={true}
109
+ expandable={expandable}
110
+ sortable={expandable}
111
+ acceptsDrop={acceptsDrop}
112
+ onMoveItem={onMoveItem}
113
+ {...dropTargetProps}
114
+ renderItem={(
115
+ item: T,
116
+ _: number,
117
+ { isDragging }: { isDragging: boolean }
118
+ ) => {
119
+ const id = getId(item);
120
+ const isHovered = hoveredId === id && !isDragging;
121
+ const expanded = getExpanded?.(item);
122
+ const itemIsRenamable = getRenamable?.(item) ?? renamable;
123
+ const isSelected = selectedIds.includes(id);
124
+ const isRenaming = itemIsRenamable && onRename && renamingId === id;
125
+ const isTestingRenaming = testRenamingId === id;
126
+
127
+ const thumbnail = renderThumbnail?.(item, isSelected);
128
+ const action =
129
+ (isHovered || isSelected) &&
130
+ !isRenaming &&
131
+ renderAction?.(item, isSelected);
132
+ const end =
133
+ action ||
134
+ (detailPosition === "end" && renderDetail?.(item, isSelected));
135
+ const below =
136
+ detailPosition === "below" && renderDetail?.(item, isSelected);
137
+
138
+ return (
139
+ <ViewComponent.Row
140
+ id={id}
141
+ key={id}
142
+ role="option"
143
+ sortable={expandable}
144
+ aria-roledescription={itemRoleDescription}
145
+ aria-label={getName(item)}
146
+ aria-selected={isSelected}
147
+ className={cx("cursor-pointer p-1 rounded")}
148
+ backgroundColor={
149
+ isSelected ? cssVars.colors.primaryPastel : undefined
150
+ }
151
+ chevronClassName={cx(expandable && "relative left-2")}
152
+ style={cssVarOverrides as React.CSSProperties}
153
+ hovered={isHovered}
154
+ selected={isSelected}
155
+ expanded={expanded}
156
+ depth={getDepth?.(item)}
157
+ onHoverChange={(hovered: boolean) =>
158
+ setHoveredId(hovered ? id : undefined)
159
+ }
160
+ onPress={(e: ListView.ClickInfo) => {
161
+ handleSelect(id, e);
162
+ }}
163
+ onDoubleClick={() => {
164
+ onDoubleClickItem?.(id);
165
+ }}
166
+ onKeyDown={(e: React.KeyboardEvent) => {
167
+ if (e.key !== "Enter" && e.key !== " ") return;
168
+ e.preventDefault();
169
+ handleSelect(id, e);
170
+ if (e.key === "Enter") {
171
+ setRenamingId(id);
172
+ }
173
+ }}
174
+ menuItems={menuItems}
175
+ onSelectMenuItem={handleMenuAction}
176
+ onContextMenu={() => {
177
+ if (!isSelected) {
178
+ handleSelect(id, {
179
+ shiftKey: false,
180
+ altKey: false,
181
+ metaKey: false,
182
+ ctrlKey: false,
183
+ });
184
+ }
185
+ }}
186
+ tabIndex={0}
187
+ onClickChevron={() => {
188
+ if (!expandable) return;
189
+ setExpanded?.(item, !expanded);
190
+ }}
191
+ >
192
+ <div className="flex flex-1 items-center px-2 gap-3">
193
+ {thumbnail && (
194
+ <div
195
+ className={cx(
196
+ "rounded overflow-hidden flex-none flex items-center justify-center",
197
+ size === "medium" ? "w-8 h-8" : "w-16 h-16",
198
+ isSelected && "text-primary"
199
+ )}
200
+ style={{
201
+ backgroundColor: isSelected
202
+ ? cssVars.colors.primaryPastel
203
+ : cssVars.colors.inputBackground,
204
+ }}
205
+ tabIndex={-1}
206
+ >
207
+ {thumbnail}
208
+ </div>
209
+ )}
210
+ <div className="flex flex-col flex-1 w-0">
211
+ <div className="flex items-center min-h-[27px] flex-1 gap-2">
212
+ <div className="flex-1 w-0 flex items-center" tabIndex={-1}>
213
+ {isRenaming ? (
214
+ <ViewComponent.EditableRowTitle
215
+ value={getName(item)}
216
+ placeholder="Enter name"
217
+ className="font-medium"
218
+ autoFocus={!isTestingRenaming}
219
+ onSubmitEditing={(value: string) => {
220
+ if (value !== getName(item)) {
221
+ onRename?.(item, value);
222
+ }
223
+ setRenamingId(undefined);
224
+ }}
225
+ />
226
+ ) : (
227
+ <ViewComponent.RowTitle
228
+ className={cx(
229
+ "font-medium",
230
+ isSelected && "text-primary"
231
+ )}
232
+ >
233
+ {getName(item)}
234
+ </ViewComponent.RowTitle>
235
+ )}
236
+ </div>
237
+ {end && (
238
+ <div
239
+ className={cx(
240
+ "flex items-center gap-2",
241
+ isSelected && "text-primary"
242
+ )}
243
+ tabIndex={-1}
244
+ >
245
+ {end}
246
+ </div>
247
+ )}
248
+ </div>
249
+ {below && (
250
+ <div
251
+ className={cx(
252
+ "flex items-center",
253
+ isSelected && "text-primary"
254
+ )}
255
+ tabIndex={-1}
256
+ >
257
+ {below}
258
+ </div>
259
+ )}
260
+ </div>
261
+ </div>
262
+ </ViewComponent.Row>
263
+ );
264
+ }}
265
+ />
266
+ );
267
+ })
268
+ );
@@ -147,9 +147,10 @@ function ListViewEditableRowTitle({
147
147
  // the `onBlur` fires correctly.
148
148
  element.focus();
149
149
 
150
+ // must be a delay of 1ms or more to ensure any radix ui components have finished any focus behaviors
150
151
  setTimeout(() => {
151
152
  element.select();
152
- }, 0);
153
+ }, 1);
153
154
  }, [autoFocus]);
154
155
 
155
156
  return (
@@ -1124,15 +1125,15 @@ export namespace ListView {
1124
1125
  export const DragIndicator = ListViewDragIndicatorElement;
1125
1126
  export const rowHeight = ROW_HEIGHT;
1126
1127
  export const sectionHeaderLabelHeight = SECTION_HEADER_LABEL_HEIGHT;
1127
- export const calculateHeight = (
1128
- items: number,
1129
- headerCount: number,
1130
- headerVariant: ListViewSectionHeaderVariant
1131
- ) => {
1128
+ export const calculateHeight = ({
1129
+ menuItemsCount,
1130
+ sectionHeaderCount,
1131
+ }: {
1132
+ menuItemsCount: number;
1133
+ sectionHeaderCount: number;
1134
+ }) => {
1132
1135
  return (
1133
- items * rowHeight +
1134
- headerCount *
1135
- (headerVariant === "label" ? sectionHeaderLabelHeight : rowHeight)
1136
+ menuItemsCount * rowHeight + sectionHeaderCount * sectionHeaderLabelHeight
1136
1137
  );
1137
1138
  };
1138
1139
  export const normalizeDestinationIndex = normalizeListDestinationIndex;
@@ -1,13 +1,15 @@
1
1
  import {
2
2
  ComboboxMenu,
3
- ComboboxOption,
4
3
  Divider,
5
4
  IScoredItem,
6
5
  InputField,
7
6
  ListView,
7
+ SelectableMenuItem,
8
8
  Small,
9
+ cssVars,
9
10
  fuzzyFilter,
10
11
  getNextIndex,
12
+ isSelectableMenuItem,
11
13
  } from "@noya-app/noya-designsystem";
12
14
  import { getCurrentPlatform, handleKeyboardEvent } from "@noya-app/noya-keymap";
13
15
  import React, {
@@ -32,10 +34,10 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
32
34
  items,
33
35
  width = 200,
34
36
  }: {
35
- onSelect: (item: ComboboxOption) => void;
36
- onHover?: (item: ComboboxOption) => void;
37
+ onSelect: (item: SelectableMenuItem<string>) => void;
38
+ onHover?: (item: SelectableMenuItem<string>) => void;
37
39
  onClose: () => void;
38
- items: ComboboxOption[];
40
+ items: SelectableMenuItem<string>[];
39
41
  width?: number;
40
42
  },
41
43
  forwardedRef: React.ForwardedRef<ISearchCompletionMenu>
@@ -56,12 +58,14 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
56
58
  const results = useMemo(
57
59
  () =>
58
60
  fuzzyFilter({
59
- items: items.map((item) => item.name),
61
+ items: items.map((item) =>
62
+ typeof item.title === "string" ? item.title : item.value
63
+ ),
60
64
  query: search,
61
65
  }).map((item) => ({
62
66
  ...item,
63
67
  ...items[item.index],
64
- })) as (ComboboxOption & IScoredItem)[],
68
+ })) as (SelectableMenuItem<string> & IScoredItem)[],
65
69
  [items, search]
66
70
  );
67
71
 
@@ -96,7 +100,7 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
96
100
  };
97
101
 
98
102
  const selectAndClose = useCallback(
99
- (item: ComboboxOption) => {
103
+ (item: SelectableMenuItem<string>) => {
100
104
  onSelect(item);
101
105
  onClose();
102
106
  },
@@ -152,32 +156,11 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
152
156
  borderRadius: 0,
153
157
  outline: "none",
154
158
  boxShadow: "none",
155
- backgroundColor: "white",
159
+ backgroundColor: cssVars.colors.inputBackground,
156
160
  padding: "4px 12px",
157
161
  }}
158
162
  onChange={setSearch}
159
163
  onKeyDown={handleKeyDown}
160
- // onBlur={(event) => {
161
- // const isWithinPopover =
162
- // event.relatedTarget &&
163
- // event.relatedTarget instanceof HTMLElement &&
164
- // event.relatedTarget.role === 'dialog';
165
-
166
- // if (isWithinPopover) {
167
- // inputRef.current?.focus();
168
-
169
- // event.stopPropagation();
170
- // event.preventDefault();
171
- // }
172
- // }}
173
- // onPointerDown={(event) => {
174
- // event.stopPropagation();
175
- // event.preventDefault();
176
- // }}
177
- // onFocusCapture={(event) => {
178
- // event.stopPropagation();
179
- // event.preventDefault();
180
- // }}
181
164
  />
182
165
  </InputField.Root>
183
166
  <Divider />
@@ -188,8 +171,9 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
188
171
  items={results}
189
172
  selectedIndex={index}
190
173
  onSelectItem={(item) => {
191
- if (item.type === "sectionHeader") return;
192
- selectAndClose(item);
174
+ if (isSelectableMenuItem(item)) {
175
+ selectAndClose(item);
176
+ }
193
177
  }}
194
178
  onHoverIndex={setIndex}
195
179
  />
@@ -2,16 +2,16 @@ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
2
2
  import React, {
3
3
  createContext,
4
4
  forwardRef,
5
- memo,
6
5
  ReactNode,
7
6
  useCallback,
8
7
  useContext,
9
8
  useMemo,
10
9
  } from "react";
10
+ import { memoGeneric } from "../../../noya-react-utils/src/utils/reactGenerics";
11
11
  import { useLabel } from "../hooks/useLabel";
12
12
  import { cx } from "../utils/classNames";
13
13
  import { renderIcon } from "./Icons";
14
- import { RegularMenuItem } from "./internal/Menu";
14
+ import { SelectableMenuItem } from "./internal/Menu";
15
15
  import { Tooltip } from "./Tooltip";
16
16
 
17
17
  type SegmentedControlColorScheme = "primary" | "secondary";
@@ -43,7 +43,10 @@ export type SegmentedControlItemProps<T extends string> = {
43
43
  className?: string;
44
44
  style?: React.CSSProperties;
45
45
  tooltip?: ReactNode;
46
- } & Pick<RegularMenuItem<T>, "value" | "disabled" | "icon" | "role">;
46
+ } & Pick<
47
+ SelectableMenuItem<T>,
48
+ "value" | "disabled" | "icon" | "role" | "title"
49
+ >;
47
50
 
48
51
  const SegmentedControlItem = forwardRef(function SegmentedControlItem<
49
52
  T extends string,
@@ -104,7 +107,7 @@ const Separator = ({ transparent }: { transparent: boolean }) => (
104
107
  />
105
108
  );
106
109
 
107
- export const SegmentedControl = memo(function SegmentedControl<
110
+ export const SegmentedControl = memoGeneric(function SegmentedControl<
108
111
  T extends string,
109
112
  >({
110
113
  id: idProp,
@@ -14,9 +14,15 @@ import React, {
14
14
  } from "react";
15
15
  import { useLabel, useLabelPosition } from "../hooks/useLabel";
16
16
  import { cx } from "../utils/classNames";
17
+
17
18
  import { Button } from "./Button";
18
19
  import { renderIcon } from "./Icons";
19
- import { MenuItem, RegularMenuItem, styles } from "./internal/Menu";
20
+ import {
21
+ isSelectableMenuItem,
22
+ MenuItem,
23
+ SelectableMenuItem,
24
+ styles,
25
+ } from "./internal/Menu";
20
26
  import { MenuComponents, MenuViewport } from "./internal/MenuViewport";
21
27
  import { InsetLabel } from "./Label";
22
28
 
@@ -141,10 +147,8 @@ export const SelectMenu = memoGeneric(function SelectMenu<
141
147
  ...props
142
148
  }: Props<T>) {
143
149
  const selectedItem = items.find(
144
- (item): item is RegularMenuItem<T> =>
145
- item.type !== "separator" &&
146
- item.type !== "sectionHeader" &&
147
- item.value === value
150
+ (item): item is SelectableMenuItem<T> =>
151
+ isSelectableMenuItem(item) && item.value === value
148
152
  );
149
153
  const icon = selectedItem?.icon;
150
154
  const [internalOpen, setInternalOpen] = useState(open);
@@ -38,7 +38,7 @@ export const Switch = function Switch({
38
38
  onChange(newValue);
39
39
  }}
40
40
  className={cx(
41
- "all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer [webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
41
+ "all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer ring-offset-background [webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
42
42
  colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
43
43
  "focus:z-interactable",
44
44
  className
@@ -1,14 +1,15 @@
1
1
  import { DropdownChevronIcon } from "@noya-app/noya-icons";
2
2
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
3
3
  import React from "react";
4
+
4
5
  import { BaseToolbar } from "./BaseToolbar";
5
6
  import { Button } from "./Button";
6
7
  import { DropdownMenu } from "./DropdownMenu";
7
8
  import { renderIcon } from "./Icons";
8
- import type { MenuItem } from "./internal/Menu";
9
9
  import {
10
10
  getKeyboardShortcutsForMenuItems,
11
- SectionHeader,
11
+ isSelectableMenuItem,
12
+ MenuItem,
12
13
  } from "./internal/Menu";
13
14
  import { Spacer } from "./Spacer";
14
15
 
@@ -86,9 +87,9 @@ export function ToolbarMenu<T extends string>({
86
87
  <>
87
88
  {items.map((item, i) => {
88
89
  if (item.type === "separator") return null;
89
- if (item.type === "sectionHeader") return <SectionHeader {...item} />;
90
+ if (item.type === "sectionHeader") return null;
90
91
 
91
- if (!item.items) {
92
+ if (isSelectableMenuItem(item)) {
92
93
  return (
93
94
  <Button
94
95
  key={i}
@@ -14,6 +14,7 @@ type TreeRowBaseProps = {
14
14
  expanded?: boolean;
15
15
  chevronClassName?: string;
16
16
  onClickChevron?: ({ altKey }: { altKey: boolean }) => void;
17
+ onDoubleClick?: () => void;
17
18
  };
18
19
 
19
20
  type TreeViewRowProps<MenuItemType extends string> =
@@ -73,17 +73,17 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
73
73
  children,
74
74
  onChangeLayoutState,
75
75
  leftOptions: {
76
- initialSize: leftInitialSize = 280,
77
76
  minSize: leftMinSize = 200,
78
77
  maxSize: leftMaxSize = 500,
78
+ initialSize: leftInitialSize = 280,
79
79
  resizable: leftResizable = true,
80
80
  style: leftStyle,
81
81
  className: leftClassName,
82
82
  } = {},
83
83
  rightOptions: {
84
- initialSize: rightInitialSize = 280,
85
84
  minSize: rightMinSize = 200,
86
85
  maxSize: rightMaxSize = 500,
86
+ initialSize: rightInitialSize = 280,
87
87
  resizable: rightResizable = true,
88
88
  style: rightStyle,
89
89
  className: rightClassName,
@@ -99,8 +99,21 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
99
99
  : (size / windowSize.width) * 100;
100
100
  }
101
101
 
102
- const leftSidebarPercentage = getPercentage(leftInitialSize);
103
- const rightSidebarPercentage = getPercentage(rightInitialSize);
102
+ function clampPercentage(
103
+ value: number,
104
+ min: number | undefined,
105
+ max: number | undefined
106
+ ) {
107
+ let result = value;
108
+ if (min !== undefined) {
109
+ result = Math.max(result, min);
110
+ }
111
+ if (max !== undefined) {
112
+ result = Math.min(result, max);
113
+ }
114
+ return result;
115
+ }
116
+
104
117
  const leftSidebarMinPercentage =
105
118
  leftMinSize !== undefined ? getPercentage(leftMinSize) : undefined;
106
119
  const rightSidebarMinPercentage =
@@ -110,6 +123,17 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
110
123
  const rightSidebarMaxPercentage =
111
124
  rightMaxSize !== undefined ? getPercentage(rightMaxSize) : undefined;
112
125
 
126
+ const leftSidebarPercentage = clampPercentage(
127
+ getPercentage(leftInitialSize),
128
+ leftSidebarMinPercentage,
129
+ leftSidebarMaxPercentage
130
+ );
131
+ const rightSidebarPercentage = clampPercentage(
132
+ getPercentage(rightInitialSize),
133
+ rightSidebarMinPercentage,
134
+ rightSidebarMaxPercentage
135
+ );
136
+
113
137
  const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
114
138
  const leftSidebarRef = useRef<ImperativePanelHandle>(null);
115
139
  const rightSidebarRef = useRef<ImperativePanelHandle>(null);