@noya-app/noya-designsystem 0.1.43 → 0.1.45

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 (44) hide show
  1. package/.turbo/turbo-build.log +12 -9
  2. package/CHANGELOG.md +15 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +323 -80
  5. package/dist/index.d.ts +323 -80
  6. package/dist/index.js +2126 -1373
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2012 -1272
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -3
  11. package/src/components/AnimatePresence.tsx +10 -1
  12. package/src/components/Avatar.tsx +2 -0
  13. package/src/components/Checkbox.tsx +6 -1
  14. package/src/components/Combobox.tsx +28 -14
  15. package/src/components/ComboboxMenu.tsx +30 -15
  16. package/src/components/CommandPalette.tsx +69 -0
  17. package/src/components/ContextMenu.tsx +33 -134
  18. package/src/components/DropdownMenu.tsx +47 -155
  19. package/src/components/Fade.tsx +62 -0
  20. package/src/components/InputField.tsx +109 -133
  21. package/src/components/Label.tsx +81 -7
  22. package/src/components/LabeledField.tsx +112 -0
  23. package/src/components/ListView.tsx +55 -52
  24. package/src/components/Popover.tsx +12 -1
  25. package/src/components/SearchCompletionMenu.tsx +206 -0
  26. package/src/components/SegmentedControl.tsx +5 -2
  27. package/src/components/SelectMenu.tsx +108 -124
  28. package/src/components/SidebarList.tsx +252 -0
  29. package/src/components/Slider.tsx +18 -26
  30. package/src/components/Text.tsx +1 -0
  31. package/src/components/TextArea.tsx +4 -1
  32. package/src/components/Toolbar.tsx +6 -2
  33. package/src/components/TreeView.tsx +3 -0
  34. package/src/components/WorkspaceLayout.tsx +38 -18
  35. package/src/components/internal/Menu.tsx +58 -10
  36. package/src/components/internal/MenuViewport.tsx +151 -0
  37. package/src/components/internal/SelectItem.tsx +111 -0
  38. package/src/hooks/useIndent.ts +12 -0
  39. package/src/hooks/useLabel.ts +51 -0
  40. package/src/hooks/usePreservePanelSize.tsx +50 -19
  41. package/src/index.tsx +12 -5
  42. package/src/utils/combobox.ts +4 -1
  43. package/src/utils/createSectionedMenu.ts +11 -8
  44. package/src/utils/selection.ts +36 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -23,10 +23,10 @@
23
23
  "@dnd-kit/core": "3.1.1",
24
24
  "@dnd-kit/modifiers": "3.0.0",
25
25
  "@dnd-kit/sortable": "4.0.0",
26
- "@noya-app/noya-colorpicker": "0.1.15",
26
+ "@noya-app/noya-colorpicker": "0.1.16",
27
27
  "@noya-app/noya-utils": "0.1.3",
28
28
  "@noya-app/noya-geometry": "0.1.6",
29
- "@noya-app/noya-icons": "0.1.5",
29
+ "@noya-app/noya-icons": "0.1.6",
30
30
  "@noya-app/noya-keymap": "0.1.3",
31
31
  "@radix-ui/primitive": "^1.0.0",
32
32
  "@radix-ui/react-avatar": "^1.0.1",
@@ -32,6 +32,7 @@ type AnimatePresenceProps = {
32
32
  * "crossfade" - crossfade between the previous and next animation
33
33
  */
34
34
  mode?: "wait" | "crossfade";
35
+ className?: string;
35
36
  };
36
37
 
37
38
  const defaultDuration = 150;
@@ -42,6 +43,7 @@ type PresenceChildProps = {
42
43
  isPresent: boolean;
43
44
  onExitComplete: () => void;
44
45
  waitingToEnter?: boolean;
46
+ className?: string;
45
47
  } & Pick<
46
48
  AnimatePresenceProps,
47
49
  "animationStyles" | "children" | "duration" | "skipInitialAnimation"
@@ -71,6 +73,7 @@ const PresenceChild = ({
71
73
  animationStyles,
72
74
  onExitComplete,
73
75
  skipInitialAnimation,
76
+ className,
74
77
  }: PresenceChildProps) => {
75
78
  const [animationState, setAnimationState] = useState<AnimationState>(() =>
76
79
  skipInitialAnimation && isPresent
@@ -131,7 +134,11 @@ const PresenceChild = ({
131
134
  return null;
132
135
  }
133
136
 
134
- return <div style={mergedAnimationStyles[animationState]}>{children}</div>;
137
+ return (
138
+ <div style={mergedAnimationStyles[animationState]} className={className}>
139
+ {children}
140
+ </div>
141
+ );
135
142
  };
136
143
 
137
144
  type MountedChild = {
@@ -146,6 +153,7 @@ export const AnimatePresence = ({
146
153
  animationStyles,
147
154
  skipInitialAnimation,
148
155
  mode = "wait",
156
+ className,
149
157
  }: AnimatePresenceProps) => {
150
158
  const [mountedChildren, setMountedChildren] = useState<MountedChild[]>([]);
151
159
  const previousChildrenRef = useRef<React.ReactElement[]>([]);
@@ -250,6 +258,7 @@ export const AnimatePresence = ({
250
258
  isPresent={isPresent}
251
259
  skipInitialAnimation={skipInitialAnimation}
252
260
  onExitComplete={() => removeChild(child.key?.toString() ?? "")}
261
+ className={className}
253
262
  >
254
263
  {child}
255
264
  </PresenceChild>
@@ -140,6 +140,7 @@ export const Avatar = React.memo(
140
140
  backgroundColor,
141
141
  variant = "normal",
142
142
  children,
143
+ className,
143
144
  ...props
144
145
  }: AvatarProps,
145
146
  forwardedRef: React.ForwardedRef<HTMLDivElement>
@@ -153,6 +154,7 @@ export const Avatar = React.memo(
153
154
  size={size}
154
155
  backgroundColor={getBackgroundColor}
155
156
  variant={variant}
157
+ className={className}
156
158
  {...props}
157
159
  >
158
160
  {image ? (
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import { useLabel } from "../hooks/useLabel";
2
3
  import { cx } from "../utils/classNames";
3
4
 
4
5
  interface CheckboxProps
@@ -17,6 +18,7 @@ export const Checkbox = React.memo(
17
18
  { className, style, colorScheme = "secondary", ...props },
18
19
  ref
19
20
  ) {
21
+ const { fieldId: id } = useLabel({ fieldId: props.id });
20
22
  return (
21
23
  <div
22
24
  className={cx(
@@ -47,8 +49,10 @@ export const Checkbox = React.memo(
47
49
  focus:ring-offset-1
48
50
  focus:z-interactable
49
51
  transition-all
50
- `}
52
+ `}
53
+ aria-checked={props.checked}
51
54
  {...props}
55
+ id={id}
52
56
  />
53
57
  <svg
54
58
  className={`
@@ -66,6 +70,7 @@ export const Checkbox = React.memo(
66
70
  viewBox="0 0 16 16"
67
71
  fill="none"
68
72
  xmlns="http://www.w3.org/2000/svg"
73
+ aria-hidden="true"
69
74
  >
70
75
  <path
71
76
  d="M3 6L4.5 7.5L8 4"
@@ -11,6 +11,7 @@ import React, {
11
11
  useRef,
12
12
  useState,
13
13
  } from "react";
14
+ import { useLabel } from "../hooks/useLabel";
14
15
  import { cx } from "../utils/classNames";
15
16
  import {
16
17
  ComboboxItem,
@@ -45,7 +46,7 @@ export type BaseComboboxProps = {
45
46
 
46
47
  export type StringModeOnlyProps = {
47
48
  mode: "string";
48
- initialValue?: string;
49
+ value?: string;
49
50
  onChange?: (value: string) => void;
50
51
  onSelectItem?: (value: string) => void;
51
52
  onBlur?: (didSubmit: boolean, value: string) => void;
@@ -57,7 +58,7 @@ export type StringModeProps = BaseComboboxProps & StringModeOnlyProps;
57
58
 
58
59
  export type OptionModeOnlyProps = {
59
60
  mode?: "option";
60
- initialValue?: ComboboxOption;
61
+ value?: ComboboxOption;
61
62
  onChange?: (value: ComboboxOption) => void;
62
63
  onSelectItem?: (value: ComboboxOption) => void;
63
64
  onBlur?: (didSubmit: boolean, value: ComboboxOption) => void;
@@ -78,14 +79,13 @@ export interface ComboboxRef {
78
79
  export const Combobox = memo(
79
80
  forwardRef(function Combobox(
80
81
  {
81
- id,
82
82
  loading,
83
83
  placeholder,
84
84
  items,
85
85
  size,
86
86
  style,
87
87
  className,
88
- label,
88
+ label: labelProp,
89
89
  children,
90
90
  hideMenuWhenEmptyValue = false,
91
91
  readOnly = false,
@@ -103,7 +103,7 @@ export const Combobox = memo(
103
103
  if (rest.mode === "string") {
104
104
  return {
105
105
  mode: rest.mode,
106
- initialValue: rest.initialValue,
106
+ value: rest.value,
107
107
  onChange: rest.onChange,
108
108
  onSelectItem: rest.onSelectItem,
109
109
  onHoverItem: rest.onHoverItem,
@@ -113,7 +113,7 @@ export const Combobox = memo(
113
113
  } else {
114
114
  return {
115
115
  mode: rest.mode,
116
- initialValue: rest.initialValue,
116
+ value: rest.value,
117
117
  onChange: rest.onChange,
118
118
  onSelectItem: rest.onSelectItem,
119
119
  onHoverItem: rest.onHoverItem,
@@ -122,7 +122,7 @@ export const Combobox = memo(
122
122
  }
123
123
  }, [
124
124
  rest.mode,
125
- rest.initialValue,
125
+ rest.value,
126
126
  rest.onChange,
127
127
  rest.onSelectItem,
128
128
  rest.onHoverItem,
@@ -131,10 +131,17 @@ export const Combobox = memo(
131
131
  ]);
132
132
 
133
133
  const ref = useRef<HTMLInputElement>(null);
134
- const initialValueString =
135
- typeof props.initialValue === "string"
136
- ? props.initialValue
137
- : props.initialValue?.name ?? "";
134
+
135
+ function getInitialValueString() {
136
+ if (props.mode === "string") {
137
+ return props.value ?? "";
138
+ } else {
139
+ return props.value?.name ?? "";
140
+ }
141
+ }
142
+
143
+ const initialValueString = getInitialValueString();
144
+
138
145
  const [comboboxState] = useState(
139
146
  () =>
140
147
  new ComboboxState(
@@ -147,12 +154,19 @@ export const Combobox = memo(
147
154
  const [isFocused, setIsFocused] = useState(false);
148
155
  const listRef = useRef<ListView.VirtualizedList>(null);
149
156
  const [shouldBlur, setShouldBlur] = useState(false);
157
+ const { fieldId: id } = useLabel({
158
+ fieldId: rest.id,
159
+ });
150
160
 
151
161
  // Keep state in sync with items prop
152
162
  useEffect(() => {
153
163
  comboboxState.setItems(items);
154
164
  }, [items, comboboxState]);
155
165
 
166
+ useEffect(() => {
167
+ comboboxState.setFilter(initialValueString);
168
+ }, [comboboxState, initialValueString]);
169
+
156
170
  const { filter, selectedIndex, filteredItems } = state;
157
171
 
158
172
  const shouldShowMenu =
@@ -401,9 +415,10 @@ export const Combobox = memo(
401
415
 
402
416
  useEffect(() => {
403
417
  if (!shouldBlur) return;
418
+ if (document.activeElement !== ref.current) return;
419
+ setShouldBlur(false);
404
420
  ref.current?.focus();
405
421
  handleBlur();
406
- setShouldBlur(false);
407
422
  }, [shouldBlur, handleBlur]);
408
423
 
409
424
  return (
@@ -488,11 +503,10 @@ export const Combobox = memo(
488
503
  )}
489
504
  {children}
490
505
  {loading && (
491
- <InputField.Label>
506
+ <InputField.Label htmlFor={id}>
492
507
  <ActivityIndicator />
493
508
  </InputField.Label>
494
509
  )}
495
- <InputField.Label>{label}</InputField.Label>
496
510
  {!readOnly && (
497
511
  <InputField.Button variant="floating" onClick={handleChevronClick}>
498
512
  <DropdownChevronIcon
@@ -1,10 +1,13 @@
1
1
  import { Size } from "@noya-app/noya-geometry";
2
+ import { CheckIcon } from "@noya-app/noya-icons";
2
3
  import React, { ForwardedRef, forwardRef, memo } from "react";
3
4
  import { cx } from "../utils/classNames";
4
5
  import { InternalComboboxItem } from "../utils/combobox";
5
6
  import { fuzzyTokenize } from "../utils/fuzzyScorer";
7
+ import { renderIcon } from "./Icons";
6
8
  import { IVirtualizedList, ListView } from "./ListView";
7
9
  import { Spacer } from "./Spacer";
10
+ import { KeyboardShortcut, styles } from "./internal/Menu";
8
11
 
9
12
  interface ComboboxMenuProps {
10
13
  items: InternalComboboxItem[];
@@ -55,28 +58,40 @@ export const ComboboxMenu = memo(
55
58
  <ListView.Row
56
59
  key={item.id}
57
60
  selected={i === selectedIndex}
58
- onPress={() => onSelectItem(item)}
61
+ disabled={item.disabled}
62
+ onPress={() => !item.disabled && onSelectItem(item)}
59
63
  onHoverChange={(hovered) => {
60
- if (hovered) {
64
+ if (hovered && !item.disabled) {
61
65
  onHoverIndex(i);
62
66
  }
63
67
  }}
64
68
  >
65
- {tokens.map((token, j) => (
66
- <span
67
- key={`${item.id}-token-${j}`}
68
- className={cx(
69
- token.type === "match" ? "font-bold" : "font-normal",
70
- "whitespace-pre"
71
- )}
72
- >
73
- {token.text}
74
- </span>
75
- ))}
76
- {item.icon && (
69
+ <div className="flex flex-1 items-center">
70
+ {item.checked && (
71
+ <div className={styles.itemIndicatorStyle}>
72
+ <CheckIcon />
73
+ </div>
74
+ )}
75
+ {tokens.map((token, j) => (
76
+ <span
77
+ key={`${item.id}-token-${j}`}
78
+ className={cx(
79
+ token.type === "match" ? "font-bold" : "font-normal",
80
+ "whitespace-pre"
81
+ )}
82
+ >
83
+ {token.text}
84
+ </span>
85
+ ))}
86
+ </div>
87
+ {(item.shortcut || item.icon) && (
77
88
  <>
78
89
  <Spacer.Horizontal />
79
- {item.icon}
90
+ {item.shortcut ? (
91
+ <KeyboardShortcut shortcut={item.shortcut} />
92
+ ) : (
93
+ item.icon && renderIcon(item.icon)
94
+ )}
80
95
  </>
81
96
  )}
82
97
  </ListView.Row>
@@ -0,0 +1,69 @@
1
+ import { Dialog } from "@noya-app/noya-designsystem";
2
+ import { AutoSizer } from "@noya-app/react-utils";
3
+ import React, { memo } from "react";
4
+ import {
5
+ ISearchCompletionMenu,
6
+ SearchCompletionMenu,
7
+ } from "./SearchCompletionMenu";
8
+
9
+ export const CommandPalette = memo(function CommandPalette({
10
+ showCommandPalette,
11
+ setShowCommandPalette,
12
+ items,
13
+ onSelect,
14
+ onHover,
15
+ }: {
16
+ showCommandPalette: boolean;
17
+ setShowCommandPalette: (value: boolean) => void;
18
+ } & Pick<
19
+ React.ComponentProps<typeof SearchCompletionMenu>,
20
+ "items" | "onSelect" | "onHover"
21
+ >) {
22
+ const menuRef = React.useRef<ISearchCompletionMenu>(null);
23
+
24
+ const handleClose = React.useCallback(() => {
25
+ setShowCommandPalette(false);
26
+ }, [setShowCommandPalette]);
27
+
28
+ return (
29
+ <Dialog
30
+ style={{
31
+ display: "flex",
32
+ flexDirection: "column",
33
+ maxWidth: "500px",
34
+ minHeight: "100px",
35
+ padding: "1px",
36
+ top: "100px",
37
+ left: "50%",
38
+ transform: "translate(-50%, 0)",
39
+ }}
40
+ showCloseButton={false}
41
+ open={showCommandPalette}
42
+ onOpenChange={(value) => {
43
+ setShowCommandPalette(value);
44
+ }}
45
+ closeOnInteractOutside
46
+ onOpenAutoFocus={() => {
47
+ menuRef.current?.focus();
48
+ setTimeout(() => {
49
+ menuRef.current?.focus();
50
+ }, 0);
51
+ }}
52
+ >
53
+ <div className="flex flex-col flex-1">
54
+ <AutoSizer>
55
+ {(size) => (
56
+ <SearchCompletionMenu
57
+ ref={menuRef}
58
+ width={size.width}
59
+ items={items}
60
+ onSelect={onSelect}
61
+ onHover={onHover}
62
+ onClose={handleClose}
63
+ />
64
+ )}
65
+ </AutoSizer>
66
+ </div>
67
+ </Dialog>
68
+ );
69
+ });
@@ -1,19 +1,13 @@
1
- import { CheckIcon, ChevronRightIcon } from "@noya-app/noya-icons";
2
1
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
3
2
  import { memoGeneric } from "@noya-app/react-utils";
4
3
  import * as RadixContextMenu from "@radix-ui/react-context-menu";
5
4
  import React, { ReactNode, useCallback, useMemo } from "react";
6
- import { renderIcon } from "./Icons";
7
- import { Spacer } from "./Spacer";
8
5
  import {
9
- CHECKBOX_RIGHT_INSET,
10
- CHECKBOX_WIDTH,
11
- KeyboardShortcut,
12
6
  MenuItem,
13
- SEPARATOR_ITEM,
14
7
  getKeyboardShortcutsForMenuItems,
15
8
  styles,
16
9
  } from "./internal/Menu";
10
+ import { MenuViewport } from "./internal/MenuViewport";
17
11
 
18
12
  export interface MenuItemProps<T extends string> {
19
13
  value?: T;
@@ -27,90 +21,17 @@ export interface MenuItemProps<T extends string> {
27
21
  items?: MenuItem<T>[];
28
22
  }
29
23
 
30
- const ContextMenuItem = memoGeneric(function ContextMenuItem<T extends string>({
31
- value,
32
- children,
33
- onSelect,
34
- checked,
35
- disabled,
36
- indented,
37
- icon,
38
- items,
39
- shortcut,
40
- }: MenuItemProps<T>) {
41
- // The pointer event within the context menu will bubble outside of the
42
- // context menu unless we stop it here.
43
- const handlePointerDown = useCallback(
44
- (event: React.PointerEvent) => event.stopPropagation(),
45
- []
46
- );
47
-
48
- const handleSelectItem = useCallback(() => {
49
- if (!value) return;
50
-
51
- onSelect(value);
52
- }, [onSelect, value]);
53
-
54
- if (checked) {
55
- return (
56
- <RadixContextMenu.CheckboxItem
57
- checked={checked}
58
- disabled={disabled}
59
- onSelect={handleSelectItem}
60
- className={styles.itemStyle({ disabled })}
61
- >
62
- <RadixContextMenu.ItemIndicator className={styles.itemIndicatorStyle}>
63
- <CheckIcon />
64
- </RadixContextMenu.ItemIndicator>
65
- {children}
66
- </RadixContextMenu.CheckboxItem>
67
- );
68
- }
69
-
70
- const element = (
71
- <RadixContextMenu.Item
72
- className={styles.itemStyle({ disabled })}
73
- disabled={disabled}
74
- onSelect={handleSelectItem}
75
- onPointerDown={handlePointerDown}
76
- >
77
- {indented && (
78
- <Spacer.Horizontal size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET} />
79
- )}
80
- {icon && (
81
- <>
82
- {renderIcon(icon)}
83
- <Spacer.Horizontal size={8} />
84
- </>
85
- )}
86
- {children}
87
- {shortcut && (
88
- <>
89
- <Spacer.Horizontal />
90
- <Spacer.Horizontal size={24} />
91
- <KeyboardShortcut shortcut={shortcut} />
92
- </>
93
- )}
94
- {items && items.length > 0 && (
95
- <>
96
- <Spacer.Horizontal />
97
- <Spacer.Horizontal size={16} />
98
- <ChevronRightIcon />
99
- </>
100
- )}
101
- </RadixContextMenu.Item>
102
- );
103
-
104
- if (items && items.length > 0) {
105
- return (
106
- <ContextMenuRoot isNested items={items} onSelect={onSelect}>
107
- {element}
108
- </ContextMenuRoot>
109
- );
110
- } else {
111
- return element;
112
- }
113
- });
24
+ const Components = {
25
+ Separator: RadixContextMenu.Separator,
26
+ ItemText: React.Fragment,
27
+ Item: RadixContextMenu.Item,
28
+ CheckboxItem: RadixContextMenu.CheckboxItem,
29
+ ItemIndicator: RadixContextMenu.ItemIndicator,
30
+ SubTrigger: RadixContextMenu.SubTrigger,
31
+ SubContent: RadixContextMenu.SubContent,
32
+ Sub: RadixContextMenu.Sub,
33
+ Portal: RadixContextMenu.Portal,
34
+ };
114
35
 
115
36
  /* ----------------------------------------------------------------------------
116
37
  * Root
@@ -120,7 +41,6 @@ export interface MenuProps<T extends string> {
120
41
  children: ReactNode;
121
42
  items: MenuItem<T>[];
122
43
  onSelect: (value: T) => void;
123
- isNested?: boolean;
124
44
  shouldBindKeyboardShortcuts?: boolean;
125
45
  onOpenChange?: (open: boolean) => void;
126
46
  }
@@ -129,20 +49,15 @@ function ContextMenuRoot<T extends string>({
129
49
  items,
130
50
  children,
131
51
  onSelect,
132
- isNested,
133
52
  shouldBindKeyboardShortcuts,
134
53
  onOpenChange,
135
54
  }: MenuProps<T>) {
136
- const hasCheckedItem = items.some(
137
- (item) => item !== SEPARATOR_ITEM && item.checked
138
- );
139
-
140
55
  const keymap = useMemo(
141
56
  () =>
142
- isNested || shouldBindKeyboardShortcuts === false
57
+ shouldBindKeyboardShortcuts === false
143
58
  ? {}
144
59
  : getKeyboardShortcutsForMenuItems(items, onSelect),
145
- [isNested, items, onSelect, shouldBindKeyboardShortcuts]
60
+ [items, onSelect, shouldBindKeyboardShortcuts]
146
61
  );
147
62
 
148
63
  useKeyboardShortcuts(keymap);
@@ -155,46 +70,30 @@ function ContextMenuRoot<T extends string>({
155
70
  event.preventDefault();
156
71
  }, []);
157
72
 
158
- const RootComponent = isNested ? RadixContextMenu.Sub : RadixContextMenu.Root;
159
- const TriggerComponent = isNested
160
- ? RadixContextMenu.SubTrigger
161
- : RadixContextMenu.Trigger;
162
- const ContentComponent: typeof RadixContextMenu.Content = isNested
163
- ? RadixContextMenu.SubContent
164
- : RadixContextMenu.Content;
73
+ const handleSelectItem = useCallback(
74
+ (value: T) => {
75
+ if (!value) return;
76
+
77
+ onSelect(value);
78
+ },
79
+ [onSelect]
80
+ );
165
81
 
166
82
  return (
167
- <RootComponent onOpenChange={onOpenChange}>
168
- <TriggerComponent asChild onPointerDown={onPointerDown}>
83
+ <RadixContextMenu.Root onOpenChange={onOpenChange}>
84
+ <RadixContextMenu.Trigger asChild onPointerDown={onPointerDown}>
169
85
  {children}
170
- </TriggerComponent>
86
+ </RadixContextMenu.Trigger>
171
87
  <RadixContextMenu.Portal>
172
- <ContentComponent className={styles.contentStyle}>
173
- {items.map((item, index) =>
174
- item === SEPARATOR_ITEM ? (
175
- <RadixContextMenu.Separator
176
- key={index}
177
- className={styles.separatorStyle}
178
- />
179
- ) : (
180
- <ContextMenuItem
181
- key={item.value ?? index}
182
- value={item.value}
183
- indented={hasCheckedItem}
184
- checked={item.checked ?? false}
185
- disabled={item.disabled ?? false}
186
- icon={item.icon}
187
- onSelect={onSelect}
188
- items={item.items}
189
- shortcut={item.shortcut}
190
- >
191
- {item.title}
192
- </ContextMenuItem>
193
- )
194
- )}
195
- </ContentComponent>
88
+ <RadixContextMenu.Content className={styles.contentStyle}>
89
+ <MenuViewport
90
+ items={items}
91
+ Components={Components}
92
+ onSelect={handleSelectItem}
93
+ />
94
+ </RadixContextMenu.Content>
196
95
  </RadixContextMenu.Portal>
197
- </RootComponent>
96
+ </RadixContextMenu.Root>
198
97
  );
199
98
  }
200
99