ui/search: remove portal, fix styling
This commit is contained in:
@@ -2,9 +2,11 @@ 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, VirtualizerOptions } from "@tanstack/solid-virtual";
|
||||
import { CollectionNode } from "@kobalte/core/*";
|
||||
import cx from "classnames";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
|
||||
export interface Option {
|
||||
value: string;
|
||||
@@ -23,6 +25,10 @@ export interface SearchMultipleProps<T> {
|
||||
placeholder?: string;
|
||||
virtualizerOptions?: Partial<VirtualizerOptions<Element, Element>>;
|
||||
height: string; // e.g. '14.5rem'
|
||||
headerClass?: string;
|
||||
headerChildren?: JSX.Element;
|
||||
loading?: boolean;
|
||||
loadingComponent?: JSX.Element;
|
||||
}
|
||||
export function SearchMultiple<T extends Option>(
|
||||
props: SearchMultipleProps<T>,
|
||||
@@ -72,7 +78,6 @@ export function SearchMultiple<T extends Option>(
|
||||
props.onChange(values);
|
||||
}}
|
||||
class={styles.searchContainer}
|
||||
style={{ "--container-height": props.height }}
|
||||
placement="bottom-start"
|
||||
options={props.options}
|
||||
optionValue="value"
|
||||
@@ -89,8 +94,12 @@ export function SearchMultiple<T extends Option>(
|
||||
triggerMode="manual"
|
||||
noResetInputOnBlur={true}
|
||||
>
|
||||
<Combobox.Control<T> class={styles.searchHeader}>
|
||||
<Combobox.Control<T>
|
||||
class={cx(styles.searchHeader, props.headerClass || "bg-inv-3")}
|
||||
>
|
||||
{(state) => (
|
||||
<>
|
||||
{props.headerChildren}
|
||||
<div class={styles.inputContainer}>
|
||||
<Icon icon="Search" color="quaternary" />
|
||||
<Combobox.Input
|
||||
@@ -121,20 +130,15 @@ export function SearchMultiple<T extends Option>(
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Combobox.Control>
|
||||
<Combobox.Portal>
|
||||
<Combobox.Content
|
||||
class={styles.searchContent}
|
||||
tabIndex={-1}
|
||||
style={{ "--container-height": props.height }}
|
||||
>
|
||||
<Combobox.Listbox<T>
|
||||
ref={(el) => {
|
||||
listboxRef = el;
|
||||
}}
|
||||
style={{
|
||||
height: "100%",
|
||||
height: props.height,
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
"overflow-y": "auto",
|
||||
@@ -145,6 +149,7 @@ export function SearchMultiple<T extends Option>(
|
||||
);
|
||||
virtualizer().scrollToIndex(idx);
|
||||
}}
|
||||
class={styles.listbox}
|
||||
>
|
||||
{(items) => {
|
||||
// Update the virtualizer with the filtered items
|
||||
@@ -152,6 +157,15 @@ export function SearchMultiple<T extends Option>(
|
||||
setComboboxItems(arr);
|
||||
|
||||
return (
|
||||
<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`,
|
||||
@@ -191,11 +205,12 @@ export function SearchMultiple<T extends Option>(
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}}
|
||||
</Combobox.Listbox>
|
||||
</Combobox.Content>
|
||||
</Combobox.Portal>
|
||||
{/* </Combobox.Content> */}
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.searchHeader {
|
||||
@apply bg-inv-3 flex gap-2 items-center p-2 rounded-md z-50;
|
||||
@apply flex gap-2 items-center p-2 rounded-t-md z-50;
|
||||
@apply px-3 pt-3 pb-2;
|
||||
}
|
||||
|
||||
@@ -42,18 +42,21 @@
|
||||
}
|
||||
|
||||
.searchItem {
|
||||
@apply flex flex-col justify-center overflow-hidden;
|
||||
box-shadow: 0 1px 0 0 theme(colors.border.inv.2);
|
||||
|
||||
&[data-highlighted],
|
||||
&:focus,
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
@apply bg-inv-acc-2;
|
||||
@apply bg-inv-acc-2 rounded-md;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@apply bg-inv-acc-3;
|
||||
@apply bg-inv-acc-3 rounded-md;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
@apply flex flex-col justify-center;
|
||||
}
|
||||
|
||||
.searchContainer {
|
||||
@@ -61,8 +64,6 @@
|
||||
|
||||
@apply rounded-lg;
|
||||
|
||||
height: var(--container-height, 14.5rem);
|
||||
|
||||
border: 1px solid #2b4647;
|
||||
|
||||
background:
|
||||
@@ -78,9 +79,8 @@
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.searchContent {
|
||||
@apply px-3;
|
||||
height: calc(var(--container-height, 14.5rem) - 3.5rem);
|
||||
.listbox {
|
||||
@apply px-3 pt-3.5;
|
||||
}
|
||||
|
||||
@keyframes contentHide {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SearchMultiple,
|
||||
SearchMultipleProps,
|
||||
} from "./MultipleSearch";
|
||||
import { JSX, Show } from "solid-js";
|
||||
import { Show } from "solid-js";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Search",
|
||||
@@ -72,6 +72,7 @@ export interface Module {
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
height: "14.5rem",
|
||||
// Test with lots of modules
|
||||
options: generateModules(1000),
|
||||
renderItem: (item: Module) => {
|
||||
@@ -119,6 +120,7 @@ export const Default: Story = {
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
height: "14.5rem",
|
||||
// Test with lots of modules
|
||||
loading: true,
|
||||
options: [],
|
||||
@@ -151,19 +153,6 @@ type MachineOrTag =
|
||||
type: "tag";
|
||||
};
|
||||
|
||||
interface WrapIfProps {
|
||||
condition: boolean;
|
||||
wrapper: (children: JSX.Element) => JSX.Element;
|
||||
children: JSX.Element;
|
||||
}
|
||||
const WrapIf = (props: WrapIfProps) => {
|
||||
if (props.condition) {
|
||||
return props.wrapper(props.children);
|
||||
} else {
|
||||
return props.children;
|
||||
}
|
||||
};
|
||||
|
||||
const machinesAndTags: MachineOrTag[] = [
|
||||
{ value: "machine-1", label: "Machine 1", type: "machine" },
|
||||
{ value: "machine-2", label: "Machine 2", type: "machine" },
|
||||
|
||||
@@ -6,6 +6,7 @@ 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";
|
||||
import cx from "classnames";
|
||||
|
||||
export interface Option {
|
||||
value: string;
|
||||
@@ -18,6 +19,8 @@ export interface SearchProps<T> {
|
||||
renderItem: (item: T) => JSX.Element;
|
||||
loading?: boolean;
|
||||
loadingComponent?: JSX.Element;
|
||||
headerClass?: string;
|
||||
height: string; // e.g. '14.5rem'
|
||||
}
|
||||
export function Search<T extends Option>(props: SearchProps<T>) {
|
||||
// Controlled input value, to allow resetting the input itself
|
||||
@@ -80,7 +83,9 @@ export function Search<T extends Option>(props: SearchProps<T>) {
|
||||
triggerMode="manual"
|
||||
noResetInputOnBlur={true}
|
||||
>
|
||||
<Combobox.Control<T> class={styles.searchHeader}>
|
||||
<Combobox.Control<T>
|
||||
class={cx(styles.searchHeader, props.headerClass || "bg-inv-3")}
|
||||
>
|
||||
{(state) => (
|
||||
<div class={styles.inputContainer}>
|
||||
<Icon icon="Search" color="quaternary" />
|
||||
@@ -114,18 +119,17 @@ export function Search<T extends Option>(props: SearchProps<T>) {
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Control>
|
||||
<Combobox.Portal>
|
||||
<Combobox.Content class={styles.searchContent} tabIndex={-1}>
|
||||
<Combobox.Listbox<T>
|
||||
ref={(el) => {
|
||||
listboxRef = el;
|
||||
}}
|
||||
style={{
|
||||
height: "100%",
|
||||
height: props.height,
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
"overflow-y": "auto",
|
||||
}}
|
||||
class={styles.listbox}
|
||||
scrollToItem={(key) => {
|
||||
const idx = comboboxItems().findIndex(
|
||||
(option) => option.rawValue.value === key,
|
||||
@@ -161,10 +165,7 @@ export function Search<T extends Option>(props: SearchProps<T>) {
|
||||
items().getItem(virtualRow.key as string);
|
||||
|
||||
if (!item) {
|
||||
console.warn(
|
||||
"Item not found for key:",
|
||||
virtualRow.key,
|
||||
);
|
||||
console.warn("Item not found for key:", virtualRow.key);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
@@ -191,8 +192,6 @@ export function Search<T extends Option>(props: SearchProps<T>) {
|
||||
);
|
||||
}}
|
||||
</Combobox.Listbox>
|
||||
</Combobox.Content>
|
||||
</Combobox.Portal>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user