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

fix: restore autocomplete clear behavior

parent 7a548ab8
...@@ -53,6 +53,8 @@ Native HeroUI 静态架构门禁已通过:`src/xinyuan-portable.css` 与 EChar ...@@ -53,6 +53,8 @@ Native HeroUI 静态架构门禁已通过:`src/xinyuan-portable.css` 与 EChar
驾驶舱模块标题沿用“审计项目管理”基准的 HeroUI `Card.Header` 左对齐横向结构;`Card.Title` 使用共享 `.rc-module-title`(16px/600、24px 行高、主题前景色)与 `min-w-0 flex-1 text-left`,操作或描述信息放在标题右侧,不通过页面私有 CSS 改写字号、颜色或字重。 驾驶舱模块标题沿用“审计项目管理”基准的 HeroUI `Card.Header` 左对齐横向结构;`Card.Title` 使用共享 `.rc-module-title`(16px/600、24px 行高、主题前景色)与 `min-w-0 flex-1 text-left`,操作或描述信息放在标题右侧,不通过页面私有 CSS 改写字号、颜色或字重。
普通业务单选 Autocomplete 在已有值时显示 HeroUI 官方 `Autocomplete.ClearButton`,由 Trigger 内部清除语义同步受控值且不会展开下拉;固定少量选项的页容量 Autocomplete 必须显式传 `showSearch={false}`、`isClearable={false}`,不显示搜索或删除图标。
台账详情预览使用固定功能标题,记录标题作为正文首项展示;编辑态在同一位置使用 HeroUI `Input` 修改顶层 `record.title`,不改变 `metadata`。标题展示尊重当前 View 的 `shortTitle` 配置。 台账详情预览使用固定功能标题,记录标题作为正文首项展示;编辑态在同一位置使用 HeroUI `Input` 修改顶层 `record.title`,不改变 `metadata`。标题展示尊重当前 View 的 `shortTitle` 配置。
详情弹窗结构固定为 HeroUI `Modal.Root > Modal.Backdrop > Modal.Container > Modal.Dialog`。字段控件直接来自 `@heroui/react`(`Input`、`TextArea`、`DatePicker`、`Autocomplete`)或 `@heroui-pro/react`(`DropZone`);预览态使用静态只读值,编辑态使用 `secondary` 控件,权限禁用字段不显示灰色伪控件。不得引入 Catalyst、Headless UI、原生表单控件或自制 Modal/字段外观。 详情弹窗结构固定为 HeroUI `Modal.Root > Modal.Backdrop > Modal.Container > Modal.Dialog`。字段控件直接来自 `@heroui/react`(`Input`、`TextArea`、`DatePicker`、`Autocomplete`)或 `@heroui-pro/react`(`DropZone`);预览态使用静态只读值,编辑态使用 `secondary` 控件,权限禁用字段不显示灰色伪控件。不得引入 Catalyst、Headless UI、原生表单控件或自制 Modal/字段外观。
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
- 列表顺序:页面 Tabs、结构化筛选 Surface、透明数据集标题与工具栏、DataGrid、Pagination。 - 列表顺序:页面 Tabs、结构化筛选 Surface、透明数据集标题与工具栏、DataGrid、Pagination。
- 筛选 Surface 最多首显三个结构化字段,溢出字段通过 HeroUI Disclosure 展开;不显示刷新或查询按钮,字段变化自动筛选,关键词 SearchField 约 300ms 延迟。 - 筛选 Surface 最多首显三个结构化字段,溢出字段通过 HeroUI Disclosure 展开;不显示刷新或查询按钮,字段变化自动筛选,关键词 SearchField 约 300ms 延迟。
- 业务列表使用 HeroUI Pagination,并在列表总数信息旁提供页容量选择器;当前标准选项为 `20 / 50 / 100 条/页`,默认值必须是选项中的一个(本应用默认 `20`)。切换页容量时回到第 1 页,并复用原有查询回调传递新的 `pageSize`。 - 业务列表使用 HeroUI Pagination,并在列表总数信息旁提供页容量选择器;当前标准选项为 `20 / 50 / 100 条/页`,默认值必须是选项中的一个(本应用默认 `20`)。切换页容量时回到第 1 页,并复用原有查询回调传递新的 `pageSize`。
- 页容量选择器使用 HeroUI Autocomplete 的受控单选结构;选项数量较少且固定时必须传 `showSearch={false}`,不渲染“输入关键词搜索”框,也不显示清除/删除图标(`isClearable={false}`)。选择器只能切换有效页容量,不能提交空值。 - 普通业务单选使用 HeroUI Autocomplete 的受控单选结构,已有值时显示官方 `Autocomplete.ClearButton`;页容量选择器属于例外,选项数量较少且固定时必须传 `showSearch={false}` 和 `isClearable={false}`,不渲染“输入关键词搜索”框,也不显示清除/删除图标。选择器只能切换有效页容量,不能提交空值。
- 页容量选择器保留加载禁用状态、当前值回显和 `20 / 50 / 100` 的顺序;页面不得把不存在的值(例如 `10`)作为初始 `pageSize` 或展示文本。 - 页容量选择器保留加载禁用状态、当前值回显和 `20 / 50 / 100` 的顺序;页面不得把不存在的值(例如 `10`)作为初始 `pageSize` 或展示文本。
- Dashboard/KPI 外层圆角 24px,DataGrid 20px,字段 12px,Modal 24px。 - Dashboard/KPI 外层圆角 24px,DataGrid 20px,字段 12px,Modal 24px。
- Dashboard 模块标题统一使用 HeroUI `Card.Header` 的左对齐横向结构(`w-full flex-row items-center gap-3 text-left`),`Card.Title` 使用共享 `.rc-module-title`(16px/600、24px 行高、主题前景色)与 `min-w-0 flex-1 text-left`;页面只承担布局类,不得自定义标题外观。 - Dashboard 模块标题统一使用 HeroUI `Card.Header` 的左对齐横向结构(`w-full flex-row items-center gap-3 text-left`),`Card.Title` 使用共享 `.rc-module-title`(16px/600、24px 行高、主题前景色)与 `min-w-0 flex-1 text-left`;页面只承担布局类,不得自定义标题外观。
......
...@@ -604,6 +604,7 @@ export function DataTablePagination({ ...@@ -604,6 +604,7 @@ export function DataTablePagination({
placeholder="每页条数" placeholder="每页条数"
selectedKey={String(pageSize)} selectedKey={String(pageSize)}
showSearch={false} showSearch={false}
isClearable={false}
valueText={`${pageSize} 条/页`} valueText={`${pageSize} 条/页`}
variant="secondary" variant="secondary"
onSelectionChange={(key) => { onSelectionChange={(key) => {
......
import { Check, ChevronDown, Close, FIELD_SUFFIX_ICON_STYLE, Search } from '@/components/AppIcons'; import { Check, ChevronDown, Close, FIELD_SUFFIX_ICON_STYLE, Search } from '@/components/AppIcons';
import { Autocomplete, Label, ListBox, SearchField, Spinner } from '@heroui/react'; import { Autocomplete, Label, ListBox, SearchField, Spinner } from '@heroui/react';
import XinyuanClearButton from './XinyuanClearButton';
function defaultFilter(textValue, inputValue) { function defaultFilter(textValue, inputValue) {
const query = String(inputValue || '').trim().toLocaleLowerCase(); const query = String(inputValue || '').trim().toLocaleLowerCase();
...@@ -16,7 +15,8 @@ export default function XinyuanAutocomplete({ ...@@ -16,7 +15,8 @@ export default function XinyuanAutocomplete({
onInputChange, onOpenChange, onSelectionChange, options = [], placeholder = '请选择', placement = 'bottom start', onInputChange, onOpenChange, onSelectionChange, options = [], placeholder = '请选择', placement = 'bottom start',
popoverHeader, renderOption, searchPlaceholder = '输入关键词搜索', selectedKey, selectedKeys, popoverHeader, renderOption, searchPlaceholder = '输入关键词搜索', selectedKey, selectedKeys,
selectionMode = 'single', shouldFilter = true, valueText, variant = 'secondary', selectionMode = 'single', shouldFilter = true, valueText, variant = 'secondary',
showSearch = true, isClearable = false, showSearch = true, isClearable = true,
onClear,
}) { }) {
const effectiveAriaLabel = ariaLabelProp || ariaLabel || placeholder; const effectiveAriaLabel = ariaLabelProp || ariaLabel || placeholder;
const selectedKeySet = selectionMode === 'multiple' const selectedKeySet = selectionMode === 'multiple'
...@@ -44,10 +44,10 @@ export default function XinyuanAutocomplete({ ...@@ -44,10 +44,10 @@ export default function XinyuanAutocomplete({
}; };
return ( return (
<Autocomplete {...rootSelectionProps} aria-label={effectiveAriaLabel} className={className} fullWidth={fullWidth} isDisabled={isDisabled} isOpen={isOpen} isRequired={isRequired} name={name} selectionMode={selectionMode} variant={variant} onOpenChange={onOpenChange} onSelectionChange={onSelectionChange}> <Autocomplete {...rootSelectionProps} aria-label={effectiveAriaLabel} className={className} fullWidth={fullWidth} isDisabled={isDisabled} isOpen={isOpen} isRequired={isRequired} name={name} onClear={onClear} selectionMode={selectionMode} variant={variant} onOpenChange={onOpenChange} onSelectionChange={onSelectionChange}>
<Autocomplete.Trigger> <Autocomplete.Trigger>
<Autocomplete.Value><span className="block min-w-0 truncate" title={resolvedValueText}>{resolvedValueText}</span></Autocomplete.Value> <Autocomplete.Value><span className="block min-w-0 truncate" title={resolvedValueText}>{resolvedValueText}</span></Autocomplete.Value>
{isClearable && selectionMode === 'single' && selectedKey ? <XinyuanClearButton ariaLabel="清空选择" onPress={() => onSelectionChange?.('')} /> : null} {isClearable && selectionMode === 'single' && selectedKey !== null && selectedKey !== undefined && selectedKey !== '' ? <Autocomplete.ClearButton aria-label="清空选择" /> : null}
<Autocomplete.Indicator><ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} /></Autocomplete.Indicator> <Autocomplete.Indicator><ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} /></Autocomplete.Indicator>
</Autocomplete.Trigger> </Autocomplete.Trigger>
<Autocomplete.Popover placement={placement}> <Autocomplete.Popover placement={placement}>
......
...@@ -2,6 +2,10 @@ import { Button } from '@heroui/react'; ...@@ -2,6 +2,10 @@ import { Button } from '@heroui/react';
import { Close, FIELD_SUFFIX_ICON_STYLE } from './AppIcons'; import { Close, FIELD_SUFFIX_ICON_STYLE } from './AppIcons';
export default function XinyuanClearButton({ ariaLabel, onPress, isDisabled = false }) { export default function XinyuanClearButton({ ariaLabel, onPress, isDisabled = false }) {
const stopTrigger = (event) => {
event.stopPropagation();
};
return ( return (
<Button <Button
aria-label={ariaLabel} aria-label={ariaLabel}
...@@ -10,8 +14,12 @@ export default function XinyuanClearButton({ ariaLabel, onPress, isDisabled = fa ...@@ -10,8 +14,12 @@ export default function XinyuanClearButton({ ariaLabel, onPress, isDisabled = fa
isIconOnly isIconOnly
size="sm" size="sm"
variant="ghost" variant="ghost"
onClick={(event) => event.stopPropagation()} onPointerDown={stopTrigger}
onPress={onPress} onMouseDown={stopTrigger}
onClick={(event) => {
stopTrigger(event);
onPress?.();
}}
> >
<Close <Close
className="inline-flex size-3.5 items-center justify-center leading-none [&>svg]:m-0 [&>svg]:block [&>svg]:size-3.5" className="inline-flex size-3.5 items-center justify-center leading-none [&>svg]:m-0 [&>svg]:block [&>svg]:size-3.5"
......
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