Migrate icon buttons to Button component and simplify size variants
All checks were successful
CI / check (push) Successful in 48s
CI / build-image (push) Successful in 18s

Replace raw <button> elements with Button variant="ghost" in stat-block
panel, toast, player modals. Add icon-sm size variant (h-6 w-6) for
compact contexts. Consolidate text button sizes into a single default
(h-8 px-3), removing the redundant sm variant. Add size prop to
ConfirmButton for consistent sizing.

Button now has three sizes: default (text), icon, icon-sm.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-13 17:14:44 +01:00
parent f9ef64bb00
commit 85acb5c185
10 changed files with 56 additions and 59 deletions

View File

@@ -553,9 +553,7 @@ export function ActionBar({
</div> </div>
)} )}
{!browseMode && nameInput.length >= 2 && !hasSuggestions && ( {!browseMode && nameInput.length >= 2 && !hasSuggestions && (
<Button type="submit" size="sm"> <Button type="submit">Add</Button>
Add
</Button>
)} )}
{showRollAllInitiative && onRollAllInitiative && ( {showRollAllInitiative && onRollAllInitiative && (
<Button <Button

View File

@@ -28,9 +28,7 @@ export function BulkImportPrompt({
<div className="rounded-md border border-green-500/50 bg-green-500/10 px-3 py-2 text-sm text-green-400"> <div className="rounded-md border border-green-500/50 bg-green-500/10 px-3 py-2 text-sm text-green-400">
All sources loaded All sources loaded
</div> </div>
<Button size="sm" onClick={onDone}> <Button onClick={onDone}>Done</Button>
Done
</Button>
</div> </div>
); );
} }
@@ -42,9 +40,7 @@ export function BulkImportPrompt({
Loaded {importState.completed}/{importState.total} sources ( Loaded {importState.completed}/{importState.total} sources (
{importState.failed} failed) {importState.failed} failed)
</div> </div>
<Button size="sm" onClick={onDone}> <Button onClick={onDone}>Done</Button>
Done
</Button>
</div> </div>
); );
} }
@@ -103,11 +99,7 @@ export function BulkImportPrompt({
/> />
</div> </div>
<Button <Button onClick={() => onStartImport(baseUrl)} disabled={isDisabled}>
size="sm"
onClick={() => onStartImport(baseUrl)}
disabled={isDisabled}
>
Load All Load All
</Button> </Button>
</div> </div>

View File

@@ -100,13 +100,14 @@ export function CreatePlayerModal({
<h2 className="text-lg font-semibold text-foreground"> <h2 className="text-lg font-semibold text-foreground">
{isEdit ? "Edit Player" : "Create Player"} {isEdit ? "Edit Player" : "Create Player"}
</h2> </h2>
<button <Button
type="button" variant="ghost"
size="icon"
onClick={onClose} onClick={onClose}
className="text-muted-foreground hover:text-hover-neutral transition-colors" className="text-muted-foreground"
> >
<X size={20} /> <X size={20} />
</button> </Button>
</div> </div>
<form onSubmit={handleSubmit} className="flex flex-col gap-4"> <form onSubmit={handleSubmit} className="flex flex-col gap-4">

View File

@@ -52,19 +52,20 @@ export function PlayerManagement({
<h2 className="text-lg font-semibold text-foreground"> <h2 className="text-lg font-semibold text-foreground">
Player Characters Player Characters
</h2> </h2>
<button <Button
type="button" variant="ghost"
size="icon"
onClick={onClose} onClick={onClose}
className="text-muted-foreground hover:text-hover-neutral transition-colors" className="text-muted-foreground"
> >
<X size={20} /> <X size={20} />
</button> </Button>
</div> </div>
{characters.length === 0 ? ( {characters.length === 0 ? (
<div className="flex flex-col items-center gap-3 py-8 text-center"> <div className="flex flex-col items-center gap-3 py-8 text-center">
<p className="text-muted-foreground">No player characters yet</p> <p className="text-muted-foreground">No player characters yet</p>
<Button onClick={onCreate} size="sm"> <Button onClick={onCreate}>
<Plus size={16} /> <Plus size={16} />
Create your first player character Create your first player character
</Button> </Button>
@@ -92,25 +93,27 @@ export function PlayerManagement({
<span className="text-xs tabular-nums text-muted-foreground"> <span className="text-xs tabular-nums text-muted-foreground">
HP {pc.maxHp} HP {pc.maxHp}
</span> </span>
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={() => onEdit(pc)} onClick={() => onEdit(pc)}
className="text-muted-foreground hover:text-hover-neutral transition-colors" className="text-muted-foreground"
title="Edit" title="Edit"
> >
<Pencil size={14} /> <Pencil size={14} />
</button> </Button>
<ConfirmButton <ConfirmButton
icon={<Trash2 size={14} />} icon={<Trash2 size={14} />}
label="Delete player character" label="Delete player character"
onConfirm={() => onDelete(pc.id)} onConfirm={() => onDelete(pc.id)}
className="h-6 w-6 text-muted-foreground" size="icon-sm"
className="text-muted-foreground"
/> />
</div> </div>
); );
})} })}
<div className="mt-2 flex justify-end"> <div className="mt-2 flex justify-end">
<Button onClick={onCreate} size="sm" variant="ghost"> <Button onClick={onCreate} variant="ghost">
<Plus size={16} /> <Plus size={16} />
Add Add
</Button> </Button>

View File

@@ -88,11 +88,7 @@ export function SourceFetchPrompt({
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button onClick={handleFetch} disabled={status === "fetching" || !url}>
size="sm"
onClick={handleFetch}
disabled={status === "fetching" || !url}
>
{status === "fetching" ? ( {status === "fetching" ? (
<Loader2 className="mr-1 h-3 w-3 animate-spin" /> <Loader2 className="mr-1 h-3 w-3 animate-spin" />
) : ( ) : (
@@ -104,7 +100,6 @@ export function SourceFetchPrompt({
<span className="text-xs text-muted-foreground">or</span> <span className="text-xs text-muted-foreground">or</span>
<Button <Button
size="sm"
variant="outline" variant="outline"
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
disabled={status === "fetching"} disabled={status === "fetching"}

View File

@@ -48,7 +48,6 @@ export function SourceManager({ onCacheCleared }: SourceManagerProps) {
Cached Sources Cached Sources
</span> </span>
<Button <Button
size="sm"
variant="outline" variant="outline"
className="hover:text-hover-destructive hover:border-hover-destructive" className="hover:text-hover-destructive hover:border-hover-destructive"
onClick={handleClearAll} onClick={handleClearAll}

View File

@@ -8,6 +8,7 @@ import { useSwipeToDismiss } from "../hooks/use-swipe-to-dismiss.js";
import { BulkImportPrompt } from "./bulk-import-prompt.js"; import { BulkImportPrompt } from "./bulk-import-prompt.js";
import { SourceFetchPrompt } from "./source-fetch-prompt.js"; import { SourceFetchPrompt } from "./source-fetch-prompt.js";
import { StatBlock } from "./stat-block.js"; import { StatBlock } from "./stat-block.js";
import { Button } from "./ui/button.js";
interface StatBlockPanelProps { interface StatBlockPanelProps {
creatureId: CreatureId | null; creatureId: CreatureId | null;
@@ -81,36 +82,39 @@ function PanelHeader({
<div className="flex items-center justify-between border-b border-border px-4 py-2"> <div className="flex items-center justify-between border-b border-border px-4 py-2">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{panelRole === "browse" && ( {panelRole === "browse" && (
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={onToggleFold} onClick={onToggleFold}
className="text-muted-foreground hover:text-hover-neutral" className="text-muted-foreground"
aria-label="Fold stat block panel" aria-label="Fold stat block panel"
> >
<PanelRightClose className="h-4 w-4" /> <PanelRightClose className="h-4 w-4" />
</button> </Button>
)} )}
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{panelRole === "browse" && showPinButton && ( {panelRole === "browse" && showPinButton && (
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={onPin} onClick={onPin}
className="text-muted-foreground hover:text-hover-neutral" className="text-muted-foreground"
aria-label="Pin creature" aria-label="Pin creature"
> >
<Pin className="h-4 w-4" /> <Pin className="h-4 w-4" />
</button> </Button>
)} )}
{panelRole === "pinned" && ( {panelRole === "pinned" && (
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={onUnpin} onClick={onUnpin}
className="text-muted-foreground hover:text-hover-neutral" className="text-muted-foreground"
aria-label="Unpin creature" aria-label="Unpin creature"
> >
<PinOff className="h-4 w-4" /> <PinOff className="h-4 w-4" />
</button> </Button>
)} )}
</div> </div>
</div> </div>
@@ -195,14 +199,15 @@ function MobileDrawer({
{...handlers} {...handlers}
> >
<div className="flex items-center justify-between border-b border-border px-4 py-2"> <div className="flex items-center justify-between border-b border-border px-4 py-2">
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={onDismiss} onClick={onDismiss}
className="text-muted-foreground hover:text-hover-neutral" className="text-muted-foreground"
aria-label="Fold stat block panel" aria-label="Fold stat block panel"
> >
<PanelRightClose className="h-4 w-4" /> <PanelRightClose className="h-4 w-4" />
</button> </Button>
</div> </div>
<div className="h-[calc(100%-41px)] overflow-y-auto p-4"> <div className="h-[calc(100%-41px)] overflow-y-auto p-4">
{children} {children}

View File

@@ -1,6 +1,7 @@
import { X } from "lucide-react"; import { X } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { Button } from "./ui/button.js";
interface ToastProps { interface ToastProps {
message: string; message: string;
@@ -33,13 +34,14 @@ export function Toast({
/> />
</div> </div>
)} )}
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={onDismiss} onClick={onDismiss}
className="text-muted-foreground hover:text-hover-neutral" className="text-muted-foreground"
> >
<X className="h-3 w-3" /> <X className="h-3 w-3" />
</button> </Button>
</div> </div>
</div>, </div>,
document.body, document.body,

View File

@@ -13,9 +13,9 @@ const buttonVariants = cva(
ghost: "hover:bg-hover-neutral-bg hover:text-hover-neutral", ghost: "hover:bg-hover-neutral-bg hover:text-hover-neutral",
}, },
size: { size: {
default: "h-9 px-4 py-2", default: "h-8 px-3 text-xs",
sm: "h-8 px-3 text-xs",
icon: "h-8 w-8", icon: "h-8 w-8",
"icon-sm": "h-6 w-6",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -13,6 +13,7 @@ interface ConfirmButtonProps {
readonly onConfirm: () => void; readonly onConfirm: () => void;
readonly icon: ReactElement; readonly icon: ReactElement;
readonly label: string; readonly label: string;
readonly size?: "icon" | "icon-sm";
readonly className?: string; readonly className?: string;
readonly disabled?: boolean; readonly disabled?: boolean;
} }
@@ -23,6 +24,7 @@ export function ConfirmButton({
onConfirm, onConfirm,
icon, icon,
label, label,
size = "icon",
className, className,
disabled, disabled,
}: ConfirmButtonProps) { }: ConfirmButtonProps) {
@@ -94,7 +96,7 @@ export function ConfirmButton({
<div ref={wrapperRef} className="inline-flex"> <div ref={wrapperRef} className="inline-flex">
<Button <Button
variant="ghost" variant="ghost"
size="icon" size={size}
className={cn( className={cn(
className, className,
isConfirming isConfirming