refactor(ui): replace inline INPUT_CLS/LABEL_CLS/BTN_DANGER constants and action link classes with CSS component classes

Remove duplicated Tailwind class string constants from 15 component files.
Use app-input, app-select, app-label, app-action-danger-btn, and
app-action-delete CSS component classes from globals.css instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 09:21:03 +02:00
parent 9ba49c9ab8
commit 05aa864359
15 changed files with 159 additions and 197 deletions
@@ -18,8 +18,6 @@ import type { CatalogField } from "~/lib/blueprint-field-catalog.js";
// Styles
// ---------------------------------------------------------------------------
const INPUT_CLS = "app-input";
const BTN_PRIMARY =
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
@@ -496,7 +494,7 @@ export function BlueprintFieldCatalog({
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search fields..."
className={`${INPUT_CLS} w-full`}
className="app-input"
autoFocus
/>
</div>
@@ -636,7 +634,7 @@ export function BlueprintFieldCatalog({
)
}
placeholder="field_key"
className={`${INPUT_CLS} font-mono`}
className="app-input font-mono"
/>
</div>
<div className="flex flex-col gap-1">
@@ -651,7 +649,7 @@ export function BlueprintFieldCatalog({
setCustomLabel(e.target.value)
}
placeholder="Display Label"
className={INPUT_CLS}
className="app-input"
/>
</div>
<div className="flex flex-col gap-1">
@@ -665,7 +663,7 @@ export function BlueprintFieldCatalog({
e.target.value as FieldType,
)
}
className={INPUT_CLS}
className="app-input"
>
{FIELD_TYPES.map((ft) => (
<option key={ft.value} value={ft.value}>
@@ -18,16 +18,12 @@ const FIELD_TYPES: { value: FieldType; label: string }[] = [
{ value: FieldType.EMAIL, label: "Email" },
];
const INPUT_CLS = "app-input";
const BTN_PRIMARY =
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
const BTN_SECONDARY =
"px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium";
const BTN_DANGER = "app-action-danger-btn";
function makeEmptyField(order: number): BlueprintFieldDefinition {
return {
id: Math.random().toString(36).slice(2),
@@ -73,19 +69,19 @@ function OptionsEditor({ options, onChange }: OptionsEditorProps) {
value={opt.value}
onChange={(e) => updateOption(idx, "value", e.target.value)}
placeholder="value"
className={`${INPUT_CLS} flex-1`}
className="app-input flex-1"
/>
<input
type="text"
value={opt.label}
onChange={(e) => updateOption(idx, "label", e.target.value)}
placeholder="label"
className={`${INPUT_CLS} flex-1`}
className="app-input flex-1"
/>
<button
type="button"
onClick={() => removeOption(idx)}
className={BTN_DANGER}
className="app-action-danger-btn"
aria-label="Remove option"
>
×
@@ -140,7 +136,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
value={field.key}
onChange={(e) => update("key", e.target.value)}
placeholder="field_key"
className={`${INPUT_CLS} w-36 font-mono`}
className="app-input w-36 font-mono"
aria-label="Field key"
/>
@@ -150,7 +146,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
value={field.label}
onChange={(e) => update("label", e.target.value)}
placeholder="Label"
className={`${INPUT_CLS} w-40`}
className="app-input w-40"
aria-label="Field label"
/>
@@ -166,7 +162,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
: undefined;
onChange({ ...field, type: t, options: clearedOptions } as BlueprintFieldDefinition);
}}
className={`${INPUT_CLS} w-36`}
className="app-input w-36"
aria-label="Field type"
>
{FIELD_TYPES.map((ft) => (
@@ -201,7 +197,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
<button
type="button"
onClick={onDelete}
className={BTN_DANGER}
className="app-action-danger-btn"
aria-label="Delete field"
>
×
@@ -218,7 +214,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
value={field.group ?? ""}
onChange={(e) => update("group", e.target.value || undefined)}
placeholder="Section heading"
className={INPUT_CLS}
className="app-input"
/>
</div>
<div className="flex flex-col gap-1">
@@ -232,7 +228,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
update("placeholder", e.target.value || undefined)
}
placeholder="Placeholder text"
className={INPUT_CLS}
className="app-input"
/>
</div>
<div className="flex flex-col gap-1">
@@ -246,7 +242,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
update("description", e.target.value || undefined)
}
placeholder="Helper text"
className={INPUT_CLS}
className="app-input"
/>
</div>
@@ -14,8 +14,6 @@ import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js";
import { useTableSort } from "~/hooks/useTableSort.js";
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
const INPUT_CLS = "app-input";
const BTN_PRIMARY =
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
@@ -75,15 +73,15 @@ function NewBlueprintModal({ onClose, onCreated }: NewBlueprintModalProps) {
<form onSubmit={handleSubmit} className="px-6 py-5 space-y-4">
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-700">Name <span className="text-red-500">*</span></label>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Resource Extended Fields" className={INPUT_CLS} autoFocus />
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Resource Extended Fields" className="app-input" autoFocus />
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-700">Description</label>
<textarea value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Optional description" className={`${INPUT_CLS} resize-none`} rows={2} />
<textarea value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Optional description" className="app-input resize-none" rows={2} />
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-700">Target</label>
<select value={target} onChange={(e) => setTarget(e.target.value as BlueprintTargetValue)} className={INPUT_CLS}>
<select value={target} onChange={(e) => setTarget(e.target.value as BlueprintTargetValue)} className="app-input">
<option value="RESOURCE">Resource</option>
<option value="PROJECT">Project</option>
</select>
@@ -59,8 +59,6 @@ interface FieldCardProps {
// Component
// ---------------------------------------------------------------------------
const INPUT_CLS = "app-input";
export function FieldCard({ field, overrides, onChange }: FieldCardProps) {
const [expanded, setExpanded] = useState(false);
@@ -193,7 +191,7 @@ export function FieldCard({ field, overrides, onChange }: FieldCardProps) {
value={overrides.description}
onChange={(e) => update({ description: e.target.value })}
placeholder={field.description}
className={INPUT_CLS}
className="app-input"
/>
</div>
</div>
@@ -240,7 +238,7 @@ function DefaultValueInput({
onChange(e.target.value === "" ? undefined : Number(e.target.value))
}
placeholder="No default"
className={INPUT_CLS}
className="app-input"
/>
);
@@ -252,7 +250,7 @@ function DefaultValueInput({
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
className={INPUT_CLS}
className="app-input"
/>
);
@@ -263,7 +261,7 @@ function DefaultValueInput({
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
className={INPUT_CLS}
className="app-input"
>
<option value="">No default</option>
{(options ?? []).map((opt) => (
@@ -292,7 +290,7 @@ function DefaultValueInput({
onChange(e.target.value === "" ? undefined : e.target.value)
}
placeholder="https://..."
className={INPUT_CLS}
className="app-input"
/>
);
@@ -305,7 +303,7 @@ function DefaultValueInput({
onChange(e.target.value === "" ? undefined : e.target.value)
}
placeholder="name@example.com"
className={INPUT_CLS}
className="app-input"
/>
);
@@ -317,7 +315,7 @@ function DefaultValueInput({
onChange(e.target.value === "" ? undefined : e.target.value)
}
placeholder="No default"
className={`${INPUT_CLS} resize-none`}
className="app-input resize-none"
rows={2}
/>
);
@@ -331,7 +329,7 @@ function DefaultValueInput({
onChange(e.target.value === "" ? undefined : e.target.value)
}
placeholder="No default"
className={INPUT_CLS}
className="app-input"
/>
);
}
@@ -4,9 +4,6 @@ import { useState } from "react";
import type { StaffingRequirement } from "@capakraken/shared";
import { uuid } from "~/lib/uuid.js";
const INPUT_CLS = "app-input";
const BTN_DANGER = "app-action-danger-btn";
function makeEmptyPreset(): StaffingRequirement {
return {
id: uuid(),
@@ -38,7 +35,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
value={preset.role}
onChange={(e) => update("role", e.target.value)}
placeholder="Role name"
className={`${INPUT_CLS} flex-1 min-w-32`}
className="app-input flex-1 min-w-32"
aria-label="Role name"
/>
@@ -58,7 +55,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
)
}
placeholder="e.g. 3D Modeling, Lighting"
className={INPUT_CLS}
className="app-input"
aria-label="Required skills"
/>
</div>
@@ -73,7 +70,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
max={24}
step={0.5}
onChange={(e) => update("hoursPerDay", parseFloat(e.target.value) || 0)}
className={INPUT_CLS}
className="app-input"
aria-label="Hours per day"
/>
</div>
@@ -87,7 +84,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
min={1}
max={20}
onChange={(e) => update("headcount", parseInt(e.target.value, 10) || 1)}
className={INPUT_CLS}
className="app-input"
aria-label="Headcount"
/>
</div>
@@ -96,7 +93,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
<button
type="button"
onClick={onDelete}
className={`${BTN_DANGER} self-end mb-0.5`}
className="app-action-danger-btn self-end mb-0.5"
aria-label="Remove preset"
>
×
@@ -119,7 +116,7 @@ function PresetRow({ preset, onChange, onDelete }: PresetRowProps) {
)
}
placeholder="e.g. Compositing, Art Direction"
className={`${INPUT_CLS} w-full mt-0.5`}
className="app-input mt-0.5"
aria-label="Preferred skills"
/>
</div>