mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-01 15:32:50 +08:00
refactor(approval): the filter tabs come from the library, the title moves into the list column
The pane drew its own filter row — bare buttons with a hand-rolled 2px bottom border and a hardcoded #212121 for the selected one. That is the library's Tabs in `variant="neutral"`, so it now renders one, with `divider={false}` because the list column already draws its own edge. The section title moves out of the page header and into the list column via a new `listHeader` prop. It used to sit above the whole pane as a full-width strip, which pushed the detail column down and left it running short of its container; with the title inside the list column, the detail column runs the full height. Both titles are put on the same line box (text-base / leading-8 at pt-4) so they read as one row across the two columns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,12 +31,12 @@ export function DetailHeader({ title, status, instanceStatus, scope, serialNo, s
|
||||
}) {
|
||||
return (
|
||||
// Pinned to the top of the scrolling detail pane so the title/status/serial stay visible while the body scrolls.
|
||||
<div className="sticky top-0 z-10 -mx-5 mb-5 border-b border-fill-2 bg-white px-5 pb-3 pt-3">
|
||||
<div className="sticky top-0 z-10 -mx-5 mb-5 border-b border-fill-2 bg-white px-5 pb-3 pt-4">
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Compact-only back control — sits to the left of the detail title, split by a short vertical divider.
|
||||
h-6 matches the title line so the arrow centers against it under items-start. */}
|
||||
h-8 matches the title line so the arrow centers against it under items-start. */}
|
||||
{onBack && (
|
||||
<div className="flex h-6 shrink-0 items-center gap-3 md:hidden">
|
||||
<div className="flex h-8 shrink-0 items-center gap-3 md:hidden">
|
||||
<button type="button" onClick={onBack} aria-label={localize("com_approval_back")} className="flex items-center text-text-3">
|
||||
<Outlined.ArrowLeft className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -46,7 +46,9 @@ export function DetailHeader({ title, status, instanceStatus, scope, serialNo, s
|
||||
{/* Title + serial share one column so the serial line aligns with the title, not the back arrow. */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="min-w-0 flex-1 text-[16px] font-semibold text-text-primary leading-snug">{title || "--"}</h3>
|
||||
{/* Same line box as the list column's section heading (text-base/leading-8 at pt-4),
|
||||
so both titles sit on one line across the two columns. */}
|
||||
<h3 className="min-w-0 flex-1 text-base font-semibold leading-8 text-text-primary">{title || "--"}</h3>
|
||||
<StatusBadge status={status} instanceStatus={instanceStatus} scope={scope} localize={localize} />
|
||||
</div>
|
||||
<p className="mt-1.5 text-[13px] text-text-3">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Tabs } from "@bisheng/ui";
|
||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import {
|
||||
decideApprovalTaskApi,
|
||||
getApprovalInstanceDetailApi,
|
||||
@@ -40,6 +41,12 @@ export interface ApprovalPaneProps {
|
||||
setCompactView: (view: "list" | "detail") => void;
|
||||
/** Fired after any action that can change the pending-task count. */
|
||||
onPendingCountMaybeChanged?: () => void;
|
||||
/**
|
||||
* Rendered at the top of the LIST column, not above the whole pane — the detail
|
||||
* column then runs the full height of its container instead of starting below a
|
||||
* full-width header strip.
|
||||
*/
|
||||
listHeader?: ReactNode;
|
||||
}
|
||||
|
||||
export function ApprovalPane({
|
||||
@@ -49,6 +56,7 @@ export function ApprovalPane({
|
||||
compactView,
|
||||
setCompactView,
|
||||
onPendingCountMaybeChanged,
|
||||
listHeader,
|
||||
}: ApprovalPaneProps) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
@@ -292,26 +300,32 @@ export function ApprovalPane({
|
||||
<>
|
||||
{/* Left list — hidden in compact detail view */}
|
||||
<div className={cn("flex min-h-0 flex-col border-r border-fill-2 bg-white", compactView === "detail" && "hidden md:flex")}>
|
||||
<div className="flex gap-2 px-3 pt-3 pb-2">
|
||||
{activeTab === "my_tasks"
|
||||
? (["pending_me", "processed"] as TaskFilter[]).map((f) => (
|
||||
<button key={f} type="button"
|
||||
className={cn(
|
||||
"h-auto whitespace-nowrap rounded-none border-0 border-b-2 border-transparent bg-transparent px-2 py-[5px] text-sm leading-none transition-colors fine-pointer:hover:text-text-1",
|
||||
taskFilter === f ? "border-[#212121] text-text-1 font-medium" : "text-text-3 font-normal")}
|
||||
onClick={() => setTaskFilter(f)}>
|
||||
{f === "pending_me" ? localize("com_approval_task_filter_pending") : localize("com_approval_task_filter_processed")}
|
||||
</button>
|
||||
))
|
||||
: (["in_progress", "completed"] as RequestsFilter[]).map((f) => (
|
||||
<button key={f} type="button"
|
||||
className={cn(
|
||||
"h-auto whitespace-nowrap rounded-none border-0 border-b-2 border-transparent bg-transparent px-2 py-[5px] text-sm leading-none transition-colors fine-pointer:hover:text-text-1",
|
||||
requestsFilter === f ? "border-[#212121] text-text-1 font-medium" : "text-text-3 font-normal")}
|
||||
onClick={() => setRequestsFilter(f)}>
|
||||
{f === "in_progress" ? localize("com_approval_status_pending") : localize("com_approval_tab_completed")}
|
||||
</button>
|
||||
))}
|
||||
{listHeader}
|
||||
{/* 待我处理/已处理 · 审批中/已完成 — design-system Tabs (line type, ink
|
||||
variant: the surrounding column already draws its own edges, so no
|
||||
divider, and an accent here would fight the status badges in the list). */}
|
||||
<div className="px-3 pt-3 pb-2">
|
||||
<Tabs
|
||||
size="medium"
|
||||
variant="neutral"
|
||||
divider={false}
|
||||
items={
|
||||
activeTab === "my_tasks"
|
||||
? [
|
||||
{ key: "pending_me", label: localize("com_approval_task_filter_pending") },
|
||||
{ key: "processed", label: localize("com_approval_task_filter_processed") },
|
||||
]
|
||||
: [
|
||||
{ key: "in_progress", label: localize("com_approval_status_pending") },
|
||||
{ key: "completed", label: localize("com_approval_tab_completed") },
|
||||
]
|
||||
}
|
||||
activeKey={activeTab === "my_tasks" ? taskFilter : requestsFilter}
|
||||
onChange={(key) => {
|
||||
if (activeTab === "my_tasks") setTaskFilter(key as TaskFilter);
|
||||
else setRequestsFilter(key as RequestsFilter);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Search box — reuse the app-center search field style (always expanded + clear button) */}
|
||||
|
||||
@@ -203,22 +203,22 @@ export default function SettingsPage() {
|
||||
const paneTitleClass = "text-base font-semibold leading-8 text-text-1";
|
||||
|
||||
const content = isApproval ? (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<h2 className={cn("hidden shrink-0 px-5 pt-4 md:block", paneTitleClass)}>
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
<div
|
||||
className="grid min-h-0 flex-1 grid-cols-1 md:grid-cols-[300px_minmax(0,1fr)]"
|
||||
>
|
||||
<ApprovalPane
|
||||
open
|
||||
activeTab={approvalTab}
|
||||
target={deepLink ?? undefined}
|
||||
compactView={compactView}
|
||||
setCompactView={setCompactView}
|
||||
onPendingCountMaybeChanged={refreshCount}
|
||||
/>
|
||||
</div>
|
||||
// The title rides inside the list column (via listHeader), so the detail column
|
||||
// runs the full height of the content panel instead of starting below a header strip.
|
||||
<div className="grid min-h-0 flex-1 grid-cols-1 md:grid-cols-[300px_minmax(0,1fr)]">
|
||||
<ApprovalPane
|
||||
open
|
||||
activeTab={approvalTab}
|
||||
target={deepLink ?? undefined}
|
||||
compactView={compactView}
|
||||
setCompactView={setCompactView}
|
||||
onPendingCountMaybeChanged={refreshCount}
|
||||
listHeader={
|
||||
<h2 className={cn("hidden shrink-0 px-3 pt-4 md:block", paneTitleClass)}>
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : isNotifications ? (
|
||||
// Same single-block shell as 账号信息 / 通用: one padded pane with a centered
|
||||
|
||||
Reference in New Issue
Block a user