From f589fc2327dd807a82ce6f756231fdb8eb43dd59 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:40:54 -0500 Subject: feat: fuzzy file open --- packages/app/src/components/select-dialog.tsx | 70 +++++++++++++++------------ 1 file changed, 40 insertions(+), 30 deletions(-) (limited to 'packages/app/src/components') diff --git a/packages/app/src/components/select-dialog.tsx b/packages/app/src/components/select-dialog.tsx index 2ff64f5da..ade7489d1 100644 --- a/packages/app/src/components/select-dialog.tsx +++ b/packages/app/src/components/select-dialog.tsx @@ -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 { - items: T[] + items: T[] | ((filter: string) => Promise) 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(props: SelectDialogProps) { 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(props: SelectDialogProps) { 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(props: SelectDialogProps) { }) 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(props: SelectDialogProps) { {(group) => ( <> -
- {group.category} -
+ +
+ {group.category} +
+
{(item) => ( -- cgit v1.2.3