diff --git a/pkgs/clan-app/ui/src/components/Search/MultipleSearch.tsx b/pkgs/clan-app/ui/src/components/Search/MultipleSearch.tsx index b7945962f..b1698c435 100644 --- a/pkgs/clan-app/ui/src/components/Search/MultipleSearch.tsx +++ b/pkgs/clan-app/ui/src/components/Search/MultipleSearch.tsx @@ -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 { placeholder?: string; virtualizerOptions?: Partial>; height: string; // e.g. '14.5rem' + headerClass?: string; + headerChildren?: JSX.Element; + loading?: boolean; + loadingComponent?: JSX.Element; } export function SearchMultiple( props: SearchMultipleProps, @@ -72,7 +78,6 @@ export function SearchMultiple( props.onChange(values); }} class={styles.searchContainer} - style={{ "--container-height": props.height }} placement="bottom-start" options={props.options} optionValue="value" @@ -89,69 +94,78 @@ export function SearchMultiple( triggerMode="manual" noResetInputOnBlur={true} > - class={styles.searchHeader}> + + class={cx(styles.searchHeader, props.headerClass || "bg-inv-3")} + > {(state) => ( -
- - { - inputEl = el; - }} - class={styles.searchInput} - placeholder={props.placeholder} - value={inputValue()} - onChange={(e) => { - setInputValue(e.currentTarget.value); - }} - /> -
+ // Dispatch an input event to notify combobox listeners + inputEl.dispatchEvent( + new Event("input", { bubbles: true, cancelable: true }), + ); + }} + /> + + )} - - - - ref={(el) => { - listboxRef = el; - }} - style={{ - height: "100%", - width: "100%", - overflow: "auto", - "overflow-y": "auto", - }} - scrollToItem={(key) => { - const idx = comboboxItems().findIndex( - (option) => option.rawValue.value === key, - ); - virtualizer().scrollToIndex(idx); - }} - > - {(items) => { - // Update the virtualizer with the filtered items - const arr = Array.from(items()); - setComboboxItems(arr); + + ref={(el) => { + listboxRef = el; + }} + style={{ + height: props.height, + width: "100%", + overflow: "auto", + "overflow-y": "auto", + }} + scrollToItem={(key) => { + const idx = comboboxItems().findIndex( + (option) => option.rawValue.value === key, + ); + virtualizer().scrollToIndex(idx); + }} + class={styles.listbox} + > + {(items) => { + // Update the virtualizer with the filtered items + const arr = Array.from(items()); + setComboboxItems(arr); - return ( + return ( + + + {props.loadingComponent ?? ( +
+ +
+ )} +
+
( }}
- ); - }} - -
-
+ + + ); + }} + + {/* */} ); } diff --git a/pkgs/clan-app/ui/src/components/Search/Search.module.css b/pkgs/clan-app/ui/src/components/Search/Search.module.css index 08a0c392b..d52dedebe 100644 --- a/pkgs/clan-app/ui/src/components/Search/Search.module.css +++ b/pkgs/clan-app/ui/src/components/Search/Search.module.css @@ -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 { diff --git a/pkgs/clan-app/ui/src/components/Search/Search.stories.tsx b/pkgs/clan-app/ui/src/components/Search/Search.stories.tsx index c0d9e9b2c..27da6034a 100644 --- a/pkgs/clan-app/ui/src/components/Search/Search.stories.tsx +++ b/pkgs/clan-app/ui/src/components/Search/Search.stories.tsx @@ -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" }, diff --git a/pkgs/clan-app/ui/src/components/Search/Search.tsx b/pkgs/clan-app/ui/src/components/Search/Search.tsx index 4e002d96c..5dcb69fa9 100644 --- a/pkgs/clan-app/ui/src/components/Search/Search.tsx +++ b/pkgs/clan-app/ui/src/components/Search/Search.tsx @@ -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 { renderItem: (item: T) => JSX.Element; loading?: boolean; loadingComponent?: JSX.Element; + headerClass?: string; + height: string; // e.g. '14.5rem' } export function Search(props: SearchProps) { // Controlled input value, to allow resetting the input itself @@ -80,7 +83,9 @@ export function Search(props: SearchProps) { triggerMode="manual" noResetInputOnBlur={true} > - class={styles.searchHeader}> + + class={cx(styles.searchHeader, props.headerClass || "bg-inv-3")} + > {(state) => (
@@ -114,85 +119,79 @@ export function Search(props: SearchProps) {
)} - - - - ref={(el) => { - listboxRef = el; - }} - style={{ - height: "100%", - width: "100%", - overflow: "auto", - "overflow-y": "auto", - }} - scrollToItem={(key) => { - const idx = comboboxItems().findIndex( - (option) => option.rawValue.value === key, - ); - virtualizer().scrollToIndex(idx); - }} - > - {(items) => { - // Update the virtualizer with the filtered items - const arr = Array.from(items()); - setComboboxItems(arr); + + ref={(el) => { + listboxRef = el; + }} + style={{ + height: props.height, + width: "100%", + overflow: "auto", + "overflow-y": "auto", + }} + class={styles.listbox} + scrollToItem={(key) => { + const idx = comboboxItems().findIndex( + (option) => option.rawValue.value === key, + ); + virtualizer().scrollToIndex(idx); + }} + > + {(items) => { + // Update the virtualizer with the filtered items + const arr = Array.from(items()); + setComboboxItems(arr); - return ( - - - {props.loadingComponent ?? ( -
- -
- )} -
- -
- - {(virtualRow) => { - const item: CollectionNode | undefined = - items().getItem(virtualRow.key as string); + return ( + + + {props.loadingComponent ?? ( +
+ +
+ )} +
+ +
+ + {(virtualRow) => { + const item: CollectionNode | undefined = + items().getItem(virtualRow.key as string); - if (!item) { - console.warn( - "Item not found for key:", - virtualRow.key, - ); - return null; - } - return ( - - {props.renderItem(item.rawValue)} - - ); - }} - -
-
-
- ); - }} - - - + if (!item) { + console.warn("Item not found for key:", virtualRow.key); + return null; + } + return ( + + {props.renderItem(item.rawValue)} + + ); + }} +
+
+
+
+ ); + }} + ); }