Adopt shared WebUI structural primitives
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { Link2, X } from "lucide-react";
|
||||
import { Button, Dialog, DismissibleAlert, i18nMessage } from "@govoplan/core-webui";
|
||||
import { Button, Dialog, DismissibleAlert, MetricCard, MetricGrid, i18nMessage } from "@govoplan/core-webui";
|
||||
|
||||
import type {
|
||||
CampaignAttachmentPreviewFile,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
attachmentPreviewLinkableFiles,
|
||||
attachmentPreviewMatchedFiles
|
||||
} from "../utils/attachmentPreview";
|
||||
import { WorkflowFact } from "./WorkflowNavigation";
|
||||
|
||||
export default function AttachmentLinkingPreview({
|
||||
preview,
|
||||
@@ -70,8 +69,8 @@ export default function AttachmentLinkingPreview({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="review-flow-fact-grid">
|
||||
<WorkflowFact
|
||||
<MetricGrid columns={4} density="compact" spacing="block" minimum="compact">
|
||||
<MetricCard density="compact" surface="subtle"
|
||||
label="i18n:govoplan-campaign.matched.1bf3ec5b"
|
||||
value={
|
||||
!loading && matchedFiles.length > 0 ? (
|
||||
@@ -95,19 +94,19 @@ export default function AttachmentLinkingPreview({
|
||||
)
|
||||
}
|
||||
/>
|
||||
<WorkflowFact
|
||||
<MetricCard density="compact" surface="subtle"
|
||||
label="i18n:govoplan-campaign.linked.a089f600"
|
||||
value={loading ? "..." : linkedCount || "—"}
|
||||
/>
|
||||
<WorkflowFact
|
||||
<MetricCard density="compact" surface="subtle"
|
||||
label="i18n:govoplan-campaign.need_link.fa4ab530"
|
||||
value={loading ? "..." : unlinkedCount || "—"}
|
||||
/>
|
||||
<WorkflowFact
|
||||
<MetricCard density="compact" surface="subtle"
|
||||
label="i18n:govoplan-campaign.shared_source.7d4a1bf2"
|
||||
value={loading ? "..." : preview?.shared_file_count ?? "—"}
|
||||
/>
|
||||
</div>
|
||||
</MetricGrid>
|
||||
{error && (
|
||||
<DismissibleAlert tone="danger" compact dismissible={false}>
|
||||
{error}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, type CSSProperties, type ReactNode } from "react";
|
||||
import { ChevronDown, LockKeyhole, type LucideIcon } from "lucide-react";
|
||||
import { type CSSProperties, type ReactNode } from "react";
|
||||
import { LockKeyhole, type LucideIcon } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
InlineHelp,
|
||||
StageRail,
|
||||
i18nMessage,
|
||||
@@ -107,7 +108,6 @@ export function WorkflowStage({
|
||||
}) {
|
||||
const Icon = stage.icon;
|
||||
const locked = stage.state === "locked";
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const title = i18nMessage(stage.title);
|
||||
const description = i18nMessage(stage.description);
|
||||
const stateText = i18nMessage(stage.stateLabel);
|
||||
@@ -127,11 +127,16 @@ export function WorkflowStage({
|
||||
</div>
|
||||
{nextState && <div className="review-flow-stage-line" />}
|
||||
</div>
|
||||
<article
|
||||
className={`card card-collapsible review-flow-stage-card${locked ? " is-locked" : ""}${collapsed ? " is-collapsed" : ""}`}
|
||||
<Card
|
||||
as="article"
|
||||
collapsible
|
||||
persistCollapse={false}
|
||||
className={`review-flow-stage-card${locked ? " is-locked" : ""}`}
|
||||
headerClassName="review-flow-stage-header"
|
||||
bodyClassName="review-flow-stage-content"
|
||||
actionsClassName="review-flow-stage-header-actions"
|
||||
aria-disabled={locked || undefined}
|
||||
>
|
||||
<header className="card-header review-flow-stage-header">
|
||||
title={
|
||||
<h2>
|
||||
<span>{title}</span>
|
||||
{locked && (
|
||||
@@ -153,54 +158,22 @@ export function WorkflowStage({
|
||||
)}
|
||||
<InlineHelp>{description}</InlineHelp>
|
||||
</h2>
|
||||
<div className="review-flow-stage-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="card-collapse-toggle"
|
||||
aria-expanded={!collapsed}
|
||||
aria-label={
|
||||
collapsed
|
||||
? i18nMessage("i18n:govoplan-campaign.expand_value.be085ae4", { value0: title })
|
||||
: i18nMessage("i18n:govoplan-campaign.collapse_value.29095640", { value0: title })
|
||||
}
|
||||
title={
|
||||
collapsed
|
||||
? "i18n:govoplan-campaign.show_content.0528d8d2"
|
||||
: "i18n:govoplan-campaign.show_header_only.24afefca"
|
||||
}
|
||||
onClick={() => setCollapsed((value) => !value)}
|
||||
>
|
||||
<ChevronDown size={18} strokeWidth={2.4} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className="card-body review-flow-stage-content">{children}</div>
|
||||
{locked && (
|
||||
}
|
||||
afterBody={locked ? (
|
||||
<div className="review-flow-lock-message">
|
||||
<span className="review-flow-lock-icon">
|
||||
<LockKeyhole size={20} aria-hidden="true" />
|
||||
</span>
|
||||
<span>{lockReason}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
) : undefined}
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function WorkflowFact({ label, value }: { label: string; value: ReactNode }) {
|
||||
return (
|
||||
<div className="review-flow-fact">
|
||||
<span>{label}</span>
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function stateLabel(state: FlowState): string {
|
||||
switch (state) {
|
||||
case "complete":
|
||||
|
||||
Reference in New Issue
Block a user