ui/search: add loading state

This commit is contained in:
Johannes Kirschbauer
2025-08-26 17:06:55 +02:00
parent 24c5146763
commit 53e16242b9
2 changed files with 73 additions and 35 deletions

View File

@@ -117,6 +117,27 @@ export const Default: Story = {
},
};
export const Loading: Story = {
args: {
// Test with lots of modules
loading: true,
options: [],
renderItem: () => <span></span>,
},
render: (args: SearchProps<Module>) => {
return (
<div class="absolute bottom-1/3 w-3/4 px-3">
<Search<Module>
{...args}
onChange={(module) => {
// Go to the module configuration
}}
/>
</div>
);
},
};
type MachineOrTag =
| {
value: string;

View File

@@ -2,9 +2,10 @@ import Icon from "../Icon/Icon";
import { Button } from "../Button/Button";
import styles from "./Search.module.css";
import { Combobox } from "@kobalte/core/combobox";
import { createMemo, createSignal, For, JSX } from "solid-js";
import { createMemo, createSignal, For, JSX, Match, Switch } from "solid-js";
import { createVirtualizer } from "@tanstack/solid-virtual";
import { CollectionNode } from "@kobalte/core/*";
import { Loader } from "../Loader/Loader";
export interface Option {
value: string;
@@ -15,6 +16,8 @@ export interface SearchProps<T> {
onChange: (value: T | null) => void;
options: T[];
renderItem: (item: T) => JSX.Element;
loading?: boolean;
loadingComponent?: JSX.Element;
}
export function Search<T extends Option>(props: SearchProps<T>) {
// Controlled input value, to allow resetting the input itself
@@ -136,41 +139,55 @@ export function Search<T extends Option>(props: SearchProps<T>) {
setComboboxItems(arr);
return (
<div
style={{
height: `${virtualizer().getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
<For each={virtualizer().getVirtualItems()}>
{(virtualRow) => {
const item: CollectionNode<T> | undefined =
items().getItem(virtualRow.key as string);
<Switch>
<Match when={props.loading}>
{props.loadingComponent ?? (
<div class="flex w-full justify-center py-2">
<Loader />
</div>
)}
</Match>
<Match when={!props.loading}>
<div
style={{
height: `${virtualizer().getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
<For each={virtualizer().getVirtualItems()}>
{(virtualRow) => {
const item: CollectionNode<T> | undefined =
items().getItem(virtualRow.key as string);
if (!item) {
console.warn("Item not found for key:", virtualRow.key);
return null;
}
return (
<Combobox.Item
item={item}
class={styles.searchItem}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{props.renderItem(item.rawValue)}
</Combobox.Item>
);
}}
</For>
</div>
if (!item) {
console.warn(
"Item not found for key:",
virtualRow.key,
);
return null;
}
return (
<Combobox.Item
item={item}
class={styles.searchItem}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{props.renderItem(item.rawValue)}
</Combobox.Item>
);
}}
</For>
</div>
</Match>
</Switch>
);
}}
</Combobox.Listbox>