From df2fb34e635b2779fa44f0b3c8fa05d2b69cc975 Mon Sep 17 00:00:00 2001 From: ui-arch-bot Date: Fri, 23 Oct 2026 16:47:56 +0800 Subject: [PATCH] =?UTF-8?q?iter:=20d03=20=E6=96=B0=E5=A2=9E=E6=8A=98?= =?UTF-8?q?=E5=8F=A0=E9=9D=A2=E6=9D=BF=20accordion=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 ++++ .ua/index.json | 47 +++++++++++++++++++-- .ua/iterations.log | 1 + apps/demo/main.ts | 15 +++++++ llms-full.txt | 30 +++++++++++++- llms.txt | 3 +- packages/skin/ui/src/accordion.ts | 55 +++++++++++++++++++++++++ packages/skin/ui/src/index.ts | 1 + packages/skin/ui/src/types.ts | 16 ++++++++ packages/skin/ui/ua.manifest.json | 45 +++++++++++++++++++- packages/skin/ui/ui.css | 68 +++++++++++++++++++++++++++++++ 11 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 packages/skin/ui/src/accordion.ts diff --git a/.ua/backlog.json b/.ua/backlog.json index 13ba2aa..ca328b1 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -72,6 +72,14 @@ "acceptance": "SelectProps / RadioGroupProps 新增可选 disabled(boolean | Signal);禁用时 select 触发器置灰且不展开弹层,radioGroup 整组置灰不可选;select 箭头在展开时旋转 180°;demo 加信号禁用与静态禁用示例;manifest/文档/llms-full.txt 同步;npm run check 全绿", "status": "done", "notes": "disabled 仍走 h() 透传(原生 button/input 拦截交互)零辅助代码;select 根节点补 class 函数加 ua-open,箭头旋转选择器命中;CSS 通用禁用态列表并入 .ua-select-trigger 与 .ua-radio,另加 :has() 让单选行整行弱化、禁用触发器 hover 不改边框色" + }, + { + "id": "d03", + "title": "新增折叠面板 accordion 组件", + "motivation": "长表单/设置页需要分区收起,皮肤包无折叠容器;grid 行高过渡可零测量实现展开动画", + "acceptance": "新增 accordion.ts:items(value/title/content/disabled)+ 单开(手风琴)与 multiple 多开,active 信号可选受控,标题为原生 button(aria-expanded/controls + region 关联),内容区 0fr→1fr 过渡且 reduced-motion 静态化;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿", + "status": "done", + "notes": "contents 走 appendChild 既有语义,字符串/DOM/响应式函数通吃;未传 active 时内部 signal 持有展开集合,受控模式只认 active 单开;CSS 用 grid-template-rows 0fr→1fr 过渡免测量,reduced-motion 列表补 ua-acc-*;demo 新增 sec-accordion(多开 + 禁用面板)" } ] } diff --git a/.ua/index.json b/.ua/index.json index 27a149a..8cb044a 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T08:46:56.936Z", + "generatedAt": "2026-10-23T08:47:49.411Z", "repoRoot": ".", "packages": [ { @@ -130,7 +130,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "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-* 令牌。", + "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / 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", @@ -150,6 +150,7 @@ "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用", + "accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控", "card(props, ...children):卡片容器", "badge(props):徽标,variant = default | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger", @@ -175,6 +176,8 @@ "RadioGroupProps", "MenuItem", "MenuProps", + "AccordionItem", + "AccordionProps", "OptionItem", "CardProps", "BadgeProps", @@ -201,7 +204,7 @@ "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" ], "docs": { - "intro": "21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", + "intro": "22 个 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'", @@ -510,6 +513,44 @@ "需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定" ] }, + { + "id": "accordion", + "title": "折叠面板 Accordion", + "demo": "sec-accordion", + "props": [ + { + "n": "items", + "t": "AccordionItem[]", + "r": 1, + "d": "面板列表:{ value, title, content, disabled? }" + }, + { + "n": "active", + "t": "Signal", + "d": "受控展开面板信号(单开模式);缺省由组件内部持有状态" + }, + { + "n": "multiple", + "t": "boolean", + "d": "允许多个面板同时展开;缺省 false(手风琴,单开)", + "df": "false" + }, + { + "n": "onChange", + "t": "(active: string) => void", + "k": 1, + "d": "展开变化回调:收起时传空串" + } + ], + "example": "accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] })", + "steps": [ + "准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数", + "默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true", + "需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态", + "标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联", + "内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换" + ] + }, { "id": "card", "title": "卡片 Card", diff --git a/.ua/iterations.log b/.ua/iterations.log index 9e19d73..de4bc1f 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -7,3 +7,4 @@ {"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 全绿"} {"time": "2026-10-23T17:02:00+08:00", "id": "d02", "result": "done", "summary": "select/radioGroup 补 disabled(布尔|信号),并修复 select 根节点缺 ua-open 导致箭头不旋转的既有缺陷;CSS :has() 行级弱化;demo/manifest/llms 同步,check 全绿"} +{"time": "2026-10-23T16:47:56+08:00", "id": "d03", "result": "done", "summary": "新增折叠面板 accordion:单开/多开 + active 受控 + 禁用面板,grid 行高 0fr→1fr 过渡免测量,aria 关联完整;manifest/llms/demo 同步,check 全绿"} diff --git a/apps/demo/main.ts b/apps/demo/main.ts index d159591..fd310d1 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -1,5 +1,6 @@ import { h, mount, signal } from '@ui-arch/frame'; import { + accordion, alert, badge, button, @@ -233,6 +234,20 @@ const app = h( h('span', { class: 'out' }, () => (menuPick() ? `已选:${menuPick()}` : '未选择')), ), ), + section( + 'sec-accordion', + '折叠面板', + 'accordion:标题按钮 + grid 行高过渡,默认手风琴单开,multiple 可多开,支持禁用面板。', + accordion({ + multiple: true, + items: [ + { value: 'a', title: '账户设置', content: '修改昵称、头像与绑定邮箱。' }, + { value: 'b', title: '通知偏好', content: alert({ message: '通知偏好:每日摘要已开启', variant: 'info' }) }, + { value: 'c', title: '危险操作', content: '删除账号后数据不可恢复。' }, + { value: 'd', title: '企业版专属(禁用)', content: '升级后可配置 SSO。', disabled: true }, + ], + }), + ), section( 'sec-table', '数据表格', diff --git a/llms-full.txt b/llms-full.txt index 7cd3c7d..4831808 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T08:46:56.936Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T08:47:49.411Z,数据源:各包 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 -21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 +22 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 ### button — 按钮 Button @@ -233,6 +233,31 @@ const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, { 现场演示:apps/docs/demo.html#sec-form +### accordion — 折叠面板 Accordion + +参数: + +- items(必填):AccordionItem[] —— 面板列表:{ value, title, content, disabled? } +- active:Signal —— 受控展开面板信号(单开模式);缺省由组件内部持有状态 +- multiple(默认 false):boolean —— 允许多个面板同时展开;缺省 false(手风琴,单开) +- onChange(回调):(active: string) => void —— 展开变化回调:收起时传空串 + +示例: + +``` +accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] }) +``` + +手把手: + +1. 准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数 +2. 默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true +3. 需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态 +4. 标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联 +5. 内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换 + +现场演示:apps/docs/demo.html#sec-accordion + ### card — 卡片 Card 参数: @@ -548,6 +573,7 @@ API 速览: - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 - radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用 +- accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控 - card(props, ...children):卡片容器 - badge(props):徽标,variant = default | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger diff --git a/llms.txt b/llms.txt index 7b763e4..2d3a2d8 100644 --- a/llms.txt +++ b/llms.txt @@ -45,7 +45,7 @@ ## @ui-arch/ui (v0.1.0) - 类型: skin-dom · 目录: packages/skin/ui -- 描述: 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-* 令牌。 +- 描述: DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / 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 支持布尔或信号禁用 @@ -55,6 +55,7 @@ - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 - radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用 + - accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控 - card(props, ...children):卡片容器 - badge(props):徽标,variant = default | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger diff --git a/packages/skin/ui/src/accordion.ts b/packages/skin/ui/src/accordion.ts new file mode 100644 index 0000000..85ea33e --- /dev/null +++ b/packages/skin/ui/src/accordion.ts @@ -0,0 +1,55 @@ +import { h, signal } from '@ui-arch/frame'; +import type { AccordionItem, AccordionProps } from './types.js'; + +/*** 折叠面板:标题按钮 + grid 行高过渡;单开(手风琴)或多开,展开态由 active 信号驱动或内部持有 ***/ +export function accordion(props: AccordionProps): HTMLElement { + // —— 状态集中声明 —— + const internal = signal([]); // 内部展开集合(未传 active 时使用) + const opened = (v: string): boolean => (props.active ? props.active() === v : internal().includes(v)); + + const toggle = (item: AccordionItem): void => { + if (props.active) { + const next = props.active() === item.value ? '' : item.value; // 单开:再点收起 + props.active(next); + props.onChange?.(next); + return; + } + const cur = internal(); + const next = cur.includes(item.value) + ? cur.filter((v) => v !== item.value) + : props.multiple + ? [...cur, item.value] + : [item.value]; + internal(next); + props.onChange?.(next[0] ?? ''); + }; + + // —— 面板:标题为原生 button(键盘可达),内容包一层 overflow 容器供过渡裁剪 —— + const rows = props.items.map((item, i) => + h( + 'div', + { class: () => `ua-acc-item${opened(item.value) ? ' ua-open' : ''}` }, + h( + 'button', + { + type: 'button', + class: 'ua-acc-head', + id: `ua-acc-h-${i}`, + 'aria-expanded': () => String(opened(item.value)), + 'aria-controls': `ua-acc-b-${i}`, + disabled: item.disabled ?? false, // 布尔透传即得禁用态 + onClick: () => toggle(item), + }, + h('span', { class: 'ua-acc-title' }, item.title), + h('span', { class: 'ua-acc-caret' }, '▸'), + ), + h( + 'div', + { class: 'ua-acc-body', id: `ua-acc-b-${i}`, role: 'region', 'aria-labelledby': `ua-acc-h-${i}` }, + h('div', { class: 'ua-acc-inner' }, item.content), + ), + ), + ); + + return h('div', { class: 'ua-accordion' }, ...rows); +} diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 35085fe..03a2425 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -7,6 +7,7 @@ export { switchButton } from './switch.js'; export { select } from './select.js'; export { menu } from './menu.js'; export { radioGroup } from './radioGroup.js'; +export { accordion } from './accordion.js'; export { card } from './card.js'; export { badge } from './badge.js'; export { alert } from './alert.js'; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 56678e0..3a4534f 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -97,6 +97,22 @@ export interface MenuProps { onSelect?: (value: string) => void; // 选中回调(禁用项不触发) } +/*** 折叠面板条目 ***/ +export interface AccordionItem { + value: string; // 面板标识(回调与展开集合用) + title: string; // 标题文案(同时作为触发器) + content: unknown; // 面板内容:字符串 / DOM 节点 / 响应式函数 + disabled?: boolean; // 禁用该面板:标题置灰且不可展开 +} + +/*** 折叠面板入参 ***/ +export interface AccordionProps { + items: AccordionItem[]; // 面板列表 + active?: Signal; // 展开面板信号(单开模式);缺省由组件内部持有状态 + multiple?: boolean; // 是否允许多个面板同时展开;缺省 false(手风琴) + onChange?: (active: string) => void; // 展开变化回调:收起时传空串 +} + /*** 多行输入入参 ***/ export interface TextareaProps { placeholder?: string; // 占位文案 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index ec3a8fc..696df86 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1,7 +1,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "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-* 令牌。", + "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / 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", @@ -21,6 +21,7 @@ "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用", + "accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控", "card(props, ...children):卡片容器", "badge(props):徽标,variant = default | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger", @@ -46,6 +47,8 @@ "RadioGroupProps", "MenuItem", "MenuProps", + "AccordionItem", + "AccordionProps", "OptionItem", "CardProps", "BadgeProps", @@ -72,7 +75,7 @@ "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" ], "docs": { - "intro": "21 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", + "intro": "22 个 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'", @@ -381,6 +384,44 @@ "需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定" ] }, + { + "id": "accordion", + "title": "折叠面板 Accordion", + "demo": "sec-accordion", + "props": [ + { + "n": "items", + "t": "AccordionItem[]", + "r": 1, + "d": "面板列表:{ value, title, content, disabled? }" + }, + { + "n": "active", + "t": "Signal", + "d": "受控展开面板信号(单开模式);缺省由组件内部持有状态" + }, + { + "n": "multiple", + "t": "boolean", + "d": "允许多个面板同时展开;缺省 false(手风琴,单开)", + "df": "false" + }, + { + "n": "onChange", + "t": "(active: string) => void", + "k": 1, + "d": "展开变化回调:收起时传空串" + } + ], + "example": "accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] })", + "steps": [ + "准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数", + "默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true", + "需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态", + "标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联", + "内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换" + ] + }, { "id": "card", "title": "卡片 Card", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index cfc6541..e88c1f2 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -384,6 +384,71 @@ background: var(--ua-color-border); } +/* —— 折叠面板 —— */ +.ua-accordion { + display: flex; + flex-direction: column; + border: 1px solid var(--ua-color-border); + border-radius: var(--ua-radius-md); + overflow: hidden; + background: var(--ua-color-bg); +} +.ua-acc-item + .ua-acc-item { + border-top: 1px solid var(--ua-color-border); +} +.ua-acc-head { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 12px; + border: none; + background: none; + color: var(--ua-color-text); + font-size: var(--ua-font-size-lg); + text-align: left; + cursor: pointer; + transition: background 0.15s; +} +.ua-acc-head:hover:not(:disabled) { + background: color-mix(in srgb, var(--ua-color-accent) 6%, transparent); +} +.ua-acc-head:focus-visible { + outline: none; + box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ua-color-accent) 45%, transparent); +} +.ua-acc-head:disabled { + opacity: 0.45; + cursor: not-allowed; +} +.ua-acc-caret { + font-size: 10px; + color: var(--ua-color-text-muted); + transition: transform 0.22s ease; +} +.ua-acc-item.ua-open .ua-acc-caret { + transform: rotate(90deg); +} +/* 内容区用 grid 行高过渡:0fr → 1fr,无需测量高度 */ +.ua-acc-body { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.24s ease; +} +.ua-acc-item.ua-open .ua-acc-body { + grid-template-rows: 1fr; +} +.ua-acc-inner { + overflow: hidden; + padding: 0 12px; + color: var(--ua-color-text-muted); + font-size: var(--ua-font-size-md); +} +.ua-acc-item.ua-open .ua-acc-inner { + padding-bottom: 12px; +} + /* —— 多行输入 —— */ .ua-textarea { width: 100%; @@ -885,6 +950,9 @@ .ua-menu-pop, .ua-menu-caret, .ua-menu-item, + .ua-acc-head, + .ua-acc-caret, + .ua-acc-body, .ua-switch-track, .ua-switch-thumb, .ua-tab,