iter: d01 新增下拉菜单 menu 组件(弹层/键盘导航/reduced-motion)

This commit is contained in:
ui-arch-bot
2026-10-23 16:46:00 +08:00
parent f9324ddc6a
commit 84277a744e
11 changed files with 357 additions and 9 deletions
+8
View File
@@ -56,6 +56,14 @@
"acceptance": "TextFieldProps / TextareaProps 新增可选 disabled(boolean | Signal<boolean>),禁用时不响应输入、置灰 + not-allowed 光标;demo 禁用示例区补一个信号绑定输入框;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "与 c01 同模式:disabled 透传 h() 即得布尔/信号双支持;禁用态 CSS 追加 .ua-input / .ua-textarea;demo 复用 locked 信号联动开关与输入框"
},
{
"id": "d01",
"title": "新增下拉菜单 menu 组件",
"motivation": "工具栏/行操作场景缺省可折叠的动作集合,select 只能选值不能派发动作;可复用自绘弹层与微动画既有模式",
"acceptance": "packages/skin/ui 新增 menu.ts:触发器 + 自绘弹层,items 支持 disabled / danger / divider,点条目 / 点外部 / Esc 关闭,↑↓ 环形导航(禁用项跳过),Enter/Space 选中,Esc 收起后焦点回触发器;只消费 --ua-* 令牌并带 prefers-reduced-motion 兜底;导出 + manifest + 文档 + llms-full.txt + demo 同步;npm run check 全绿",
"status": "done",
"notes": "API menu({ label, items, variant, align, onSelect });MenuItem = { value, label, disabled?, danger?, divider? };弹层 ua-open 同时挂在根与弹层(根管箭头旋转、弹层管出入场),危险条目取 danger 色;键盘 ↑↓ 环形导航 + Esc 回焦触发器;demo 新增 sec-menu 双示例(含禁用/危险/分隔线与右对齐)"
}
]
}
+51 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-23T08:10:07.974Z",
"generatedAt": "2026-10-23T08:45:45.013Z",
"repoRoot": ".",
"packages": [
{
@@ -130,7 +130,7 @@
{
"name": "@ui-arch/ui",
"type": "skin-dom",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"tags": [
"components",
"dom",
@@ -148,6 +148,7 @@
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画",
"menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭",
"radioGroup(props):单选组,options + value 信号双向绑定",
"card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger",
@@ -172,6 +173,8 @@
"SwitchProps",
"SelectProps",
"RadioGroupProps",
"MenuItem",
"MenuProps",
"OptionItem",
"CardProps",
"BadgeProps",
@@ -198,7 +201,7 @@
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
],
"docs": {
"intro": "20 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"intro": "21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
@@ -421,6 +424,51 @@
"选中项在弹层中用淡色弱化标记,不抢视觉焦点"
]
},
{
"id": "menu",
"title": "下拉菜单 Menu",
"demo": "sec-menu",
"props": [
{
"n": "label",
"t": "string",
"r": 1,
"d": "触发器文案"
},
{
"n": "items",
"t": "MenuItem[]",
"r": 1,
"d": "菜单条目:{ value, label, disabled?, danger?, divider? }"
},
{
"n": "variant",
"t": "'primary' | 'ghost'",
"d": "触发器视觉强度;缺省 ghost",
"df": "ghost"
},
{
"n": "align",
"t": "'start' | 'end'",
"d": "弹层对齐方向;缺省 start(与触发器左对齐)",
"df": "start"
},
{
"n": "onSelect",
"t": "(value: string) => void",
"k": 1,
"d": "选中回调,禁用项不触发"
}
],
"example": "menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })",
"steps": [
"准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组",
"传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'",
"点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)",
"键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过",
"靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true"
]
},
{
"id": "radioGroup",
"title": "单选组 RadioGroup",
+1
View File
@@ -5,3 +5,4 @@
{"time":"2026-10-23T16:03:46+08:00","id":"b05","result":"done","summary":"文档站核查确认已 manifest 驱动;补齐默认值信息量缺口:CLI propsTable 新增默认值列(df 字段)+ llms-full 默认标注,16 处默认值提取;check 全绿"}
{"time":"2026-10-23T16:08:17+08:00","id":"c01","result":"done","summary":"button/checkbox/switchButton 补 disabled(boolean|Signal),复用 h() 布尔/信号既有语义零辅助代码;CSS 通用禁用态 + demo 信号联动示例;check 全绿"}
{"time":"2026-10-23T16:10:31+08:00","id":"c02","result":"done","summary":"textField/textarea 补 disabled(boolean|Signal),与 c01 同模式;禁用态 CSS 覆盖输入类;demo locked 信号联动;check 全绿"}
{"time": "2026-10-23T16:55:00+08:00", "id": "d01", "result": "done", "summary": "新增下拉菜单 menu:触发器 + 自绘弹层,disabled/danger/divider 条目,↑↓ 环形导航 + Esc 回焦,reduced-motion 兜底;manifest/llms-full/demo 同步,check 全绿"}
+35
View File
@@ -8,6 +8,7 @@ import {
dataTable,
form,
formItem,
menu,
modal,
pagination,
progress,
@@ -71,6 +72,9 @@ const formResult = signal('');
// —— 分页联动状态:dataTable 与独立 pagination 共用同一页码信号 ——
const tablePage = signal(1);
// —— 菜单选中结果:演示 onSelect 回调 ——
const menuPick = signal('');
// —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled ——
const locked = signal(true);
@@ -191,6 +195,37 @@ const app = h(
h('div', { class: 'row' }, button({ label: '知道了', onClick: () => modalOpen(false) }), badge({ label: 'modal', variant: 'success' })),
),
),
section(
'sec-menu',
'下拉菜单',
'menu 触发器 + 自绘弹层:条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部或 Esc 收起。',
h(
'div',
{ class: 'row' },
menu({
label: '文件操作',
items: [
{ value: 'rename', label: '重命名' },
{ value: 'copy', label: '创建副本' },
{ value: 'archive', label: '归档', disabled: true },
{ divider: true, value: '', label: '' },
{ value: 'delete', label: '删除', danger: true },
],
onSelect: (v) => menuPick(v),
}),
menu({
label: '右对齐',
align: 'end',
variant: 'primary',
items: [
{ value: 'profile', label: '个人资料' },
{ value: 'settings', label: '偏好设置' },
],
onSelect: (v) => menuPick(v),
}),
h('span', { class: 'out' }, () => (menuPick() ? `已选:${menuPick()}` : '未选择')),
),
),
section(
'sec-table',
'数据表格',
+29 -2
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-23T08:10:07.974Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-23T08:45:45.013Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -40,7 +40,7 @@
## @ui-arch/ui(skin-dom)v0.1.0
20 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
### button — 按钮 Button
@@ -182,6 +182,32 @@ const city = signal(''); select({ options: [{ value: 'bj', label: '北京' }], v
现场演示:apps/docs/demo.html#sec-form
### menu — 下拉菜单 Menu
参数:
- label(必填):string —— 触发器文案
- items(必填):MenuItem[] —— 菜单条目:{ value, label, disabled?, danger?, divider? }
- variant(默认 ghost):'primary' | 'ghost' —— 触发器视觉强度;缺省 ghost
- align(默认 start):'start' | 'end' —— 弹层对齐方向;缺省 start(与触发器左对齐)
- onSelect(回调):(value: string) => void —— 选中回调,禁用项不触发
示例:
```
menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })
```
手把手:
1. 准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组
2. 传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'
3. 点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)
4. 键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过
5. 靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true
现场演示:apps/docs/demo.html#sec-menu
### radioGroup — 单选组 RadioGroup
参数:
@@ -517,6 +543,7 @@ API 速览:
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭
- radioGroup(props):单选组,options + value 信号双向绑定
- card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger
+2 -1
View File
@@ -45,7 +45,7 @@
## @ui-arch/ui (v0.1.0)
- 类型: skin-dom · 目录: packages/skin/ui
- 描述: DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。
- 描述: DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。
- API:
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
@@ -53,6 +53,7 @@
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭
- radioGroup(props):单选组,options + value 信号双向绑定
- card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger
+1
View File
@@ -5,6 +5,7 @@ export { textarea } from './textarea.js';
export { checkbox } from './checkbox.js';
export { switchButton } from './switch.js';
export { select } from './select.js';
export { menu } from './menu.js';
export { radioGroup } from './radioGroup.js';
export { card } from './card.js';
export { badge } from './badge.js';
+82
View File
@@ -0,0 +1,82 @@
import { effect, h, signal } from '@ui-arch/frame';
import type { MenuItem, MenuProps } from './types.js';
/*** 下拉菜单:触发器 + 自绘弹层,项支持禁用 / 危险 / 分隔线;点项 / 点外部 / Esc 关闭,↑↓ 环形导航 ***/
export function menu(props: MenuProps): HTMLElement {
// —— 状态集中声明 ——
const open = signal(false); // 弹层开合
const focusables: HTMLButtonElement[] = []; // 可聚焦项(跳过分隔线与禁用项),供键盘导航
const pick = (item: MenuItem): void => {
open(false); // 选中即收起
props.onSelect?.(item.value);
};
// —— 菜单项:分隔线走独立分支,其余为原生 button 保证键盘可达 ——
const rows = props.items.map((item) => {
if (item.divider) return h('div', { class: 'ua-menu-divider', role: 'separator' });
const btn = h(
'button',
{
type: 'button',
class: `ua-menu-item${item.danger ? ' is-danger' : ''}`,
role: 'menuitem',
disabled: item.disabled ? true : undefined,
onClick: () => pick(item),
},
item.label,
) as HTMLButtonElement;
if (!item.disabled) focusables.push(btn); // 禁用项不参与导航
return btn;
});
const trigger = h(
'button',
{
type: 'button',
class: `ua-btn ua-btn-${props.variant ?? 'ghost'} ua-menu-trigger`,
'aria-haspopup': 'menu',
'aria-expanded': () => String(open()),
onClick: () => open(!open()),
},
props.label,
h('span', { class: 'ua-menu-caret' }, '▾'),
);
const pop = h(
'div',
{
class: () => `ua-menu-pop${open() ? ' ua-open' : ''}${props.align === 'end' ? ' is-end' : ''}`,
role: 'menu',
},
...rows,
);
const root = h('div', { class: () => `ua-menu${open() ? ' ua-open' : ''}` }, trigger, pop);
root.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
open(false);
trigger.focus(); // 收起后焦点回到触发器,键盘流不断
return;
}
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return; // 卫语句:只接管上下键
e.preventDefault();
if (!open()) {
open(true);
focusables[0]?.focus(); // 首次按下即展开并进首项
return;
}
const at = focusables.indexOf(document.activeElement as HTMLButtonElement);
const step = e.key === 'ArrowDown' ? 1 : -1;
focusables[(at + step + focusables.length) % focusables.length]?.focus(); // 环形移动
});
document.addEventListener('click', (e) => {
if (root.contains(e.target as Node)) return; // 卫语句:组件内点击不处理
open(false); // 点外部收起
});
effect(() => open()); // 建立依赖,保证开合信号生效
return root;
}
+18
View File
@@ -77,6 +77,24 @@ export interface RadioGroupProps {
onChange?: (value: string) => void; // 变更回调
}
/*** 菜单项:divider 渲染成分隔线,其余为可选条目 ***/
export interface MenuItem {
value: string; // 条目标识(经 onSelect 透传)
label: string; // 条目文案
disabled?: boolean; // 禁用该条目:置灰且不参与键盘导航
danger?: boolean; // 危险操作条目:文案取 danger 语义色
divider?: boolean; // 该条目渲染为分隔线(忽略 value / label)
}
/*** 下拉菜单入参 ***/
export interface MenuProps {
label: string; // 触发器文案
items: MenuItem[]; // 菜单条目列表
variant?: 'primary' | 'ghost'; // 触发器视觉强度;缺省 ghost
align?: 'start' | 'end'; // 弹层对齐方向;缺省 start(与触发器左对齐)
onSelect?: (value: string) => void; // 选中回调(禁用项不触发)
}
/*** 多行输入入参 ***/
export interface TextareaProps {
placeholder?: string; // 占位文案
+50 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@ui-arch/ui",
"type": "skin-dom",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / card / badge / alert / toast / tooltip / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"tags": [
"components",
"dom",
@@ -19,6 +19,7 @@
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画",
"menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭",
"radioGroup(props):单选组,options + value 信号双向绑定",
"card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger",
@@ -43,6 +44,8 @@
"SwitchProps",
"SelectProps",
"RadioGroupProps",
"MenuItem",
"MenuProps",
"OptionItem",
"CardProps",
"BadgeProps",
@@ -69,7 +72,7 @@
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
],
"docs": {
"intro": "20 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"intro": "21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
@@ -292,6 +295,51 @@
"选中项在弹层中用淡色弱化标记,不抢视觉焦点"
]
},
{
"id": "menu",
"title": "下拉菜单 Menu",
"demo": "sec-menu",
"props": [
{
"n": "label",
"t": "string",
"r": 1,
"d": "触发器文案"
},
{
"n": "items",
"t": "MenuItem[]",
"r": 1,
"d": "菜单条目:{ value, label, disabled?, danger?, divider? }"
},
{
"n": "variant",
"t": "'primary' | 'ghost'",
"d": "触发器视觉强度;缺省 ghost",
"df": "ghost"
},
{
"n": "align",
"t": "'start' | 'end'",
"d": "弹层对齐方向;缺省 start(与触发器左对齐)",
"df": "start"
},
{
"n": "onSelect",
"t": "(value: string) => void",
"k": 1,
"d": "选中回调,禁用项不触发"
}
],
"example": "menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })",
"steps": [
"准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组",
"传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'",
"点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)",
"键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过",
"靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true"
]
},
{
"id": "radioGroup",
"title": "单选组 RadioGroup",
+79
View File
@@ -308,6 +308,82 @@
background: color-mix(in srgb, var(--ua-color-accent) 14%, transparent);
}
/* —— 下拉菜单 —— */
.ua-menu {
position: relative;
display: inline-block;
}
.ua-menu-trigger {
gap: 6px;
}
.ua-menu-caret {
font-size: 10px;
opacity: 0.7;
transition: transform 0.2s;
}
.ua-menu.ua-open .ua-menu-caret {
transform: rotate(180deg);
}
.ua-menu-pop {
position: absolute;
z-index: 20;
top: calc(100% + 4px);
left: 0;
min-width: 160px;
display: flex;
flex-direction: column;
gap: 2px;
padding: 4px;
border: 1px solid var(--ua-color-border);
border-radius: var(--ua-radius-md);
background: var(--ua-color-surface);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
opacity: 0;
transform: translateY(-4px);
visibility: hidden;
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
}
.ua-menu-pop.is-end {
left: auto;
right: 0;
}
.ua-menu-pop.ua-open {
opacity: 1;
transform: none;
visibility: visible;
}
.ua-menu-item {
display: block;
width: 100%;
text-align: left;
padding: 6px 10px;
border: none;
border-radius: var(--ua-radius-sm);
background: none;
color: var(--ua-color-text);
font-size: var(--ua-font-size-md);
cursor: pointer;
transition: background 0.12s;
}
.ua-menu-item:hover:not(:disabled) {
background: color-mix(in srgb, var(--ua-color-accent) 8%, transparent);
}
.ua-menu-item.is-danger {
color: var(--ua-color-danger);
}
.ua-menu-item.is-danger:hover:not(:disabled) {
background: color-mix(in srgb, var(--ua-color-danger) 10%, transparent);
}
.ua-menu-item:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.ua-menu-divider {
height: 1px;
margin: 4px 2px;
background: var(--ua-color-border);
}
/* —— 多行输入 —— */
.ua-textarea {
width: 100%;
@@ -795,6 +871,9 @@
.ua-select-arrow,
.ua-select-pop,
.ua-option,
.ua-menu-pop,
.ua-menu-caret,
.ua-menu-item,
.ua-switch-track,
.ua-switch-thumb,
.ua-tab,