157d24db73
Reworks search to not be a page and instead to be inline dropdown. <img width="763" alt="Screenshot 2024-02-05 at 13 22 58" src="https://github.com/tldraw/tldraw/assets/469604/4e5a8076-62cd-44bb-b8e7-7f5ecdc4af24"> - rework search completely - rm Search Results css - uses Ariakit and add appropriate hooks / styling - I couldn't use Radix unfortunately since they're still working on adding a Combox: https://github.com/radix-ui/primitives/issues/1342 - I'm open to other suggestions but Ariakit plays nicely with Radix and keeps things open to migrate to Radix in the future - fixes bug with not scrolling to right place when having a direct link - adds categories in the search results - examples / reference / learn - and adds category icons. Let me know if there's a better policy for adding new SVG icons cc @steveruizok ### Change Type - [x] `minor` — New feature ### Test Plan 1. Test searches using normal method for each type (examples, docs, refs) 2. Test searches using AI for each type (ditto) ### Release Notes - Docs: rework the search to be an inline dropdown.
123 lines
2.9 KiB
TypeScript
123 lines
2.9 KiB
TypeScript
import {
|
|
Combobox,
|
|
ComboboxCancel,
|
|
ComboboxGroup,
|
|
ComboboxGroupLabel,
|
|
ComboboxItem,
|
|
ComboboxItemValue,
|
|
ComboboxPopover,
|
|
ComboboxProvider,
|
|
} from '@ariakit/react'
|
|
import { ComponentType, ForwardedRef, forwardRef, startTransition, useState } from 'react'
|
|
import './Autocomplete.css'
|
|
import { Icon } from './Icon'
|
|
import Spinner from './Spinner'
|
|
|
|
export type DropdownOption = {
|
|
label: string
|
|
value: string
|
|
group?: string
|
|
}
|
|
|
|
type AutocompleteProps = {
|
|
customUI?: React.ReactNode
|
|
groups?: string[]
|
|
groupsToIcon?: {
|
|
[key: string]: ComponentType<{
|
|
className?: string
|
|
}>
|
|
}
|
|
groupsToLabel?: { [key: string]: string }
|
|
isLoading: boolean
|
|
options: DropdownOption[]
|
|
onChange: (value: string) => void
|
|
onInputChange: (value: string) => void
|
|
}
|
|
|
|
const DEFAULT_GROUP = 'autocomplete-default'
|
|
|
|
const Autocomplete = forwardRef(function Autocomplete(
|
|
{
|
|
customUI,
|
|
groups = [DEFAULT_GROUP],
|
|
groupsToIcon,
|
|
groupsToLabel,
|
|
isLoading,
|
|
options,
|
|
onInputChange,
|
|
onChange,
|
|
}: AutocompleteProps,
|
|
ref: ForwardedRef<HTMLInputElement>
|
|
) {
|
|
const [open, setOpen] = useState(false)
|
|
const [value, setValue] = useState('')
|
|
|
|
const renderedGroups = groups.map((group) => {
|
|
const filteredOptions = options.filter(
|
|
({ group: optionGroup }) => optionGroup === group || group === DEFAULT_GROUP
|
|
)
|
|
if (filteredOptions.length === 0) return null
|
|
|
|
return (
|
|
<ComboboxGroup>
|
|
{groupsToLabel?.[group] && (
|
|
<ComboboxGroupLabel className="autocomplete__group">
|
|
{groupsToLabel?.[group]}
|
|
</ComboboxGroupLabel>
|
|
)}
|
|
{filteredOptions.map(({ label, value }) => {
|
|
const Icon = groupsToIcon?.[group]
|
|
return (
|
|
<ComboboxItem key={`${label}-${value}`} className="autocomplete__item" value={value}>
|
|
{Icon && <Icon className="autocomplete__item__icon" />}
|
|
<ComboboxItemValue value={label} />
|
|
</ComboboxItem>
|
|
)
|
|
})}
|
|
</ComboboxGroup>
|
|
)
|
|
})
|
|
|
|
return (
|
|
<ComboboxProvider<string>
|
|
defaultSelectedValue=""
|
|
open={open}
|
|
setOpen={setOpen}
|
|
resetValueOnHide
|
|
includesBaseElement={false}
|
|
setValue={(newValue) => {
|
|
startTransition(() => setValue(newValue))
|
|
onInputChange(newValue)
|
|
}}
|
|
setSelectedValue={(newValue) => onChange(newValue)}
|
|
>
|
|
<div className="autocomplete__wrapper">
|
|
{isLoading ? (
|
|
<Spinner className="autocomplete__icon" />
|
|
) : (
|
|
<Icon className="autocomplete__icon" icon="search" small />
|
|
)}
|
|
|
|
<Combobox
|
|
autoSelect
|
|
placeholder="Search…"
|
|
ref={ref}
|
|
className="autocomplete__input"
|
|
value={value}
|
|
/>
|
|
|
|
{value && <ComboboxCancel className="autocomplete__cancel" />}
|
|
|
|
{value && (
|
|
<ComboboxPopover className="autocomplete__popover">
|
|
{customUI}
|
|
{options.length === 0 && <span>No results found.</span>}
|
|
{options.length !== 0 && renderedGroups}
|
|
</ComboboxPopover>
|
|
)}
|
|
</div>
|
|
</ComboboxProvider>
|
|
)
|
|
})
|
|
|
|
export default Autocomplete
|