ui/tagSelect: simplify by requiring objects with value key

This commit is contained in:
Johannes Kirschbauer
2025-08-28 10:10:25 +02:00
parent 640f15d55e
commit c574b84278
3 changed files with 83 additions and 63 deletions

View File

@@ -1,22 +1,3 @@
.dummybg {
padding: 1rem;
width: 20rem;
min-height: 10rem;
border-radius: 8px;
border: 1px solid #2e4a4b;
background:
linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%),
linear-gradient(
180deg,
theme(colors.bg.inv.2) 0%,
theme(colors.bg.inv.3) 100%
);
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.trigger {
@apply rounded-md bg-inv-4 w-full min-h-11;

View File

@@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { TagSelect, TagSelectProps } from "./TagSelect";
import { Tag } from "../Tag/Tag";
import Icon from "../Icon/Icon";
import { createSignal } from "solid-js";
const meta = {
title: "Components/Custom/SelectStepper",
@@ -11,28 +12,51 @@ const meta = {
export default meta;
type Story = StoryObj<TagSelectProps<string>>;
interface Item {
value: string;
label: string;
}
const Item = (item: string) => (
type Story = StoryObj<TagSelectProps<Item>>;
const Item = (item: Item) => (
<Tag
inverted
icon={(tag) => (
<Icon icon={"Machine"} size="0.5rem" inverted={tag.inverted} />
)}
>
{item}
{item.label}
</Tag>
);
export const Default: Story = {
args: {
renderItem: Item,
values: ["foo", "bar"],
options: ["foo", "bar", "baz", "qux", "quux"],
onChange: (values: string[]) => {
console.log("Selected values:", values);
},
onClick: () => {
console.log("Combobox clicked");
},
label: "Peer",
options: [
{ value: "foo", label: "Foo" },
{ value: "bar", label: "Bar" },
{ value: "baz", label: "Baz" },
{ value: "qux", label: "Qux" },
{ value: "quux", label: "Quux" },
{ value: "corge", label: "Corge" },
{ value: "grault", label: "Grault" },
],
} satisfies Partial<TagSelectProps<Item>>,
render: (args: TagSelectProps<Item>) => {
const [state, setState] = createSignal<Item[]>([]);
return (
<TagSelect<Item>
{...args}
values={state()}
onClick={() => {
console.log("Clicked, current values:");
setState(() => [
{ value: "baz", label: "Baz" },
{ value: "qux", label: "Qux" },
]);
}}
/>
);
},
};

View File

@@ -5,43 +5,58 @@ import styles from "./TagSelect.module.css";
import { Combobox } from "@kobalte/core/combobox";
import { Button } from "../Button/Button";
// Base props common to both modes
export interface TagSelectProps<T> {
// Define any props needed for the SelectStepper component
onClick: () => void;
label: string;
values: T[];
options: T[];
onChange: (values: T[]) => void;
onClick: () => void;
renderItem: (item: T) => JSX.Element;
}
export function TagSelect<T>(props: TagSelectProps<T>) {
/**
* Shallowly interactive field for selecting multiple tags / machines.
* It does only handle click and focus interactions
* Displays the selected items as tags
*/
export function TagSelect<T extends { value: unknown }>(
props: TagSelectProps<T>,
) {
const optionValue = "value";
return (
<div class={styles.dummybg}>
<div class="flex flex-col gap-1.5">
<div class="flex w-full items-center gap-2 px-1.5">
<Typography
hierarchy="body"
weight="medium"
class="flex gap-2 uppercase"
size="s"
inverted
color="secondary"
>
Servers
</Typography>
<Icon icon="Info" color="tertiary" inverted />
<Button icon="Settings" hierarchy="primary" ghost class="ml-auto" />
</div>
<Combobox<T>
multiple
value={props.values}
onChange={props.onChange}
options={props.options}
allowsEmptyCollection
class="w-full"
<div class="flex flex-col gap-1.5">
<div class="flex w-full items-center gap-2 px-1.5 py-0">
<Typography
hierarchy="label"
weight="medium"
class="flex gap-2 uppercase"
size="xs"
inverted
color="secondary"
>
<Combobox.Control<T> aria-label="Fruits">
{(state) => (
{props.label}
</Typography>
<Icon icon="Info" color="tertiary" inverted size={11} />
<Button
icon="Settings"
hierarchy="primary"
ghost
class="ml-auto"
size="xs"
/>
</div>
<Combobox<T>
multiple
optionValue={optionValue}
value={props.values}
options={props.options}
allowsEmptyCollection
class="w-full"
>
<Combobox.Control<T> aria-label="Fruits">
{(state) => {
console.log("combobox state selected", state.selectedOptions());
return (
<Combobox.Trigger
tabIndex={1}
class={styles.trigger}
@@ -62,10 +77,10 @@ export function TagSelect<T>(props: TagSelectProps<T>) {
<For each={state.selectedOptions()}>{props.renderItem}</For>
</div>
</Combobox.Trigger>
)}
</Combobox.Control>
</Combobox>
</div>
);
}}
</Combobox.Control>
</Combobox>
</div>
);
}