feat(page): add composable page primitives
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: 标题 Portal
|
||||
description: 将页面标题安全地挂载到应用外壳,同时保留 SSR fallback。
|
||||
order: 20
|
||||
toc:
|
||||
- id: info-resolver
|
||||
title: 页面信息解析器
|
||||
- id: managed-target
|
||||
title: 管理标题目标
|
||||
- id: fallback
|
||||
title: Fallback
|
||||
- id: nested-managers
|
||||
title: 嵌套 Manager
|
||||
---
|
||||
|
||||
## 页面信息解析器 {#info-resolver}
|
||||
|
||||
`PagesManager.getPageInfo` 用于把路由或布局元数据接入页面组件。它接收 `PageContainer` 可选的 `Partial<PageInfo>`,返回补全后的 `PageInfo`;没有可用的托管信息时也可以返回 `undefined`:
|
||||
|
||||
```tsx
|
||||
<PagesManager
|
||||
getPageInfo={(info) => ({
|
||||
breadcrumb: info?.breadcrumb ?? routeMetadata.breadcrumb,
|
||||
description: info?.description ?? routeMetadata.description,
|
||||
subtitle: info?.subtitle ?? routeMetadata.subtitle,
|
||||
title: info?.title ?? routeMetadata.title,
|
||||
})}
|
||||
>
|
||||
<Outlet />
|
||||
</PagesManager>
|
||||
```
|
||||
|
||||
解析器返回 `undefined` 时,`PageContainer` 仍会把自身局部信息与继承的页面信息合并。
|
||||
|
||||
## 管理标题目标 {#managed-target}
|
||||
|
||||
布局可以把 DOM 容器交给 `PagesManager`,页面再通过 `PageTitlePortal` 把标题放进外部 Header:
|
||||
|
||||
```tsx
|
||||
function ApplicationLayout() {
|
||||
const [titleContainer, setTitleContainer] =
|
||||
React.useState<HTMLElement | null>(null)
|
||||
|
||||
return (
|
||||
<PagesManager titleContainer={titleContainer}>
|
||||
<header>
|
||||
<div ref={setTitleContainer} />
|
||||
</header>
|
||||
<Outlet />
|
||||
</PagesManager>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductsPage() {
|
||||
return (
|
||||
<PageTitlePortal>
|
||||
<PageTitle>商品</PageTitle>
|
||||
</PageTitlePortal>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`titleContainer` 接受 `Element` 或 `DocumentFragment`。`PageTitlePortal` 也可以通过自己的 `container` prop 覆盖 Manager。
|
||||
|
||||
## Fallback {#fallback}
|
||||
|
||||
没有可用目标时,Portal 默认原位渲染 `children`,因此服务端渲染和第一次客户端渲染不会丢失页面标题。传入 `fallback` 可以替换原位内容,包括传入 `null` 隐藏它。
|
||||
|
||||
## 嵌套 Manager {#nested-managers}
|
||||
|
||||
嵌套 `PagesManager` 在未传 `getPageInfo` 和 `titleContainer` 时会继承父级解析器与标题目标。显式传入 `null` 作为 `titleContainer` 可以阻止标题目标继承,使该子树中的标题继续原位渲染。
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: 商品页面示例
|
||||
description: 使用路由链接组合响应式商品管理页面。
|
||||
order: 30
|
||||
toc:
|
||||
- id: page-tabs
|
||||
title: 页面 Tabs
|
||||
- id: complete-example
|
||||
title: 完整示例
|
||||
---
|
||||
|
||||
## 页面 Tabs {#page-tabs}
|
||||
|
||||
`PageTabs` 采用标准的 Root、List、Trigger、Indicator 和 Content 组合:
|
||||
|
||||
```tsx
|
||||
<PageTabs defaultValue="overview" placement="top-start" variant="default">
|
||||
<PageTabsList aria-label="商品页面">
|
||||
<PageTabsExtra>
|
||||
<Button variant="ghost">返回</Button>
|
||||
</PageTabsExtra>
|
||||
<PageTabsTrigger value="overview">概览</PageTabsTrigger>
|
||||
<PageTabsTrigger value="inventory">库存</PageTabsTrigger>
|
||||
<PageTabsTrigger value="settings">设置</PageTabsTrigger>
|
||||
<PageTabsIndicator />
|
||||
<PageTabsExtra align="end">
|
||||
<Button variant="ghost">帮助</Button>
|
||||
</PageTabsExtra>
|
||||
</PageTabsList>
|
||||
<PageTabsContent value="overview">商品概览</PageTabsContent>
|
||||
<PageTabsContent value="inventory">库存管理</PageTabsContent>
|
||||
<PageTabsContent value="settings">商品设置</PageTabsContent>
|
||||
</PageTabs>
|
||||
```
|
||||
|
||||
`placement` 由 `top | right | bottom | left` 和 `start | center | end` 组合,例如 `top-start`、`top-center`、`left-end`。顶部和底部横向排列,左侧和右侧纵向排列;后缀控制 Tabs 组在主轴上的对齐。
|
||||
|
||||
`variant` 接受 `line`、`card` 或 `default`。`line` 在当前 Trigger 的内容侧绘制前景色线条,`card` 将带边框的标签与内容边界衔接,`default` 则渲染带凸起选中面的圆角浅色轨道。`PageTabsIndicator` 会自动跟随当前 Trigger。
|
||||
|
||||
`PageTabsExtra` 通过 `align="start | end"` 放到 List 两端,默认是 `start`。同一端可以声明多个 Extra,它们会保持原始顺序。
|
||||
|
||||
## 完整示例 {#complete-example}
|
||||
|
||||
```tsx
|
||||
<PageContainer
|
||||
info={{
|
||||
breadcrumb: [{ label: "商品", href: "/products" }, { label: "蓝牙耳机" }],
|
||||
description: "更新详情、库存和销售渠道。",
|
||||
title: "蓝牙耳机",
|
||||
}}
|
||||
>
|
||||
<PageHeader>
|
||||
<PageBreadcrumb aria-label="当前位置" />
|
||||
<PageHeading>
|
||||
<PageTitle />
|
||||
<PageDescription />
|
||||
<PageHeadingExtra align="end">
|
||||
<Button variant="outline">预览</Button>
|
||||
<Button>保存</Button>
|
||||
</PageHeadingExtra>
|
||||
</PageHeading>
|
||||
<PageHeaderExtra>
|
||||
<ProductMetadata />
|
||||
</PageHeaderExtra>
|
||||
</PageHeader>
|
||||
<ProductRouteTabs />
|
||||
<PageSection aria-label="商品详情">
|
||||
<ProductForm />
|
||||
</PageSection>
|
||||
</PageContainer>
|
||||
```
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: 页面组合
|
||||
description: 组合标题、页面操作、面包屑和内容 Section。
|
||||
order: 10
|
||||
toc:
|
||||
- id: stylesheet
|
||||
title: 样式入口
|
||||
- id: basic-page
|
||||
title: 基础页面
|
||||
- id: page-info
|
||||
title: 页面信息
|
||||
- id: breadcrumb
|
||||
title: 面包屑
|
||||
---
|
||||
|
||||
## 样式入口 {#stylesheet}
|
||||
|
||||
在应用 Tailwind 样式入口加入:
|
||||
|
||||
```css
|
||||
@import "@workspace/page/globals.css";
|
||||
```
|
||||
|
||||
## 基础页面 {#basic-page}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
PageBackButton,
|
||||
PageBreadcrumb,
|
||||
PageContainer,
|
||||
PageDescription,
|
||||
PageHeader,
|
||||
PageHeaderExtra,
|
||||
PageHeading,
|
||||
PageHeadingExtra,
|
||||
PageSection,
|
||||
PageSubtitle,
|
||||
PageTitle,
|
||||
} from "@workspace/page"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export function ProductsPage() {
|
||||
return (
|
||||
<PageContainer
|
||||
info={{
|
||||
breadcrumb: [
|
||||
{ label: "首页", href: "/" },
|
||||
{ label: "商品目录", href: "/products" },
|
||||
{ label: "商品" },
|
||||
],
|
||||
description: "管理商品、价格和上架状态。",
|
||||
subtitle: "商品目录",
|
||||
title: "商品",
|
||||
}}
|
||||
>
|
||||
<PageHeader>
|
||||
<PageBreadcrumb />
|
||||
<PageHeading>
|
||||
<PageHeadingExtra align="start">
|
||||
<PageBackButton href="/products">商品列表</PageBackButton>
|
||||
</PageHeadingExtra>
|
||||
<PageTitle />
|
||||
<PageSubtitle />
|
||||
<PageDescription />
|
||||
<PageHeadingExtra align="end">
|
||||
<Button>新建商品</Button>
|
||||
</PageHeadingExtra>
|
||||
</PageHeading>
|
||||
<PageHeaderExtra>
|
||||
<ProductStatus />
|
||||
</PageHeaderExtra>
|
||||
</PageHeader>
|
||||
|
||||
<PageSection aria-label="商品列表">
|
||||
<ProductsTable />
|
||||
</PageSection>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 页面信息 {#page-info}
|
||||
|
||||
`PageContainer.info` 接受 `Partial<PageInfo>`,并通过 `usePageInfo()` 暴露解析后的值。外层 `PagesManager` 提供 `getPageInfo` 后,该函数会接收局部信息,并可使用路由或布局元数据将其补全。`PageTitle`、`PageSubtitle`、`PageDescription` 和 `PageBreadcrumb` 在自身未提供内容或 items 时读取对应的页面信息;组件显式提供的内容优先。两个来源都没有可渲染内容时,组件返回 `null`,不会在页面中留下空元素。
|
||||
|
||||
`PageHeadingExtra` 接受 `align="start | end"`。同一侧的多个 Extra 会保持声明顺序,`PageHeaderExtra` 则提供标题下方不限定内容类型的区域。所有容器都保留 `className` 和原生 HTML props。
|
||||
|
||||
`PageBackButton` 未指定目标地址时使用浏览器历史记录返回。传入 `href` 可渲染原生链接,传入 `render` 可接入特定路由库的 Link;自定义 `onClick` 调用 `preventDefault()` 后也会阻止默认的历史返回。
|
||||
|
||||
## 面包屑 {#breadcrumb}
|
||||
|
||||
按照从根节点到当前页面的顺序向 `PageBreadcrumb` 传入 `items`。最后一项会渲染为当前页面;带 `href` 的项目使用原生链接,`render` 则可以接入特定路由库的 Link。当路径超过 `maxItems`(默认 `4`)时,中间项目会收入支持键盘操作的溢出菜单。
|
||||
|
||||
省略 `items` 后仍可使用原有组合 API,Breadcrumb Item、Link 和 Separator 继续从 `@workspace/ui/components/breadcrumb` 导入。
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 使用路由无关的原子组件建立一致的页面结构。
|
||||
order: 1
|
||||
toc:
|
||||
- id: role
|
||||
title: 包的职责
|
||||
- id: components
|
||||
title: 组件组成
|
||||
- id: boundaries
|
||||
title: 设计边界
|
||||
---
|
||||
|
||||
## 包的职责 {#role}
|
||||
|
||||
`@workspace/page` 提供应用内容页的结构组件,包括标题、说明、操作区、面包屑、路由标签和内容 Section。它只负责语义、响应式布局和视觉约定,不创建路由,也不管理业务数据。
|
||||
|
||||
## 组件组成 {#components}
|
||||
|
||||
- `PageContainer`:页面内容的纵向根容器。
|
||||
- `PageInfo`、`usePageInfo`:向页面结构组件共享可选的页面元信息。
|
||||
- `PageHeader`、`PageHeading`:组织主要标题内容。
|
||||
- `PageTitle`、`PageSubtitle`、`PageDescription`:语义化标题信息。
|
||||
- `PageHeadingExtra`:在标题的起始侧或末尾侧放置任意内容。
|
||||
- `PageHeaderExtra`:提供标题下方不限定内容类型的区域。
|
||||
- `PageBackButton`:支持历史返回、原生链接及路由 Link。
|
||||
- `PageBreadcrumb`:通过数据生成带溢出菜单的路径,也可以复用 `@workspace/ui` 的组合式 Breadcrumb API。
|
||||
- `PageTabs`:管理受控或非受控的当前值、方向和键盘交互。
|
||||
- `PageTabsList`、`PageTabsTrigger`、`PageTabsIndicator`:组成支持四侧放置和多种活动样式的标签列表。
|
||||
- `PageTabsExtra`:把一个或多个页面级操作放到标签列表的起始端或末端。
|
||||
- `PageTabsContent`:渲染与 Trigger 值匹配的内容面板。
|
||||
- `PageSection`:页面主体的独立语义区段。
|
||||
- `PagesManager`、`PageTitlePortal`:可选地把标题挂载到外部布局。
|
||||
|
||||
## 设计边界 {#boundaries}
|
||||
|
||||
`PageInfo` 只提供展示元信息,不管理路由状态。组件显式内容会覆盖对应元信息,缺少值时不渲染空节点。Page Tabs 基于 Base UI 管理值、Panel 显隐、键盘导航和 Indicator 定位,但不创建应用路由。需要与路由同步时,通过 `value` 和 `onValueChange` 在应用层完成。`PagesManager` 不保存页面栈,只向子树提供标题容器。
|
||||
Reference in New Issue
Block a user