summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/hooks
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-10-22 17:31:44 -0500
committerAdam <[email protected]>2025-10-22 17:31:49 -0500
commit89b703c387aed3ee918d826b788b4be1729bdde9 (patch)
tree9a22d26bfc5e921789924e2344d04113660b67a3 /packages/ui/src/hooks
parenteff12cb48453e45590a53a7705c5044a0da9e7f7 (diff)
downloadopencode-89b703c387aed3ee918d826b788b4be1729bdde9.tar.gz
opencode-89b703c387aed3ee918d826b788b4be1729bdde9.zip
wip: desktop work
Diffstat (limited to 'packages/ui/src/hooks')
-rw-r--r--packages/ui/src/hooks/index.ts1
-rw-r--r--packages/ui/src/hooks/use-filtered-list.tsx89
2 files changed, 90 insertions, 0 deletions
diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts
new file mode 100644
index 000000000..7eef78091
--- /dev/null
+++ b/packages/ui/src/hooks/index.ts
@@ -0,0 +1 @@
+export * from "./use-filtered-list"
diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx
new file mode 100644
index 000000000..b3ddf69ed
--- /dev/null
+++ b/packages/ui/src/hooks/use-filtered-list.tsx
@@ -0,0 +1,89 @@
+import fuzzysort from "fuzzysort"
+import { entries, flatMap, groupBy, map, pipe } from "remeda"
+import { createMemo, createResource } from "solid-js"
+import { createStore } from "solid-js/store"
+import { createList } from "solid-list"
+
+export interface FilteredListProps<T> {
+ items: T[] | ((filter: string) => Promise<T[]>)
+ key: (item: T) => string
+ filterKeys?: string[]
+ current?: T
+ groupBy?: (x: T) => string
+ sortBy?: (a: T, b: T) => number
+ sortGroupsBy?: (a: { category: string; items: T[] }, b: { category: string; items: T[] }) => number
+ onSelect?: (value: T | undefined) => void
+}
+
+export function useFilteredList<T>(props: FilteredListProps<T>) {
+ const [store, setStore] = createStore<{ filter: string }>({ filter: "" })
+
+ 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.filterKeys && 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.filterKeys! }).map((x) => x.obj)
+ },
+ groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
+ entries(),
+ map(([k, v]) => ({ category: k, items: props.sortBy ? v.sort(props.sortBy) : v })),
+ (groups) => (props.sortGroupsBy ? groups.sort(props.sortGroupsBy) : groups),
+ )
+ return result
+ },
+ )
+
+ const flat = createMemo(() => {
+ return pipe(
+ grouped() || [],
+ flatMap((x) => x.items),
+ )
+ })
+
+ const list = createList({
+ items: () => flat().map(props.key),
+ initialActive: props.current ? props.key(props.current) : props.key(flat()[0]),
+ loop: true,
+ })
+
+ const reset = () => {
+ const all = flat()
+ if (all.length === 0) return
+ list.setActive(props.key(all[0]))
+ }
+
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Enter") {
+ event.preventDefault()
+ const selected = flat().find((x) => props.key(x) === list.active())
+ if (selected) props.onSelect?.(selected)
+ } else {
+ list.onKeyDown(event)
+ }
+ }
+
+ const onInput = (value: string) => {
+ setStore("filter", value)
+ reset()
+ }
+
+ return {
+ filter: () => store.filter,
+ grouped,
+ flat,
+ reset,
+ clear: () => setStore("filter", ""),
+ onKeyDown,
+ onInput,
+ active: list.active,
+ setActive: list.setActive,
+ }
+}