@noya-app/noya-designsystem 0.1.50 → 0.1.51

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -27,6 +27,7 @@
27
27
  "@noya-app/noya-geometry": "0.1.10",
28
28
  "@noya-app/noya-icons": "0.1.7",
29
29
  "@noya-app/noya-keymap": "0.1.3",
30
+ "@noya-app/noya-tailwind-config": "0.1.2",
30
31
  "@radix-ui/primitive": "^1.0.0",
31
32
  "@radix-ui/react-avatar": "^1.0.1",
32
33
  "@radix-ui/react-compose-refs": "^1.0.0",
@@ -47,17 +48,16 @@
47
48
  "kiwi.js": "^1.1.3",
48
49
  "react-resizable-panels": "2.0.22",
49
50
  "react-window": "^1.8.8",
50
- "vscode-fuzzy-scorer": "^0.0.4"
51
+ "vscode-fuzzy-scorer": "^0.0.4",
52
+ "react-virtualized": "9.22.6"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@tailwindcss/container-queries": "^0.1.1",
54
56
  "@types/react": "*",
55
57
  "@types/react-dom": "*",
56
- "@types/react-virtualized": "^9.21.21",
58
+ "@types/react-virtualized": "^9.21.30",
57
59
  "@types/react-window": "^1.8.5",
58
- "esbuild-plugin-react-virtualized": "1.0.4",
59
60
  "esbuild-plugin-tailwindcss": "^1.2.1",
60
- "react-virtualized": "9.22.5",
61
61
  "tailwind-merge": "^2.5.4",
62
62
  "tailwindcss": "^3.4.14"
63
63
  },
@@ -12,6 +12,7 @@ type ActionMenuProps<TMenu extends string> = {
12
12
  onOpenChange: (open: boolean) => void;
13
13
  className?: string;
14
14
  style?: React.CSSProperties;
15
+ variant?: "normal" | "bare";
15
16
  };
16
17
 
17
18
  export const ActionMenu = memoGeneric(function ActionMenu<
@@ -23,16 +24,17 @@ export const ActionMenu = memoGeneric(function ActionMenu<
23
24
  onOpenChange,
24
25
  className,
25
26
  style,
27
+ variant = "normal",
26
28
  }: ActionMenuProps<TMenu>) {
27
29
  const internalStyle = useMemo(() => {
28
30
  return {
29
31
  backgroundColor: selected
30
32
  ? cssVars.colors.primaryPastel
31
33
  : cssVars.colors.inputBackground,
32
- padding: "4px",
34
+ padding: variant === "bare" ? 0 : 4,
33
35
  ...style,
34
36
  };
35
- }, [selected, style]);
37
+ }, [selected, style, variant]);
36
38
 
37
39
  return (
38
40
  <DropdownMenu
@@ -1,6 +1,6 @@
1
+ import { forwardRefGeneric } from "@noya-app/react-utils";
1
2
  import React from "react";
2
- import { forwardRefGeneric } from "../../../noya-react-utils/src/utils/reactGenerics";
3
- import { Grid, GridViewSize } from "../components/Grid";
3
+ import { Grid } from "../components/Grid";
4
4
  import { MenuItem } from "../components/internal/Menu";
5
5
  import {
6
6
  ListView,
@@ -20,6 +20,7 @@ export type CollectionRef = {
20
20
  export type CollectionThumbnailProps<T> = {
21
21
  item: T;
22
22
  selected: boolean;
23
+ size?: CollectionItemSize;
23
24
  };
24
25
 
25
26
  export type CollectionRenderActionProps<T> = {
@@ -51,6 +52,7 @@ export interface CollectionProps<T, M extends string = string> {
51
52
  renderThumbnail?: ({
52
53
  item,
53
54
  selected,
55
+ size,
54
56
  }: CollectionThumbnailProps<T>) => React.ReactNode;
55
57
  renderAction?:
56
58
  | "menu"
@@ -66,7 +68,7 @@ export interface CollectionProps<T, M extends string = string> {
66
68
  /** Position of the detail content. Defaults to 'end'. */
67
69
  detailPosition?: "end" | "below";
68
70
  /** Size of the list items. Defaults to 'medium'. */
69
- size?: GridViewSize;
71
+ size?: CollectionItemSize;
70
72
  /** Size of the thumbnail. Defaults to 'auto', which will be based on the size prop. */
71
73
  thumbnailSize?: CollectionThumbnailSize;
72
74
  /** For testing: Override the hover state with a specific item ID */
@@ -105,3 +107,10 @@ export const Collection = forwardRefGeneric(function Collection<
105
107
  <List ref={ref} {...rest} />
106
108
  );
107
109
  });
110
+ export type CollectionItemSize =
111
+ | "xxs"
112
+ | "xs"
113
+ | "small"
114
+ | "medium"
115
+ | "large"
116
+ | "xl";
@@ -25,6 +25,8 @@ interface ComboboxMenuProps<T extends string> {
25
25
  listSize: Size;
26
26
  style?: React.CSSProperties;
27
27
  indented?: boolean;
28
+ /** @default right */
29
+ iconPosition?: "left" | "right";
28
30
  }
29
31
 
30
32
  export const ComboboxMenu = memoGeneric(
@@ -37,6 +39,7 @@ export const ComboboxMenu = memoGeneric(
37
39
  listSize,
38
40
  style,
39
41
  indented,
42
+ iconPosition = "right",
40
43
  }: ComboboxMenuProps<T>,
41
44
  forwardedRef: ForwardedRef<IVirtualizedList>
42
45
  ) {
@@ -100,6 +103,12 @@ export const ComboboxMenu = memoGeneric(
100
103
  size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET}
101
104
  />
102
105
  ) : null}
106
+ {iconPosition === "left" && item.icon && (
107
+ <>
108
+ {renderIcon(item.icon)}
109
+ <Spacer.Horizontal size={8} />
110
+ </>
111
+ )}
103
112
  {tokens.map((token, j) => (
104
113
  <span
105
114
  key={`${item.value}-token-${j}`}
@@ -112,7 +121,7 @@ export const ComboboxMenu = memoGeneric(
112
121
  </span>
113
122
  ))}
114
123
  </div>
115
- {(item.shortcut || item.icon) && (
124
+ {(item.shortcut || (item.icon && iconPosition === "right")) && (
116
125
  <>
117
126
  {item.shortcut ? (
118
127
  <KeyboardShortcut shortcut={item.shortcut} />
@@ -1,23 +1,25 @@
1
- import { useFileDropTarget } from "@noya-app/react-utils";
2
- import React, { useImperativeHandle, useRef, useState } from "react";
3
1
  import {
4
2
  forwardRefGeneric,
5
3
  memoGeneric,
6
- } from "../../../noya-react-utils/src/utils/reactGenerics";
4
+ useFileDropTarget,
5
+ } from "@noya-app/react-utils";
6
+ import React, { useImperativeHandle, useRef, useState } from "react";
7
7
  import { cx } from "../utils/classNames";
8
8
  import { updateSelection } from "../utils/selection";
9
9
  import { ActionMenu } from "./ActionMenu";
10
- import { CollectionProps, CollectionRef } from "./Collection";
10
+ import {
11
+ CollectionItemSize,
12
+ CollectionProps,
13
+ CollectionRef,
14
+ } from "./Collection";
11
15
  import { EditableText } from "./EditableText";
12
16
  import { GridView } from "./GridView";
13
17
  import { ListView } from "./ListView";
14
18
 
15
- export type GridViewSize = "xxs" | "xs" | "small" | "medium" | "large" | "xl";
16
-
17
- export const getGridSize = (size: GridViewSize) => {
19
+ export const getGridSize = (size: CollectionItemSize) => {
18
20
  // Define size ranges that allow items to grow/shrink within bounds
19
21
  const sizes: Record<
20
- Exclude<GridViewSize, number>,
22
+ Exclude<CollectionItemSize, number>,
21
23
  {
22
24
  minColumnWidth: string;
23
25
  gap: number;
@@ -173,6 +175,7 @@ export const Grid = memoGeneric(
173
175
  const thumbnail = renderThumbnail?.({
174
176
  item,
175
177
  selected: isSelected,
178
+ size,
176
179
  });
177
180
  const onOpenChange = (open: boolean) => {
178
181
  setDropdownOpenId(open ? id : undefined);
@@ -8,8 +8,11 @@ import { cssVars } from "../theme";
8
8
  import { cx } from "../utils/classNames";
9
9
  import { updateSelection } from "../utils/selection";
10
10
  import { ActionMenu } from "./ActionMenu";
11
- import { CollectionProps, CollectionRef } from "./Collection";
12
- import { GridViewSize } from "./Grid";
11
+ import {
12
+ CollectionItemSize,
13
+ CollectionProps,
14
+ CollectionRef,
15
+ } from "./Collection";
13
16
  import { ListView, ListViewItemInfo } from "./ListView";
14
17
  import { TreeView } from "./TreeView";
15
18
 
@@ -149,6 +152,7 @@ export const List = memoGeneric(
149
152
  const thumbnail = renderThumbnail?.({
150
153
  item,
151
154
  selected: isSelected,
155
+ size,
152
156
  });
153
157
  const onOpenChange = (open: boolean) => {
154
158
  setDropdownOpenId(open ? id : undefined);
@@ -169,6 +173,7 @@ export const List = memoGeneric(
169
173
  onSelect={(action) => handleMenuAction(action, item)}
170
174
  selected={isSelected}
171
175
  onOpenChange={onOpenChange}
176
+ variant="bare"
172
177
  />
173
178
  ));
174
179
 
@@ -238,9 +243,10 @@ export const List = memoGeneric(
238
243
  {thumbnail && (
239
244
  <div
240
245
  className={cx(
241
- "rounded overflow-hidden flex-none flex items-center justify-center",
246
+ "overflow-hidden flex-none flex items-center justify-center",
242
247
  thumbnailSize === "auto" && thumbnailSizeMap[size],
243
- isSelected && "text-primary"
248
+ isSelected && "text-primary",
249
+ size === "small" ? "rounded-sm" : "rounded"
244
250
  )}
245
251
  tabIndex={-1}
246
252
  >
@@ -316,37 +322,37 @@ const DetailContainer = memo(function DetailContainer({
316
322
  );
317
323
  });
318
324
 
319
- const pyMap: Record<GridViewSize, string> = {
320
- xxs: "py-1",
321
- xs: "py-1",
322
- small: "py-1",
325
+ const pyMap: Record<CollectionItemSize, string> = {
326
+ xxs: "py-0.5",
327
+ xs: "py-0.5",
328
+ small: "py-0.5",
323
329
  medium: "py-1",
324
330
  large: "py-2",
325
331
  xl: "py-3",
326
332
  };
327
333
 
328
- const thumbnailSizeMap: Record<GridViewSize, string> = {
329
- xxs: "w-4 h-4",
330
- xs: "w-4 h-4",
331
- small: "w-6 h-6",
334
+ const thumbnailSizeMap: Record<CollectionItemSize, string> = {
335
+ xxs: "w-5 h-5",
336
+ xs: "w-5 h-5",
337
+ small: "w-5 h-5",
332
338
  medium: "w-8 h-8",
333
339
  large: "w-16 h-16",
334
340
  xl: "w-24 h-24",
335
341
  };
336
342
 
337
- const rowGapMap: Record<GridViewSize, string> = {
338
- xxs: "gap-3",
339
- xs: "gap-3",
340
- small: "gap-3",
343
+ const rowGapMap: Record<CollectionItemSize, string> = {
344
+ xxs: "gap-2",
345
+ xs: "gap-2",
346
+ small: "gap-2",
341
347
  medium: "gap-3",
342
348
  large: "gap-3",
343
349
  xl: "gap-4",
344
350
  };
345
351
 
346
- const fontStyleMap: Record<GridViewSize, string> = {
347
- xxs: "font-medium",
348
- xs: "font-medium",
349
- small: "font-medium",
352
+ const fontStyleMap: Record<CollectionItemSize, string> = {
353
+ xxs: "font-medium text-list-small",
354
+ xs: "font-medium text-list-small",
355
+ small: "font-medium text-list-small",
350
356
  medium: "font-medium",
351
357
  large: "font-medium",
352
358
  xl: "font-medium text-heading4 text-text",
@@ -2,6 +2,7 @@ import { ImageIcon, SpeakerLoudIcon, VideoIcon } from "@noya-app/noya-icons";
2
2
  import React, { memo, useMemo } from "react";
3
3
  import { cssVars } from "../theme";
4
4
  import { cx } from "../utils/classNames";
5
+ import { CollectionItemSize } from "./Collection";
5
6
  import { IconName, Icons } from "./Icons";
6
7
 
7
8
  type ColorScheme = "primary" | "secondary" | "icon" | "warning";
@@ -29,6 +30,7 @@ type MediaThumbnailProps = {
29
30
  * If provided, use this icon instead of the default one for the asset type
30
31
  */
31
32
  iconName?: IconName;
33
+ size?: CollectionItemSize;
32
34
  };
33
35
 
34
36
  export const getThumbnailColors = (
@@ -67,11 +69,6 @@ export const getThumbnailColors = (
67
69
  }
68
70
  };
69
71
 
70
- const iconStyles = {
71
- width: "max(15px, 20%)",
72
- height: "max(15px, 20%)",
73
- };
74
-
75
72
  export const MediaThumbnail = memo(function MediaThumbnail({
76
73
  contentType: contentTypeProp,
77
74
  selected = false,
@@ -79,12 +76,20 @@ export const MediaThumbnail = memo(function MediaThumbnail({
79
76
  colorScheme = "icon",
80
77
  url,
81
78
  iconName = "FileIcon",
79
+ size = "medium",
82
80
  }: MediaThumbnailProps) {
83
81
  const { icon: iconColor, background: backgroundColor } = getThumbnailColors(
84
82
  colorScheme,
85
83
  selected
86
84
  );
87
85
  const contentType = getAssetType(contentTypeProp);
86
+ const iconStyles = useMemo(
87
+ () => ({
88
+ width: iconSizeMap[size],
89
+ height: iconSizeMap[size],
90
+ }),
91
+ [size]
92
+ );
88
93
 
89
94
  const content = useMemo(() => {
90
95
  switch (contentType) {
@@ -109,7 +114,7 @@ export const MediaThumbnail = memo(function MediaThumbnail({
109
114
  const Icon = Icons[iconName];
110
115
  return <Icon color={iconColor} style={iconStyles} />;
111
116
  }
112
- }, [contentType, url, iconColor, iconName]);
117
+ }, [contentType, url, iconColor, iconStyles, iconName]);
113
118
 
114
119
  return (
115
120
  <div
@@ -123,3 +128,12 @@ export const MediaThumbnail = memo(function MediaThumbnail({
123
128
  </div>
124
129
  );
125
130
  });
131
+
132
+ const iconSizeMap: Record<CollectionItemSize, number | string> = {
133
+ xxs: 12,
134
+ xs: 12,
135
+ small: 12,
136
+ medium: "max(15px, 20%)",
137
+ large: "max(15px, 20%)",
138
+ xl: "max(15px, 20%)",
139
+ };
@@ -119,21 +119,21 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
119
119
  (event: React.KeyboardEvent<HTMLInputElement>) => {
120
120
  handleKeyboardEvent(event.nativeEvent, getCurrentPlatform(navigator), {
121
121
  ArrowUp: () => {
122
- const nextIndex = getNextIndex(
123
- results,
124
- index,
125
- "previous",
126
- (item) => item.disabled ?? false
127
- );
122
+ const nextIndex = getNextIndex({
123
+ items: results,
124
+ currentIndex: index,
125
+ direction: "previous",
126
+ isDisabled: (item) => item.disabled ?? false,
127
+ });
128
128
  setIndex(nextIndex);
129
129
  },
130
130
  ArrowDown: () => {
131
- const nextIndex = getNextIndex(
132
- results,
133
- index,
134
- "next",
135
- (item) => item.disabled ?? false
136
- );
131
+ const nextIndex = getNextIndex({
132
+ items: results,
133
+ currentIndex: index,
134
+ direction: "next",
135
+ isDisabled: (item) => item.disabled ?? false,
136
+ });
137
137
  setIndex(nextIndex);
138
138
  },
139
139
  Tab: () => selectAndClose(results[index]!),
@@ -1,3 +1,4 @@
1
+ import { memoGeneric } from "@noya-app/react-utils";
1
2
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
2
3
  import React, {
3
4
  createContext,
@@ -7,7 +8,6 @@ import React, {
7
8
  useContext,
8
9
  useMemo,
9
10
  } 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";
@@ -2,8 +2,11 @@ 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 { memoGeneric } from "@noya-app/react-utils";
6
+ import { cssVars } from "../theme";
5
7
  import { BaseToolbar } from "./BaseToolbar";
6
8
  import { Button } from "./Button";
9
+ import { DividerVertical } from "./Divider";
7
10
  import { DropdownMenu } from "./DropdownMenu";
8
11
  import { renderIcon } from "./Icons";
9
12
  import {
@@ -76,7 +79,11 @@ export function Toolbar<T extends string = string>({
76
79
  );
77
80
  }
78
81
 
79
- export function ToolbarMenu<T extends string>({
82
+ const activeButtonStyle = {
83
+ backgroundColor: cssVars.colors.primaryPastel,
84
+ };
85
+
86
+ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
80
87
  items,
81
88
  onSelectMenuItem,
82
89
  }: {
@@ -86,14 +93,18 @@ export function ToolbarMenu<T extends string>({
86
93
  return (
87
94
  <>
88
95
  {items.map((item, i) => {
89
- if (item.type === "separator") return null;
90
96
  if (item.type === "sectionHeader") return null;
91
97
 
98
+ if (item.type === "separator") {
99
+ return <DividerVertical key={i} overflow={4} />;
100
+ }
101
+
92
102
  if (isSelectableMenuItem(item)) {
93
103
  return (
94
104
  <Button
95
105
  key={i}
96
106
  disabled={item.disabled}
107
+ style={item.checked ? activeButtonStyle : undefined}
97
108
  onClick={() => {
98
109
  if (onSelectMenuItem && item.value) {
99
110
  onSelectMenuItem(item.value);
@@ -101,12 +112,8 @@ export function ToolbarMenu<T extends string>({
101
112
  }}
102
113
  >
103
114
  {item.title}
104
- {item.icon && (
105
- <>
106
- <Spacer.Horizontal inline size={6} />
107
- {renderIcon(item.icon)}
108
- </>
109
- )}
115
+ {item.title && item.icon && <Spacer.Horizontal inline size={6} />}
116
+ {item.icon && renderIcon(item.icon)}
110
117
  </Button>
111
118
  );
112
119
  }
@@ -121,8 +128,13 @@ export function ToolbarMenu<T extends string>({
121
128
  }
122
129
  }}
123
130
  >
124
- <Button disabled={item.disabled}>
131
+ <Button
132
+ disabled={item.disabled}
133
+ style={item.checked ? activeButtonStyle : undefined}
134
+ >
125
135
  {item.title}
136
+ {item.title && item.icon && <Spacer.Horizontal inline size={6} />}
137
+ {item.icon && renderIcon(item.icon)}
126
138
  <Spacer.Horizontal size={6} />
127
139
  <DropdownChevronIcon />
128
140
  </Button>
@@ -131,4 +143,4 @@ export function ToolbarMenu<T extends string>({
131
143
  })}
132
144
  </>
133
145
  );
134
- }
146
+ });
@@ -8,7 +8,12 @@ import {
8
8
  import { forwardRefGeneric } from "@noya-app/react-utils";
9
9
  import React from "react";
10
10
  import { cx } from "../../utils/classNames";
11
- import { Collection, CollectionProps, CollectionRef } from "../Collection";
11
+ import {
12
+ Collection,
13
+ CollectionItemSize,
14
+ CollectionProps,
15
+ CollectionRef,
16
+ } from "../Collection";
12
17
 
13
18
  export const FileExplorerLayout = ({
14
19
  children,
@@ -53,13 +58,18 @@ export const FileExplorerCollection = forwardRefGeneric(
53
58
 
54
59
  export const FileExplorerDetail = ({
55
60
  selected,
61
+ size,
56
62
  children,
57
63
  }: {
58
64
  selected: boolean;
59
65
  children: React.ReactNode;
66
+ size?: CollectionItemSize;
60
67
  }) => (
61
68
  <span
62
- className={cx("text-sm", selected ? "text-primary" : "text-text-muted")}
69
+ className={cx(
70
+ size === "small" ? "text-list-small" : "text-sm",
71
+ selected ? "text-primary" : "text-text-muted"
72
+ )}
63
73
  >
64
74
  {children}
65
75
  </span>
@@ -126,6 +126,8 @@ type SubmittableProps = Props & {
126
126
  onSubmit: (value: string) => void;
127
127
  allowSubmittingWithSameValue?: boolean;
128
128
  submitAutomaticallyAfterDelay?: number;
129
+ /** @default true */
130
+ submitOnBlur?: boolean;
129
131
  };
130
132
 
131
133
  const SubmittableTextInput = forwardRef(function SubmittableTextInput(
@@ -138,6 +140,7 @@ const SubmittableTextInput = forwardRef(function SubmittableTextInput(
138
140
  onFocusCapture,
139
141
  allowSubmittingWithSameValue = false,
140
142
  submitAutomaticallyAfterDelay,
143
+ submitOnBlur = true,
141
144
  ...rest
142
145
  }: SubmittableProps,
143
146
  forwardedRef: ForwardedRef<HTMLInputElement>
@@ -218,9 +221,11 @@ const SubmittableTextInput = forwardRef(function SubmittableTextInput(
218
221
 
219
222
  onFocusChange?.(false);
220
223
 
221
- handleSubmit();
224
+ if (submitOnBlur) {
225
+ handleSubmit();
226
+ }
222
227
  },
223
- [onBlur, onFocusChange, handleSubmit]
228
+ [onBlur, onFocusChange, handleSubmit, submitOnBlur]
224
229
  );
225
230
 
226
231
  const handleFocusCapture = useCallback(
package/src/index.css CHANGED
@@ -82,6 +82,7 @@
82
82
  --n-floating-button: rgb(248, 248, 250);
83
83
  --n-block-border: rgb(184, 201, 218);
84
84
  --n-block-highlight: rgb(255, 165, 0);
85
+ --n-visual-editor-line-placeholder-content: "Write or press / for commands";
85
86
  }
86
87
 
87
88
  [data-theme="dark"] {
@@ -125,3 +126,9 @@
125
126
  --n-table-row-background: var(--n-sidebar-background);
126
127
  --n-floating-button: #333333;
127
128
  }
129
+
130
+ .visual-editor-line-placeholder::after {
131
+ content: var(--n-visual-editor-line-placeholder-content);
132
+ color: var(--n-text-disabled);
133
+ pointer-events: none;
134
+ }
@@ -1,4 +1,4 @@
1
- import tailwindConfig from "../../tailwind.config";
1
+ import tailwindConfig from "@noya-app/noya-tailwind-config";
2
2
  import { convertKebabToCamelCase } from "./themeUtils";
3
3
 
4
4
  export type Theme = (typeof tailwindConfig.theme)["extend"];
@@ -1,4 +1,4 @@
1
- import tailwindConfig from "../../tailwind.config";
1
+ import tailwindConfig from "@noya-app/noya-tailwind-config";
2
2
 
3
3
  type ClassNameItem = string | number | BigInt | boolean | null | undefined;
4
4
  type Category = "text" | "color" | "font" | "flex";
@@ -168,12 +168,12 @@ export class ComboboxState<T extends string> {
168
168
 
169
169
  moveSelection(direction: "up" | "down") {
170
170
  const filteredItems = this.getFilteredItems();
171
- this.selectedIndex = getNextIndex(
172
- filteredItems,
173
- this.selectedIndex,
174
- direction === "down" ? "next" : "previous",
175
- (item) => isNonSelectableMenuItem(item)
176
- );
171
+ this.selectedIndex = getNextIndex({
172
+ items: filteredItems,
173
+ currentIndex: this.selectedIndex,
174
+ direction: direction === "down" ? "next" : "previous",
175
+ isDisabled: (item) => isNonSelectableMenuItem(item),
176
+ });
177
177
  this.notifyChange();
178
178
  }
179
179
 
@@ -257,7 +257,7 @@ export class ComboboxState<T extends string> {
257
257
  }
258
258
  }
259
259
 
260
- function filterWithGroupedSections<T extends string>(
260
+ export function filterWithGroupedSections<T extends string>(
261
261
  items: MenuItem<T>[],
262
262
  query: string
263
263
  ): ScoredMenuItem<T>[] {
@@ -341,12 +341,17 @@ function filterWithGroupedSections<T extends string>(
341
341
  return result;
342
342
  }
343
343
 
344
- export function getNextIndex<T>(
345
- items: T[],
346
- currentIndex: number,
347
- direction: "next" | "previous",
348
- isDisabled: (item: T) => boolean
349
- ): number {
344
+ export function getNextIndex<T>({
345
+ items,
346
+ currentIndex,
347
+ direction,
348
+ isDisabled,
349
+ }: {
350
+ items: T[];
351
+ currentIndex: number;
352
+ direction: "next" | "previous";
353
+ isDisabled: (item: T) => boolean;
354
+ }): number {
350
355
  // Make sure the current index is within bounds
351
356
  currentIndex =
352
357
  currentIndex < 0