feat: bootstrap auto virtual tryon admin frontend
This commit is contained in:
116
src/components/ui/status-badge.tsx
Normal file
116
src/components/ui/status-badge.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { HTMLAttributes } from "react";
|
||||
|
||||
import type {
|
||||
OrderStatus,
|
||||
ReviewDecision,
|
||||
StepStatus,
|
||||
WorkflowStepName,
|
||||
} from "@/lib/types/backend";
|
||||
import {
|
||||
ORDER_STATUS_META,
|
||||
REVIEW_DECISION_META,
|
||||
STEP_STATUS_META,
|
||||
WORKFLOW_STEP_META,
|
||||
type StatusMeta,
|
||||
type StatusTone,
|
||||
} from "@/lib/types/status";
|
||||
|
||||
type StatusBadgeVariant =
|
||||
| "order"
|
||||
| "reviewDecision"
|
||||
| "stepStatus"
|
||||
| "workflowStep";
|
||||
|
||||
type StatusBadgeBaseProps = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
type OrderStatusBadgeProps = StatusBadgeBaseProps & {
|
||||
status: OrderStatus;
|
||||
variant?: "order";
|
||||
};
|
||||
|
||||
type ReviewDecisionBadgeProps = StatusBadgeBaseProps & {
|
||||
status: ReviewDecision;
|
||||
variant: "reviewDecision";
|
||||
};
|
||||
|
||||
type StepStatusBadgeProps = StatusBadgeBaseProps & {
|
||||
status: StepStatus;
|
||||
variant: "stepStatus";
|
||||
};
|
||||
|
||||
type WorkflowStepBadgeProps = StatusBadgeBaseProps & {
|
||||
status: WorkflowStepName | null;
|
||||
variant: "workflowStep";
|
||||
};
|
||||
|
||||
export type StatusBadgeProps =
|
||||
| OrderStatusBadgeProps
|
||||
| ReviewDecisionBadgeProps
|
||||
| StepStatusBadgeProps
|
||||
| WorkflowStepBadgeProps;
|
||||
|
||||
const TONE_STYLES: Record<StatusTone, string> = {
|
||||
neutral:
|
||||
"border-[rgba(76,69,60,0.14)] bg-[rgba(110,98,84,0.08)] text-[var(--ink-muted)]",
|
||||
info: "border-[rgba(57,86,95,0.16)] bg-[rgba(57,86,95,0.1)] text-[#2e4d56]",
|
||||
warning:
|
||||
"border-[rgba(145,104,46,0.18)] bg-[rgba(202,164,97,0.14)] text-[#7a5323]",
|
||||
success:
|
||||
"border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.14)] text-[#50633b]",
|
||||
danger:
|
||||
"border-[rgba(140,74,67,0.18)] bg-[rgba(140,74,67,0.12)] text-[#7f3f38]",
|
||||
};
|
||||
|
||||
function joinClasses(...values: Array<string | false | null | undefined>) {
|
||||
return values.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function getMetaFromRecord<T extends string>(
|
||||
record: Record<T, StatusMeta>,
|
||||
status: string,
|
||||
variant: StatusBadgeVariant,
|
||||
): StatusMeta {
|
||||
const meta = record[status as T];
|
||||
|
||||
if (!meta) {
|
||||
throw new Error(`Invalid status "${status}" for variant "${variant}".`);
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
function resolveStatusMeta(props: StatusBadgeProps): StatusMeta {
|
||||
switch (props.variant) {
|
||||
case "reviewDecision":
|
||||
return getMetaFromRecord(REVIEW_DECISION_META, props.status, props.variant);
|
||||
case "stepStatus":
|
||||
return getMetaFromRecord(STEP_STATUS_META, props.status, props.variant);
|
||||
case "workflowStep":
|
||||
if (props.status === null) {
|
||||
return { label: "未开始", tone: "neutral" };
|
||||
}
|
||||
|
||||
return getMetaFromRecord(WORKFLOW_STEP_META, props.status, props.variant);
|
||||
case "order":
|
||||
case undefined:
|
||||
return getMetaFromRecord(ORDER_STATUS_META, props.status, "order");
|
||||
}
|
||||
}
|
||||
|
||||
export function StatusBadge({ className, ...props }: StatusBadgeProps) {
|
||||
const meta = resolveStatusMeta(props);
|
||||
|
||||
return (
|
||||
<span
|
||||
data-tone={meta.tone}
|
||||
className={joinClasses(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-1 font-[var(--font-mono)] text-[11px] uppercase tracking-[0.16em]",
|
||||
TONE_STYLES[meta.tone],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{meta.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user