@noya-app/noya-designsystem 0.1.44 → 0.1.46

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/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +18 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +1194 -878
  5. package/dist/index.d.ts +1194 -878
  6. package/dist/index.js +11432 -10219
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +11556 -10360
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +4 -4
  11. package/src/__tests__/combobox.test.ts +137 -89
  12. package/src/components/AnimatePresence.tsx +10 -1
  13. package/src/components/Avatar.tsx +2 -0
  14. package/src/components/Breadcrumbs.tsx +29 -0
  15. package/src/components/Checkbox.tsx +6 -1
  16. package/src/components/Collection.tsx +74 -0
  17. package/src/components/Combobox.tsx +75 -56
  18. package/src/components/ComboboxMenu.tsx +61 -28
  19. package/src/components/CommandPalette.tsx +69 -0
  20. package/src/components/ContextMenu.tsx +33 -134
  21. package/src/components/DropdownMenu.tsx +46 -155
  22. package/src/components/EditableText.tsx +203 -0
  23. package/src/components/Fade.tsx +62 -0
  24. package/src/components/Grid.tsx +243 -0
  25. package/src/components/GridView.tsx +86 -96
  26. package/src/components/InputField.tsx +109 -133
  27. package/src/components/Label.tsx +81 -7
  28. package/src/components/LabeledField.tsx +112 -0
  29. package/src/components/List.tsx +268 -0
  30. package/src/components/ListView.tsx +65 -61
  31. package/src/components/Popover.tsx +12 -1
  32. package/src/components/SearchCompletionMenu.tsx +210 -0
  33. package/src/components/SegmentedControl.tsx +12 -6
  34. package/src/components/SelectMenu.tsx +110 -126
  35. package/src/components/Slider.tsx +18 -26
  36. package/src/components/Text.tsx +1 -0
  37. package/src/components/TextArea.tsx +4 -1
  38. package/src/components/Toolbar.tsx +9 -4
  39. package/src/components/TreeView.tsx +4 -0
  40. package/src/components/WorkspaceLayout.tsx +64 -20
  41. package/src/components/internal/Menu.tsx +107 -18
  42. package/src/components/internal/MenuViewport.tsx +152 -0
  43. package/src/components/internal/SelectItem.tsx +111 -0
  44. package/src/hooks/useIndent.ts +12 -0
  45. package/src/hooks/useLabel.ts +51 -0
  46. package/src/hooks/usePreservePanelSize.tsx +50 -19
  47. package/src/index.tsx +15 -6
  48. package/src/utils/combobox.ts +116 -101
  49. package/src/utils/createSectionedMenu.ts +11 -14
  50. package/src/utils/fuzzyScorer.ts +11 -8
  51. package/src/utils/selection.ts +48 -0
@@ -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
+ );
@@ -28,7 +28,7 @@ import { useHover } from "../hooks/useHover";
28
28
  import { cssVars } from "../theme";
29
29
  import { cx } from "../utils/classNames";
30
30
  import { ContextMenu } from "./ContextMenu";
31
- import { InputField, InputFieldInputProps } from "./InputField";
31
+ import { InputField } from "./InputField";
32
32
  import { MenuItem } from "./internal/Menu";
33
33
  import { ScrollArea } from "./ScrollArea";
34
34
  import {
@@ -38,7 +38,6 @@ import {
38
38
  Sortable,
39
39
  } from "./Sortable";
40
40
  import { Spacer } from "./Spacer";
41
- import { labelTextStyles } from "./SelectMenu";
42
41
 
43
42
  export type ListRowMarginType = "none" | "top" | "bottom" | "vertical";
44
43
  export type ListRowPosition = "only" | "first" | "middle" | "last";
@@ -122,24 +121,12 @@ const ListViewRowTitle = ({
122
121
  * EditableRowTitle
123
122
  * ------------------------------------------------------------------------- */
124
123
 
125
- const ListViewEditableRowTitleElement = forwardRef(
126
- (
127
- { className, ...props }: InputFieldInputProps,
128
- forwardedRef: ForwardedRef<HTMLInputElement>
129
- ) => (
130
- <InputField.Input
131
- ref={forwardedRef}
132
- className={cx(`bg-listview-editing-background `, className)}
133
- {...props}
134
- />
135
- )
136
- );
137
-
138
124
  export interface EditableRowProps {
139
125
  value: string;
140
126
  onSubmitEditing: (value: string) => void;
141
127
  autoFocus: boolean;
142
128
  placeholder?: string;
129
+ className?: string;
143
130
  }
144
131
 
145
132
  function ListViewEditableRowTitle({
@@ -147,6 +134,7 @@ function ListViewEditableRowTitle({
147
134
  onSubmitEditing,
148
135
  autoFocus,
149
136
  placeholder,
137
+ className,
150
138
  }: EditableRowProps) {
151
139
  const inputRef = useRef<HTMLInputElement | null>(null);
152
140
 
@@ -159,30 +147,24 @@ function ListViewEditableRowTitle({
159
147
  // the `onBlur` fires correctly.
160
148
  element.focus();
161
149
 
150
+ // must be a delay of 1ms or more to ensure any radix ui components have finished any focus behaviors
162
151
  setTimeout(() => {
163
152
  element.select();
164
- }, 0);
153
+ }, 1);
165
154
  }, [autoFocus]);
166
155
 
167
156
  return (
168
- <ListViewEditableRowTitleElement
157
+ <InputField.Input
169
158
  ref={inputRef}
170
- variant="bare"
171
159
  value={value}
172
160
  placeholder={placeholder}
173
161
  onSubmit={onSubmitEditing}
174
162
  allowSubmittingWithSameValue
163
+ className={cx("-mx-1.5 -my-1 bg-listview-editing-background", className)}
175
164
  />
176
165
  );
177
166
  }
178
167
 
179
- function getPositionMargin(marginType: ListRowMarginType) {
180
- return {
181
- top: marginType === "top" || marginType === "vertical" ? 8 : 0,
182
- bottom: marginType === "bottom" || marginType === "vertical" ? 8 : 0,
183
- };
184
- }
185
-
186
168
  /* ----------------------------------------------------------------------------
187
169
  * Row
188
170
  * ------------------------------------------------------------------------- */
@@ -225,18 +207,24 @@ const RowContainer = forwardRef<
225
207
  style,
226
208
  $clickable,
227
209
  "aria-describedby": _, // Causes hydration warning
210
+ role,
211
+ "aria-label": ariaLabel,
212
+ "aria-roledescription": ariaRoleDescription,
213
+ "aria-selected": ariaSelected,
228
214
  ...props
229
215
  },
230
216
  ref
231
217
  ) => {
232
- const margin = getPositionMargin($marginType);
233
-
234
218
  return (
235
219
  <div
236
220
  ref={ref}
221
+ role={role}
222
+ aria-label={ariaLabel}
223
+ aria-roledescription={ariaRoleDescription}
224
+ aria-selected={ariaSelected ?? $selected}
237
225
  className={cx(
238
226
  $isSectionHeader && $sectionHeaderVariant === "label"
239
- ? labelTextStyles
227
+ ? "font-sans text-label uppercase leading-3 font-bold text-text-muted"
240
228
  : "font-sans text-heading5 font-normal",
241
229
  !$isSectionHeader &&
242
230
  $selected &&
@@ -254,22 +242,11 @@ const RowContainer = forwardRef<
254
242
  paddingRight: $selected ? "8px" : "12px",
255
243
  paddingBottom: $selected ? "4px" : "6px",
256
244
  paddingLeft: $selected ? "8px" : "12px",
257
- borderRadius:
258
- $variant === "padded" ? "2px" : $selected ? "4px" : "",
259
- marginLeft: $variant === "padded" ? "8px" : $selected ? "4px" : "",
260
- marginRight: $variant === "padded" ? "8px" : $selected ? "4px" : "",
261
- marginTop:
262
- $variant === "padded"
263
- ? `${margin.top}px`
264
- : $selected
265
- ? "2px"
266
- : "",
267
- marginBottom:
268
- $variant === "padded"
269
- ? `${margin.bottom}px`
270
- : $selected
271
- ? "2px"
272
- : "",
245
+ borderRadius: $selected ? "4px" : "",
246
+ marginLeft: $selected ? "4px" : "",
247
+ marginRight: $selected ? "4px" : "",
248
+ marginTop: $selected ? "2px" : "",
249
+ marginBottom: $selected ? "2px" : "",
273
250
  }),
274
251
  ...($selected &&
275
252
  !$isSectionHeader && {
@@ -351,6 +328,7 @@ const ListViewDragIndicatorElement = forwardRef<
351
328
  zIndex: 1000,
352
329
  position: "absolute",
353
330
  borderRadius: "3px",
331
+ pointerEvents: "none",
354
332
  ...($relativeDropPosition === "inside"
355
333
  ? {
356
334
  inset: 2,
@@ -396,6 +374,7 @@ interface ListViewClickInfo {
396
374
  interface ListViewRowProps<MenuItemType extends string = string> {
397
375
  id?: string;
398
376
  tabIndex?: number;
377
+ role?: string;
399
378
  selected?: boolean;
400
379
  /** @default 0 */
401
380
  depth?: number;
@@ -432,6 +411,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
432
411
  {
433
412
  id,
434
413
  tabIndex = 0,
414
+ role,
435
415
  gap,
436
416
  backgroundColor,
437
417
  selected = false,
@@ -526,6 +506,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
526
506
  onContextMenu={onContextMenu}
527
507
  $isSectionHeader={isSectionHeader}
528
508
  id={id}
509
+ role={role}
529
510
  $gap={gap ?? 0}
530
511
  $backgroundColor={backgroundColor}
531
512
  {...hoverProps}
@@ -764,14 +745,18 @@ type RenderProps<T> = {
764
745
  virtualized?: Size;
765
746
  };
766
747
 
767
- type ListViewVariant = "normal" | "padded" | "bare";
748
+ type ListViewVariant = "normal" | "bare";
768
749
 
769
750
  type ListViewSectionHeaderVariant = "normal" | "label";
770
751
 
771
- type ListViewRootProps = {
752
+ export type ListViewRootProps = {
772
753
  id?: string;
773
754
  className?: string;
774
755
  style?: CSSProperties;
756
+ role?: string;
757
+ "aria-labelledby"?: string;
758
+ "aria-orientation"?: "vertical" | "horizontal";
759
+ "aria-multiselectable"?: boolean;
775
760
  onPress?: () => void;
776
761
  scrollable?: boolean;
777
762
  expandable?: boolean;
@@ -788,6 +773,11 @@ type ListViewRootProps = {
788
773
  divider?: boolean;
789
774
  gap?: number;
790
775
  colorScheme?: ListColorScheme;
776
+ containerRef?: React.RefObject<HTMLDivElement>;
777
+ onDragOver?: React.DragEventHandler<HTMLDivElement>;
778
+ onDragEnter?: React.DragEventHandler<HTMLDivElement>;
779
+ onDragLeave?: React.DragEventHandler<HTMLDivElement>;
780
+ onDrop?: React.DragEventHandler<HTMLDivElement>;
791
781
  };
792
782
 
793
783
  const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
@@ -795,6 +785,10 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
795
785
  id,
796
786
  className,
797
787
  style,
788
+ role,
789
+ "aria-labelledby": ariaLabelledby,
790
+ "aria-orientation": ariaOrientation,
791
+ "aria-multiselectable": ariaMultiselectable,
798
792
  onPress,
799
793
  scrollable = false,
800
794
  expandable = true,
@@ -812,6 +806,11 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
812
806
  pressEventName = "onClick",
813
807
  colorScheme = "primary",
814
808
  gap = 0,
809
+ containerRef,
810
+ onDragOver,
811
+ onDragEnter,
812
+ onDragLeave,
813
+ onDrop,
815
814
  }: RenderProps<T> & ListViewRootProps,
816
815
  forwardedRef: ForwardedRef<IVirtualizedList>
817
816
  ) {
@@ -1002,17 +1001,13 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
1002
1001
  const getItemHeight = useCallback(
1003
1002
  (index: number) => {
1004
1003
  const child = getItemContextValue(index);
1005
- const margin = child?.marginType
1006
- ? getPositionMargin(child.marginType)
1007
- : { top: 0, bottom: 0 };
1008
1004
  const height =
1009
- (child?.isSectionHeader && child.sectionHeaderVariant === "label"
1005
+ child?.isSectionHeader && child.sectionHeaderVariant === "label"
1010
1006
  ? SECTION_HEADER_LABEL_HEIGHT
1011
- : ROW_HEIGHT) +
1012
- (variant === "padded" ? margin.top + margin.bottom : 0);
1007
+ : ROW_HEIGHT;
1013
1008
  return height;
1014
1009
  },
1015
- [getItemContextValue, variant]
1010
+ [getItemContextValue]
1016
1011
  );
1017
1012
 
1018
1013
  const getKey = useCallback(
@@ -1027,6 +1022,10 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
1027
1022
  <ListViewDraggingContext.Provider value={draggingContextValue}>
1028
1023
  <div
1029
1024
  id={id}
1025
+ role={role}
1026
+ aria-labelledby={ariaLabelledby}
1027
+ aria-orientation={ariaOrientation}
1028
+ aria-multiselectable={ariaMultiselectable}
1030
1029
  className={cx(
1031
1030
  `flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} `,
1032
1031
  className
@@ -1035,6 +1034,11 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
1035
1034
  {...{
1036
1035
  [pressEventName]: handleClick,
1037
1036
  }}
1037
+ ref={containerRef}
1038
+ onDragOver={onDragOver}
1039
+ onDragEnter={onDragEnter}
1040
+ onDragLeave={onDragLeave}
1041
+ onDrop={onDrop}
1038
1042
  >
1039
1043
  {withScrollable((scrollElementRef: HTMLDivElement | null) =>
1040
1044
  withSortable(
@@ -1121,15 +1125,15 @@ export namespace ListView {
1121
1125
  export const DragIndicator = ListViewDragIndicatorElement;
1122
1126
  export const rowHeight = ROW_HEIGHT;
1123
1127
  export const sectionHeaderLabelHeight = SECTION_HEADER_LABEL_HEIGHT;
1124
- export const calculateHeight = (
1125
- items: number,
1126
- headerCount: number,
1127
- headerVariant: ListViewSectionHeaderVariant
1128
- ) => {
1128
+ export const calculateHeight = ({
1129
+ menuItemsCount,
1130
+ sectionHeaderCount,
1131
+ }: {
1132
+ menuItemsCount: number;
1133
+ sectionHeaderCount: number;
1134
+ }) => {
1129
1135
  return (
1130
- items * rowHeight +
1131
- headerCount *
1132
- (headerVariant === "label" ? sectionHeaderLabelHeight : rowHeight)
1136
+ menuItemsCount * rowHeight + sectionHeaderCount * sectionHeaderLabelHeight
1133
1137
  );
1134
1138
  };
1135
1139
  export const normalizeDestinationIndex = normalizeListDestinationIndex;
@@ -1,5 +1,6 @@
1
1
  import * as PopoverPrimitive from "@radix-ui/react-popover";
2
2
  import React, { ComponentProps } from "react";
3
+ import { cx } from "../utils/classNames";
3
4
  import { IconButton } from "./IconButton";
4
5
 
5
6
  const closeStyles: React.CSSProperties = {
@@ -28,6 +29,7 @@ interface Props
28
29
  sideOffset?: number;
29
30
  onClickClose?: () => void;
30
31
  showArrow?: boolean;
32
+ className?: string;
31
33
  }
32
34
 
33
35
  export function Popover({
@@ -46,13 +48,22 @@ export function Popover({
46
48
  onInteractOutside,
47
49
  onFocusOutside,
48
50
  onClickClose,
51
+ className,
49
52
  }: Props) {
50
53
  return (
51
54
  <PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
52
55
  <PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
53
56
  <PopoverPrimitive.Portal>
54
57
  <PopoverPrimitive.Content
55
- className={`rounded font-[14px] bg-popover-background shadow-[0_2px_4px_rgba(0,0,0,0.2),0_0_12px_rgba(0,0,0,0.1)] max-h-[600px] overflow-y-auto text-text-muted z-[1000] ${variant === "large" ? "w-[680px]" : variant === "normal" ? "w-[240px]" : ""}`}
58
+ className={cx(
59
+ "rounded font-[14px] bg-popover-background shadow-[0_2px_4px_rgba(0,0,0,0.2),0_0_12px_rgba(0,0,0,0.1)] max-h-[600px] overflow-y-auto text-text-muted z-[1000]",
60
+ variant === "large"
61
+ ? "w-[680px]"
62
+ : variant === "normal"
63
+ ? "w-[240px]"
64
+ : "",
65
+ className
66
+ )}
56
67
  side={side}
57
68
  align="center"
58
69
  sideOffset={sideOffset}