fix(ui) : simplify admin controls and artifact selection

This commit is contained in:
2026-10-14 22:39:53 +07:00 Unverified
parent d229cb3942
commit 024553b516
21 changed files with 148 additions and 73 deletions
+15 -23
View File
@@ -15,6 +15,7 @@ import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader,
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox";
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet } from "@/components/ui/field";
import { ClearableInput as Input } from "@/components/admin/ui/clearable-input";
import { Input as StandardInput } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
@@ -231,9 +232,9 @@ export function SortableTalentCard({
<CardAction>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -280,9 +281,9 @@ export function SortableArtifactRow({
{children(
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -612,26 +613,17 @@ export function ArtifactEditor({
>
{dragHandle}
<div className="flex min-w-0 flex-1 flex-col gap-3">
<div className="grid gap-3 lg:grid-cols-[1fr_1fr_8rem_auto]">
<div className="grid gap-3 lg:grid-cols-[1fr_8rem_auto]">
<ArtifactSelect
multiple
presentation="dropdown"
items={artifacts}
value={row.firstArtifactKey}
values={[row.firstArtifactKey, row.secondArtifactKey].filter((key): key is string => Boolean(key))}
invalid={artifactIssue("groups", activeArtifactGroupIndex, "tiers", groupIndex, "rows", rowIndex, "firstArtifactKey")}
placeholder="ค้นหาชุด Artifact แรก…"
rarityBackground
showSelectedImage
onValueChange={(firstArtifactKey) =>
setArtifactTierRows(groupIndex, (rows) => rows.map((candidate, inner) => inner === rowIndex ? { ...candidate, firstArtifactKey } : candidate))
}
/>
<ArtifactSelect
items={artifacts}
value={row.secondArtifactKey ?? ""}
placeholder="ค้นหาชุดที่สอง (ถ้ามี)…"
rarityBackground
showSelectedImage
onValueChange={(secondArtifactKey) =>
setArtifactTierRows(groupIndex, (rows) => rows.map((candidate, inner) => inner === rowIndex ? { ...candidate, secondArtifactKey: secondArtifactKey || null } : candidate))
label="เลือกชุด Artifact (สูงสุด 2 ชุด)"
placeholder="ค้นหาชุด Artifact…"
onValuesChange={(keys) =>
setArtifactTierRows(groupIndex, (rows) => rows.map((candidate, inner) => inner === rowIndex ? { ...candidate, firstArtifactKey: keys[0] ?? "", secondArtifactKey: keys[1] ?? null } : candidate))
}
/>
<Input
@@ -651,7 +643,7 @@ export function ArtifactEditor({
trigger={
<Button
type="button"
variant="ghost"
variant="plain"
size="icon"
/>
}
@@ -853,7 +845,7 @@ export function ArtifactEditor({
<StatIcons values={[stat]} />
{stat}
</FieldLabel>
<Input
<StandardInput
value={target?.value ?? ""}
placeholder="Value or text"
aria-label={`${stat} value`}
+6 -6
View File
@@ -79,9 +79,9 @@ export function SortableExtraItem({
{children(
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -163,7 +163,7 @@ export function ExtraCatalogItemsEditor({
<p className="text-sm text-muted-foreground">{labels[reference.kind]}</p>
</div>
{dragHandle}
<Button type="button" variant="ghost" size="icon-sm" onClick={() => onChange(value.filter((_, itemIndex) => itemIndex !== index))} aria-label={`ลบ ${label}`}>
<Button type="button" variant="plain" size="icon-sm" onClick={() => onChange(value.filter((_, itemIndex) => itemIndex !== index))} aria-label={`ลบ ${label}`}>
<XIcon />
</Button>
</CardContent>
@@ -352,7 +352,7 @@ export function ExtraEditor({ guide, extraSectionId, initial, characters, weapon
<CardAction className="flex items-center gap-1">
<DeleteConfirmationDialog
trigger={
<Button type="button" variant="ghost" size="icon-sm" />
<Button type="button" variant="plain" size="icon-sm" />
}
title={`ลบ ${section.title || `Extra section ${sectionIndex + 1}`}?`}
description="แถว ข้อความ รูปภาพ และเปอร์เซ็นต์ทั้งหมดในส่วนนี้จะถูกลบออกจาก Guide"
@@ -415,7 +415,7 @@ export function ExtraEditor({ guide, extraSectionId, initial, characters, weapon
trigger={
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
/>
}
@@ -470,7 +470,7 @@ export function ExtraEditor({ guide, extraSectionId, initial, characters, weapon
trigger={
<Button
type="button"
variant="destructive"
variant="plain"
size="icon-sm"
className="absolute top-2 right-2"
aria-label="ลบภาพ"
+2 -2
View File
@@ -359,9 +359,9 @@ export function SortableConditionTab({
<SimpleTooltip text={`ลากเพื่อจัดลำดับ ${label}`}>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none rounded-l-none"
className="touch-none rounded-l-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
+8 -8
View File
@@ -93,9 +93,9 @@ export function SortableTeamItem({
<SimpleTooltip text="ลากเพื่อจัดลำดับ Team">
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -134,9 +134,9 @@ export function SortableTeamMember({
<SimpleTooltip text="ลากเพื่อย้ายตำแหน่งตัวละคร">
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -844,7 +844,7 @@ export function TeamEditor({
<div key={preset.id} className="flex min-w-0 max-w-full items-center gap-1">
<Button className="min-w-0 max-w-full truncate" type="button" variant="ghost" size="sm" disabled={!selectedTeamIds.size} onClick={() => applyPreset(preset)}>{preset.name}</Button>
<SimpleTooltip text={`ลบ preset ${preset.name}`}>
<Button type="button" variant="ghost" size="icon-sm" onClick={() => setPresets((current) => current.filter((item) => item.id !== preset.id))} aria-label={`ลบ preset ${preset.name}`}><XIcon /></Button>
<Button type="button" variant="plain" size="icon-sm" onClick={() => setPresets((current) => current.filter((item) => item.id !== preset.id))} aria-label={`ลบ preset ${preset.name}`}><XIcon /></Button>
</SimpleTooltip>
</div>
))}
@@ -972,7 +972,7 @@ export function TeamEditor({
<SimpleTooltip text="Export Team">
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
onClick={() => void exportTeam(team)}
>
@@ -985,7 +985,7 @@ export function TeamEditor({
<SimpleTooltip text="ทำสำเนา Team">
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
onClick={() =>
setTeams((current) => {
@@ -1018,7 +1018,7 @@ export function TeamEditor({
trigger={
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
/>
}
+3 -3
View File
@@ -50,9 +50,9 @@ export function SortableWeaponCard({
{children(
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
{...attributes}
{...listeners}
>
@@ -265,7 +265,7 @@ export function WeaponEditor({ guide, weapons, characters, initial }: EditorBase
trigger={
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
/>
}
+7 -7
View File
@@ -331,9 +331,9 @@ function EditableColumnIndicator({
>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
aria-label={`ลากเพื่อย้าย ${String(label ?? `คอลัมน์ ${column + 1}`)}`}
{...attributes}
{...listeners}
@@ -413,9 +413,9 @@ function SortableRowIndicator({
>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
aria-label={`ลากเพื่อย้ายแถว ${row + 1}`}
{...attributes}
{...listeners}
@@ -935,7 +935,7 @@ export function ExtraSheetEditor({
>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
disabled={!canUndo}
aria-label="ย้อนกลับการแก้ไขตาราง"
@@ -1004,7 +1004,7 @@ export function ExtraSheetEditor({
>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
disabled={!canRedo}
aria-label="ทำซ้ำการแก้ไขตาราง"
@@ -1098,7 +1098,7 @@ export function ExtraSheetEditor({
<ToggleGroupItem value="bottom" aria-label="ชิดล่าง"><AlignVerticalJustifyEndIcon /></ToggleGroupItem>
</ToggleGroup>
<SimpleTooltip text="ล้างรูปแบบ">
<Button type="button" variant="ghost" size="icon-sm" aria-label="ล้างรูปแบบ" onClick={clearFormatting}>
<Button type="button" variant="plain" size="icon-sm" aria-label="ล้างรูปแบบ" onClick={clearFormatting}>
<EraserIcon />
</Button>
</SimpleTooltip>
+1 -1
View File
@@ -159,7 +159,7 @@ export function GlossaryManager({
<p className="truncate text-sm text-muted-foreground">{name ?? entry.catalogKey}</p>
</div>
<Badge variant={name ? "secondary" : "outline"}>{name ? kindLabels[entry.kind as GlossaryKind] : "Unavailable"}</Badge>
<Button variant="ghost" size="icon-sm" disabled={pending} aria-label={`ลบ ${entry.alias}`} onClick={() => startTransition(async () => {
<Button variant="plain" size="icon-sm" disabled={pending} aria-label={`ลบ ${entry.alias}`} onClick={() => startTransition(async () => {
const result = await removeGlossaryAlias(entry.id);
if (result.status === "error") toast.error(result.message);
else {
+1 -1
View File
@@ -49,7 +49,7 @@ function SortableExtraCard({ extra, index, characterKey, disabled }: { extra: Ex
<CardTitle>{extra.title}</CardTitle>
<CardDescription>Extra section {index + 1}</CardDescription>
<CardAction>
<Button type="button" variant="ghost" size="icon-sm" disabled={disabled} className="touch-none cursor-grab active:cursor-grabbing" aria-label={`จัดลำดับ ${extra.title}`} {...attributes} {...listeners}>
<Button type="button" variant="plain" size="icon-sm" disabled={disabled} className="touch-none cursor-grab active:cursor-grabbing" aria-label={`จัดลำดับ ${extra.title}`} {...attributes} {...listeners}>
<GripVerticalIcon />
</Button>
</CardAction>
+4 -4
View File
@@ -86,11 +86,11 @@ function SortableVideoLink({ link, index, guideName, onChange, onRemove }: { lin
return (
<Field ref={setNodeRef} data-invalid={invalid} style={{ transform: CSS.Transform.toString(transform), transition }} className={cn("rounded-lg border p-3", isDragging && "relative z-10 opacity-70 shadow-lg")}>
<div className="flex items-center gap-2">
<Button type="button" variant="ghost" size="icon-sm" className="touch-none cursor-grab active:cursor-grabbing" aria-label={`จัดลำดับวิดีโอ ${index + 1}`} {...attributes} {...listeners}>
<Button type="button" variant="plain" size="icon-sm" className="touch-none cursor-grab active:cursor-grabbing" aria-label={`จัดลำดับวิดีโอ ${index + 1}`} {...attributes} {...listeners}>
<GripVerticalIcon data-icon="inline-start" />
</Button>
<FieldLabel htmlFor={`guide-youtube-url-${link.id}`} className="flex-1">YouTube video {index + 1}</FieldLabel>
<Button type="button" variant="ghost" size="icon-sm" onClick={onRemove} aria-label={`ลบวิดีโอ ${index + 1}`}>
<Button type="button" variant="plain" size="icon-sm" onClick={onRemove} aria-label={`ลบวิดีโอ ${index + 1}`}>
<Trash2Icon data-icon="inline-start" />
</Button>
</div>
@@ -110,7 +110,7 @@ function SortableBleedingLayer({ layer, onChange, onRemove }: { layer: BleedingL
const selectedMode = blendModeItems.find((mode) => mode.value === layer.blendMode);
return (
<div ref={setNodeRef} style={{ transform: CSS.Transform.toString(transform), transition }} className={cn("flex items-start gap-2 rounded-lg border bg-card p-3", isDragging && "relative z-10 opacity-70 shadow-lg")}>
<Button type="button" variant="ghost" size="icon-sm" className="touch-none cursor-grab active:cursor-grabbing" aria-label="จัดลำดับ Bleeding" {...attributes} {...listeners}><GripVerticalIcon data-icon="inline-start" /></Button>
<Button type="button" variant="plain" size="icon-sm" className="touch-none cursor-grab active:cursor-grabbing" aria-label="จัดลำดับ Bleeding" {...attributes} {...listeners}><GripVerticalIcon data-icon="inline-start" /></Button>
{layer.imageUrl ? <Image src={layer.imageUrl} alt="" width={48} height={48} className="size-12 rounded object-contain bg-muted" /> : <div className="size-12 rounded bg-muted" />}
<div className="flex min-w-0 flex-1 flex-col gap-3">
<Combobox
@@ -134,7 +134,7 @@ function SortableBleedingLayer({ layer, onChange, onRemove }: { layer: BleedingL
<Slider id={`bleeding-amount-${layer.id}`} min={0} max={100} step={5} value={[layer.amount]} onValueChange={(value) => onChange({ amount: typeof value === "number" ? value : value[0] ?? 0 })} />
</Field>
</div>
<Button type="button" variant="ghost" size="icon-sm" onClick={onRemove} aria-label="ลบ Bleeding"><Trash2Icon data-icon="inline-start" /></Button>
<Button type="button" variant="plain" size="icon-sm" onClick={onRemove} aria-label="ลบ Bleeding"><Trash2Icon data-icon="inline-start" /></Button>
</div>
);
}
@@ -659,7 +659,7 @@ function SortableBlendLayerRow({
>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
className="touch-none cursor-grab active:cursor-grabbing"
aria-label="ลากเพื่อจัดลำดับ Blend mode"
@@ -715,7 +715,7 @@ function SortableBlendLayerRow({
</div>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
aria-label="ลบ Blend mode"
onClick={onRemove}
+1 -1
View File
@@ -267,7 +267,7 @@ export function StructuredDashboard({
trigger={
<Button
className="relative z-20"
variant="ghost"
variant="plain"
size="icon"
/>
}
+6 -6
View File
@@ -584,7 +584,7 @@ function VisualRecommendation({
</div>
)}
<Button
variant="destructive"
variant="plain"
size="icon-xs"
className="absolute top-2 left-2"
aria-label="Remove recommendation"
@@ -620,9 +620,9 @@ function SortableRecommendation({
{children(
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-xs"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
aria-label="Drag to reorder recommendation"
{...attributes}
{...listeners}
@@ -771,7 +771,7 @@ function SortableTeam({
<div className="flex items-center gap-1">
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-xs"
aria-label={`Copy team ${position + 1}`}
title="Copy team"
@@ -781,9 +781,9 @@ function SortableTeam({
</Button>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-xs"
className="touch-none"
className="touch-none cursor-grab active:cursor-grabbing"
aria-label={`Drag to reorder team ${position + 1}`}
{...attributes}
{...listeners}
+1 -1
View File
@@ -53,7 +53,7 @@ export function TeamImageGenerator({ characters, weapons, artifacts }: { charact
{draft.members.map((member, index) => {
const character = characters.find((item) => item.key === member.characterKey);
const compatibleWeapons = weapons.filter((weapon) => supportsWeaponType(character?.weaponType, weapon.weaponType));
return <Card key={index} size="sm"><CardHeader><CardTitle>ตัวละคร #{index + 1}</CardTitle><CardAction><Button type="button" variant="ghost" size="icon-sm" aria-label={`ลบตัวละคร ${index + 1}`} onClick={() => setDraft((current) => ({ ...current, members: current.members.filter((_, memberIndex) => memberIndex !== index) }))}><Trash2Icon /></Button></CardAction></CardHeader><CardContent><FieldGroup>
return <Card key={index} size="sm"><CardHeader><CardTitle>ตัวละคร #{index + 1}</CardTitle><CardAction><Button type="button" variant="plain" size="icon-sm" aria-label={`ลบตัวละคร ${index + 1}`} onClick={() => setDraft((current) => ({ ...current, members: current.members.filter((_, memberIndex) => memberIndex !== index) }))}><Trash2Icon /></Button></CardAction></CardHeader><CardContent><FieldGroup>
<Field><FieldLabel>ตัวละคร</FieldLabel><CharacterSelect items={characters} value={member.characterKey} placeholder="ค้นหาตัวละคร…" onValueChange={(characterKey) => updateMember(index, { ...emptyMember(), characterKey })} /></Field>
<div className="grid gap-4 sm:grid-cols-2">
<Field><FieldLabel>อาวุธ</FieldLabel><WeaponSelect items={compatibleWeapons} value={member.weaponKey} placeholder="ค้นหาอาวุธที่รองรับ…" onValueChange={(weaponKey) => updateMember(index, { weaponKey, refinement: "1" })} /></Field>
+1
View File
@@ -35,6 +35,7 @@ export function ClearableInputGroupInput({ ref, onChange, onValueChange, ...prop
{String(value).length > 0 && !props.disabled && !props.readOnly ? (
<InputGroupAddon align="inline-end">
<InputGroupButton
variant="plain"
size="icon-xs"
aria-label={`ล้าง${props["aria-label"] ?? props.placeholder ?? "ข้อความ"}`}
onClick={() => {
+1 -1
View File
@@ -222,7 +222,7 @@ export function WeaponImageGenerator({
<CardAction>
<Button
type="button"
variant="ghost"
variant="plain"
size="icon-sm"
aria-label={`ลบอาวุธ ${index + 1}`}
onClick={() => setDraft((current) => ({ ...current, rows: current.rows.filter((item) => item.id !== row.id) }))}
+69
View File
@@ -0,0 +1,69 @@
// @vitest-environment happy-dom
import { act, useState } from "react";
import { createRoot } from "react-dom/client";
import { expect, it, vi } from "vitest";
import { ArtifactSelect } from "./catalog-select";
const items = ["Gladiator", "Wanderer", "Emblem"].map((name) => ({
key: name.toLowerCase(), name, imageUrl: "/artifact.png", rarity: 5,
}));
function Picker() {
const [values, setValues] = useState<string[]>([]);
return <ArtifactSelect multiple presentation="dropdown" items={items} values={values}
onValuesChange={setValues} label="Artifact sets" placeholder="Search artifacts" />;
}
it("selects at most two artifact sets and allows removing and clearing them", async () => {
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
const container = document.createElement("div");
document.body.append(container);
const root = createRoot(container);
const option = (name: string) => Array.from(document.querySelectorAll<HTMLElement>("[role=option]"))
.find((element) => element.textContent?.includes(name))!;
const click = async (element: HTMLElement) => {
await act(async () => { element.click(); });
};
try {
await act(async () => { root.render(<Picker />); });
const trigger = container.querySelector<HTMLButtonElement>("[data-slot=combobox-trigger]")!;
await click(trigger);
const input = document.querySelector<HTMLInputElement>('input[placeholder="Search artifacts"]')!;
const search = async (value: string) => {
await act(async () => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
});
};
await search("gladiatr");
expect(document.querySelectorAll("[role=option]")).toHaveLength(1);
expect(option("Gladiator")).toBeDefined();
await act(async () => {
input.focus();
input.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
});
await act(async () => {
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
});
expect(trigger.textContent).toContain("Gladiator");
await search("");
await click(option("Wanderer"));
expect(trigger.textContent).toContain("Gladiator + Wanderer");
expect(option("Emblem").getAttribute("aria-disabled")).toBe("true");
await click(option("Gladiator"));
expect(trigger.textContent).toContain("Wanderer");
expect(trigger.textContent).not.toContain("Gladiator");
expect(option("Emblem").getAttribute("aria-disabled")).not.toBe("true");
await click(trigger);
await click(trigger);
expect(trigger.textContent).toContain("Wanderer");
await click(document.querySelector<HTMLElement>("[data-slot=combobox-clear]")!);
expect(trigger.textContent).toContain("Artifact sets");
} finally {
await act(async () => { root.unmount(); });
container.remove();
vi.unstubAllGlobals();
}
});
+14 -3
View File
@@ -5,6 +5,7 @@ import type { ReactNode } from "react";
import { CatalogItemImage, type CatalogItemKind } from "@/components/catalog-item-image";
import { TeamArtifactIcon } from "@/components/public/team-search";
import { Button } from "@/components/ui/button";
import {
ComboboxContent,
ComboboxEmpty,
@@ -255,6 +256,7 @@ type MultipleArtifactSelectProps<Item extends AdminCatalogItem> = Omit<
onValuesChange: (values: string[]) => void;
label: string;
max?: number;
presentation?: "dropdown" | "slot";
className?: string;
};
@@ -272,6 +274,7 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
emptyLabel = "ไม่พบชุด Artifact",
invalid = false,
max = 2,
presentation = "slot",
searchNames = (item) => [item.name, ...(item.aliases ?? [])],
className,
} = props;
@@ -298,8 +301,11 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
<ComboboxTrigger
aria-label={selected.length ? `${label}: ${selected.map((item) => item.name).join(", ")}` : label}
aria-invalid={invalid}
render={presentation === "dropdown" ? <Button variant="outline" /> : undefined}
className={cn(
"relative aspect-square w-full overflow-hidden rounded-md border bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&>svg]:hidden",
presentation === "dropdown"
? "h-auto min-h-10 w-full min-w-0 justify-start gap-2 py-1.5"
: "relative aspect-square w-full overflow-hidden rounded-md border bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&>svg]:hidden",
className,
)}
>
@@ -307,12 +313,17 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
<TeamArtifactIcon
first={iconValue(selected[0])}
second={iconValue(selected[1])}
className="border-0"
sizes="96px"
className={presentation === "dropdown" ? "size-8 shrink-0 border-0" : "border-0"}
sizes={presentation === "dropdown" ? "32px" : "96px"}
/>
) : (
<CircleHelpIcon className="size-8 text-muted-foreground" aria-hidden="true" />
)}
{presentation === "dropdown" ? (
<span className="min-w-0 flex-1 truncate text-left">
{selected.length ? selected.map((item) => item.name).join(" + ") : label}
</span>
) : null}
</ComboboxTrigger>
<ComboboxContent className="min-w-72">
<ComboboxInput placeholder={placeholder} showClear showTrigger={false} />
+2
View File
@@ -15,6 +15,8 @@ const buttonVariants = cva(
"border-control-border bg-control text-secondary-foreground hover:border-control-border-hover hover:bg-control-hover aria-expanded:border-primary aria-expanded:bg-primary/15 aria-expanded:text-primary",
ghost:
"border-control-border bg-control text-foreground hover:border-control-border-hover hover:bg-control-hover aria-expanded:border-primary aria-expanded:bg-primary/15 aria-expanded:text-primary aria-[current=page]:border-primary aria-[current=page]:bg-primary/15 aria-[current=page]:text-primary",
plain:
"border-transparent bg-transparent hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
destructive:
"border-destructive bg-destructive/20 text-destructive hover:bg-destructive/30 focus-visible:border-destructive focus-visible:ring-destructive/40",
link: "border-transparent text-primary underline underline-offset-4 hover:decoration-2",
+2 -2
View File
@@ -62,7 +62,7 @@ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
return (
<ComboboxPrimitive.Clear
data-slot="combobox-clear"
render={<InputGroupButton variant="ghost" size="icon-xs" />}
render={<InputGroupButton variant="plain" size="icon-xs" />}
className={cn(className)}
{...props}
>
@@ -275,7 +275,7 @@ function ComboboxChip({
{children}
{showRemove && (
<ComboboxPrimitive.ChipRemove
render={<Button variant="ghost" size="icon-xs" />}
render={<Button variant="plain" size="icon-xs" />}
className="-ml-1 opacity-50 hover:opacity-100"
data-slot="combobox-chip-remove"
>
+1 -1
View File
@@ -64,7 +64,7 @@ function DialogContent({
data-slot="dialog-close"
render={
<Button
variant="ghost"
variant="plain"
className="absolute top-2 right-2"
size="icon-sm"
/>
+1 -1
View File
@@ -64,7 +64,7 @@ function SheetContent({
data-slot="sheet-close"
render={
<Button
variant="ghost"
variant="plain"
className="absolute top-3 right-3"
size="icon-sm"
/>