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. ...@@ -53,6 +53,11 @@ paths are not valid delivery mechanisms.
Filters use a direct HeroUI `Surface`; lists remain unframed around the Filters use a direct HeroUI `Surface`; lists remain unframed around the
DataGrid. Cards are reserved for discrete business objects and analytical tools. 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 ## Component Rules
...@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools. ...@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
their documented `@heroui-pro/react/*` subpaths. their documented `@heroui-pro/react/*` subpaths.
- Routine text buttons are text-only; icon actions use Ant Design icons, - Routine text buttons are text-only; icon actions use Ant Design icons,
tooltips and accessible names. 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. - 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 - Date and date-time fields use HeroUI DatePicker and preserve the existing
string protocol at the page boundary. string protocol at the page boundary.
- Files use HeroUI Pro DropZone while preserving the page-owned `File` object. - 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. ...@@ -69,6 +81,9 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
- Routine success feedback is a fixed, auto-dismissing Toast. - Routine success feedback is a fixed, auto-dismissing Toast.
- Modal view mode uses one secondary Surface for the complete read-only field - Modal view mode uses one secondary Surface for the complete read-only field
group; edit mode removes that Surface and renders secondary fields directly. 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 - Empty results stay empty and request failures stay errors. UI code must never
populate dashboard or list surfaces with fabricated records or numbers. 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 { ActionBar } from "@heroui-pro/react/action-bar";
import { DataGrid } from "@heroui-pro/react/data-grid"; import { DataGrid } from "@heroui-pro/react/data-grid";
import { EmptyState } from "@heroui-pro/react/empty-state"; import { EmptyState } from "@heroui-pro/react/empty-state";
...@@ -240,18 +240,6 @@ export function DataTableTextPair({ primary, secondary, className = "" }) { ...@@ -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({ export function DataTablePersonIdentity({
name, name,
account, account,
...@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({ ...@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({
return ( return (
<Tooltip delay={0}> <Tooltip delay={0}>
<div <div
className={["flex min-w-0 items-center gap-2", className] className={["flex min-w-0 items-center", className]
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}
tabIndex={0} 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="grid min-w-0 gap-1">
<span className="min-w-0 truncate text-sm font-medium"> <span className="min-w-0 truncate text-sm font-medium">
{primaryText} {primaryText}
...@@ -347,7 +331,7 @@ export function DataTablePagination({ ...@@ -347,7 +331,7 @@ export function DataTablePagination({
{hasPageSizePicker ? ( {hasPageSizePicker ? (
<XinyuanAutocomplete <XinyuanAutocomplete
aria-label="每页条数" aria-label="每页条数"
className="w-24 shrink-0" className="w-32 shrink-0"
isDisabled={isDisabled} isDisabled={isDisabled}
options={pageSizeOptionsForPicker} options={pageSizeOptionsForPicker}
placeholder="每页条数" placeholder="每页条数"
......
...@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({ ...@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({
fullWidth = true, fullWidth = true,
inputValue, inputValue,
isDisabled = false, isDisabled = false,
isClearable = false,
isLoading = false, isLoading = false,
isOpen, isOpen,
isRequired = false, isRequired = false,
name, name,
onInputChange, onInputChange,
onClear,
onOpenChange, onOpenChange,
onSelectionChange, onSelectionChange,
options = [], options = [],
...@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({ ...@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({
(selectedOptions.length > 0 (selectedOptions.length > 0
? selectedOptions.map((option) => option.label).join("、") ? selectedOptions.map((option) => option.label).join("、")
: placeholder); : placeholder);
const hasSelection = Array.from(selectedKeyStringSet).some(Boolean);
const rootSelectionProps = const rootSelectionProps =
selectionMode === "multiple" ? { selectedKeys } : { selectedKey }; selectionMode === "multiple" ? { selectedKeys } : { selectedKey };
const listBoxSelectionProps = const listBoxSelectionProps =
...@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({ ...@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({
isOpen={isOpen} isOpen={isOpen}
isRequired={isRequired} isRequired={isRequired}
name={name} name={name}
onClear={onClear}
selectionMode={selectionMode} selectionMode={selectionMode}
variant={variant} variant={variant}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
...@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({ ...@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({
{resolvedValueText} {resolvedValueText}
</span> </span>
</Autocomplete.Value> </Autocomplete.Value>
{isClearable && hasSelection ? (
<Autocomplete.ClearButton
aria-label={`清除${ariaLabel || placeholder}`}
/>
) : null}
<Autocomplete.Indicator> <Autocomplete.Indicator>
<ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} /> <ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} />
</Autocomplete.Indicator> </Autocomplete.Indicator>
......
...@@ -13,7 +13,6 @@ import { ...@@ -13,7 +13,6 @@ import {
CircleStackIcon, CircleStackIcon,
DocumentTextIcon, DocumentTextIcon,
FolderOpenIcon, FolderOpenIcon,
InformationCircleIcon,
ShieldCheckIcon, ShieldCheckIcon,
ShieldExclamationIcon, ShieldExclamationIcon,
} from "@/components/AppIcons"; } from "@/components/AppIcons";
...@@ -29,7 +28,6 @@ import { ...@@ -29,7 +28,6 @@ import {
Chip, Chip,
ProgressBar as HeroProgressBar, ProgressBar as HeroProgressBar,
Spinner, Spinner,
Tooltip,
} from "@heroui/react"; } from "@heroui/react";
import { EmptyState } from "@heroui-pro/react/empty-state"; import { EmptyState } from "@heroui-pro/react/empty-state";
import { KPI } from "@heroui-pro/react/kpi"; import { KPI } from "@heroui-pro/react/kpi";
...@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) { ...@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) {
<Card.Title className="rc-module-title min-w-0 flex-1"> <Card.Title className="rc-module-title min-w-0 flex-1">
{title} {title}
</Card.Title> </Card.Title>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Header> </Card.Header>
) : null} ) : null}
<Card.Content className={className}>{children}</Card.Content> <Card.Content className={className}>{children}</Card.Content>
...@@ -65,9 +60,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) { ...@@ -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"> <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"> <Card.Title className="rc-module-title inline-flex min-w-0 items-center gap-1.5">
<span>{title}</span> <span>{title}</span>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Title> </Card.Title>
<span <span
aria-hidden="true" aria-hidden="true"
...@@ -85,23 +77,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) { ...@@ -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 }) { function MetricCard({ icon: Icon, label, value, hint, tone }) {
const numericValue = Number(value || 0); const numericValue = Number(value || 0);
...@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) { ...@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) {
<KPI.Title> <KPI.Title>
<span className="inline-flex items-center gap-1.5"> <span className="inline-flex items-center gap-1.5">
{label} {label}
{hint ? <TitleInfoTooltip label={label} text={hint} /> : null}
</span> </span>
</KPI.Title> </KPI.Title>
</KPI.Header> </KPI.Header>
...@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) { ...@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) {
return ( return (
<h2 className="rc-section-title inline-flex items-center gap-1.5"> <h2 className="rc-section-title inline-flex items-center gap-1.5">
<span>{title}</span> <span>{title}</span>
{description ? (
<TitleInfoTooltip label={title} text={description} />
) : null}
</h2> </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