Commit a4933ad8 authored by Andy-bubu's avatar Andy-bubu

refine material center controls and surfaces

parent 811086b9
......@@ -53,6 +53,11 @@ paths are not valid delivery mechanisms.
Filters use a direct HeroUI `Surface`; lists remain unframed around the
DataGrid. Cards are reserved for discrete business objects and analytical tools.
Material archive filters use an explicit-submit rhythm: draft fields stay local
until `查询`, with a compact secondary reset icon button immediately before the
primary red query button and the native Disclosure `展开` control to its right.
Reset and category Tab changes submit their corresponding complete filter state;
typing alone never reloads the list.
## Component Rules
......@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
their documented `@heroui-pro/react/*` subpaths.
- Routine text buttons are text-only; icon actions use Ant Design icons,
tooltips and accessible names.
- Business page, module and field titles stay visually clean without adjacent
information-tip icons; reserve Tooltips for actual icon actions and controls.
- Dropdown-like selection uses HeroUI Autocomplete/ListBox contracts.
- Form checkboxes on light surfaces use HeroUI `Checkbox` with
`variant="secondary"` so the unchecked control has a visible neutral fill
against white surfaces. Keep the official `Checkbox.Content` ->
`Checkbox.Control` -> `Checkbox.Indicator` compound structure; selected
controls retain the brand accent, and field values/handlers remain unchanged.
- Date and date-time fields use HeroUI DatePicker and preserve the existing
string protocol at the page boundary.
- Files use HeroUI Pro DropZone while preserving the page-owned `File` object.
......@@ -69,6 +81,9 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
- Routine success feedback is a fixed, auto-dismissing Toast.
- Modal view mode uses one secondary Surface for the complete read-only field
group; edit mode removes that Surface and renders secondary fields directly.
- Detail view modal footers expose only the contextual edit action; destructive
material actions stay in the DataGrid row menu or batch ActionBar to reduce
accidental deletion from a read-only view.
- Empty results stay empty and request failures stay errors. UI code must never
populate dashboard or list surfaces with fabricated records or numbers.
......
import { Avatar, Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react";
import { Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react";
import { ActionBar } from "@heroui-pro/react/action-bar";
import { DataGrid } from "@heroui-pro/react/data-grid";
import { EmptyState } from "@heroui-pro/react/empty-state";
......@@ -240,18 +240,6 @@ export function DataTableTextPair({ primary, secondary, className = "" }) {
);
}
function getPersonFallback(name) {
const normalized = String(name ?? "").trim();
if (!normalized || normalized === "-") {
return "?";
}
const compact = normalized.replace(/\s+/g, "");
if (/^[\u3400-\u9fff]/.test(compact)) {
return Array.from(compact)[0];
}
return Array.from(compact).slice(0, 2).join("").toUpperCase();
}
export function DataTablePersonIdentity({
name,
account,
......@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({
return (
<Tooltip delay={0}>
<div
className={["flex min-w-0 items-center gap-2", className]
className={["flex min-w-0 items-center", className]
.filter(Boolean)
.join(" ")}
tabIndex={0}
>
<Avatar size="sm" variant="soft" className="shrink-0">
{avatar ? <Avatar.Image alt={primaryText} src={avatar} /> : null}
<Avatar.Fallback>{getPersonFallback(primaryText)}</Avatar.Fallback>
</Avatar>
<span className="grid min-w-0 gap-1">
<span className="min-w-0 truncate text-sm font-medium">
{primaryText}
......@@ -347,7 +331,7 @@ export function DataTablePagination({
{hasPageSizePicker ? (
<XinyuanAutocomplete
aria-label="每页条数"
className="w-24 shrink-0"
className="w-32 shrink-0"
isDisabled={isDisabled}
options={pageSizeOptionsForPicker}
placeholder="每页条数"
......
......@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({
fullWidth = true,
inputValue,
isDisabled = false,
isClearable = false,
isLoading = false,
isOpen,
isRequired = false,
name,
onInputChange,
onClear,
onOpenChange,
onSelectionChange,
options = [],
......@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({
(selectedOptions.length > 0
? selectedOptions.map((option) => option.label).join("、")
: placeholder);
const hasSelection = Array.from(selectedKeyStringSet).some(Boolean);
const rootSelectionProps =
selectionMode === "multiple" ? { selectedKeys } : { selectedKey };
const listBoxSelectionProps =
......@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({
isOpen={isOpen}
isRequired={isRequired}
name={name}
onClear={onClear}
selectionMode={selectionMode}
variant={variant}
onOpenChange={onOpenChange}
......@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({
{resolvedValueText}
</span>
</Autocomplete.Value>
{isClearable && hasSelection ? (
<Autocomplete.ClearButton
aria-label={`清除${ariaLabel || placeholder}`}
/>
) : null}
<Autocomplete.Indicator>
<ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} />
</Autocomplete.Indicator>
......
......@@ -13,7 +13,6 @@ import {
CircleStackIcon,
DocumentTextIcon,
FolderOpenIcon,
InformationCircleIcon,
ShieldCheckIcon,
ShieldExclamationIcon,
} from "@/components/AppIcons";
......@@ -29,7 +28,6 @@ import {
Chip,
ProgressBar as HeroProgressBar,
Spinner,
Tooltip,
} from "@heroui/react";
import { EmptyState } from "@heroui-pro/react/empty-state";
import { KPI } from "@heroui-pro/react/kpi";
......@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) {
<Card.Title className="rc-module-title min-w-0 flex-1">
{title}
</Card.Title>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Header>
) : null}
<Card.Content className={className}>{children}</Card.Content>
......@@ -65,9 +60,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
<div className="flex w-full min-w-0 items-center justify-between gap-4">
<Card.Title className="rc-module-title inline-flex min-w-0 items-center gap-1.5">
<span>{title}</span>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Title>
<span
aria-hidden="true"
......@@ -85,23 +77,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
);
}
function TitleInfoTooltip({ label, text }) {
return (
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={`${label}说明`}
className="rc-title-info-trigger"
type="button"
variant="ghost"
>
<InformationCircleIcon size={14} />
</Button>
<Tooltip.Content>{text}</Tooltip.Content>
</Tooltip>
);
}
function MetricCard({ icon: Icon, label, value, hint, tone }) {
const numericValue = Number(value || 0);
......@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) {
<KPI.Title>
<span className="inline-flex items-center gap-1.5">
{label}
{hint ? <TitleInfoTooltip label={label} text={hint} /> : null}
</span>
</KPI.Title>
</KPI.Header>
......@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) {
return (
<h2 className="rc-section-title inline-flex items-center gap-1.5">
<span>{title}</span>
{description ? (
<TitleInfoTooltip label={title} text={description} />
) : null}
</h2>
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment