feat(breadcrumb): 支持 onNavigate 接管点击(SPA 路由)

- 可选 prop,不传时保持普通链接
- 文档契约 + props 表 + 变更记录同步
This commit is contained in:
stash
2026-10-29 11:26:20 +08:00
parent 52296b3368
commit 12e3047f1a
3 changed files with 26 additions and 6 deletions
+3 -2
View File
@@ -410,8 +410,8 @@
### 3.26 Breadcrumb ✅ ### 3.26 Breadcrumb ✅
- **用途**:详情页路径导航。 - **用途**:详情页路径导航。
- **Props**:`items: { label, href? }[]`、`separator`(默认 `/`)。 - **Props**:`items: { label, href? }[]`、`separator`(默认 `/`)、`onNavigate(href)`(接管点击,用于 SPA 路由;传了就阻止默认跳转)。
- **规则**:最后一项忽略 `href`,并标 `aria-current="page"`。 - **规则**:最后一项忽略 `href`,并标 `aria-current="page"`;不传 `onNavigate` 时走普通链接(整页跳转)。
- **展示页**:`#breadcrumb` - **展示页**:`#breadcrumb`
### 3.27 Tabs ✅ ### 3.27 Tabs ✅
@@ -666,3 +666,4 @@
| 2026-10-28 | **1:1 补全(视觉 + 交互)**:`Title` 增加 `weight`(600);`Input` 增加 `size`(44);`Button` 增加 `spinning`(图标原地旋转);`Cover` 改为 hover 上浮 2px(`lift`,默认开;`hoverZoom` 默认关);`ScrollTop` 增加 `offset`;`Menu` 的 hover 改主色、子菜单改 `grid-template-rows` 高度动画(0.28s);`Avatar xl` 徽标 20px、无图底色用新令牌 `--su-avatar-gradient`。**影响面**:`Cover`/`Menu`/`Avatar`/`Title` 的所有展示页 demo 与应用页面需重截图 | | 2026-10-28 | **1:1 补全(视觉 + 交互)**:`Title` 增加 `weight`(600);`Input` 增加 `size`(44);`Button` 增加 `spinning`(图标原地旋转);`Cover` 改为 hover 上浮 2px(`lift`,默认开;`hoverZoom` 默认关);`ScrollTop` 增加 `offset`;`Menu` 的 hover 改主色、子菜单改 `grid-template-rows` 高度动画(0.28s);`Avatar xl` 徽标 20px、无图底色用新令牌 `--su-avatar-gradient`。**影响面**:`Cover`/`Menu`/`Avatar`/`Title` 的所有展示页 demo 与应用页面需重截图 |
| 2026-10-28 | **修复**:`Input` 聚焦不再叠 outline(文本框点击会命中 `:focus-visible`,导致边框 + 外环「双框」;现在只用边框变主色)。`Layout` 增加 `hideScrollbar`(内部滚动条可隐藏)。影响面:所有 `Input` 页面视觉(去掉了多余外环)、`Layout screen` 页面可选用 | | 2026-10-28 | **修复**:`Input` 聚焦不再叠 outline(文本框点击会命中 `:focus-visible`,导致边框 + 外环「双框」;现在只用边框变主色)。`Layout` 增加 `hideScrollbar`(内部滚动条可隐藏)。影响面:所有 `Input` 页面视觉(去掉了多余外环)、`Layout screen` 页面可选用 |
| 2026-10-28 | **补齐剩余能力**:新增 `Image`(比例 / 填充 / 失败降级)与 `FormField`(标签 + 提示 + 错误,不做校验);`Button` 增加 `size="lg"`(44);展示页补 `Title weight`、`Avatar xl/tone/icon`、`Cover lift`、`Menu itemHeight`、`Layout screen/hideScrollbar`、`Input size`、`FormField` 示例;新增 `themes/example.css` 主题示例(只改变量) | | 2026-10-28 | **补齐剩余能力**:新增 `Image`(比例 / 填充 / 失败降级)与 `FormField`(标签 + 提示 + 错误,不做校验);`Button` 增加 `size="lg"`(44);展示页补 `Title weight`、`Avatar xl/tone/icon`、`Cover lift`、`Menu itemHeight`、`Layout screen/hideScrollbar`、`Input size`、`FormField` 示例;新增 `themes/example.css` 主题示例(只改变量) |
| 2026-10-29 | `Breadcrumb` 增加 `onNavigate`(可选):传了就用 `preventDefault` 接管点击,交给 SPA 路由;不传保持普通链接。兼容性:新增可选 prop,无破坏性 |
+1
View File
@@ -51,6 +51,7 @@ export function Breadcrumbs() {
rows={[ rows={[
{ name: 'items', type: 'BreadcrumbItem[]', required: true, desc: '{ label, href? }[]' }, { name: 'items', type: 'BreadcrumbItem[]', required: true, desc: '{ label, href? }[]' },
{ name: 'separator', type: 'ReactNode', defaultValue: "'/'", desc: '分隔符' }, { name: 'separator', type: 'ReactNode', defaultValue: "'/'", desc: '分隔符' },
{ name: 'onNavigate', type: '(href: string) => void', desc: '接管点击(SPA 路由),不传则整页跳转' },
]} ]}
/> />
</Section> </Section>
+22 -4
View File
@@ -1,7 +1,12 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import styles from './Breadcrumb.module.css' import styles from './Breadcrumb.module.css'
/** 面包屑:详情页的路径导航,最后一项是当前页(不可点)。 */ /**
* 面包屑:详情页的路径导航,最后一项是当前页(不可点)。
*
* 传了 `onNavigate` 就接管中间项的点击(SPA 路由),
* 不传时保持普通链接行为(整页跳转)。
*/
export type BreadcrumbItem = { export type BreadcrumbItem = {
label: ReactNode label: ReactNode
href?: string href?: string
@@ -11,10 +16,12 @@ export type BreadcrumbProps = {
items: BreadcrumbItem[] items: BreadcrumbItem[]
/** 分隔符,默认 `/` */ /** 分隔符,默认 `/` */
separator?: ReactNode separator?: ReactNode
/** 点击中间项时回调(用于 SPA 路由);传了就会阻止默认跳转 */
onNavigate?: (href: string) => void
className?: string className?: string
} }
export function Breadcrumb({ items, separator = '/', className }: BreadcrumbProps) { export function Breadcrumb({ items, separator = '/', onNavigate, className }: BreadcrumbProps) {
return ( return (
<nav className={[styles.root, className].filter(Boolean).join(' ')} aria-label="面包屑"> <nav className={[styles.root, className].filter(Boolean).join(' ')} aria-label="面包屑">
<ol className={styles.list}> <ol className={styles.list}>
@@ -22,9 +29,20 @@ export function Breadcrumb({ items, separator = '/', className }: BreadcrumbProp
const last = index === items.length - 1 const last = index === items.length - 1
return ( return (
<li key={`${index}-${typeof item.label === 'string' ? item.label : ''}`} className={styles.item}> <li
key={`${index}-${typeof item.label === 'string' ? item.label : ''}`}
className={styles.item}
>
{item.href !== undefined && !last ? ( {item.href !== undefined && !last ? (
<a className={styles.link} href={item.href}> <a
className={styles.link}
href={item.href}
onClick={(event) => {
if (onNavigate === undefined || item.href === undefined) return
event.preventDefault()
onNavigate(item.href)
}}
>
{item.label} {item.label}
</a> </a>
) : ( ) : (