From 4a58441dcc459bbd0c0d6c5d8ac88612b5a94383 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 13 Feb 2025 09:42:09 -0500 Subject: [PATCH] fix: Ariakit Combobox Virtualization Ariakit Combobox was not working well with several virtualization libraries as automated focus management was conflicting with scrolling/styling required of other virtualization methods. The entire strategy was replaced using experimental ariakit virtualization component `SelectRenderer` Performance of component was also improved as a result of latest ariakit lib changes --- client/package.json | 3 +- client/src/components/ui/ControlCombobox.tsx | 184 +++++++++---------- package-lock.json | 52 +++--- 3 files changed, 115 insertions(+), 124 deletions(-) diff --git a/client/package.json b/client/package.json index 993cf300714..0a0379b6f1d 100644 --- a/client/package.json +++ b/client/package.json @@ -28,7 +28,8 @@ }, "homepage": "https://librechat.ai", "dependencies": { - "@ariakit/react": "^0.4.11", + "@ariakit/react": "^0.4.15", + "@ariakit/react-core": "^0.4.15", "@codesandbox/sandpack-react": "^2.19.10", "@dicebear/collection": "^7.0.4", "@dicebear/core": "^7.0.4", diff --git a/client/src/components/ui/ControlCombobox.tsx b/client/src/components/ui/ControlCombobox.tsx index d4a95f25d72..9c4fd16c30a 100644 --- a/client/src/components/ui/ControlCombobox.tsx +++ b/client/src/components/ui/ControlCombobox.tsx @@ -1,10 +1,10 @@ +import { Search } from 'lucide-react'; import * as Ariakit from '@ariakit/react'; import { matchSorter } from 'match-sorter'; -import { AutoSizer, List } from 'react-virtualized'; -import { startTransition, useMemo, useState, useEffect, useRef, memo } from 'react'; -import { cn } from '~/utils'; +import { useMemo, useState, useRef, memo, useEffect } from 'react'; +import { SelectRenderer } from '@ariakit/react-core/select/select-renderer'; import type { OptionWithIcon } from '~/common'; -import { Search } from 'lucide-react'; +import { cn } from '~/utils'; interface ControlComboboxProps { selectedValue: string; @@ -35,11 +35,33 @@ function ControlCombobox({ const buttonRef = useRef(null); const [buttonWidth, setButtonWidth] = useState(null); + const getItem = (option: OptionWithIcon) => ({ + id: `item-${option.value}`, + value: option.value as string | undefined, + label: option.label, + icon: option.icon, + }); + + const combobox = Ariakit.useComboboxStore({ + defaultItems: items.map(getItem), + resetValueOnHide: true, + value: searchValue, + setValue: setSearchValue, + }); + + const select = Ariakit.useSelectStore({ + combobox, + defaultItems: items.map(getItem), + value: selectedValue, + setValue, + }); + const matches = useMemo(() => { - return matchSorter(items, searchValue, { + const filteredItems = matchSorter(items, searchValue, { keys: ['value', 'label'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); + return filteredItems.map(getItem); }, [searchValue, items]); useEffect(() => { @@ -48,104 +70,74 @@ function ControlCombobox({ } }, [isCollapsed]); - const rowRenderer = ({ - index, - key, - style, - }: { - index: number; - key: string; - style: React.CSSProperties; - }) => { - const item = matches[index]; - return ( - + + {ariaLabel} + + } - style={style} > - {item.icon != null && ( -
- {item.icon} + {SelectIcon != null && ( +
+ {SelectIcon}
)} - {item.label} - - ); - }; - - return ( -
- { - startTransition(() => { - setSearchValue(value); - }); - }} + {!isCollapsed && ( + {displayValue ?? selectPlaceholder} + )} + + - - {ariaLabel} - - {SelectIcon != null && ( -
- {SelectIcon} -
- )} - {!isCollapsed && ( - - {displayValue ?? selectPlaceholder} - - )} -
- -
-
- - -
-
-
- - {({ width }) => ( - - )} - -
-
-
-
+
+
+ + +
+
+
+ + + {({ value, icon, label, ...item }) => ( + } + > + {icon != null && ( +
+ {icon} +
+ )} + {label} +
+ )} +
+
+
+
); } diff --git a/package-lock.json b/package-lock.json index 77a282d8071..3b72a2f66f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1598,7 +1598,8 @@ "version": "v0.7.7-rc1", "license": "ISC", "dependencies": { - "@ariakit/react": "^0.4.11", + "@ariakit/react": "^0.4.15", + "@ariakit/react-core": "^0.4.15", "@codesandbox/sandpack-react": "^2.19.10", "@dicebear/collection": "^7.0.4", "@dicebear/core": "^7.0.4", @@ -1711,6 +1712,22 @@ "vite-plugin-pwa": "^0.21.1" } }, + "client/node_modules/@ariakit/react": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.15.tgz", + "integrity": "sha512-0V2LkNPFrGRT+SEIiObx/LQjR6v3rR+mKEDUu/3tq7jfCZ+7+6Q6EMR1rFaK+XMkaRY1RWUcj/rRDWAUWnsDww==", + "dependencies": { + "@ariakit/react-core": "0.4.15" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "client/node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -2818,35 +2835,16 @@ } }, "node_modules/@ariakit/core": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.10.tgz", - "integrity": "sha512-mX3EabQbfVh5uTjsTJ3+gjj7KGdTNhIN0qZHJd5Z2iPUnKl9NBy23Lgu6PEskpVsKAZ3proirjguD7U9fKMs/A==", - "license": "MIT" - }, - "node_modules/@ariakit/react": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.11.tgz", - "integrity": "sha512-nLpPrmNcspqNhk4o+epsgeZfP1+Fkh4uIzNe5yrFkXolRkqHGKAxl4Hi82e0yxIBUbYbZIEwsZQQVceF1L6xrw==", - "license": "MIT", - "dependencies": { - "@ariakit/react-core": "0.4.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.14.tgz", + "integrity": "sha512-hpzZvyYzGhP09S9jW1XGsU/FD5K3BKsH1eG/QJ8rfgEeUdPS7BvHPt5lHbOeJ2cMrRzBEvsEzLi1ivfDifHsVA==" }, "node_modules/@ariakit/react-core": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.11.tgz", - "integrity": "sha512-i6KedWhjZkNC7tMEKO0eNjjq2HRPiHyGaBS2x2VaWwzBepoYtjyvxRXyqLJ3gaiNdlwckN1TZsRDfD+viy13IQ==", - "license": "MIT", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.15.tgz", + "integrity": "sha512-Up8+U97nAPJdyUh9E8BCEhJYTA+eVztWpHoo1R9zZfHd4cnBWAg5RHxEmMH+MamlvuRxBQA71hFKY/735fDg+A==", "dependencies": { - "@ariakit/core": "0.4.10", + "@ariakit/core": "0.4.14", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" },