-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
📱 fix: Resolve Android Device and Accessibility Issues of Sidebar Com…
…bobox (#3689) * chore: Update @ariakit/react dependency to version 0.4.8 * refactor: Fix Combobox Android issue with radix-ui * fix: Improve scrolling behavior by setting abort scroll state to false after scrolling to end * wip: first pass switcher rewrite * feat: Add button width calculation for ComboboxComponent * refactor: Update ComboboxComponent styling for improved layout and appearance * refactor: Update AssistantSwitcher component to handle null values for assistant names and avatar URLs * refactor: Update ModelSwitcher component to use SimpleCombobox for improved functionality and styling * refactor: Update Switcher Separator styling for improved layout and appearance * refactor: Improve accessibility by adding aria-label to ComboboxComponent select items * refactor: rename SimpleCombobox -> ControlCombobox
- Loading branch information
1 parent
b22f1c1
commit 87d95a9
Showing
9 changed files
with
185 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import * as Ariakit from '@ariakit/react'; | ||
import { matchSorter } from 'match-sorter'; | ||
import { startTransition, useMemo, useState, useEffect, useRef } from 'react'; | ||
import { cn } from '~/utils'; | ||
import type { OptionWithIcon } from '~/common'; | ||
import { Search } from 'lucide-react'; | ||
|
||
interface ControlComboboxProps { | ||
selectedValue: string; | ||
displayValue?: string; | ||
items: OptionWithIcon[]; | ||
setValue: (value: string) => void; | ||
ariaLabel: string; | ||
searchPlaceholder?: string; | ||
selectPlaceholder?: string; | ||
isCollapsed: boolean; | ||
SelectIcon?: React.ReactNode; | ||
} | ||
|
||
export default function ControlCombobox({ | ||
selectedValue, | ||
displayValue, | ||
items, | ||
setValue, | ||
ariaLabel, | ||
searchPlaceholder, | ||
selectPlaceholder, | ||
isCollapsed, | ||
SelectIcon, | ||
}: ControlComboboxProps) { | ||
const [searchValue, setSearchValue] = useState(''); | ||
const buttonRef = useRef<HTMLButtonElement>(null); | ||
const [buttonWidth, setButtonWidth] = useState<number | null>(null); | ||
|
||
const matches = useMemo(() => { | ||
return matchSorter(items, searchValue, { | ||
keys: ['value', 'label'], | ||
baseSort: (a, b) => (a.index < b.index ? -1 : 1), | ||
}); | ||
}, [searchValue, items]); | ||
|
||
useEffect(() => { | ||
if (buttonRef.current && !isCollapsed) { | ||
setButtonWidth(buttonRef.current.offsetWidth); | ||
} | ||
}, [isCollapsed]); | ||
|
||
return ( | ||
<div className="flex w-full items-center justify-center px-1"> | ||
<Ariakit.ComboboxProvider | ||
resetValueOnHide | ||
setValue={(value) => { | ||
startTransition(() => { | ||
setSearchValue(value); | ||
}); | ||
}} | ||
> | ||
<Ariakit.SelectProvider value={selectedValue} setValue={setValue}> | ||
<Ariakit.SelectLabel className="sr-only">{ariaLabel}</Ariakit.SelectLabel> | ||
<Ariakit.Select | ||
ref={buttonRef} | ||
className={cn( | ||
'flex items-center justify-center gap-2 rounded-full bg-surface-secondary', | ||
'text-text-primary hover:bg-surface-tertiary', | ||
'border border-border-light', | ||
isCollapsed ? 'h-10 w-10' : 'h-10 w-full rounded-md px-3 py-2 text-sm', | ||
)} | ||
> | ||
{SelectIcon != null && ( | ||
<div className="assistant-item flex h-5 w-5 items-center justify-center overflow-hidden rounded-full"> | ||
{SelectIcon} | ||
</div> | ||
)} | ||
{!isCollapsed && ( | ||
<span className="flex-grow truncate text-left"> | ||
{displayValue ?? selectPlaceholder} | ||
</span> | ||
)} | ||
</Ariakit.Select> | ||
<Ariakit.SelectPopover | ||
gutter={4} | ||
portal | ||
className="z-50 overflow-hidden rounded-md border border-border-light bg-surface-secondary shadow-lg" | ||
style={{ width: isCollapsed ? '300px' : buttonWidth ?? '300px' }} | ||
> | ||
<div className="p-2"> | ||
<div className="relative"> | ||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-primary" /> | ||
<Ariakit.Combobox | ||
autoSelect | ||
placeholder={searchPlaceholder} | ||
className="w-full rounded-md border border-border-light bg-surface-tertiary py-2 pl-9 pr-3 text-sm text-text-primary focus:outline-none" | ||
/> | ||
</div> | ||
</div> | ||
<Ariakit.ComboboxList className="max-h-[50vh] overflow-auto"> | ||
{matches.map((item) => ( | ||
<Ariakit.SelectItem | ||
key={item.value} | ||
value={`${item.value ?? ''}`} | ||
aria-label={`${item.label ?? item.value ?? ''}`} | ||
className={cn( | ||
'flex cursor-pointer items-center px-3 py-2 text-sm', | ||
'text-text-primary hover:bg-surface-tertiary', | ||
'data-[active-item]:bg-surface-tertiary', | ||
)} | ||
render={<Ariakit.ComboboxItem />} | ||
> | ||
{item.icon != null && ( | ||
<div className="assistant-item mr-2 flex h-5 w-5 items-center justify-center overflow-hidden rounded-full"> | ||
{item.icon} | ||
</div> | ||
)} | ||
<span className="flex-grow truncate text-left">{item.label}</span> | ||
</Ariakit.SelectItem> | ||
))} | ||
</Ariakit.ComboboxList> | ||
</Ariakit.SelectPopover> | ||
</Ariakit.SelectProvider> | ||
</Ariakit.ComboboxProvider> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.