ui/tags: refactor generic children and icon

This commit is contained in:
Johannes Kirschbauer
2025-08-26 14:11:14 +02:00
parent d11d83f699
commit f7cde8eb0f
5 changed files with 64 additions and 40 deletions

View File

@@ -164,17 +164,26 @@ export const MachineTags = (props: MachineTagsProps) => {
<For each={state.selectedOptions()}>
{(option) => (
<Tag
label={option.value}
inverted={props.inverted}
action={
option.disabled || props.disabled || props.readOnly
? undefined
: {
icon: "Close",
onClick: () => state.remove(option),
}
interactive={
!(option.disabled || props.disabled || props.readOnly)
}
/>
icon={({ inverted }) =>
option.disabled ||
props.disabled ||
props.readOnly ? undefined : (
<Icon
role="button"
icon={"Close"}
size="0.5rem"
inverted={inverted}
onClick={() => state.remove(option)}
/>
)
}
>
{option.value}
</Tag>
)}
</For>
<Show when={!props.readOnly}>

View File

@@ -19,7 +19,9 @@ span.tag {
&.has-action {
@apply pr-1.5;
}
&.is-interactive {
&:hover {
@apply bg-def-acc-3;
}

View File

@@ -1,6 +1,7 @@
import { Tag, TagProps } from "@/src/components/Tag/Tag";
import { Meta, type StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { expect, fn } from "storybook/test";
import { fn } from "storybook/test";
import Icon from "../Icon/Icon";
const meta: Meta<TagProps> = {
title: "Components/Tag",
@@ -13,27 +14,44 @@ type Story = StoryObj<TagProps>;
export const Default: Story = {
args: {
label: "Label",
children: "Label",
},
};
const IconAction = ({
inverted,
handleActionClick,
}: {
inverted: boolean;
handleActionClick: () => void;
}) => (
<Icon
role="button"
icon={"Close"}
size="0.5rem"
onClick={() => {
console.log("icon clicked");
handleActionClick();
fn();
}}
inverted={inverted}
/>
);
export const WithAction: Story = {
args: {
...Default.args,
action: {
icon: "Close",
onClick: fn(),
},
icon: IconAction,
interactive: true,
},
play: async ({ canvas, step, userEvent, args }: StoryContext) => {
await userEvent.click(canvas.getByRole("button"));
await expect(args.action.onClick).toHaveBeenCalled();
// await expect(args.icon.onClick).toHaveBeenCalled();
},
};
export const Inverted: Story = {
args: {
label: "Label",
children: "Label",
inverted: true,
},
};

View File

@@ -2,18 +2,19 @@ import "./Tag.css";
import cx from "classnames";
import { Typography } from "@/src/components/Typography/Typography";
import { createSignal, Show } from "solid-js";
import Icon, { IconVariant } from "../Icon/Icon";
import { createSignal, JSX } from "solid-js";
export interface TagAction {
icon: IconVariant;
onClick: () => void;
interface IconActionProps {
inverted: boolean;
handleActionClick: () => void;
}
export interface TagProps {
label: string;
action?: TagAction;
export interface TagProps extends JSX.HTMLAttributes<HTMLSpanElement> {
children?: JSX.Element;
icon?: (state: IconActionProps) => JSX.Element;
inverted?: boolean;
interactive?: boolean;
class?: string;
}
export const Tag = (props: TagProps) => {
@@ -23,7 +24,6 @@ export const Tag = (props: TagProps) => {
const handleActionClick = () => {
setIsActive(true);
props.action?.onClick();
setTimeout(() => setIsActive(false), 150);
};
@@ -32,23 +32,18 @@ export const Tag = (props: TagProps) => {
class={cx("tag", {
inverted: inverted(),
active: isActive(),
"has-action": props.action,
"has-icon": props.icon,
"is-interactive": props.interactive,
class: props.class,
})}
aria-label={props.label}
aria-readonly={!props.action}
>
<Typography hierarchy="label" size="xs" inverted={inverted()}>
{props.label}
{props.children}
</Typography>
<Show when={props.action}>
<Icon
role="button"
icon={props.action!.icon}
size="0.5rem"
inverted={inverted()}
onClick={handleActionClick}
/>
</Show>
{props.icon?.({
inverted: inverted(),
handleActionClick,
})}
</span>
);
};

View File

@@ -15,7 +15,7 @@ export const TagGroup = (props: TagGroupProps) => {
return (
<div class={cx("tag-group", props.class, { inverted: inverted() })}>
<For each={props.labels}>
{(label) => <Tag label={label} inverted={inverted()} />}
{(label) => <Tag inverted={inverted()}>{label}</Tag>}
</For>
</div>
);