ui/search: fix divider and text styles

This commit is contained in:
Johannes Kirschbauer
2025-08-28 22:36:43 +02:00
parent fc5b0e4113
commit ca69530591
3 changed files with 47 additions and 16 deletions

View File

@@ -43,20 +43,32 @@
.searchItem { .searchItem {
@apply flex flex-col justify-center overflow-hidden; @apply flex flex-col justify-center overflow-hidden;
box-shadow: 0 1px 0 0 theme(colors.border.inv.2);
&[data-highlighted], &.hasDivider {
&:focus, box-shadow: 0 1px 0 0 theme(colors.border.inv.2);
&:focus-visible, }
&:hover {
/* Next element is hovered */
&:has(+ &:hover) {
box-shadow: unset;
}
&:not([aria-disabled="true"])[data-highlighted],
&:not([aria-disabled="true"]):focus,
&:not([aria-disabled="true"]):focus-visible,
&:not([aria-disabled="true"]):hover {
@apply bg-inv-acc-2 rounded-md; @apply bg-inv-acc-2 rounded-md;
box-shadow: unset; box-shadow: unset;
} }
&:active { &:not([aria-disabled="true"]):active {
@apply bg-inv-acc-3 rounded-md; @apply bg-inv-acc-3 rounded-md;
box-shadow: unset; box-shadow: unset;
} }
&[aria-disabled="true"] {
@apply cursor-not-allowed;
}
} }
.searchContainer { .searchContainer {

View File

@@ -55,8 +55,8 @@ function generateModules(count: number): Module[] {
modules.push({ modules.push({
value: `lolcat/module-${i + 1}`, value: `lolcat/module-${i + 1}`,
label: `Module ${i + 1}`, label: `Module ${i + 1}`,
description: `${greek[i % greek.length]}#${i + 1}`, description: `${greek[i % greek.length]}#${i + 1} this is a very long description to test text wrapping in the search component`,
input: "lolcat", input: "lolcat-flake-part-from-nixpkgs-via-nix-via-clan-flake",
}); });
} }
@@ -78,7 +78,7 @@ export const Default: Story = {
renderItem: (item: Module) => { renderItem: (item: Module) => {
return ( return (
<div class="flex items-center justify-between gap-2 rounded-md px-2 py-1 pr-4"> <div class="flex items-center justify-between gap-2 rounded-md px-2 py-1 pr-4">
<div class="flex size-8 items-center justify-center rounded-md bg-white"> <div class="flex size-8 shrink-0 items-center justify-center rounded-md bg-white">
<Icon icon="Code" /> <Icon icon="Code" />
</div> </div>
<div class="flex w-full flex-col"> <div class="flex w-full flex-col">
@@ -95,8 +95,12 @@ export const Default: Story = {
inverted inverted
class="flex justify-between" class="flex justify-between"
> >
<span>{item.description}</span> <span class="inline-block max-w-72 truncate align-middle">
<span>by {item.input}</span> {item.description}
</span>
<span class="inline-block max-w-20 truncate align-middle">
by {item.input}
</span>
</Typography> </Typography>
</div> </div>
</div> </div>
@@ -105,7 +109,7 @@ export const Default: Story = {
}, },
render: (args: SearchProps<Module>) => { render: (args: SearchProps<Module>) => {
return ( return (
<div class="absolute bottom-1/3 w-3/4 px-3"> <div class="fixed bottom-10 left-1/2 mb-2 w-[30rem] -translate-x-1/2">
<Search<Module> <Search<Module>
{...args} {...args}
onChange={(module) => { onChange={(module) => {
@@ -145,11 +149,13 @@ type MachineOrTag =
value: string; value: string;
label: string; label: string;
type: "machine"; type: "machine";
disabled?: boolean;
} }
| { | {
members: string[]; members: string[];
value: string; value: string;
label: string; label: string;
disabled?: boolean;
type: "tag"; type: "tag";
}; };
@@ -193,7 +199,13 @@ export const Multiple: Story = {
</Show> </Show>
</Combobox.ItemIndicator> </Combobox.ItemIndicator>
<Combobox.ItemLabel class="flex items-center gap-2"> <Combobox.ItemLabel class="flex items-center gap-2">
<Typography hierarchy="body" size="s" weight="medium" inverted> <Typography
hierarchy="body"
size="s"
weight="medium"
inverted
color={opts.disabled ? "quaternary" : "primary"}
>
{item.label} {item.label}
</Typography> </Typography>
<Show when={item.type === "tag" && item}> <Show when={item.type === "tag" && item}>
@@ -226,6 +238,7 @@ export const Multiple: Story = {
<div class="absolute bottom-1/3 w-3/4 px-3"> <div class="absolute bottom-1/3 w-3/4 px-3">
<SearchMultiple<MachineOrTag> <SearchMultiple<MachineOrTag>
{...args} {...args}
divider
height="20rem" height="20rem"
virtualizerOptions={{ virtualizerOptions={{
estimateSize: () => 38, estimateSize: () => 38,

View File

@@ -11,17 +11,20 @@ import cx from "classnames";
export interface Option { export interface Option {
value: string; value: string;
label: string; label: string;
disabled?: boolean;
} }
export interface SearchProps<T> { export interface SearchProps<T> {
onChange: (value: T | null) => void; onChange: (value: T | null) => void;
options: T[]; options: T[];
renderItem: (item: T) => JSX.Element; renderItem: (item: T, opts: { disabled: boolean }) => JSX.Element;
loading?: boolean; loading?: boolean;
loadingComponent?: JSX.Element; loadingComponent?: JSX.Element;
headerClass?: string; headerClass?: string;
height: string; // e.g. '14.5rem' height: string; // e.g. '14.5rem'
divider?: boolean;
} }
export function Search<T extends Option>(props: SearchProps<T>) { export function Search<T extends Option>(props: SearchProps<T>) {
// Controlled input value, to allow resetting the input itself // Controlled input value, to allow resetting the input itself
const [value, setValue] = createSignal<T | null>(null); const [value, setValue] = createSignal<T | null>(null);
@@ -65,13 +68,14 @@ export function Search<T extends Option>(props: SearchProps<T>) {
setInputValue(value ? value.label : ""); setInputValue(value ? value.label : "");
props.onChange(value); props.onChange(value);
}} }}
class={styles.searchContainer} class={cx(styles.searchContainer, props.divider && styles.hasDivider)}
placement="bottom-start" placement="bottom-start"
options={props.options} options={props.options}
optionValue="value" optionValue="value"
optionTextValue="label" optionTextValue="label"
optionLabel="label" optionLabel="label"
placeholder="Search a service" placeholder="Search a service"
optionDisabled={"disabled"}
sameWidth={true} sameWidth={true}
open={true} open={true}
gutter={7} gutter={7}
@@ -181,7 +185,9 @@ export function Search<T extends Option>(props: SearchProps<T>) {
transform: `translateY(${virtualRow.start}px)`, transform: `translateY(${virtualRow.start}px)`,
}} }}
> >
{props.renderItem(item.rawValue)} {props.renderItem(item.rawValue, {
disabled: item.disabled,
})}
</Combobox.Item> </Combobox.Item>
); );
}} }}