Skip to content

Commit

Permalink
feat: support search for select component (#171 close AlistGo/alist#6430
Browse files Browse the repository at this point in the history
)
  • Loading branch information
liuycy authored May 12, 2024
1 parent 2ef620e commit 91de45c
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 36 deletions.
117 changes: 117 additions & 0 deletions src/components/SelectOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { createSignal, createEffect, on, Show, For } from "solid-js"
import {
hope,
useSelectContext,
SelectTrigger,
SelectPlaceholder,
SelectValue,
SelectIcon,
Input,
SelectContent,
SelectListbox,
SelectOption,
SelectOptionText,
SelectOptionIndicator,
} from "@hope-ui/solid"
import { BsSearch } from "solid-icons/bs"
import { useT } from "~/hooks"

interface SelectOptionsProps {
options: { key: string; label: string }[]
searchable?: boolean
readonly?: boolean
}

const SearchIcon = hope(BsSearch, { baseStyle: { color: "$neutral11" } })

export const SelectOptions = (props: SelectOptionsProps) => {
const t = useT()
const selectContext = useSelectContext()

const [searching, setSearching] = createSignal(false)
const [displayValue, setDisplayValue] = createSignal("")

let tid: ReturnType<typeof setTimeout>

const displayOptions = () => {
if (!props.searchable) return props.options
return props.options.filter((o) =>
new RegExp(displayValue(), "i").test(o.label),
)
}

const selectedLabel = () =>
selectContext.state.selectedOptions.map((o) => o.textValue).join(",")

createEffect(on(selectedLabel, setDisplayValue))

return (
<>
<SelectTrigger
as={
props.searchable
? "div" // pressing "Space" will open option list, prevent this behavior by setting as "div"
: undefined
}
>
<Show
fallback={
<>
<SelectPlaceholder>{t("global.choose")}</SelectPlaceholder>
<SelectValue />
<SelectIcon />
</>
}
when={!props.readonly && props.searchable}
>
<Input
border="none"
variant="unstyled"
placeholder={
searching()
? selectedLabel() || t("global.choose")
: t("global.choose")
}
value={searching() ? displayValue() : selectedLabel()}
onKeyDown={(e) => e.stopPropagation()}
onClick={(e) => {
if (!selectContext.state.opened) return
e.stopPropagation()
}}
onInput={(e) => {
clearTimeout(tid)
setDisplayValue(e.currentTarget.value)
}}
onBlur={() => {
setSearching(false)
tid = setTimeout(
() => setDisplayValue(""),
300 /* transition duration of <SelectContent /> */,
)
}}
onFocus={() => {
clearTimeout(tid)
setDisplayValue("")
setSearching(true)
}}
/>
<Show when={searching()} fallback={<SelectIcon />}>
<SearchIcon />
</Show>
</Show>
</SelectTrigger>
<SelectContent>
<SelectListbox>
<For each={displayOptions()}>
{(item) => (
<SelectOption value={item.key}>
<SelectOptionText>{item.label}</SelectOptionText>
<SelectOptionIndicator />
</SelectOption>
)}
</For>
</SelectListbox>
</SelectContent>
</>
)
}
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./Base"
export * from "./Paginator"
export * from "./icons"
export * from "./EncodingSelect"
export * from "./SelectOptions"
1 change: 1 addition & 0 deletions src/pages/manage/storages/AddOrEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const AddOrEdit = () => {
default=""
readonly={id !== undefined}
required
searchable
type={Type.Select}
options={id ? storage.driver : Object.keys(drivers()).join(",")}
value={storage.driver}
Expand Down
58 changes: 22 additions & 36 deletions src/pages/manage/storages/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,13 @@ import {
FormLabel,
Input,
Select,
SelectContent,
SelectIcon,
SelectListbox,
SelectOption,
SelectOptionIndicator,
SelectOptionText,
SelectPlaceholder,
SelectTrigger,
SelectValue,
Switch as HopeSwitch,
Textarea,
} from "@hope-ui/solid"
import { For, Match, Show, Switch } from "solid-js"
import { Match, Show, Switch } from "solid-js"
import { useT } from "~/hooks"
import { DriverItem, Type } from "~/types"
import { SelectOptions } from "~/components"

export type ItemProps = DriverItem & {
readonly?: boolean
Expand All @@ -38,7 +30,13 @@ export type ItemProps = DriverItem & {
value: number
}
| {
type: Type.String | Type.Text | Type.Select
type: Type.String | Type.Text
onChange?: (value: string) => void
value: string
}
| {
type: Type.Select
searchable?: boolean
onChange?: (value: string) => void
value: string
}
Expand Down Expand Up @@ -122,31 +120,19 @@ const Item = (props: ItemProps) => {
: undefined
}
>
<SelectTrigger>
<SelectPlaceholder>{t("global.choose")}</SelectPlaceholder>
<SelectValue />
<SelectIcon />
</SelectTrigger>
<SelectContent>
<SelectListbox>
<For each={props.options?.split(",")}>
{(item) => (
<SelectOption value={item}>
<SelectOptionText>
{t(
(props.options_prefix ??
(props.driver === "common"
? `storages.common.${props.name}s`
: `drivers.${props.driver}.${props.name}s`)) +
`.${item}`,
)}
</SelectOptionText>
<SelectOptionIndicator />
</SelectOption>
)}
</For>
</SelectListbox>
</SelectContent>
<SelectOptions
readonly={props.readonly}
searchable={props.type === Type.Select && props.searchable}
options={props.options.split(",").map((key) => ({
key,
label: t(
(props.options_prefix ??
(props.driver === "common"
? `storages.common.${props.name}s`
: `drivers.${props.driver}.${props.name}s`)) + `.${key}`,
),
}))}
/>
</Select>
</Match>
</Switch>
Expand Down

0 comments on commit 91de45c

Please sign in to comment.