iter(g29): 新增 carousel 轮播组件
This commit is contained in:
@@ -588,6 +588,14 @@
|
||||
"acceptance": "menu/select/popover/tooltip/image 的 Esc 处理各自 stopPropagation,只关本层;演示页在弹窗内放下拉可复现逐层关闭;npm run check 全绿",
|
||||
"status": "done",
|
||||
"notes": "自补:既有组件可用性细节;演示页弹窗内加下拉可复现"
|
||||
},
|
||||
{
|
||||
"id": "g29",
|
||||
"title": "新增 carousel 轮播组件",
|
||||
"motivation": "组件库缺轮播,首页 banner/图片墙只能外部引库,样式与令牌体系脱节",
|
||||
"acceptance": "carousel({ items, index, autoplay, height, label, onChange }) 横排位移 + 指示点/箭头/←→ 键切换 + 悬停暂停 + 环形回绕;组件数 39→40 三处计数同步;npm run check 全绿",
|
||||
"status": "open",
|
||||
"notes": "自补:组件补充"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+50
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "ui-arch-registry-index/0.1",
|
||||
"generatedAt": "2026-10-26T03:38:39.607Z",
|
||||
"generatedAt": "2026-10-26T03:39:41.448Z",
|
||||
"repoRoot": ".",
|
||||
"packages": [
|
||||
{
|
||||
@@ -182,7 +182,8 @@
|
||||
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
||||
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列",
|
||||
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值",
|
||||
"list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态"
|
||||
"list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态",
|
||||
"carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控"
|
||||
],
|
||||
"keyTypes": [
|
||||
"ButtonProps",
|
||||
@@ -1813,6 +1814,53 @@
|
||||
"需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线",
|
||||
"items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "carousel",
|
||||
"title": "轮播 Carousel",
|
||||
"demo": "sec-carousel",
|
||||
"props": [
|
||||
{
|
||||
"n": "items",
|
||||
"t": "unknown[]",
|
||||
"r": 1,
|
||||
"d": "每张幻灯片内容,顺序即播放顺序"
|
||||
},
|
||||
{
|
||||
"n": "index",
|
||||
"t": "Signal<number>",
|
||||
"d": "当前下标信号;不传则内部维护"
|
||||
},
|
||||
{
|
||||
"n": "autoplay",
|
||||
"t": "number",
|
||||
"d": "自动前进间隔(毫秒);缺省不自动播放"
|
||||
},
|
||||
{
|
||||
"n": "height",
|
||||
"t": "number",
|
||||
"d": "固定高度像素;缺省由内容撑开"
|
||||
},
|
||||
{
|
||||
"n": "label",
|
||||
"t": "string",
|
||||
"d": "区域无障碍名称;缺省「轮播」"
|
||||
},
|
||||
{
|
||||
"n": "onChange",
|
||||
"t": "(index: number) => void",
|
||||
"k": 1,
|
||||
"d": "切换回调:点击指示点/箭头/键盘/自动前进都会触发"
|
||||
}
|
||||
],
|
||||
"example": "const i = signal(0); carousel({ items: [h('div', null, 'A'), h('div', null, 'B')], index: i, height: 150, autoplay: 4000, onChange: (k) => console.log(k) })",
|
||||
"steps": [
|
||||
"items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序",
|
||||
"index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护",
|
||||
"height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停",
|
||||
"键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu",
|
||||
"指示点与箭头都可点击,末张下一张环形回到首张"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
|
||||
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
|
||||
- `packages/skin/ui`(@ui-arch/ui):39 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
||||
- `packages/skin/ui`(@ui-arch/ui):40 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
||||
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
|
||||
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
|
||||
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ ui-arch-mvp/
|
||||
│ │ ├── table-core/ # 表格状态内核
|
||||
│ │ └── frame/ # 信号 + hyperscript 微框架
|
||||
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
|
||||
│ │ └── ui/ # 39 个组件 + ui.css
|
||||
│ │ └── ui/ # 40 个组件 + ui.css
|
||||
│ ├── design/ # 资产层(可发布)
|
||||
│ │ └── tokens/ # --ua-* 设计令牌
|
||||
│ └── tools/ # 工程层(private,不发布)
|
||||
|
||||
@@ -11,10 +11,10 @@ npm run demo # 打开演示:apps/demo/dist/demo.html
|
||||
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
||||
```
|
||||
|
||||
## 组件(共 39 个)
|
||||
## 组件(共 40 个)
|
||||
|
||||
<!-- ua:components -->
|
||||
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip
|
||||
accordion · alert · avatar · badge · breadcrumb · button · carousel · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip
|
||||
<!-- /ua:components -->
|
||||
|
||||
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / descriptions / tag / avatar / card)、其他(divider / timeline)。
|
||||
@@ -25,7 +25,7 @@ accordion · alert · avatar · badge · breadcrumb · button · card · checkbo
|
||||
|---|---|
|
||||
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
||||
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
||||
| `packages/skin/ui` | @ui-arch/ui:39 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
||||
| `packages/skin/ui` | @ui-arch/ui:40 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
||||
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
||||
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
||||
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
formItem,
|
||||
inputNumber,
|
||||
list,
|
||||
carousel,
|
||||
menu,
|
||||
modal,
|
||||
pagination,
|
||||
@@ -174,6 +175,7 @@ const tableLoading = signal(false);
|
||||
// —— 菜单选中结果:演示 onSelect 回调 ——
|
||||
const menuPick = signal('');
|
||||
const listPick = signal(''); // 列表点击选中的条目标题
|
||||
const slideIndex = signal(0); // 轮播当前下标(外部受控演示)
|
||||
|
||||
// —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled ——
|
||||
const locked = signal(true);
|
||||
@@ -596,6 +598,27 @@ const content = h(
|
||||
),
|
||||
),
|
||||
),
|
||||
section(
|
||||
'sec-carousel',
|
||||
'轮播',
|
||||
'carousel:items 横排位移,指示点 / 箭头 / ←→ 键切换,autoplay 毫秒自动前进且悬停暂停;index 传信号即可外部受控。',
|
||||
h(
|
||||
'div',
|
||||
{ class: 'row' },
|
||||
carousel({
|
||||
height: 150,
|
||||
autoplay: 4000,
|
||||
index: slideIndex,
|
||||
onChange: (i) => slideIndex(i),
|
||||
items: [
|
||||
h('div', { style: 'flex:1;display:flex;align-items:center;justify-content:center;height:100%;font-size:15px;background:var(--ua-color-surface);color:var(--ua-color-text)' }, '① 令牌驱动的幻灯片 —— 悬停自动暂停'),
|
||||
h('div', { style: 'flex:1;display:flex;align-items:center;justify-content:center;height:100%;font-size:15px;background:color-mix(in srgb, var(--ua-color-accent) 12%, transparent);color:var(--ua-color-text)' }, '② 指示点与箭头均可点击,←→ 也能切'),
|
||||
h('div', { style: 'flex:1;display:flex;align-items:center;justify-content:center;height:100%;font-size:15px;background:color-mix(in srgb, var(--ua-color-success) 14%, transparent);color:var(--ua-color-text)' }, '③ 环形回绕:末张的下一张回到首张'),
|
||||
],
|
||||
}),
|
||||
h('span', { class: 'out' }, () => `第 ${slideIndex() + 1} / 3 张`),
|
||||
),
|
||||
),
|
||||
section(
|
||||
'sec-menu',
|
||||
'下拉菜单',
|
||||
|
||||
+29
-1
@@ -1,6 +1,6 @@
|
||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||
|
||||
> 自动生成于 2026-10-26T03:38:39.607Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||
> 自动生成于 2026-10-26T03:39:41.448Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||
|
||||
## 快速上手
|
||||
@@ -1036,6 +1036,33 @@ list({ header: '待办', bordered: true, items: [{ title: '报销单 RB-2048', d
|
||||
|
||||
现场演示:apps/docs/demo.html#sec-list
|
||||
|
||||
### carousel — 轮播 Carousel
|
||||
|
||||
参数:
|
||||
|
||||
- items(必填):unknown[] —— 每张幻灯片内容,顺序即播放顺序
|
||||
- index:Signal<number> —— 当前下标信号;不传则内部维护
|
||||
- autoplay:number —— 自动前进间隔(毫秒);缺省不自动播放
|
||||
- height:number —— 固定高度像素;缺省由内容撑开
|
||||
- label:string —— 区域无障碍名称;缺省「轮播」
|
||||
- onChange(回调):(index: number) => void —— 切换回调:点击指示点/箭头/键盘/自动前进都会触发
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
const i = signal(0); carousel({ items: [h('div', null, 'A'), h('div', null, 'B')], index: i, height: 150, autoplay: 4000, onChange: (k) => console.log(k) })
|
||||
```
|
||||
|
||||
手把手:
|
||||
|
||||
1. items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序
|
||||
2. index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护
|
||||
3. height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停
|
||||
4. 键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu
|
||||
5. 指示点与箭头都可点击,末张下一张环形回到首张
|
||||
|
||||
现场演示:apps/docs/demo.html#sec-carousel
|
||||
|
||||
API 速览:
|
||||
|
||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||
@@ -1079,6 +1106,7 @@ API 速览:
|
||||
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
||||
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
|
||||
- list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态
|
||||
- carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控
|
||||
|
||||
一行用法:
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
||||
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
|
||||
- list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态
|
||||
- carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控
|
||||
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
|
||||
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||||
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { effect, h, signal } from '@ui-arch/frame';
|
||||
import type { CarouselProps } from './types.js';
|
||||
|
||||
/*** 轮播:items 横排 + translateX 位移;指示点 / 左右箭头 / ←→ 键切换,autoplay 毫秒自动前进且悬停暂停 ***/
|
||||
export function carousel(props: CarouselProps): HTMLElement {
|
||||
// —— 状态集中声明 ——
|
||||
const total = props.items.length;
|
||||
const index = props.index ?? signal(0); // 未传外部信号时用内部信号,行为一致
|
||||
const wrap = (i: number) => (total ? ((i % total) + total) % total : 0); // 环形取模,负数也回绕
|
||||
const dots: HTMLButtonElement[] = []; // 指示点,顺序即下标
|
||||
const track = h('div', { class: 'ua-carousel-track' }) as HTMLElement; // 幻灯片轨道,靠 transform 位移
|
||||
const dotsBar = h('div', { class: 'ua-carousel-dots' }) as HTMLElement;
|
||||
|
||||
/*** 跳到指定下标(自动环形),并回传 onChange ***/
|
||||
const go = (i: number) => {
|
||||
if (!total) return; // 卫语句:无内容不动作
|
||||
index(wrap(i));
|
||||
props.onChange?.(index());
|
||||
};
|
||||
|
||||
props.items.forEach((item, i) => {
|
||||
track.append(h('div', { class: 'ua-carousel-slide', role: 'group', 'aria-label': `第 ${i + 1} 张` }, item));
|
||||
const dot = h('button', { type: 'button', class: 'ua-carousel-dot', 'aria-label': `跳到第 ${i + 1} 张`, onClick: () => go(i) }) as HTMLButtonElement;
|
||||
dots.push(dot);
|
||||
dotsBar.append(dot);
|
||||
});
|
||||
|
||||
/*** 生成左右箭头 ***/
|
||||
const arrow = (dir: 1 | -1, label: string) =>
|
||||
h('button', { type: 'button', class: `ua-carousel-arrow ua-carousel-${dir === 1 ? 'next' : 'prev'}`, 'aria-label': label, onClick: () => go(index() + dir) }, dir === 1 ? '›' : '‹');
|
||||
|
||||
const root = h(
|
||||
'div',
|
||||
{ class: 'ua-carousel', tabindex: '0', role: 'region', 'aria-label': props.label ?? '轮播' },
|
||||
track,
|
||||
arrow(-1, '上一张'),
|
||||
arrow(1, '下一张'),
|
||||
dotsBar,
|
||||
) as HTMLElement;
|
||||
if (props.height) root.style.height = `${props.height}px`; // 固定高度由调用方指定,缺省随内容
|
||||
|
||||
effect(() => {
|
||||
const i = wrap(index());
|
||||
track.style.transform = `translateX(${-i * 100}%)`; // 整屏位移,配合 CSS transition 出滑动效果
|
||||
dots.forEach((dot, k) => {
|
||||
dot.classList.toggle('ua-carousel-dot-active', k === i);
|
||||
dot.setAttribute('aria-current', String(k === i)); // 读屏标注当前张
|
||||
});
|
||||
});
|
||||
|
||||
root.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; // 卫语句:只接管方向键
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // 不冒泡:避免外层 tabs/menu 同时响应
|
||||
go(index() + (e.key === 'ArrowRight' ? 1 : -1));
|
||||
});
|
||||
|
||||
if (props.autoplay && total > 1) {
|
||||
let paused = false; // 悬停期间暂停自动播放
|
||||
root.addEventListener('pointerenter', () => { paused = true; });
|
||||
root.addEventListener('pointerleave', () => { paused = false; });
|
||||
window.setInterval(() => { if (!paused) go(index() + 1); }, props.autoplay);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export { rate } from './rate.js';
|
||||
export { empty } from './empty.js';
|
||||
export { divider } from './divider.js';
|
||||
export { list } from './list.js';
|
||||
export { carousel } from './carousel.js';
|
||||
export { descriptions } from './descriptions.js';
|
||||
export { statistic } from './statistic.js';
|
||||
export { timeline } from './timeline.js';
|
||||
|
||||
@@ -420,4 +420,14 @@ export interface ListProps {
|
||||
onItemClick?: (item: ListItem, index: number) => void; // 条目点击回调;提供后条目可点击并带 hover 反馈
|
||||
}
|
||||
|
||||
/*** 轮播入参 ***/
|
||||
export interface CarouselProps {
|
||||
items: unknown[]; // 每张幻灯片内容,顺序即播放顺序
|
||||
index?: Signal<number>; // 当前下标信号;不传则内部维护
|
||||
autoplay?: number; // 自动前进间隔(毫秒);缺省不自动播放
|
||||
height?: number; // 固定高度像素;缺省由内容撑开
|
||||
label?: string; // 区域无障碍名称;缺省「轮播」
|
||||
onChange?: (index: number) => void; // 切换回调(点击/箭头/键盘/自动都触发)
|
||||
}
|
||||
|
||||
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
||||
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列",
|
||||
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值",
|
||||
"list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态"
|
||||
"list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态",
|
||||
"carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控"
|
||||
],
|
||||
"keyTypes": [
|
||||
"ButtonProps",
|
||||
@@ -1684,6 +1685,53 @@
|
||||
"需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线",
|
||||
"items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "carousel",
|
||||
"title": "轮播 Carousel",
|
||||
"demo": "sec-carousel",
|
||||
"props": [
|
||||
{
|
||||
"n": "items",
|
||||
"t": "unknown[]",
|
||||
"r": 1,
|
||||
"d": "每张幻灯片内容,顺序即播放顺序"
|
||||
},
|
||||
{
|
||||
"n": "index",
|
||||
"t": "Signal<number>",
|
||||
"d": "当前下标信号;不传则内部维护"
|
||||
},
|
||||
{
|
||||
"n": "autoplay",
|
||||
"t": "number",
|
||||
"d": "自动前进间隔(毫秒);缺省不自动播放"
|
||||
},
|
||||
{
|
||||
"n": "height",
|
||||
"t": "number",
|
||||
"d": "固定高度像素;缺省由内容撑开"
|
||||
},
|
||||
{
|
||||
"n": "label",
|
||||
"t": "string",
|
||||
"d": "区域无障碍名称;缺省「轮播」"
|
||||
},
|
||||
{
|
||||
"n": "onChange",
|
||||
"t": "(index: number) => void",
|
||||
"k": 1,
|
||||
"d": "切换回调:点击指示点/箭头/键盘/自动前进都会触发"
|
||||
}
|
||||
],
|
||||
"example": "const i = signal(0); carousel({ items: [h('div', null, 'A'), h('div', null, 'B')], index: i, height: 150, autoplay: 4000, onChange: (k) => console.log(k) })",
|
||||
"steps": [
|
||||
"items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序",
|
||||
"index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护",
|
||||
"height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停",
|
||||
"键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu",
|
||||
"指示点与箭头都可点击,末张下一张环形回到首张"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2071,3 +2071,74 @@ button.ua-step-dot:hover {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* —— 轮播 —— */
|
||||
.ua-carousel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: var(--ua-radius-md);
|
||||
background: var(--ua-color-surface);
|
||||
outline: none;
|
||||
}
|
||||
.ua-carousel:focus-visible {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ua-color-accent) 45%, transparent);
|
||||
}
|
||||
.ua-carousel-track {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
transition: transform 0.32s ease;
|
||||
}
|
||||
.ua-carousel-slide {
|
||||
flex: 0 0 100%;
|
||||
display: flex;
|
||||
}
|
||||
.ua-carousel-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid var(--ua-color-border);
|
||||
border-radius: 50%;
|
||||
background: var(--ua-color-bg);
|
||||
color: var(--ua-color-text);
|
||||
font-size: var(--ua-font-size-lg);
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.ua-carousel-prev { left: var(--ua-space-2); }
|
||||
.ua-carousel-next { right: var(--ua-space-2); }
|
||||
.ua-carousel:hover .ua-carousel-arrow,
|
||||
.ua-carousel:focus-within .ua-carousel-arrow { opacity: 1; }
|
||||
.ua-carousel-arrow:hover { background: var(--ua-color-surface); }
|
||||
.ua-carousel-dots {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: var(--ua-space-2);
|
||||
display: flex;
|
||||
gap: var(--ua-space-1);
|
||||
justify-content: center;
|
||||
}
|
||||
.ua-carousel-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--ua-color-border);
|
||||
cursor: pointer;
|
||||
transition: width 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
.ua-carousel-dot-active {
|
||||
width: 16px;
|
||||
border-radius: var(--ua-radius-sm);
|
||||
background: var(--ua-color-accent);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ua-carousel-track,
|
||||
.ua-carousel-dot,
|
||||
.ua-carousel-arrow { transition: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user