ui/search: fix divider and text styles
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user