feat: fuzzy file open

This commit is contained in:
Adam
2025-09-24 12:40:54 -05:00
parent d3b6545e7c
commit f589fc2327
4 changed files with 75 additions and 36 deletions

View File

@@ -1,22 +1,18 @@
import { createEffect, Show, For, createMemo, type JSX } from "solid-js"
import { createEffect, Show, For, createMemo, type JSX, createResource } from "solid-js"
import { Dialog } from "@kobalte/core/dialog"
import { Icon, IconButton } from "@/ui"
import { createStore } from "solid-js/store"
import { entries, flatMap, groupBy, map, mapValues, pipe } from "remeda"
import { entries, flatMap, groupBy, map, pipe } from "remeda"
import { createList } from "solid-list"
import fuzzysort from "fuzzysort"
interface SelectDialogProps<T> {
items: T[]
items: T[] | ((filter: string) => Promise<T[]>)
key: (item: T) => string
render: (item: T) => JSX.Element
filter?: string[]
current?: T
placeholder?: string
filter?:
| false
| {
keys: string[]
}
groupBy?: (x: T) => string
onSelect?: (value: T | undefined) => void
onClose?: () => void
@@ -29,24 +25,31 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
mouseActive: false,
})
const grouped = createMemo(() => {
const needle = store.filter.toLowerCase()
const result = pipe(
props.items,
(x) =>
!needle || !props.filter
? x
: fuzzysort.go(needle, x, { keys: props.filter && props.filter.keys }).map((x) => x.obj),
groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
mapValues((x) => x.sort((a, b) => props.key(a).localeCompare(props.key(b)))),
entries(),
map(([k, v]) => ({ category: k, items: v })),
)
return result
})
const [grouped] = createResource(
() => store.filter,
async (filter) => {
const needle = filter.toLowerCase()
const all = (typeof props.items === "function" ? await props.items(needle) : props.items) || []
const result = pipe(
all,
(x) => {
if (!needle) return x
if (!props.filter && Array.isArray(x) && x.every((e) => typeof e === "string")) {
return fuzzysort.go(needle, x).map((x) => x.target) as T[]
}
return fuzzysort.go(needle, x, { keys: props.filter! }).map((x) => x.obj)
},
groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
// mapValues((x) => x.sort((a, b) => props.key(a).localeCompare(props.key(b)))),
entries(),
map(([k, v]) => ({ category: k, items: v })),
)
return result
},
)
const flat = createMemo(() => {
return pipe(
grouped(),
grouped() || [],
flatMap((x) => x.items),
)
})
@@ -55,7 +58,11 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
initialActive: props.current ? props.key(props.current) : undefined,
loop: true,
})
const resetSelection = () => list.setActive(props.key(flat()[0]))
const resetSelection = () => {
const all = flat()
if (all.length === 0) return
list.setActive(props.key(all[0]))
}
createEffect(() => {
store.filter
@@ -64,8 +71,9 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
})
createEffect(() => {
if (store.mouseActive) return
if (list.active() === props.key(flat()[0])) {
const all = flat()
if (store.mouseActive || all.length === 0) return
if (list.active() === props.key(all[0])) {
scrollRef?.scrollTo(0, 0)
return
}
@@ -156,9 +164,11 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
<For each={grouped()}>
{(group) => (
<>
<div class="top-0 sticky z-10 bg-background-panel p-2 text-xs text-text-muted/60 tracking-wider uppercase">
{group.category}
</div>
<Show when={group.category}>
<div class="top-0 sticky z-10 bg-background-panel p-2 text-xs text-text-muted/60 tracking-wider uppercase">
{group.category}
</div>
</Show>
<div class="p-2">
<For each={group.items}>
{(item) => (