refactor(ui): replace inline INPUT_CLS/BTN_DANGER/action link constants with component classes
- Replace 13 local INPUT_CLS/SELECT_CLS/LABEL_CLS/BTN_DANGER constants with app-input, app-select, app-label, app-action-danger-btn component classes (CustomFieldFilterBar, RolePresetsEditor, FieldCard, BlueprintFieldCatalog, BlueprintFieldEditor, BlueprintsClient, EstimateWizard, EstimateWorkspace- DraftEditor, DemandLineEditor, ScopeItemEditor, AssumptionEditor, ProjectWizard, BulkEditModal) - Replace inline text-blue-600/text-red-500 action link strings with app-action-edit / app-action-delete in AllocationsClient, ProjectsClient, ScenarioPlanner, ProjectDemandsTable, RolesClient, BlueprintsClient, CreateTaskModal, RateCardsClient, UsersClient, ManagementLevelsClient Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -189,7 +189,7 @@ export function ManagementLevelsClient() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmDeleteLevel(level.id)}
|
||||
className="text-xs text-red-500 hover:text-red-700 font-medium"
|
||||
className="app-action-delete"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -531,7 +531,7 @@ export function RateCardsClient() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmDeleteLine(line.id)}
|
||||
className="text-xs text-red-500 hover:text-red-700 font-medium"
|
||||
className="app-action-delete"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -669,7 +669,7 @@ export function UsersClient() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeleteTarget({ userId: user.id, userName: user.name ?? user.email })}
|
||||
className="text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 font-medium"
|
||||
className="app-action-delete"
|
||||
title="Permanently delete user"
|
||||
>
|
||||
Delete
|
||||
|
||||
@@ -552,12 +552,12 @@ export function AllocationsClient() {
|
||||
})}
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button type="button" onClick={() => openEdit(alloc)} className="text-xs font-medium text-blue-600 hover:text-blue-800 hover:underline dark:text-blue-300 dark:hover:text-blue-200">Edit</button>
|
||||
<button type="button" onClick={() => openEdit(alloc)} className="app-action-edit">Edit</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmDelete({ single: alloc })}
|
||||
disabled={singleDeletePending}
|
||||
className="text-xs font-medium text-red-500 hover:text-red-700 hover:underline disabled:opacity-50 dark:text-red-300 dark:hover:text-red-200"
|
||||
className="app-action-delete disabled:opacity-50"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@@ -977,7 +977,7 @@ export function AllocationsClient() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openEdit(demand as AllocationWithDetails)}
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-800 hover:underline dark:text-blue-300 dark:hover:text-blue-200"
|
||||
className="app-action-edit"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
@@ -985,7 +985,7 @@ export function AllocationsClient() {
|
||||
type="button"
|
||||
onClick={() => setConfirmDelete({ single: demand as AllocationWithDetails })}
|
||||
disabled={singleDeletePending}
|
||||
className="text-xs font-medium text-red-500 hover:text-red-700 hover:underline disabled:opacity-50 dark:text-red-300 dark:hover:text-red-200"
|
||||
className="app-action-delete disabled:opacity-50"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -18,8 +18,7 @@ import type { CatalogField } from "~/lib/blueprint-field-catalog.js";
|
||||
// Styles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500";
|
||||
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";
|
||||
|
||||
@@ -18,8 +18,7 @@ const FIELD_TYPES: { value: FieldType; label: string }[] = [
|
||||
{ value: FieldType.EMAIL, label: "Email" },
|
||||
];
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
||||
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";
|
||||
@@ -27,8 +26,7 @@ const BTN_PRIMARY =
|
||||
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 =
|
||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
||||
const BTN_DANGER = "app-action-danger-btn";
|
||||
|
||||
function makeEmptyField(order: number): BlueprintFieldDefinition {
|
||||
return {
|
||||
|
||||
@@ -14,8 +14,7 @@ import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js";
|
||||
import { useTableSort } from "~/hooks/useTableSort.js";
|
||||
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
||||
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";
|
||||
@@ -430,7 +429,7 @@ export function BlueprintsClient() {
|
||||
}
|
||||
}}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="text-xs text-red-500 hover:text-red-700 disabled:opacity-50"
|
||||
className="app-action-delete disabled:opacity-50"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -59,8 +59,7 @@ interface FieldCardProps {
|
||||
// Component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
||||
const INPUT_CLS = "app-input";
|
||||
|
||||
export function FieldCard({ field, overrides, onChange }: FieldCardProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
@@ -4,11 +4,8 @@ import { useState } from "react";
|
||||
import type { StaffingRequirement } from "@capakraken/shared";
|
||||
import { uuid } from "~/lib/uuid.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
||||
|
||||
const BTN_DANGER =
|
||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
||||
const INPUT_CLS = "app-input";
|
||||
const BTN_DANGER = "app-action-danger-btn";
|
||||
|
||||
function makeEmptyPreset(): StaffingRequirement {
|
||||
return {
|
||||
|
||||
@@ -14,10 +14,9 @@ import { ResourceCombobox } from "~/components/ui/ResourceCombobox.js";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
import { uuid } from "~/lib/uuid.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
||||
const SELECT_CLS = INPUT_CLS;
|
||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
||||
const INPUT_CLS = "app-input";
|
||||
const SELECT_CLS = "app-select w-full";
|
||||
const LABEL_CLS = "app-label";
|
||||
const STEP_LABELS = ["Setup", "Assumptions", "Scope", "Staffing", "Review"];
|
||||
|
||||
interface AssumptionRow {
|
||||
|
||||
@@ -39,9 +39,8 @@ interface ResourceListView {
|
||||
resources: ResourceOption[];
|
||||
}
|
||||
|
||||
const INPUT_CLS =
|
||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
||||
const INPUT_CLS = "app-input";
|
||||
const LABEL_CLS = "app-label";
|
||||
|
||||
function toNumber(value: string) {
|
||||
const parsed = Number.parseFloat(value);
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
||||
const INPUT_CLS = "app-input";
|
||||
const LABEL_CLS = "app-label";
|
||||
|
||||
export interface EditableAssumption {
|
||||
id?: string;
|
||||
|
||||
@@ -13,9 +13,8 @@ import type { EstimateResourceSnapshotView } from "~/components/estimates/Estima
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { formatMoney } from "~/lib/format.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
||||
const INPUT_CLS = "app-input";
|
||||
const LABEL_CLS = "app-label";
|
||||
|
||||
function toNumber(value: string) {
|
||||
const parsed = Number.parseFloat(value);
|
||||
|
||||
@@ -4,9 +4,8 @@ import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { isSpreadsheetFile } from "~/lib/excel.js";
|
||||
import { parseScopeImport } from "~/lib/scopeImportParser.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
||||
const INPUT_CLS = "app-input";
|
||||
const LABEL_CLS = "app-label";
|
||||
|
||||
export interface EditableScopeItem {
|
||||
id?: string;
|
||||
|
||||
@@ -238,7 +238,7 @@ export function CreateTaskModal({ onClose, onSuccess }: CreateTaskModalProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setUserId("")}
|
||||
className="text-xs text-red-500 hover:text-red-700"
|
||||
className="app-action-delete"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
|
||||
@@ -166,7 +166,7 @@ export function ProjectDemandsTable({ demands, project }: ProjectDemandsTablePro
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditTarget(demand as unknown as AllocationWithDetails)}
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
|
||||
className="app-action-edit"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
@@ -30,13 +30,9 @@ const ALLOCATION_TYPE_OPTIONS = [
|
||||
{ value: "EXT", label: "EXT" },
|
||||
] as const;
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm w-full";
|
||||
|
||||
const SELECT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm w-full bg-white";
|
||||
|
||||
const LABEL_CLS = "block text-xs font-medium text-gray-600 mb-1";
|
||||
const INPUT_CLS = "app-input";
|
||||
const SELECT_CLS = "app-select w-full";
|
||||
const LABEL_CLS = "app-label";
|
||||
|
||||
const BTN_PRIMARY =
|
||||
"px-5 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50 transition-colors";
|
||||
@@ -44,8 +40,7 @@ const BTN_PRIMARY =
|
||||
const BTN_SECONDARY =
|
||||
"px-5 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium transition-colors";
|
||||
|
||||
const BTN_DANGER =
|
||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
||||
const BTN_DANGER = "app-action-danger-btn";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ function ScenarioRowEditor({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRestore(row.key)}
|
||||
className="text-xs text-blue-600 hover:text-blue-800 underline"
|
||||
className="app-action-edit"
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
|
||||
@@ -5,8 +5,7 @@ import { FieldType } from "@capakraken/shared";
|
||||
import type { BlueprintFieldDefinition } from "@capakraken/shared";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
||||
const INPUT_CLS = "app-input";
|
||||
|
||||
interface Props {
|
||||
selectedIds: string[];
|
||||
|
||||
@@ -280,7 +280,7 @@ export function RolesClient() {
|
||||
setConfirmDelete(role);
|
||||
setActionError(null);
|
||||
}}
|
||||
className="text-xs text-red-500 hover:text-red-700"
|
||||
className="app-action-delete"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -11,8 +11,7 @@ interface Props {
|
||||
onSetFilter: (key: string, value: string, type: FieldType) => void;
|
||||
}
|
||||
|
||||
const INPUT_CLS =
|
||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm bg-white";
|
||||
const INPUT_CLS = "app-input";
|
||||
|
||||
export function CustomFieldFilterBar({ filterableFields, activeFilters, onSetFilter }: Props) {
|
||||
if (filterableFields.length === 0) return null;
|
||||
|
||||
Reference in New Issue
Block a user