@xsolla/xui-multi-select 0.184.0 → 0.185.0

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/README.md CHANGED
@@ -174,6 +174,9 @@ Add backdrop, click-outside, and Escape handling in your layout as needed (see S
174
174
  | `removeTagsButtons` | `boolean` | `true` | Show a remove button on each tag. |
175
175
  | `extraClear` | `boolean` | `false` | Show a clear-all button. |
176
176
  | `maxHeight` | `number` | `300` | Maximum dropdown height in pixels. |
177
+ | `searchable` | `boolean` | `false` | Show a search input at the top of the built-in dropdown that filters options in memory by label. Web only. |
178
+ | `searchPlaceholder` | `string` | `'Search'` | Placeholder for the search input (used when `searchable`). |
179
+ | `noOptionsMessage` | `string` | `'No results'` | Message shown when the search filter matches no options. |
177
180
  | `iconLeft` | `ReactNode` | — | Icon rendered on the left of the control. |
178
181
  | `iconRight` | `ReactNode` | — | Icon on the right (overrides the default caret). |
179
182
  | `dropdownMenu` | `boolean` | `true` | When `false`, hides the built-in list and disables click-to-open; use with an external picker (e.g. `GroupSelect`) wired to the same `value` / `onChange`. |
@@ -268,8 +271,61 @@ const options = [
268
271
  <MultiSelect options={options} value={["react"]} disabled />;
269
272
  ```
270
273
 
274
+ ### Searchable
275
+
276
+ Enable `searchable` to show a search field at the top of the dropdown. It filters the options in memory by label as the user types, mirroring the [`Select`](./select.md) component's `searchable` behavior. Customize the input hint with `searchPlaceholder` and the empty-result text with `noOptionsMessage`. Web only — the prop is ignored on native.
277
+
278
+ ```tsx
279
+ const options = [
280
+ { label: "React", value: "react" },
281
+ { label: "Vue", value: "vue" },
282
+ { label: "Angular", value: "angular" },
283
+ { label: "Svelte", value: "svelte" },
284
+ { label: "Solid", value: "solid" },
285
+ ];
286
+
287
+ const [selected, setSelected] = useState<MultiSelectValue>([]);
288
+
289
+ <MultiSelect
290
+ label="Frameworks"
291
+ options={options}
292
+ value={selected}
293
+ onChange={setSelected}
294
+ searchable
295
+ searchPlaceholder="Search frameworks"
296
+ noOptionsMessage="No frameworks found"
297
+ placeholder="Select frameworks"
298
+ />;
299
+ ```
300
+
301
+ ### Long option labels
302
+
303
+ Long, unbreakable option labels (URLs, IDs, tokens) wrap inside the dropdown menu instead of forcing a horizontal scrollbar; the menu width stays pinned to the control.
304
+
305
+ ```tsx
306
+ const options = [
307
+ {
308
+ label:
309
+ "https://prototype.xsolla.dev/mini-apps/some-really-long-slug?token=abcdef0123456789",
310
+ value: "url",
311
+ },
312
+ { label: "urn:xsolla:resource:0123456789abcdef0123456789abcdef", value: "id" },
313
+ { label: "Short", value: "short" },
314
+ ];
315
+
316
+ const [selected, setSelected] = useState<MultiSelectValue>([]);
317
+
318
+ <MultiSelect
319
+ options={options}
320
+ value={selected}
321
+ onChange={setSelected}
322
+ placeholder="Select options"
323
+ />;
324
+ ```
325
+
271
326
  ## Accessibility
272
327
 
273
328
  - Selection is rendered as a checkbox list inside the dropdown.
274
329
  - The dropdown is keyboard navigable; selection state is announced to assistive technology.
275
330
  - An `errorMessage` marks the control as invalid for screen readers.
331
+ - When `searchable`, the dropdown's search input exposes an `aria-label` matching `searchPlaceholder`; options filter in memory as the user types (web only).
@@ -5,6 +5,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
5
5
  type MultiSelectValue = (string | number)[];
6
6
  type MultiSelectVariant = "tag" | "text";
7
7
  type MultiSelectSize = "xs" | "sm" | "md" | "lg" | "xl";
8
+ type MultiSelectOptionOverflow = "truncate" | "wrap" | "scroll";
8
9
  type MultiSelectState = "default" | "hover" | "focus" | "disable" | "error";
9
10
  interface MultiSelectOption {
10
11
  label: ReactNode;
@@ -83,6 +84,35 @@ interface MultiSelectProps extends ThemeOverrideProps {
83
84
  * @default 300
84
85
  */
85
86
  maxHeight?: number;
87
+ /**
88
+ * How long option labels in the dropdown handle horizontal overflow.
89
+ * - `"truncate"` — clamp each label to a single line with an ellipsis.
90
+ * - `"wrap"` — allow labels to wrap onto multiple lines (rows grow taller).
91
+ * - `"scroll"` — keep labels on one line and scroll the list horizontally
92
+ * (legacy behavior). Web only — falls back to `"wrap"` on native, which
93
+ * cannot horizontally scroll a text row.
94
+ * @default "truncate"
95
+ */
96
+ optionOverflow?: MultiSelectOptionOverflow;
97
+ /**
98
+ * When true, shows a search input at the top of the built-in dropdown that
99
+ * filters options in memory by their label as the user types. Mirrors the
100
+ * `searchable` behavior of the Select component. Web only — ignored on
101
+ * native and when `dropdownMenu` is false.
102
+ * @default false
103
+ */
104
+ searchable?: boolean;
105
+ /**
106
+ * Placeholder text for the search input. Only used when `searchable` is true.
107
+ * @default "Search"
108
+ */
109
+ searchPlaceholder?: string;
110
+ /**
111
+ * Message shown in the dropdown when the search filter matches no options.
112
+ * Only used when `searchable` is true.
113
+ * @default "No results"
114
+ */
115
+ noOptionsMessage?: string;
86
116
  /**
87
117
  * When false, the built-in options list and backdrop are not shown and the control
88
118
  * does not open on click. Use with an external picker (e.g. grouped select) wired
@@ -112,4 +142,4 @@ interface MultiSelectProps extends ThemeOverrideProps {
112
142
 
113
143
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
114
144
 
115
- export { MultiSelect, type MultiSelectOption, type MultiSelectProps, type MultiSelectSize, type MultiSelectState, type MultiSelectValue, type MultiSelectVariant };
145
+ export { MultiSelect, type MultiSelectOption, type MultiSelectOptionOverflow, type MultiSelectProps, type MultiSelectSize, type MultiSelectState, type MultiSelectValue, type MultiSelectVariant };
package/native/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
5
5
  type MultiSelectValue = (string | number)[];
6
6
  type MultiSelectVariant = "tag" | "text";
7
7
  type MultiSelectSize = "xs" | "sm" | "md" | "lg" | "xl";
8
+ type MultiSelectOptionOverflow = "truncate" | "wrap" | "scroll";
8
9
  type MultiSelectState = "default" | "hover" | "focus" | "disable" | "error";
9
10
  interface MultiSelectOption {
10
11
  label: ReactNode;
@@ -83,6 +84,35 @@ interface MultiSelectProps extends ThemeOverrideProps {
83
84
  * @default 300
84
85
  */
85
86
  maxHeight?: number;
87
+ /**
88
+ * How long option labels in the dropdown handle horizontal overflow.
89
+ * - `"truncate"` — clamp each label to a single line with an ellipsis.
90
+ * - `"wrap"` — allow labels to wrap onto multiple lines (rows grow taller).
91
+ * - `"scroll"` — keep labels on one line and scroll the list horizontally
92
+ * (legacy behavior). Web only — falls back to `"wrap"` on native, which
93
+ * cannot horizontally scroll a text row.
94
+ * @default "truncate"
95
+ */
96
+ optionOverflow?: MultiSelectOptionOverflow;
97
+ /**
98
+ * When true, shows a search input at the top of the built-in dropdown that
99
+ * filters options in memory by their label as the user types. Mirrors the
100
+ * `searchable` behavior of the Select component. Web only — ignored on
101
+ * native and when `dropdownMenu` is false.
102
+ * @default false
103
+ */
104
+ searchable?: boolean;
105
+ /**
106
+ * Placeholder text for the search input. Only used when `searchable` is true.
107
+ * @default "Search"
108
+ */
109
+ searchPlaceholder?: string;
110
+ /**
111
+ * Message shown in the dropdown when the search filter matches no options.
112
+ * Only used when `searchable` is true.
113
+ * @default "No results"
114
+ */
115
+ noOptionsMessage?: string;
86
116
  /**
87
117
  * When false, the built-in options list and backdrop are not shown and the control
88
118
  * does not open on click. Use with an external picker (e.g. grouped select) wired
@@ -112,4 +142,4 @@ interface MultiSelectProps extends ThemeOverrideProps {
112
142
 
113
143
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
114
144
 
115
- export { MultiSelect, type MultiSelectOption, type MultiSelectProps, type MultiSelectSize, type MultiSelectState, type MultiSelectValue, type MultiSelectVariant };
145
+ export { MultiSelect, type MultiSelectOption, type MultiSelectOptionOverflow, type MultiSelectProps, type MultiSelectSize, type MultiSelectState, type MultiSelectValue, type MultiSelectVariant };
package/native/index.js CHANGED
@@ -312,10 +312,6 @@ var isWeb = false;
312
312
  // src/MultiSelect.tsx
313
313
  var import_xui_core4 = require("@xsolla/xui-core");
314
314
 
315
- // src/MultiSelectControl.tsx
316
- var import_react8 = require("react");
317
- var import_xui_core3 = require("@xsolla/xui-core");
318
-
319
315
  // ../../foundation/icons-base/dist/web/index.mjs
320
316
  var import_styled_components = __toESM(require("styled-components"), 1);
321
317
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1094,6 +1090,13 @@ var ExclamationMarkCr = (props) => /* @__PURE__ */ (0, import_jsx_runtime152.jsx
1094
1090
  var solidContent172 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_remove--solid"><path id="Union" d="M20.5 3C20.7761 3 21 3.22386 21 3.5V20.5C21 20.7761 20.7761 21 20.5 21H3.5C3.22386 21 3 20.7761 3 20.5V3.5C3 3.22386 3.22386 3 3.5 3H20.5ZM12 10.5859L7.20703 5.79297L5.79297 7.20703L10.5859 12L5.79297 16.793L7.20703 18.207L12 13.4141L16.793 18.207L18.207 16.793L13.4141 12L18.207 7.20703L16.793 5.79297L12 10.5859Z" style="fill: currentColor"/></g></svg>`;
1095
1091
  var lineContent172 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_remove--line"><path id="Union" d="M20.0303 5.41455L13.4199 12.0249L20.0195 18.6245L18.6055 20.0386L12.0059 13.439L5.41406 20.0308L4 18.6167L10.5918 12.0249L4.00879 5.44189L5.42285 4.02783L12.0059 10.6108L18.6162 4.00049L20.0303 5.41455Z" style="fill: currentColor"/></g></svg>`;
1096
1092
  var Remove = (props) => /* @__PURE__ */ (0, import_jsx_runtime176.jsx)(BaseIcon, { ...props, solidContent: solidContent172, lineContent: lineContent172 });
1093
+ var solidContent706 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--solid"><path id="Union" d="M10.0215 2.02148C14.4398 2.02148 18.0215 5.60321 18.0215 10.0215C18.0215 11.8699 17.3926 13.5702 16.3398 14.9248L21.9785 20.5645L20.5645 21.9785L14.9248 16.3389C13.5702 17.3918 11.8701 18.0215 10.0215 18.0215C5.60321 18.0215 2.02148 14.4398 2.02148 10.0215C2.02148 5.60321 5.60321 2.02148 10.0215 2.02148ZM10.0215 4.02148C6.70778 4.02148 4.02148 6.70778 4.02148 10.0215C4.02148 13.3352 6.70778 16.0215 10.0215 16.0215C13.3352 16.0215 16.0215 13.3352 16.0215 10.0215C16.0215 6.70778 13.3352 4.02148 10.0215 4.02148ZM7.19336 7.19336C8.75546 5.63126 11.2875 5.63126 12.8496 7.19336C14.4117 8.75546 14.4117 11.2875 12.8496 12.8496C11.2875 14.4117 8.75546 14.4117 7.19336 12.8496C5.63126 11.2875 5.63127 8.75546 7.19336 7.19336Z" style="fill: currentColor"/></g></svg>`;
1094
+ var lineContent706 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--line"><path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M10 2C14.4183 2 18 5.58172 18 10C18 11.8484 17.3711 13.5488 16.3184 14.9033L21.957 20.543L20.543 21.957L14.9033 16.3174C13.5487 17.3703 11.8486 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4Z" style="fill: currentColor"/></g></svg>`;
1095
+ var Search = (props) => /* @__PURE__ */ (0, import_jsx_runtime710.jsx)(BaseIcon, { ...props, solidContent: solidContent706, lineContent: lineContent706 });
1096
+
1097
+ // src/MultiSelectControl.tsx
1098
+ var import_react8 = require("react");
1099
+ var import_xui_core3 = require("@xsolla/xui-core");
1097
1100
 
1098
1101
  // src/useMultiSelectControl.tsx
1099
1102
  var import_react7 = require("react");
@@ -2186,10 +2189,14 @@ var MultiSelect = (0, import_react10.forwardRef)(
2186
2189
  iconLeft,
2187
2190
  iconRight,
2188
2191
  maxHeight = 300,
2192
+ optionOverflow = "truncate",
2189
2193
  dropdownMenu = true,
2190
2194
  onTriggerPress,
2191
2195
  menuOpen = false,
2192
2196
  menuMinWidth,
2197
+ searchable = false,
2198
+ searchPlaceholder = "Search",
2199
+ noOptionsMessage = "No results",
2193
2200
  testID,
2194
2201
  themeMode,
2195
2202
  themeProductContext
@@ -2198,8 +2205,12 @@ var MultiSelect = (0, import_react10.forwardRef)(
2198
2205
  const modalId = (0, import_xui_core4.useModalId)();
2199
2206
  const controlRef = (0, import_react10.useRef)(null);
2200
2207
  const menuRef = (0, import_react10.useRef)(null);
2208
+ const containerRef = (0, import_react10.useRef)(null);
2209
+ const searchInputRef = (0, import_react10.useRef)(null);
2201
2210
  const [menuPosition, setMenuPosition] = (0, import_react10.useState)(null);
2211
+ const [searchValue, setSearchValue] = (0, import_react10.useState)("");
2202
2212
  const sizeStyles = theme.sizing.input(size);
2213
+ const inputColors = theme.colors.control.input;
2203
2214
  const state = externalState || (disabled ? "disable" : "default");
2204
2215
  const isDisable = state === "disable" || disabled;
2205
2216
  const isError = state === "error" || !!errorMessage;
@@ -2224,7 +2235,19 @@ var MultiSelect = (0, import_react10.forwardRef)(
2224
2235
  onClose();
2225
2236
  }
2226
2237
  }, [isDisable, dropdownMenu, onClose]);
2227
- const menuItems = options.map((opt) => {
2238
+ (0, import_react10.useEffect)(() => {
2239
+ if (isOpen && searchable && isWeb) {
2240
+ searchInputRef.current?.focus();
2241
+ }
2242
+ }, [isOpen, searchable]);
2243
+ (0, import_react10.useEffect)(() => {
2244
+ if (!isOpen) setSearchValue("");
2245
+ }, [isOpen]);
2246
+ const getOptionText = (opt) => typeof opt.label === "string" ? opt.label : String(opt.value);
2247
+ const filteredOptions = searchable && searchValue ? options.filter(
2248
+ (opt) => getOptionText(opt).toLowerCase().includes(searchValue.toLowerCase())
2249
+ ) : options;
2250
+ const menuItems = filteredOptions.map((opt) => {
2228
2251
  const id = String(opt.value);
2229
2252
  const checked = values.map(String).includes(id);
2230
2253
  return {
@@ -2242,6 +2265,22 @@ var MultiSelect = (0, import_react10.forwardRef)(
2242
2265
  };
2243
2266
  const controlMenuOpen = dropdownMenu ? isOpen : Boolean(menuOpen);
2244
2267
  const controlOnClick = dropdownMenu ? onSelectClick : onTriggerPress;
2268
+ const isScrollOverflow = optionOverflow === "scroll";
2269
+ const isWrapOverflow = optionOverflow === "wrap";
2270
+ const optionListOverflowX = isScrollOverflow ? "auto" : "hidden";
2271
+ const optionRowMinWidth = isScrollOverflow ? void 0 : 0;
2272
+ const optionLabelNumberOfLines = optionOverflow === "truncate" ? 1 : void 0;
2273
+ const optionLabelStyle = isScrollOverflow ? { whiteSpace: "nowrap" } : isWrapOverflow ? {
2274
+ minWidth: 0,
2275
+ whiteSpace: "normal",
2276
+ overflowWrap: "break-word",
2277
+ wordBreak: "break-word"
2278
+ } : {
2279
+ minWidth: 0,
2280
+ whiteSpace: "nowrap",
2281
+ overflow: "hidden",
2282
+ textOverflow: "ellipsis"
2283
+ };
2245
2284
  const updateMenuPosition = (0, import_react10.useCallback)(() => {
2246
2285
  if (!isWeb || !controlRef.current) return;
2247
2286
  const rect = controlRef.current.getBoundingClientRect();
@@ -2261,13 +2300,25 @@ var MultiSelect = (0, import_react10.forwardRef)(
2261
2300
  window.removeEventListener("scroll", updateMenuPosition, true);
2262
2301
  };
2263
2302
  }, [controlMenuOpen, dropdownMenu, isDisable, updateMenuPosition]);
2303
+ (0, import_react10.useEffect)(() => {
2304
+ if (!isWeb || !dropdownMenu || !isOpen || isDisable) return;
2305
+ const handleOutsideMouseDown = (event) => {
2306
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
2307
+ const targetNode = event.target;
2308
+ const isInside = (el) => !!el && (path.length ? path.includes(el) : !!targetNode && el.contains(targetNode));
2309
+ if (isInside(containerRef.current) || isInside(menuRef.current)) return;
2310
+ onClose();
2311
+ };
2312
+ document.addEventListener("mousedown", handleOutsideMouseDown);
2313
+ return () => document.removeEventListener("mousedown", handleOutsideMouseDown);
2314
+ }, [dropdownMenu, isOpen, isDisable, onClose]);
2264
2315
  const externalFieldLayout = !dropdownMenu ? {
2265
2316
  width: "100%",
2266
2317
  minWidth: menuMinWidth ?? EXTERNAL_MENU_MIN_WIDTH_DEFAULT,
2267
2318
  boxSizing: "border-box"
2268
2319
  } : void 0;
2269
2320
  const dropdown = dropdownMenu && isOpen && !isDisable && (!isWeb || menuPosition) && /* @__PURE__ */ (0, import_jsx_runtime734.jsxs)(import_jsx_runtime734.Fragment, { children: [
2270
- /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2321
+ !isWeb && /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2271
2322
  Box,
2272
2323
  {
2273
2324
  "data-modal-id": modalId,
@@ -2283,7 +2334,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2283
2334
  onPress: onClose
2284
2335
  }
2285
2336
  ),
2286
- /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2337
+ /* @__PURE__ */ (0, import_jsx_runtime734.jsxs)(
2287
2338
  Box,
2288
2339
  {
2289
2340
  ref: menuRef,
@@ -2292,7 +2343,6 @@ var MultiSelect = (0, import_react10.forwardRef)(
2292
2343
  borderColor: theme.colors.border.secondary,
2293
2344
  borderWidth: 1,
2294
2345
  borderRadius: sizeStyles.radius,
2295
- paddingVertical: 4,
2296
2346
  style: {
2297
2347
  position: isWeb ? "fixed" : "absolute",
2298
2348
  top: isWeb ? menuPosition?.top : "100%",
@@ -2302,54 +2352,134 @@ var MultiSelect = (0, import_react10.forwardRef)(
2302
2352
  marginTop: isWeb ? void 0 : 4,
2303
2353
  zIndex: 1001,
2304
2354
  // Above control (1000) and backdrop (999)
2305
- boxShadow: theme.shadow.popover,
2306
- maxHeight,
2307
- overflowY: "auto"
2355
+ boxShadow: theme.shadow.popover
2308
2356
  },
2309
- children: menuItems.map((item, _index) => {
2310
- const brandColors = theme.colors.control.brand.primary;
2311
- const contentColors = theme.colors.content;
2312
- return /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2357
+ children: [
2358
+ searchable && isWeb && /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2313
2359
  Box,
2314
2360
  {
2315
- testID,
2316
2361
  paddingHorizontal: sizeStyles.paddingHorizontal,
2317
- paddingVertical: 8,
2318
- onPress: () => {
2319
- if (!item.disabled) {
2320
- handleItemToggle(item.id, !item.checked);
2362
+ paddingVertical: sizeStyles.paddingVertical,
2363
+ borderBottomWidth: 1,
2364
+ borderColor: theme.colors.border.secondary,
2365
+ children: /* @__PURE__ */ (0, import_jsx_runtime734.jsxs)(
2366
+ Box,
2367
+ {
2368
+ flexDirection: "row",
2369
+ alignItems: "center",
2370
+ gap: sizeStyles.paddingHorizontal / 2,
2371
+ paddingHorizontal: 4,
2372
+ children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2374
+ Icon,
2375
+ {
2376
+ size: sizeStyles.iconSize - 2,
2377
+ color: inputColors.placeholder,
2378
+ children: /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(Search, {})
2379
+ }
2380
+ ),
2381
+ /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2382
+ Box,
2383
+ {
2384
+ as: "input",
2385
+ ref: searchInputRef,
2386
+ testID: testID ? `${testID}-search-input` : void 0,
2387
+ flex: 1,
2388
+ type: "text",
2389
+ "aria-label": searchPlaceholder,
2390
+ value: searchValue,
2391
+ onChange: (e) => setSearchValue(e.target.value),
2392
+ placeholder: searchPlaceholder,
2393
+ style: {
2394
+ border: "none",
2395
+ outline: "none",
2396
+ background: "transparent",
2397
+ color: inputColors.text,
2398
+ fontSize: sizeStyles.fontSize,
2399
+ width: "100%"
2400
+ }
2401
+ }
2402
+ )
2403
+ ]
2321
2404
  }
2322
- },
2323
- flexDirection: "row",
2324
- alignItems: "center",
2325
- justifyContent: "space-between",
2326
- backgroundColor: item.checked ? brandColors.bg : "transparent",
2327
- hoverStyle: !item.disabled && !item.checked ? {
2328
- backgroundColor: theme.colors.control.input.bgHover
2329
- } : void 0,
2405
+ )
2406
+ }
2407
+ ),
2408
+ /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2409
+ Box,
2410
+ {
2411
+ paddingVertical: 4,
2330
2412
  style: {
2331
- cursor: item.disabled ? "not-allowed" : "pointer",
2332
- opacity: item.disabled ? 0.5 : 1
2413
+ maxHeight: searchable ? maxHeight - 60 : maxHeight,
2414
+ overflowY: "auto",
2415
+ overflowX: optionListOverflowX
2333
2416
  },
2334
- children: /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2335
- Text,
2417
+ children: menuItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2418
+ Box,
2336
2419
  {
2337
- color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2338
- fontSize: sizeStyles.fontSize,
2339
- fontWeight: "400",
2340
- children: item.children
2420
+ paddingVertical: sizeStyles.paddingVertical * 2,
2421
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2422
+ alignItems: "center",
2423
+ children: /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2424
+ Text,
2425
+ {
2426
+ color: theme.colors.content.tertiary,
2427
+ fontSize: sizeStyles.fontSize,
2428
+ children: noOptionsMessage
2429
+ }
2430
+ )
2341
2431
  }
2342
- )
2343
- },
2344
- item.id
2345
- );
2346
- })
2432
+ ) : menuItems.map((item, _index) => {
2433
+ const brandColors = theme.colors.control.brand.primary;
2434
+ const contentColors = theme.colors.content;
2435
+ return /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2436
+ Box,
2437
+ {
2438
+ testID,
2439
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2440
+ paddingVertical: 8,
2441
+ onPress: () => {
2442
+ if (!item.disabled) {
2443
+ handleItemToggle(item.id, !item.checked);
2444
+ }
2445
+ },
2446
+ flexDirection: "row",
2447
+ alignItems: "center",
2448
+ justifyContent: "space-between",
2449
+ backgroundColor: item.checked ? brandColors.bg : "transparent",
2450
+ hoverStyle: !item.disabled && !item.checked ? {
2451
+ backgroundColor: theme.colors.control.input.bgHover
2452
+ } : void 0,
2453
+ style: {
2454
+ cursor: item.disabled ? "not-allowed" : "pointer",
2455
+ opacity: item.disabled ? 0.5 : 1,
2456
+ minWidth: optionRowMinWidth
2457
+ },
2458
+ children: /* @__PURE__ */ (0, import_jsx_runtime734.jsx)(
2459
+ Text,
2460
+ {
2461
+ color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2462
+ fontSize: sizeStyles.fontSize,
2463
+ fontWeight: "400",
2464
+ numberOfLines: optionLabelNumberOfLines,
2465
+ style: optionLabelStyle,
2466
+ children: item.children
2467
+ }
2468
+ )
2469
+ },
2470
+ item.id
2471
+ );
2472
+ })
2473
+ }
2474
+ )
2475
+ ]
2347
2476
  }
2348
2477
  )
2349
2478
  ] });
2350
2479
  return /* @__PURE__ */ (0, import_jsx_runtime734.jsxs)(
2351
2480
  Box,
2352
2481
  {
2482
+ ref: containerRef,
2353
2483
  testID,
2354
2484
  flexDirection: "column",
2355
2485
  gap: sizeStyles.fieldGap,