diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 34ac022..0261dee 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -16,7 +16,7 @@ import { type LucideIcon, } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; -import { buttonCls } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Field, inputCls } from "@/components/work-orders/ui"; import { isoDay, parseListParams, toQuery, type Preset } from "@/lib/work-orders/filters"; diff --git a/src/app/(app)/search/page.tsx b/src/app/(app)/search/page.tsx index 46cc945..5f6742a 100644 --- a/src/app/(app)/search/page.tsx +++ b/src/app/(app)/search/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { Search } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; -import { buttonCls } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Empty, Field, inputCls, Section, StatusBadge } from "@/components/work-orders/ui"; import { isoDay, parseListParams } from "@/lib/work-orders/filters"; diff --git a/src/app/(app)/settings/checklists/page.tsx b/src/app/(app)/settings/checklists/page.tsx index 9c50ca3..090367f 100644 --- a/src/app/(app)/settings/checklists/page.tsx +++ b/src/app/(app)/settings/checklists/page.tsx @@ -4,7 +4,8 @@ import { z } from "zod"; import { Plus } from "lucide-react"; import { PageHead, Pill } from "@/components/mockup-ui"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { SettingsTemplatesNav, templatesPageContext } from "@/components/work-orders/settings-nav"; import { Check, Empty, Field, inputCls, Section } from "@/components/work-orders/ui"; import { DEFAULT_CHECKLIST_ITEMS, DEFAULT_REQUIRED_PHOTOS } from "@/lib/work-orders/defaults"; diff --git a/src/app/(app)/work-orders/[id]/page.tsx b/src/app/(app)/work-orders/[id]/page.tsx index e840afa..9e3d7b0 100644 --- a/src/app/(app)/work-orders/[id]/page.tsx +++ b/src/app/(app)/work-orders/[id]/page.tsx @@ -3,7 +3,8 @@ import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { ArrowLeft, Pencil, Siren, UsersRound } from "lucide-react"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { ChecklistTab, DocumentsTab, diff --git a/src/app/(app)/work-orders/page.tsx b/src/app/(app)/work-orders/page.tsx index d5611f1..a6e7b21 100644 --- a/src/app/(app)/work-orders/page.tsx +++ b/src/app/(app)/work-orders/page.tsx @@ -3,7 +3,8 @@ import { getTranslations } from "next-intl/server"; import { LayoutGrid, Plus, RefreshCw, Rows3, X } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Empty, Field, GroupIcon, inputCls, LinkTabs } from "@/components/work-orders/ui"; import { WorkOrderFields } from "@/components/work-orders/work-order-fields"; diff --git a/src/components/work-orders/action-form.tsx b/src/components/work-orders/action-form.tsx index 4606956..ef1e63c 100644 --- a/src/components/work-orders/action-form.tsx +++ b/src/components/work-orders/action-form.tsx @@ -6,22 +6,12 @@ import { AlertCircle, CheckCircle2 } from "lucide-react"; import { IDLE_STATE, type ActionState } from "@/lib/work-orders/action-state"; import type { CompletionBlocker } from "@/lib/work-orders/status"; import { cn } from "@/lib/utils"; +import { buttonCls, type ButtonVariant } from "@/components/work-orders/button-cls"; + +export { buttonCls }; type Action = (prev: ActionState, fd: FormData) => Promise; -const VARIANTS = { - primary: "bg-cta text-cta-foreground hover:opacity-90", - default: "bg-primary text-primary-foreground hover:opacity-90", - outline: "border border-border bg-background hover:bg-muted", - danger: "border border-[var(--risk)] bg-background text-[var(--risk)] hover:bg-muted", - ghost: "hover:bg-muted text-muted-foreground", -} as const; - -export const buttonCls = (variant: keyof typeof VARIANTS = "default") => - cn( - "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg px-4 font-heading text-sm font-semibold transition-colors focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50", - VARIANTS[variant], - ); /** * Form bound to a work order server action. Shows translated errors (incl. structured @@ -44,7 +34,7 @@ export function ActionForm({ submitLabel: string; pendingLabel?: string; successText?: string; - variant?: keyof typeof VARIANTS; + variant?: ButtonVariant; namespace?: "workOrders" | "settingsTemplates"; className?: string; footerClassName?: string; diff --git a/src/components/work-orders/button-cls.ts b/src/components/work-orders/button-cls.ts new file mode 100644 index 0000000..c668591 --- /dev/null +++ b/src/components/work-orders/button-cls.ts @@ -0,0 +1,21 @@ +import { cn } from "@/lib/utils"; + +/** + * Button class names shared by server and client components. Kept out of "use client" modules: + * calling a function exported from a client module inside a server component fails at runtime. + */ +const VARIANTS = { + primary: "bg-cta text-cta-foreground hover:opacity-90", + default: "bg-primary text-primary-foreground hover:opacity-90", + outline: "border border-border bg-background hover:bg-muted", + danger: "border border-[var(--risk)] bg-background text-[var(--risk)] hover:bg-muted", + ghost: "hover:bg-muted text-muted-foreground", +} as const; + +export type ButtonVariant = keyof typeof VARIANTS; + +export const buttonCls = (variant: ButtonVariant = "default") => + cn( + "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg px-4 font-heading text-sm font-semibold transition-colors focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50", + VARIANTS[variant], + ); diff --git a/src/components/work-orders/detail-tabs.tsx b/src/components/work-orders/detail-tabs.tsx index 6700197..ac9dbdf 100644 --- a/src/components/work-orders/detail-tabs.tsx +++ b/src/components/work-orders/detail-tabs.tsx @@ -27,7 +27,8 @@ import { removePhotoRequirementAction, } from "@/server/actions/work_orders/work-orders"; import { formatDate, formatDateTime } from "@/lib/work-orders/time"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { Check, Dl, Empty, Field, inputCls, Section } from "@/components/work-orders/ui"; import type { CompletionBlocker } from "@/lib/work-orders/status";