feat(docs): add bilingual package documentation site
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Appearance
|
||||
description: Finish the application with persistent theme, color, and compact-layout preferences.
|
||||
order: 22
|
||||
toc:
|
||||
- id: provide-ui-state
|
||||
title: Provide UI state
|
||||
- id: apply-preferences
|
||||
title: Apply preferences
|
||||
- id: add-locales
|
||||
title: Add locales
|
||||
---
|
||||
|
||||
## Provide UI state {#provide-ui-state}
|
||||
|
||||
Mount `UiStateProvider` near the application root. A controlled provider can persist every state update through its change handler.
|
||||
|
||||
```tsx
|
||||
<UiStateProvider>
|
||||
<AppearanceController />
|
||||
<App />
|
||||
<ThemeToggleButton />
|
||||
</UiStateProvider>
|
||||
```
|
||||
|
||||
## Apply preferences {#apply-preferences}
|
||||
|
||||
Render `AppearanceController` once so the current state becomes document classes and theme variables. `useUiState` reads or updates individual preferences, while `useResolvedTheme` returns the effective light or dark scheme after resolving system mode.
|
||||
|
||||
## Add locales {#add-locales}
|
||||
|
||||
Add the appearance catalog only when the application uses these controls:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/blocks/appearance/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
The application shell is now complete. Add only the other block-specific catalogs used by the product.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Media library
|
||||
description: Supply a storage adapter and add browsing, upload, folders, and asset selection.
|
||||
order: 21
|
||||
toc:
|
||||
- id: implement-the-adapter
|
||||
title: Implement the adapter
|
||||
- id: provide-media
|
||||
title: Provide media
|
||||
- id: selection-flows
|
||||
title: Selection flows
|
||||
---
|
||||
|
||||
## Implement the adapter {#implement-the-adapter}
|
||||
|
||||
`MediaAdapter` is the media feature's complete persistence boundary. Implement its query, upload, folder, update, and delete operations with the product's storage service.
|
||||
|
||||
## Provide media {#provide-media}
|
||||
|
||||
Mount the adapter once around the media surfaces:
|
||||
|
||||
```tsx
|
||||
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
|
||||
|
||||
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
|
||||
<MediaLibrary />
|
||||
</MediaProvider>
|
||||
```
|
||||
|
||||
The optional `notify` callback translates operational results into the application's toast or notification system.
|
||||
|
||||
## Selection flows {#selection-flows}
|
||||
|
||||
Use `MediaPickerDialog` for single or multiple asset selection. `readMediaDimensions`, `formatMediaFileSize`, and `defaultMediaReference` cover the common presentation work around uploads and previews.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Notifications
|
||||
description: Connect notification state without coupling the UI to a backend.
|
||||
order: 20
|
||||
toc:
|
||||
- id: notifications-query
|
||||
title: Notifications query
|
||||
---
|
||||
|
||||
## Notifications query {#notifications-query}
|
||||
|
||||
Notifications use TanStack Query as their data boundary:
|
||||
|
||||
```tsx
|
||||
const notifications = useNotifications({
|
||||
queryKey: ["notifications"],
|
||||
queryFn: loadNotifications,
|
||||
})
|
||||
```
|
||||
|
||||
The result includes unread counts, optimistic read-state updates, action execution, refetching, and pending or error status. `AppLayout` can create the sheet directly from the same query options.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Chats
|
||||
description: Add a responsive conversation workspace and connect its events to application services.
|
||||
order: 30
|
||||
toc:
|
||||
- id: render-the-workspace
|
||||
title: Render the workspace
|
||||
- id: controlled-state
|
||||
title: Controlled state
|
||||
- id: media-and-pagination
|
||||
title: Media and pagination
|
||||
---
|
||||
|
||||
## Render the workspace {#render-the-workspace}
|
||||
|
||||
`ChatWorkspace` renders conversation discovery, the active thread, and the composer:
|
||||
|
||||
```tsx
|
||||
import { ChatWorkspace } from "@workspace/blocks/chats"
|
||||
|
||||
;<ChatWorkspace
|
||||
conversations={conversations}
|
||||
onSend={({ conversation, message }) => sendMessage(conversation, message)}
|
||||
/>
|
||||
```
|
||||
|
||||
## Controlled state {#controlled-state}
|
||||
|
||||
The active conversation, draft, search query, and Enter-to-send preference can be controlled individually. Leave a value undefined when the workspace should own that state.
|
||||
|
||||
The UI emits events but does not select a transport or persistence format. Map API responses into `ChatConversation` and the exported message variants at the application boundary.
|
||||
|
||||
## Media and pagination {#media-and-pagination}
|
||||
|
||||
Provide `resolveMediaUrl` when stored media references need signed or transformed URLs. Use `onLoadEarlierMessages` with `hasMoreMessages` and `isLoadingMoreMessages` to connect historical pagination.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Application shell
|
||||
description: Assemble the shared header, sidebar, breadcrumbs, chat, and notification surfaces.
|
||||
order: 11
|
||||
toc:
|
||||
- id: render-app-layout
|
||||
title: Render AppLayout
|
||||
- id: layout-inputs
|
||||
title: Layout inputs
|
||||
- id: optional-preset
|
||||
title: Optional preset
|
||||
---
|
||||
|
||||
## Render AppLayout {#render-app-layout}
|
||||
|
||||
`AppLayout` is the integration point for the rest of this tutorial:
|
||||
|
||||
```tsx
|
||||
import { AppLayout } from "@workspace/blocks/layout"
|
||||
|
||||
export function WorkspaceLayout() {
|
||||
return (
|
||||
<AppLayout
|
||||
navigationGroups={navigationGroups}
|
||||
notifications={{
|
||||
queryKey: ["notifications"],
|
||||
queryFn: loadNotifications,
|
||||
}}
|
||||
chatThreads={chatThreads}
|
||||
>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Layout inputs {#layout-inputs}
|
||||
|
||||
The shell composes `AppSidebar`, `AppBreadcrumb`, `AppQueryIndicator`, `UserMenu`, a notification sheet, and chat threads. Pass `headerActions` when the product needs additional global controls.
|
||||
|
||||
The route content remains the layout's `children`, so the package does not constrain the router used by the application.
|
||||
|
||||
## Optional preset {#optional-preset}
|
||||
|
||||
`@workspace/blocks/layouts/vega` provides an optional visual shell. It lives on a separate entry point so the standard layout does not include preset-specific code.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Add the package, its styles, and the providers required by the tutorial.
|
||||
order: 10
|
||||
toc:
|
||||
- id: add-the-package
|
||||
title: Add the package
|
||||
- id: import-styles
|
||||
title: Import styles
|
||||
- id: application-providers
|
||||
title: Application providers
|
||||
---
|
||||
|
||||
## Add the package {#add-the-package}
|
||||
|
||||
Add the workspace dependency to the consuming application:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/blocks": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Blocks use `@workspace/ui`, React Query, and the workspace internationalization runtime. The workspace package manager resolves those dependencies for local applications.
|
||||
|
||||
## Import styles {#import-styles}
|
||||
|
||||
Import the block stylesheet once from the application entry:
|
||||
|
||||
```ts
|
||||
import "@workspace/blocks/globals.css"
|
||||
```
|
||||
|
||||
Keep feature imports on their explicit subpaths. For example, importing `@workspace/blocks/media` does not pull chat into the same module graph.
|
||||
|
||||
## Application providers {#application-providers}
|
||||
|
||||
The full shell expects React Query and internationalization to be available above it. Add those providers before the route tree, then continue to the application-shell chapter.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Navigation
|
||||
description: Define navigation groups once and connect them to desktop and mobile surfaces.
|
||||
order: 12
|
||||
toc:
|
||||
- id: define-groups
|
||||
title: Define groups
|
||||
- id: provide-navigation
|
||||
title: Provide navigation
|
||||
- id: route-state
|
||||
title: Route state
|
||||
---
|
||||
|
||||
## Define groups {#define-groups}
|
||||
|
||||
Start with typed `NavigationGroup` values. Items may represent direct routes or nested navigation branches.
|
||||
|
||||
```ts
|
||||
import type { NavigationGroup } from "@workspace/blocks/navigation"
|
||||
|
||||
export const navigationGroups: NavigationGroup[] = [
|
||||
{
|
||||
id: "workspace",
|
||||
label: "Workspace",
|
||||
items: [
|
||||
{ id: "dashboard", label: "Dashboard", to: "/dashboard" },
|
||||
{ id: "media", label: "Media", to: "/media" },
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Provide navigation {#provide-navigation}
|
||||
|
||||
`AppLayout` already mounts the provider and the responsive navigation surfaces. For a custom shell, compose them directly:
|
||||
|
||||
```tsx
|
||||
<NavigationProvider groups={navigationGroups}>
|
||||
<PrimaryNavigation groups={navigationGroups} />
|
||||
<MobileNavigationSheet groups={navigationGroups} />
|
||||
</NavigationProvider>
|
||||
```
|
||||
|
||||
## Route state {#route-state}
|
||||
|
||||
Use `resolveNavigationRouteState` for the standard route model. Applications with custom routing can supply a `GetNavigationRouteState` implementation while keeping the same navigation UI.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Understand how the Blocks package turns UI primitives into complete application features.
|
||||
order: 1
|
||||
toc:
|
||||
- id: what-you-will-build
|
||||
title: What you will build
|
||||
- id: design-boundary
|
||||
title: Design boundary
|
||||
- id: tutorial-map
|
||||
title: Tutorial map
|
||||
---
|
||||
|
||||
## What you will build {#what-you-will-build}
|
||||
|
||||
This tutorial builds an application shell step by step. You will begin with the shared layout, connect navigation, then add notifications, chat, media, and appearance preferences.
|
||||
|
||||
By the end, the application owns its data and business rules while `@workspace/blocks` owns the reusable presentation and interaction patterns.
|
||||
|
||||
## Design boundary {#design-boundary}
|
||||
|
||||
Blocks sit above `@workspace/ui`. They combine low-level components into features, but they do not choose your API, database, router configuration, or storage provider.
|
||||
|
||||
> Keep data access in the host application. Pass it into a block through props, query functions, providers, or adapters.
|
||||
|
||||
## Tutorial map {#tutorial-map}
|
||||
|
||||
1. Install the package and global styles.
|
||||
2. Create the application shell.
|
||||
3. Define navigation and route state.
|
||||
4. Connect notifications.
|
||||
5. Add chat and media workflows.
|
||||
6. Finish with persistent appearance controls.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: 外观设置
|
||||
description: 使用可持久化的主题、颜色和紧凑布局偏好完成应用。
|
||||
order: 22
|
||||
toc:
|
||||
- id: provide-ui-state
|
||||
title: 提供 UI 状态
|
||||
- id: apply-preferences
|
||||
title: 应用偏好
|
||||
- id: add-locales
|
||||
title: 添加词典
|
||||
---
|
||||
|
||||
## 提供 UI 状态 {#provide-ui-state}
|
||||
|
||||
在应用根节点附近挂载 `UiStateProvider`。受控 Provider 可以通过变更处理器持久化每次状态更新。
|
||||
|
||||
```tsx
|
||||
<UiStateProvider>
|
||||
<AppearanceController />
|
||||
<App />
|
||||
<ThemeToggleButton />
|
||||
</UiStateProvider>
|
||||
```
|
||||
|
||||
## 应用偏好 {#apply-preferences}
|
||||
|
||||
渲染一次 `AppearanceController`,使当前状态转换为文档类名和主题变量。`useUiState` 读取或更新单个偏好,`useResolvedTheme` 在解析系统模式后返回最终的亮色或暗色主题。
|
||||
|
||||
## 添加词典 {#add-locales}
|
||||
|
||||
只有应用使用这些控件时才添加外观词典:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/blocks/appearance/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
至此应用外壳已经完成。其他功能也只需添加产品实际使用的 Block 词典。
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: 媒体库
|
||||
description: 提供存储 Adapter,并加入浏览、上传、文件夹和资源选择能力。
|
||||
order: 21
|
||||
toc:
|
||||
- id: implement-the-adapter
|
||||
title: 实现 Adapter
|
||||
- id: provide-media
|
||||
title: 提供媒体能力
|
||||
- id: selection-flows
|
||||
title: 选择流程
|
||||
---
|
||||
|
||||
## 实现 Adapter {#implement-the-adapter}
|
||||
|
||||
`MediaAdapter` 是媒体功能完整的持久化边界。通过产品存储服务实现查询、上传、文件夹、更新和删除操作。
|
||||
|
||||
## 提供媒体能力 {#provide-media}
|
||||
|
||||
在媒体界面外挂载一次 Adapter:
|
||||
|
||||
```tsx
|
||||
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
|
||||
|
||||
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
|
||||
<MediaLibrary />
|
||||
</MediaProvider>
|
||||
```
|
||||
|
||||
可选的 `notify` 回调把操作结果转换为应用的 Toast 或通知。
|
||||
|
||||
## 选择流程 {#selection-flows}
|
||||
|
||||
单选或多选资源使用 `MediaPickerDialog`。`readMediaDimensions`、`formatMediaFileSize` 和 `defaultMediaReference` 覆盖上传与预览相关的常用展示工作。
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: 通知
|
||||
description: 接入通知状态,同时避免让 UI 与后端耦合。
|
||||
order: 20
|
||||
toc:
|
||||
- id: notifications-query
|
||||
title: 通知查询
|
||||
---
|
||||
|
||||
## 通知查询 {#notifications-query}
|
||||
|
||||
通知以 TanStack Query 作为数据边界:
|
||||
|
||||
```tsx
|
||||
const notifications = useNotifications({
|
||||
queryKey: ["notifications"],
|
||||
queryFn: loadNotifications,
|
||||
})
|
||||
```
|
||||
|
||||
返回结果包含未读数量、乐观已读更新、操作执行、重新请求以及等待和错误状态。`AppLayout` 可以直接通过相同查询选项创建通知 Sheet。
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: 聊天示例
|
||||
description: 添加响应式会话工作区,并将事件连接到应用服务。
|
||||
order: 30
|
||||
toc:
|
||||
- id: render-the-workspace
|
||||
title: 渲染工作区
|
||||
- id: controlled-state
|
||||
title: 受控状态
|
||||
- id: media-and-pagination
|
||||
title: 媒体与分页
|
||||
---
|
||||
|
||||
## 渲染工作区 {#render-the-workspace}
|
||||
|
||||
`ChatWorkspace` 渲染会话查找、当前线程和消息编辑器:
|
||||
|
||||
```tsx
|
||||
import { ChatWorkspace } from "@workspace/blocks/chats"
|
||||
|
||||
;<ChatWorkspace
|
||||
conversations={conversations}
|
||||
onSend={({ conversation, message }) => sendMessage(conversation, message)}
|
||||
/>
|
||||
```
|
||||
|
||||
## 受控状态 {#controlled-state}
|
||||
|
||||
当前会话、草稿、搜索词和回车发送偏好都可以单独受控。当工作区应自行管理某项状态时,不传对应值即可。
|
||||
|
||||
UI 只发送事件,不选择传输方式或持久化格式。在应用边界将 API 响应转换为 `ChatConversation` 和导出的消息类型。
|
||||
|
||||
## 媒体与分页 {#media-and-pagination}
|
||||
|
||||
当已存储媒体需要签名或转换 URL 时,提供 `resolveMediaUrl`。使用 `onLoadEarlierMessages`、`hasMoreMessages` 和 `isLoadingMoreMessages` 接入历史消息分页。
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: 应用外壳
|
||||
description: 组装共享 Header、Sidebar、面包屑、聊天和通知界面。
|
||||
order: 11
|
||||
toc:
|
||||
- id: render-app-layout
|
||||
title: 渲染 AppLayout
|
||||
- id: layout-inputs
|
||||
title: 布局输入
|
||||
- id: optional-preset
|
||||
title: 可选预设
|
||||
---
|
||||
|
||||
## 渲染 AppLayout {#render-app-layout}
|
||||
|
||||
`AppLayout` 是本教程其余部分的集成点:
|
||||
|
||||
```tsx
|
||||
import { AppLayout } from "@workspace/blocks/layout"
|
||||
|
||||
export function WorkspaceLayout() {
|
||||
return (
|
||||
<AppLayout
|
||||
navigationGroups={navigationGroups}
|
||||
notifications={{
|
||||
queryKey: ["notifications"],
|
||||
queryFn: loadNotifications,
|
||||
}}
|
||||
chatThreads={chatThreads}
|
||||
>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 布局输入 {#layout-inputs}
|
||||
|
||||
应用外壳组合了 `AppSidebar`、`AppBreadcrumb`、`AppQueryIndicator`、`UserMenu`、通知 Sheet 和聊天线程。产品需要更多全局控件时可传入 `headerActions`。
|
||||
|
||||
路由内容仍作为布局的 `children`,因此该包不会限制应用使用的路由器。
|
||||
|
||||
## 可选预设 {#optional-preset}
|
||||
|
||||
`@workspace/blocks/layouts/vega` 提供可选的视觉外壳。它位于独立入口,因此标准布局不会包含预设专属代码。
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: 安装
|
||||
description: 添加教程所需的包、样式和 Providers。
|
||||
order: 10
|
||||
toc:
|
||||
- id: add-the-package
|
||||
title: 添加包
|
||||
- id: import-styles
|
||||
title: 导入样式
|
||||
- id: application-providers
|
||||
title: 应用 Providers
|
||||
---
|
||||
|
||||
## 添加包 {#add-the-package}
|
||||
|
||||
在使用方应用中添加工作区依赖:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/blocks": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Blocks 使用 `@workspace/ui`、React Query 和工作区国际化运行时。工作区包管理器会为本地应用解析这些依赖。
|
||||
|
||||
## 导入样式 {#import-styles}
|
||||
|
||||
在应用入口导入一次 Block 样式:
|
||||
|
||||
```ts
|
||||
import "@workspace/blocks/globals.css"
|
||||
```
|
||||
|
||||
功能模块应始终从显式子路径导入。例如,导入 `@workspace/blocks/media` 不会把聊天功能加入同一个模块图。
|
||||
|
||||
## 应用 Providers {#application-providers}
|
||||
|
||||
完整应用外壳要求上层已经提供 React Query 和国际化。在路由树之前加入这些 Providers,然后继续应用外壳章节。
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: 导航
|
||||
description: 统一定义导航分组,并将其连接到桌面和移动端界面。
|
||||
order: 12
|
||||
toc:
|
||||
- id: define-groups
|
||||
title: 定义分组
|
||||
- id: provide-navigation
|
||||
title: 提供导航
|
||||
- id: route-state
|
||||
title: 路由状态
|
||||
---
|
||||
|
||||
## 定义分组 {#define-groups}
|
||||
|
||||
从类型安全的 `NavigationGroup` 开始。导航项可以是直接路由,也可以是嵌套导航分支。
|
||||
|
||||
```ts
|
||||
import type { NavigationGroup } from "@workspace/blocks/navigation"
|
||||
|
||||
export const navigationGroups: NavigationGroup[] = [
|
||||
{
|
||||
id: "workspace",
|
||||
label: "工作区",
|
||||
items: [
|
||||
{ id: "dashboard", label: "仪表盘", to: "/dashboard" },
|
||||
{ id: "media", label: "媒体", to: "/media" },
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## 提供导航 {#provide-navigation}
|
||||
|
||||
`AppLayout` 已经挂载 Provider 和响应式导航界面。自定义外壳可以直接组合它们:
|
||||
|
||||
```tsx
|
||||
<NavigationProvider groups={navigationGroups}>
|
||||
<PrimaryNavigation groups={navigationGroups} />
|
||||
<MobileNavigationSheet groups={navigationGroups} />
|
||||
</NavigationProvider>
|
||||
```
|
||||
|
||||
## 路由状态 {#route-state}
|
||||
|
||||
标准路由模型使用 `resolveNavigationRouteState`。使用自定义路由的应用可以提供 `GetNavigationRouteState` 实现,同时复用相同导航 UI。
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 Blocks 包如何把 UI 基础组件组合成完整应用功能。
|
||||
order: 1
|
||||
toc:
|
||||
- id: what-you-will-build
|
||||
title: 将要构建的内容
|
||||
- id: design-boundary
|
||||
title: 设计边界
|
||||
- id: tutorial-map
|
||||
title: 教程路线
|
||||
---
|
||||
|
||||
## 将要构建的内容 {#what-you-will-build}
|
||||
|
||||
本教程会逐步构建一个应用外壳。你将从共享布局开始,接入导航,再添加通知、聊天、媒体和外观偏好。
|
||||
|
||||
完成后,应用负责数据和业务规则,`@workspace/blocks` 负责可复用的展示与交互模式。
|
||||
|
||||
## 设计边界 {#design-boundary}
|
||||
|
||||
Blocks 位于 `@workspace/ui` 之上。它们将底层组件组合成完整功能,但不会替你选择 API、数据库、路由配置或存储服务。
|
||||
|
||||
> 将数据访问保留在宿主应用中,通过属性、查询函数、Provider 或 Adapter 传给 Block。
|
||||
|
||||
## 教程路线 {#tutorial-map}
|
||||
|
||||
1. 安装包和全局样式。
|
||||
2. 创建应用外壳。
|
||||
3. 定义导航和路由状态。
|
||||
4. 接入通知。
|
||||
5. 添加聊天和媒体工作流。
|
||||
6. 使用持久化外观控件完成应用。
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Production catalogs
|
||||
description: Understand merging, compact message keys, asset generation, and SSR behavior.
|
||||
order: 20
|
||||
toc:
|
||||
- id: merge-order
|
||||
title: Merge order
|
||||
- id: build-output
|
||||
title: Build output
|
||||
- id: server-rendering
|
||||
title: Server rendering
|
||||
---
|
||||
|
||||
## Merge order {#merge-order}
|
||||
|
||||
Package sources are merged in configured order, followed by the application catalog. An application therefore translates only its own messages unless it intentionally customizes package copy.
|
||||
|
||||
> Import only the locale sources used by the application. Block-specific entries do not import catalogs from unrelated features.
|
||||
|
||||
## Build output {#build-output}
|
||||
|
||||
During production builds, the plugin:
|
||||
|
||||
1. merges configured catalogs for every locale;
|
||||
2. creates one shared schema from semantic message IDs;
|
||||
3. replaces those IDs with deterministic compact keys;
|
||||
4. emits one content-hashed JSON asset per locale.
|
||||
|
||||
The private schema is diagnostic build state and is not shipped to the browser.
|
||||
|
||||
## Server rendering {#server-rendering}
|
||||
|
||||
The SSR bundle embeds the matching compact catalog. Browser navigation loads only the selected locale asset, so adding languages does not duplicate every translation in the main application bundle.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: CLI and Message Studio
|
||||
description: Add locales, extract messages, compile catalogs, and run the translation interface.
|
||||
order: 21
|
||||
toc:
|
||||
- id: scripts
|
||||
title: Scripts
|
||||
- id: commands
|
||||
title: Commands
|
||||
- id: daily-workflow
|
||||
title: Daily workflow
|
||||
---
|
||||
|
||||
## Scripts {#scripts}
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"i18n": "workspace-i18n",
|
||||
"i18n:extract": "workspace-i18n extract",
|
||||
"i18n:compile": "workspace-i18n compile",
|
||||
"i18n:ui": "workspace-i18n ui"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Commands {#commands}
|
||||
|
||||
- `new <locale>` validates a BCP 47 tag, updates configuration, and creates the catalog.
|
||||
- `extract` finds application messages and updates catalogs.
|
||||
- `compile` produces TypeScript catalog modules.
|
||||
- `ui` starts Message Studio with Extract and Compile actions.
|
||||
|
||||
Every command accepts `--project <path>` when it is invoked outside the application directory.
|
||||
|
||||
## Daily workflow {#daily-workflow}
|
||||
|
||||
```sh
|
||||
bun run i18n extract
|
||||
bun run i18n ui
|
||||
bun run i18n compile
|
||||
```
|
||||
|
||||
Commit the application configuration and the `en-US` and `zh-Hans` catalogs. Generated private schema data remains build output.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Devtool
|
||||
description: Inspect and edit catalogs from a floating development interface.
|
||||
order: 30
|
||||
toc:
|
||||
- id: mount-the-devtool
|
||||
title: Mount the Devtool
|
||||
- id: theme-and-language
|
||||
title: Theme and language
|
||||
- id: custom-surfaces
|
||||
title: Custom surfaces
|
||||
---
|
||||
|
||||
## Mount the Devtool {#mount-the-devtool}
|
||||
|
||||
Mount the Devtool inside `I18nProvider` and only during development:
|
||||
|
||||
```tsx
|
||||
import { I18nDevtool } from "@workspace/i18n/devtool"
|
||||
|
||||
{
|
||||
import.meta.env.DEV && (
|
||||
<I18nDevtool locale="zh-Hans" dark={'[data-theme="dark"]'} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The component renders through a portal, so application overflow and stacking contexts do not clip it.
|
||||
|
||||
## Theme and language {#theme-and-language}
|
||||
|
||||
The `dark` option accepts a class name or a CSS selector. Control-panel language is independent from the translated application locale, supports `en-US` and `zh-Hans`, and falls back from `navigator.languages` to `en-US`.
|
||||
|
||||
## Custom surfaces {#custom-surfaces}
|
||||
|
||||
Use `MessagePanel`, `MessageRepositoryProvider`, and `createHttpMessageRepository` when the application needs a custom development interface instead of the default floating panel.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Runtime
|
||||
description: Load a locale catalog and consume messages through React components and hooks.
|
||||
order: 11
|
||||
toc:
|
||||
- id: load-the-catalog
|
||||
title: Load the catalog
|
||||
- id: provide-the-locale
|
||||
title: Provide the locale
|
||||
- id: translate-content
|
||||
title: Translate content
|
||||
---
|
||||
|
||||
## Load the catalog {#load-the-catalog}
|
||||
|
||||
The generated catalog loader works in development, production, and SSR builds:
|
||||
|
||||
```tsx
|
||||
import { use } from "react"
|
||||
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
|
||||
|
||||
const messages = use(loadMessageCatalog(locale))
|
||||
```
|
||||
|
||||
## Provide the locale {#provide-the-locale}
|
||||
|
||||
```tsx
|
||||
<I18nProvider
|
||||
locale={locale}
|
||||
locales={[
|
||||
{ locale: "en-US", label: "English" },
|
||||
{ locale: "zh-Hans", label: "简体中文" },
|
||||
]}
|
||||
catalogs={{ [locale]: messages }}
|
||||
>
|
||||
{children}
|
||||
</I18nProvider>
|
||||
```
|
||||
|
||||
## Translate content {#translate-content}
|
||||
|
||||
Use `Translate` for JSX content, `useTranslate` for an imperative translation function, and `useMessage` for a single descriptor. `useLocale`, `useLocales`, and `useFormatters` expose locale state and `Intl` formatters.
|
||||
|
||||
`Translate` delegates to Lingui without adding a wrapper DOM element.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Project setup
|
||||
description: Create the application configuration and mount the Vite integration.
|
||||
order: 10
|
||||
toc:
|
||||
- id: configuration
|
||||
title: Configuration
|
||||
- id: catalog-sources
|
||||
title: Catalog sources
|
||||
- id: vite-plugin
|
||||
title: Vite plugin
|
||||
---
|
||||
|
||||
## Configuration {#configuration}
|
||||
|
||||
Create `i18n.config.json` in the consuming application:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceLocale": "en-US",
|
||||
"locales": ["en-US", "zh-Hans"],
|
||||
"catalogPath": "src/locales/{locale}/messages",
|
||||
"catalogSources": ["@workspace/ui/locales/{locale}"],
|
||||
"include": ["src", "../../packages/ui/src"],
|
||||
"exclude": ["**/*.test.{ts,tsx}"]
|
||||
}
|
||||
```
|
||||
|
||||
This file is the application's single persistent internationalization configuration. CLI commands create temporary Lingui configuration only for the child process.
|
||||
|
||||
The workspace ships exactly two locales: American English (`en-US`) and Simplified Chinese (`zh-Hans`). Use those same locale keys for application catalogs and package catalog sources.
|
||||
|
||||
## Catalog sources {#catalog-sources}
|
||||
|
||||
Sources are merged from left to right, and the application catalog is always last. Put reusable package catalogs first so product-specific translations can override them.
|
||||
|
||||
## Vite plugin {#vite-plugin}
|
||||
|
||||
```ts
|
||||
import { i18n } from "@workspace/i18n/vite"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [i18n()],
|
||||
})
|
||||
```
|
||||
|
||||
The plugin serves development catalog APIs and exposes the generated loader through `@workspace/i18n/catalogs`.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Follow the complete path from application configuration to translated production catalogs.
|
||||
order: 1
|
||||
toc:
|
||||
- id: ownership-model
|
||||
title: Ownership model
|
||||
- id: workflow
|
||||
title: Workflow
|
||||
- id: package-entries
|
||||
title: Package entries
|
||||
---
|
||||
|
||||
## Ownership model {#ownership-model}
|
||||
|
||||
Every application owns its `i18n.config.json` and application catalogs. Reusable packages may ship built-in catalogs, but the application chooses which ones to merge and may override their messages.
|
||||
|
||||
This keeps product copy under application control without duplicating translations from shared packages.
|
||||
|
||||
## Workflow {#workflow}
|
||||
|
||||
1. Configure locales and catalog sources.
|
||||
2. Install the Vite plugin.
|
||||
3. Load one catalog into `I18nProvider`.
|
||||
4. Extract and translate application messages.
|
||||
5. Use the Devtool or Message Studio during development.
|
||||
6. Compile compact, cacheable production assets.
|
||||
|
||||
## Package entries {#package-entries}
|
||||
|
||||
- `@workspace/i18n` provides the React runtime.
|
||||
- `@workspace/i18n/catalogs` loads generated catalogs.
|
||||
- `@workspace/i18n/devtool` provides development surfaces.
|
||||
- `@workspace/i18n/vite` configures Vite.
|
||||
- `workspace-i18n` exposes the CLI.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 生产环境词典
|
||||
description: 理解词典合并、紧凑消息键、资源生成和 SSR 行为。
|
||||
order: 20
|
||||
toc:
|
||||
- id: merge-order
|
||||
title: 合并顺序
|
||||
- id: build-output
|
||||
title: 构建产物
|
||||
- id: server-rendering
|
||||
title: 服务端渲染
|
||||
---
|
||||
|
||||
## 合并顺序 {#merge-order}
|
||||
|
||||
包词典按配置顺序合并,应用词典最后合并。因此应用默认只需翻译自身消息,除非它有意自定义包内文案。
|
||||
|
||||
> 只导入应用实际使用的词典来源。特定 Block 的入口不会导入无关功能的词典。
|
||||
|
||||
## 构建产物 {#build-output}
|
||||
|
||||
生产构建期间,插件会:
|
||||
|
||||
1. 为每种语言合并配置的词典;
|
||||
2. 根据语义消息 ID 创建共享 Schema;
|
||||
3. 将这些 ID 替换为确定的紧凑键;
|
||||
4. 为每种语言输出一个带内容哈希的 JSON 资源。
|
||||
|
||||
私有 Schema 仅用于构建诊断,不会发送到浏览器。
|
||||
|
||||
## 服务端渲染 {#server-rendering}
|
||||
|
||||
SSR Bundle 会嵌入匹配的紧凑词典。浏览器导航只加载选中语言的资源,因此增加语言不会让主应用 Bundle 重复包含所有翻译。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: CLI 与 Message Studio
|
||||
description: 添加语言、提取消息、编译词典并运行翻译界面。
|
||||
order: 21
|
||||
toc:
|
||||
- id: scripts
|
||||
title: Scripts
|
||||
- id: commands
|
||||
title: 命令
|
||||
- id: daily-workflow
|
||||
title: 日常工作流
|
||||
---
|
||||
|
||||
## Scripts {#scripts}
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"i18n": "workspace-i18n",
|
||||
"i18n:extract": "workspace-i18n extract",
|
||||
"i18n:compile": "workspace-i18n compile",
|
||||
"i18n:ui": "workspace-i18n ui"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令 {#commands}
|
||||
|
||||
- `new <locale>` 校验 BCP 47 标签、更新配置并创建词典。
|
||||
- `extract` 查找应用消息并更新词典。
|
||||
- `compile` 生成 TypeScript 词典模块。
|
||||
- `ui` 启动带有 Extract 和 Compile 操作的 Message Studio。
|
||||
|
||||
在应用目录外执行时,每个命令都接受 `--project <path>`。
|
||||
|
||||
## 日常工作流 {#daily-workflow}
|
||||
|
||||
```sh
|
||||
bun run i18n extract
|
||||
bun run i18n ui
|
||||
bun run i18n compile
|
||||
```
|
||||
|
||||
提交应用配置以及 `en-US`、`zh-Hans` 词典。生成的私有 Schema 数据仍属于构建产物。
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Devtool
|
||||
description: 通过浮动开发界面检查和编辑词典。
|
||||
order: 30
|
||||
toc:
|
||||
- id: mount-the-devtool
|
||||
title: 挂载 Devtool
|
||||
- id: theme-and-language
|
||||
title: 主题与语言
|
||||
- id: custom-surfaces
|
||||
title: 自定义界面
|
||||
---
|
||||
|
||||
## 挂载 Devtool {#mount-the-devtool}
|
||||
|
||||
将 Devtool 挂载在 `I18nProvider` 内,并且只在开发环境启用:
|
||||
|
||||
```tsx
|
||||
import { I18nDevtool } from "@workspace/i18n/devtool"
|
||||
|
||||
{
|
||||
import.meta.env.DEV && (
|
||||
<I18nDevtool locale="zh-Hans" dark={'[data-theme="dark"]'} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
组件通过 Portal 渲染,因此不会被应用的 overflow 或层叠上下文裁剪。
|
||||
|
||||
## 主题与语言 {#theme-and-language}
|
||||
|
||||
`dark` 选项接受类名或 CSS 选择器。控制面板语言独立于被翻译的应用语言,支持 `en-US` 和 `zh-Hans`,并会从 `navigator.languages` 回退到 `en-US`。
|
||||
|
||||
## 自定义界面 {#custom-surfaces}
|
||||
|
||||
当应用需要替换默认浮动面板时,可使用 `MessagePanel`、`MessageRepositoryProvider` 和 `createHttpMessageRepository` 构建自定义开发界面。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: 运行时
|
||||
description: 加载语言词典,并通过 React 组件和 Hooks 使用消息。
|
||||
order: 11
|
||||
toc:
|
||||
- id: load-the-catalog
|
||||
title: 加载词典
|
||||
- id: provide-the-locale
|
||||
title: 提供语言
|
||||
- id: translate-content
|
||||
title: 翻译内容
|
||||
---
|
||||
|
||||
## 加载词典 {#load-the-catalog}
|
||||
|
||||
生成的词典加载器同时支持开发、生产和 SSR 构建:
|
||||
|
||||
```tsx
|
||||
import { use } from "react"
|
||||
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
|
||||
|
||||
const messages = use(loadMessageCatalog(locale))
|
||||
```
|
||||
|
||||
## 提供语言 {#provide-the-locale}
|
||||
|
||||
```tsx
|
||||
<I18nProvider
|
||||
locale={locale}
|
||||
locales={[
|
||||
{ locale: "en-US", label: "English" },
|
||||
{ locale: "zh-Hans", label: "简体中文" },
|
||||
]}
|
||||
catalogs={{ [locale]: messages }}
|
||||
>
|
||||
{children}
|
||||
</I18nProvider>
|
||||
```
|
||||
|
||||
## 翻译内容 {#translate-content}
|
||||
|
||||
JSX 内容使用 `Translate`,命令式翻译函数使用 `useTranslate`,单个描述符使用 `useMessage`。`useLocale`、`useLocales` 和 `useFormatters` 提供语言状态与 `Intl` 格式化器。
|
||||
|
||||
`Translate` 会直接委托给 Lingui,不会增加额外的 DOM 包装元素。
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: 项目配置
|
||||
description: 创建应用配置并接入 Vite 插件。
|
||||
order: 10
|
||||
toc:
|
||||
- id: configuration
|
||||
title: 配置文件
|
||||
- id: catalog-sources
|
||||
title: 词典来源
|
||||
- id: vite-plugin
|
||||
title: Vite 插件
|
||||
---
|
||||
|
||||
## 配置文件 {#configuration}
|
||||
|
||||
在使用方应用中创建 `i18n.config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceLocale": "en-US",
|
||||
"locales": ["en-US", "zh-Hans"],
|
||||
"catalogPath": "src/locales/{locale}/messages",
|
||||
"catalogSources": ["@workspace/ui/locales/{locale}"],
|
||||
"include": ["src", "../../packages/ui/src"],
|
||||
"exclude": ["**/*.test.{ts,tsx}"]
|
||||
}
|
||||
```
|
||||
|
||||
该文件是应用唯一的持久国际化配置。CLI 命令只为子进程创建临时 Lingui 配置。
|
||||
|
||||
工作区只提供简体中文(`zh-Hans`)和美式英语(`en-US`)。应用词典和包词典来源应使用相同的语言键。
|
||||
|
||||
## 词典来源 {#catalog-sources}
|
||||
|
||||
词典来源按从左到右的顺序合并,应用词典始终位于最后。将可复用包词典放在前面,使产品特有翻译可以覆盖它们。
|
||||
|
||||
## Vite 插件 {#vite-plugin}
|
||||
|
||||
```ts
|
||||
import { i18n } from "@workspace/i18n/vite"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [i18n()],
|
||||
})
|
||||
```
|
||||
|
||||
插件会提供开发词典 API,并通过 `@workspace/i18n/catalogs` 暴露生成的加载器。
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解从应用配置到生产环境翻译词典的完整流程。
|
||||
order: 1
|
||||
toc:
|
||||
- id: ownership-model
|
||||
title: 所有权模型
|
||||
- id: workflow
|
||||
title: 工作流
|
||||
- id: package-entries
|
||||
title: 包入口
|
||||
---
|
||||
|
||||
## 所有权模型 {#ownership-model}
|
||||
|
||||
每个应用都拥有自己的 `i18n.config.json` 和应用词典。可复用包可以附带内置词典,但由应用决定合并哪些词典,也可以覆盖其中的消息。
|
||||
|
||||
这样既能让产品文案始终由应用控制,也不会重复维护共享包的翻译。
|
||||
|
||||
## 工作流 {#workflow}
|
||||
|
||||
1. 配置语言和词典来源。
|
||||
2. 安装 Vite 插件。
|
||||
3. 将一个语言词典加载到 `I18nProvider`。
|
||||
4. 提取并翻译应用消息。
|
||||
5. 开发时使用 Devtool 或 Message Studio。
|
||||
6. 编译紧凑且可缓存的生产资源。
|
||||
|
||||
## 包入口 {#package-entries}
|
||||
|
||||
- `@workspace/i18n` 提供 React 运行时。
|
||||
- `@workspace/i18n/catalogs` 加载生成的词典。
|
||||
- `@workspace/i18n/devtool` 提供开发界面。
|
||||
- `@workspace/i18n/vite` 配置 Vite。
|
||||
- `workspace-i18n` 提供命令行工具。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Define an action
|
||||
description: Add product-specific behavior while preserving automatic dependency collection.
|
||||
order: 20
|
||||
toc:
|
||||
- id: declare-the-action
|
||||
title: Declare the action
|
||||
- id: custom-control
|
||||
title: Custom control
|
||||
- id: typed-values
|
||||
title: Typed values
|
||||
---
|
||||
|
||||
## Declare the action {#declare-the-action}
|
||||
|
||||
Keep the command and its editor dependencies in one definition:
|
||||
|
||||
```tsx
|
||||
const Mention = defineLexicalAction({
|
||||
name: "mention",
|
||||
label: "Insert mention",
|
||||
nodes: [MentionNode],
|
||||
plugins: [MentionPopoverPlugin],
|
||||
execute: ({ editor }) => openMentionPicker(editor),
|
||||
})
|
||||
```
|
||||
|
||||
The node and plugin are enabled whenever `<Mention />` appears inside `LexicalActions`.
|
||||
|
||||
## Custom control {#custom-control}
|
||||
|
||||
```tsx
|
||||
<Mention>
|
||||
{({ disabled, execute }) => (
|
||||
<MentionButton disabled={disabled} onSelect={execute} />
|
||||
)}
|
||||
</Mention>
|
||||
```
|
||||
|
||||
Replacing the visible control does not change dependency collection.
|
||||
|
||||
## Typed values {#typed-values}
|
||||
|
||||
The render context exposes `execute(value?)` with the action's value type. `onClick` is the no-argument shortcut for ordinary buttons.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Localize the editor
|
||||
description: Connect built-in labels and custom actions to the workspace internationalization runtime.
|
||||
order: 21
|
||||
toc:
|
||||
- id: add-the-catalog
|
||||
title: Add the catalog
|
||||
- id: fallback
|
||||
title: Fallback
|
||||
- id: custom-labels
|
||||
title: Custom labels
|
||||
---
|
||||
|
||||
## Add the catalog {#add-the-catalog}
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/lexical/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
The package ships American English (`en-US`) and Simplified Chinese (`zh-Hans`) editor catalogs.
|
||||
|
||||
## Fallback {#fallback}
|
||||
|
||||
Built-in controls follow the active `@workspace/i18n` provider. Outside a provider, they fall back to English so the editor remains usable in isolated previews and tests.
|
||||
|
||||
## Custom labels {#custom-labels}
|
||||
|
||||
An external action label can be a plain string or a message descriptor containing `id` and `message`. Descriptors participate in the same extraction workflow as application copy.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Add media
|
||||
description: Insert uploaded media and resolve images pasted or dragged into the editor.
|
||||
order: 30
|
||||
toc:
|
||||
- id: image-and-video
|
||||
title: Image and video
|
||||
- id: clipboard-images
|
||||
title: Clipboard images
|
||||
- id: editing-media
|
||||
title: Editing media
|
||||
---
|
||||
|
||||
## Image and video {#image-and-video}
|
||||
|
||||
The default `Image` and `Video` actions open built-in input dialogs. A custom uploader can hand the completed payload directly to the action:
|
||||
|
||||
```tsx
|
||||
<Image>
|
||||
{({ execute }) => (
|
||||
<ImageUploader
|
||||
onUploaded={({ src, alt, caption }) => execute({ src, alt, caption })}
|
||||
/>
|
||||
)}
|
||||
</Image>
|
||||
```
|
||||
|
||||
## Clipboard images {#clipboard-images}
|
||||
|
||||
The full preset includes `ClipboardImages`. For production, resolve pasted and dropped files through object storage:
|
||||
|
||||
```tsx
|
||||
<ClipboardImages
|
||||
resolveImage={async (file, { reportProgress, signal }) => {
|
||||
const uploaded = await uploadImage(file, {
|
||||
signal,
|
||||
onProgress: reportProgress,
|
||||
})
|
||||
return { alt: file.name, src: uploaded.url }
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Editing media {#editing-media}
|
||||
|
||||
Selected images support resizing, captions, alignment, and visible or keyboard deletion. Non-image clipboard files remain under normal browser handling.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Arrange actions
|
||||
description: Replace the preset with explicit actions and place controls in the correct editing surfaces.
|
||||
order: 11
|
||||
toc:
|
||||
- id: explicit-actions
|
||||
title: Explicit actions
|
||||
- id: placement
|
||||
title: Placement
|
||||
- id: action-groups
|
||||
title: Action groups
|
||||
---
|
||||
|
||||
## Explicit actions {#explicit-actions}
|
||||
|
||||
Switch from a preset to children when the product needs exact feature and ordering control:
|
||||
|
||||
```tsx
|
||||
<LexicalActions>
|
||||
<Undo />
|
||||
<Redo />
|
||||
<Bold />
|
||||
<Italic />
|
||||
<Link />
|
||||
</LexicalActions>
|
||||
```
|
||||
|
||||
## Placement {#placement}
|
||||
|
||||
Actions default to the fixed toolbar. The `in` prop accepts one area or several:
|
||||
|
||||
```tsx
|
||||
<Bold in={["toolbar", "bubble"]} />
|
||||
<Date in="bubble" />
|
||||
<ClearFormatting in="footer" />
|
||||
```
|
||||
|
||||
Hidden actions provide editor behavior without rendering a control. `DraggableBlocks` uses this pattern.
|
||||
|
||||
## Action groups {#action-groups}
|
||||
|
||||
`ActionGroup` can be a logical group or a visible menu. Use a menu for mutually related choices such as normal text, headings, quotes, and list styles.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Create the editor
|
||||
description: Render the root, content surface, toolbars, and a complete default action set.
|
||||
order: 10
|
||||
toc:
|
||||
- id: styles
|
||||
title: Styles
|
||||
- id: editor-structure
|
||||
title: Editor structure
|
||||
- id: presets
|
||||
title: Presets
|
||||
---
|
||||
|
||||
## Styles {#styles}
|
||||
|
||||
Import the editor stylesheet once in the application:
|
||||
|
||||
```ts
|
||||
import "@workspace/lexical/globals.css"
|
||||
```
|
||||
|
||||
## Editor structure {#editor-structure}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
LexicalActions,
|
||||
LexicalBubbleToolbar,
|
||||
LexicalContent,
|
||||
LexicalFixedToolbar,
|
||||
LexicalFooter,
|
||||
LexicalRoot,
|
||||
} from "@workspace/lexical"
|
||||
|
||||
;<LexicalRoot value="" onChange={setHtml}>
|
||||
<LexicalActions useDefaults="full" />
|
||||
<LexicalFixedToolbar />
|
||||
<LexicalContent placeholder="Start writing…" />
|
||||
<LexicalBubbleToolbar />
|
||||
<LexicalFooter />
|
||||
</LexicalRoot>
|
||||
```
|
||||
|
||||
Toolbars and the footer render no DOM when their region has no actions and no custom children.
|
||||
|
||||
## Presets {#presets}
|
||||
|
||||
Choose `minimal` for basic text editing or `full` for the complete built-in feature set. Preset mode intentionally does not accept manual action children.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn the editor's declarative action model before assembling a complete editing surface.
|
||||
order: 1
|
||||
toc:
|
||||
- id: mental-model
|
||||
title: Mental model
|
||||
- id: tutorial-map
|
||||
title: Tutorial map
|
||||
- id: stable-capabilities
|
||||
title: Stable capabilities
|
||||
---
|
||||
|
||||
## Mental model {#mental-model}
|
||||
|
||||
`@workspace/lexical` treats editor actions as declarations. Each action describes its behavior and the nodes, plugins, or embeds it requires. `LexicalRoot` collects those requirements before creating the editor.
|
||||
|
||||
Toolbars decide where an action appears; they do not separately register editor capabilities.
|
||||
|
||||
## Tutorial map {#tutorial-map}
|
||||
|
||||
You will create the root and content surface, choose a preset, customize action placement, define an application action, add media uploads, and finish with localized labels.
|
||||
|
||||
## Stable capabilities {#stable-capabilities}
|
||||
|
||||
Actions, nodes, and plugins are fixed when an editor instance is created. Give `LexicalRoot` a new React `key` when the application needs to replace the entire capability set.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: 定义 Action
|
||||
description: 在保留自动依赖收集能力的同时添加产品专属行为。
|
||||
order: 20
|
||||
toc:
|
||||
- id: declare-the-action
|
||||
title: 声明 Action
|
||||
- id: custom-control
|
||||
title: 自定义控件
|
||||
- id: typed-values
|
||||
title: 类型化参数
|
||||
---
|
||||
|
||||
## 声明 Action {#declare-the-action}
|
||||
|
||||
将命令与其编辑器依赖放在同一个定义中:
|
||||
|
||||
```tsx
|
||||
const Mention = defineLexicalAction({
|
||||
name: "mention",
|
||||
label: "插入提及",
|
||||
nodes: [MentionNode],
|
||||
plugins: [MentionPopoverPlugin],
|
||||
execute: ({ editor }) => openMentionPicker(editor),
|
||||
})
|
||||
```
|
||||
|
||||
只要 `<Mention />` 出现在 `LexicalActions` 内,它需要的节点和插件就会自动启用。
|
||||
|
||||
## 自定义控件 {#custom-control}
|
||||
|
||||
```tsx
|
||||
<Mention>
|
||||
{({ disabled, execute }) => (
|
||||
<MentionButton disabled={disabled} onSelect={execute} />
|
||||
)}
|
||||
</Mention>
|
||||
```
|
||||
|
||||
替换可见控件不会改变依赖收集结果。
|
||||
|
||||
## 类型化参数 {#typed-values}
|
||||
|
||||
渲染上下文会公开带有 Action 参数类型的 `execute(value?)`。普通按钮可以使用无参数快捷方式 `onClick`。
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: 编辑器本地化
|
||||
description: 将内置标签和自定义 Actions 接入工作区国际化运行时。
|
||||
order: 21
|
||||
toc:
|
||||
- id: add-the-catalog
|
||||
title: 添加词典
|
||||
- id: fallback
|
||||
title: 回退行为
|
||||
- id: custom-labels
|
||||
title: 自定义标签
|
||||
---
|
||||
|
||||
## 添加词典 {#add-the-catalog}
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/lexical/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
该包提供简体中文(`zh-Hans`)和美式英语(`en-US`)编辑器词典。
|
||||
|
||||
## 回退行为 {#fallback}
|
||||
|
||||
内置控件跟随当前 `@workspace/i18n` Provider。在 Provider 外部会回退为英文,使编辑器在独立预览和测试中仍可使用。
|
||||
|
||||
## 自定义标签 {#custom-labels}
|
||||
|
||||
外部 Action 标签可以是普通字符串,也可以是包含 `id` 和 `message` 的消息描述符。描述符会参与和应用文案相同的提取流程。
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: 添加媒体
|
||||
description: 插入已上传媒体,并处理粘贴或拖入编辑器的图片。
|
||||
order: 30
|
||||
toc:
|
||||
- id: image-and-video
|
||||
title: 图片和视频
|
||||
- id: clipboard-images
|
||||
title: 剪贴板图片
|
||||
- id: editing-media
|
||||
title: 编辑媒体
|
||||
---
|
||||
|
||||
## 图片和视频 {#image-and-video}
|
||||
|
||||
默认的 `Image` 和 `Video` Actions 会打开内置输入对话框。自定义上传器可以把完成后的数据直接交给 Action:
|
||||
|
||||
```tsx
|
||||
<Image>
|
||||
{({ execute }) => (
|
||||
<ImageUploader
|
||||
onUploaded={({ src, alt, caption }) => execute({ src, alt, caption })}
|
||||
/>
|
||||
)}
|
||||
</Image>
|
||||
```
|
||||
|
||||
## 剪贴板图片 {#clipboard-images}
|
||||
|
||||
完整预设包含 `ClipboardImages`。生产环境应通过对象存储解析粘贴和拖放的文件:
|
||||
|
||||
```tsx
|
||||
<ClipboardImages
|
||||
resolveImage={async (file, { reportProgress, signal }) => {
|
||||
const uploaded = await uploadImage(file, {
|
||||
signal,
|
||||
onProgress: reportProgress,
|
||||
})
|
||||
return { alt: file.name, src: uploaded.url }
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## 编辑媒体 {#editing-media}
|
||||
|
||||
选中的图片支持调整尺寸、编辑说明、对齐,以及可见按钮或键盘删除。非图片剪贴板文件继续交由浏览器的默认行为处理。
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 编排操作
|
||||
description: 用显式 Actions 替换预设,并将控件放入正确的编辑区域。
|
||||
order: 11
|
||||
toc:
|
||||
- id: explicit-actions
|
||||
title: 显式 Actions
|
||||
- id: placement
|
||||
title: 放置位置
|
||||
- id: action-groups
|
||||
title: Action 分组
|
||||
---
|
||||
|
||||
## 显式 Actions {#explicit-actions}
|
||||
|
||||
当产品需要精确控制功能和顺序时,将预设切换为子元素:
|
||||
|
||||
```tsx
|
||||
<LexicalActions>
|
||||
<Undo />
|
||||
<Redo />
|
||||
<Bold />
|
||||
<Italic />
|
||||
<Link />
|
||||
</LexicalActions>
|
||||
```
|
||||
|
||||
## 放置位置 {#placement}
|
||||
|
||||
Actions 默认显示在固定工具栏中。`in` 属性接受一个或多个区域:
|
||||
|
||||
```tsx
|
||||
<Bold in={["toolbar", "bubble"]} />
|
||||
<Date in="bubble" />
|
||||
<ClearFormatting in="footer" />
|
||||
```
|
||||
|
||||
隐藏 Action 可以提供编辑器行为而不渲染控件,`DraggableBlocks` 就使用了这种模式。
|
||||
|
||||
## Action 分组 {#action-groups}
|
||||
|
||||
`ActionGroup` 可以是逻辑分组,也可以是可见菜单。普通文本、标题、引用和列表样式等相互关联的选择适合放入菜单。
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: 创建编辑器
|
||||
description: 渲染根节点、内容区域、工具栏和完整的默认 Action 集合。
|
||||
order: 10
|
||||
toc:
|
||||
- id: styles
|
||||
title: 样式
|
||||
- id: editor-structure
|
||||
title: 编辑器结构
|
||||
- id: presets
|
||||
title: 预设
|
||||
---
|
||||
|
||||
## 样式 {#styles}
|
||||
|
||||
在应用中导入一次编辑器样式:
|
||||
|
||||
```ts
|
||||
import "@workspace/lexical/globals.css"
|
||||
```
|
||||
|
||||
## 编辑器结构 {#editor-structure}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
LexicalActions,
|
||||
LexicalBubbleToolbar,
|
||||
LexicalContent,
|
||||
LexicalFixedToolbar,
|
||||
LexicalFooter,
|
||||
LexicalRoot,
|
||||
} from "@workspace/lexical"
|
||||
|
||||
;<LexicalRoot value="" onChange={setHtml}>
|
||||
<LexicalActions useDefaults="full" />
|
||||
<LexicalFixedToolbar />
|
||||
<LexicalContent placeholder="开始输入…" />
|
||||
<LexicalBubbleToolbar />
|
||||
<LexicalFooter />
|
||||
</LexicalRoot>
|
||||
```
|
||||
|
||||
当某个区域没有 Actions 或自定义子元素时,对应工具栏和 Footer 不会渲染 DOM。
|
||||
|
||||
## 预设 {#presets}
|
||||
|
||||
基础文本编辑选择 `minimal`,完整内置功能选择 `full`。预设模式有意不接受手动 Action 子元素。
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 在组装完整编辑界面之前,先理解编辑器的声明式 Action 模型。
|
||||
order: 1
|
||||
toc:
|
||||
- id: mental-model
|
||||
title: 心智模型
|
||||
- id: tutorial-map
|
||||
title: 教程路线
|
||||
- id: stable-capabilities
|
||||
title: 稳定的能力集合
|
||||
---
|
||||
|
||||
## 心智模型 {#mental-model}
|
||||
|
||||
`@workspace/lexical` 将编辑器操作视为声明。每个 Action 描述自身行为及其需要的节点、插件或嵌入内容。`LexicalRoot` 会在创建编辑器前收集这些依赖。
|
||||
|
||||
工具栏只决定 Action 出现的位置,不负责单独注册编辑器能力。
|
||||
|
||||
## 教程路线 {#tutorial-map}
|
||||
|
||||
你将创建根节点和内容区域、选择预设、自定义 Action 位置、定义应用专属 Action、添加媒体上传,最后接入本地化标签。
|
||||
|
||||
## 稳定的能力集合 {#stable-capabilities}
|
||||
|
||||
编辑器实例创建后,其 Actions、节点和插件保持固定。当应用需要替换整套能力时,请为 `LexicalRoot` 提供新的 React `key`。
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Lifecycle and persistence
|
||||
description: Coordinate SSR, asynchronous persistence, browser synchronization, and external input validation.
|
||||
order: 20
|
||||
toc:
|
||||
- id: server-snapshots
|
||||
title: Server snapshots
|
||||
- id: persist-updates
|
||||
title: Persist updates
|
||||
- id: preference-effects
|
||||
title: Preference effects
|
||||
- id: validate-updates
|
||||
title: Validate updates
|
||||
---
|
||||
|
||||
## Server snapshots {#server-snapshots}
|
||||
|
||||
`initialPreferences` is captured when the provider creates its store. It remains the server and hydration snapshot; changing that prop later does not reset local preferences. Remount the provider when the application intentionally switches to a different preference identity.
|
||||
|
||||
## Persist updates {#persist-updates}
|
||||
|
||||
`onPreferenceChange` runs only when a value actually changes. It may return a promise, but local updates are not blocked while persistence completes. Handle retries, errors, and rollback behavior in the application boundary.
|
||||
|
||||
## Preference effects {#preference-effects}
|
||||
|
||||
Effects connect preferences to APIs outside React:
|
||||
|
||||
```tsx
|
||||
const documentEffects = [
|
||||
{
|
||||
layoutEffect: ({ preferences }) => {
|
||||
document.documentElement.dataset.theme = preferences["theme-mode"]
|
||||
},
|
||||
effect: ({ store }) => {
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const listener = () => synchronizeTheme(store.getSnapshot(), media)
|
||||
|
||||
media.addEventListener("change", listener)
|
||||
return () => media.removeEventListener("change", listener)
|
||||
},
|
||||
},
|
||||
] satisfies readonly PreferenceEffect[]
|
||||
```
|
||||
|
||||
Use `layoutEffect` for DOM changes that must happen before paint. Use `effect` for subscriptions. Keep the effects array reference stable because callbacks and cleanup functions run again whenever the snapshot or array changes.
|
||||
|
||||
## Validate updates {#validate-updates}
|
||||
|
||||
Create a type guard from the same definitions before accepting data from an untyped boundary:
|
||||
|
||||
```ts
|
||||
const isPreferenceUpdate = createPreferenceUpdateGuard(definitions)
|
||||
|
||||
if (isPreferenceUpdate(payload)) {
|
||||
await savePreference(payload)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Theme preference
|
||||
description: Build a complete light, dark, and system theme preference flow.
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-theme-mode
|
||||
title: Define theme mode
|
||||
- id: apply-the-theme
|
||||
title: Apply the theme
|
||||
- id: render-a-control
|
||||
title: Render a control
|
||||
---
|
||||
|
||||
## Define theme mode {#define-theme-mode}
|
||||
|
||||
```ts
|
||||
import "@workspace/preferences"
|
||||
|
||||
declare module "@workspace/preferences" {
|
||||
interface PreferencesCustom {
|
||||
"theme-mode": "light" | "dark" | "system"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Load the cookie on the server and pass the parsed value through `initialPreferences` so server markup and hydration share the same snapshot.
|
||||
|
||||
## Apply the theme {#apply-the-theme}
|
||||
|
||||
```tsx
|
||||
const themeEffects = [
|
||||
{
|
||||
layoutEffect: ({ preferences }) => {
|
||||
const systemDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches
|
||||
const dark =
|
||||
preferences["theme-mode"] === "dark" ||
|
||||
(preferences["theme-mode"] === "system" && systemDark)
|
||||
|
||||
document.documentElement.classList.toggle("dark", dark)
|
||||
document.documentElement.style.colorScheme = dark ? "dark" : "light"
|
||||
},
|
||||
},
|
||||
] satisfies readonly PreferenceEffect[]
|
||||
```
|
||||
|
||||
## Render a control {#render-a-control}
|
||||
|
||||
```tsx
|
||||
function ThemeModeSelect() {
|
||||
const [themeMode, setThemeMode] = usePreference("theme-mode")
|
||||
|
||||
return (
|
||||
<select
|
||||
value={themeMode}
|
||||
onChange={(event) =>
|
||||
setThemeMode(event.currentTarget.value as typeof themeMode)
|
||||
}
|
||||
>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Define and provide preferences
|
||||
description: Declare the application preference shape and mount the provider with an SSR-safe snapshot.
|
||||
order: 10
|
||||
toc:
|
||||
- id: declare-preferences
|
||||
title: Declare preferences
|
||||
- id: define-defaults
|
||||
title: Define defaults
|
||||
- id: mount-the-provider
|
||||
title: Mount the provider
|
||||
---
|
||||
|
||||
## Declare preferences {#declare-preferences}
|
||||
|
||||
Use module augmentation in an application-owned TypeScript module:
|
||||
|
||||
```ts
|
||||
import "@workspace/preferences"
|
||||
|
||||
declare module "@workspace/preferences" {
|
||||
interface PreferencesCustom {
|
||||
"navigation-density": "comfortable" | "compact"
|
||||
"theme-mode": "light" | "dark" | "system"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The declaration updates `Preferences`, `PreferenceKey`, `PreferenceValue`, `PreferenceUpdate`, and both preference hooks.
|
||||
|
||||
## Define defaults {#define-defaults}
|
||||
|
||||
Definitions keep parsing, serialization, defaults, and runtime validation together:
|
||||
|
||||
```ts
|
||||
const definitions = {
|
||||
"navigation-density": {
|
||||
cookie: "navigation-density",
|
||||
defaultValue: "comfortable",
|
||||
is: (value) => value === "comfortable" || value === "compact",
|
||||
parse: (value) => (value === "compact" ? "compact" : "comfortable"),
|
||||
serialize: (value) => value,
|
||||
},
|
||||
"theme-mode": {
|
||||
cookie: "theme-mode",
|
||||
defaultValue: "system",
|
||||
is: (value) => value === "light" || value === "dark" || value === "system",
|
||||
parse: (value) =>
|
||||
value === "light" || value === "dark" ? value : "system",
|
||||
serialize: (value) => value,
|
||||
},
|
||||
} satisfies PreferenceDefinitions<Preferences>
|
||||
|
||||
const defaultPreferences = createDefaultPreferences(definitions)
|
||||
```
|
||||
|
||||
## Mount the provider {#mount-the-provider}
|
||||
|
||||
Pass the complete server-derived snapshot to the provider:
|
||||
|
||||
```tsx
|
||||
<PreferencesProvider
|
||||
initialPreferences={preferences}
|
||||
onPreferenceChange={(update) => savePreference(update)}
|
||||
>
|
||||
<App />
|
||||
</PreferencesProvider>
|
||||
```
|
||||
|
||||
Read one preference with an API matching `useState`:
|
||||
|
||||
```tsx
|
||||
const [themeMode, setThemeMode] = usePreference("theme-mode")
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Understand the role and boundaries of the Preferences package.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: capabilities
|
||||
title: Capabilities
|
||||
- id: design-boundary
|
||||
title: Design boundary
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/preferences` is a small React store for application preferences such as theme mode, navigation density, and compact layouts. It keeps the preference vocabulary owned by the consuming application.
|
||||
|
||||
## Capabilities {#capabilities}
|
||||
|
||||
- Extend the preference shape through TypeScript module augmentation.
|
||||
- Hydrate a deterministic server snapshot with `useSyncExternalStore`.
|
||||
- Persist typed `{ key, value }` updates through an application callback.
|
||||
- Synchronize preferences with browser APIs through composable effects.
|
||||
- Validate external updates from cookies, requests, forms, or message channels.
|
||||
|
||||
## Design boundary {#design-boundary}
|
||||
|
||||
The package does not define product-specific keys and does not select a persistence service. The application supplies its complete initial snapshot and decides how accepted changes are stored.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: 生命周期与持久化
|
||||
description: 协调 SSR、异步持久化、浏览器同步和外部输入校验。
|
||||
order: 20
|
||||
toc:
|
||||
- id: server-snapshots
|
||||
title: 服务端快照
|
||||
- id: persist-updates
|
||||
title: 持久化更新
|
||||
- id: preference-effects
|
||||
title: 偏好副作用
|
||||
- id: validate-updates
|
||||
title: 校验更新
|
||||
---
|
||||
|
||||
## 服务端快照 {#server-snapshots}
|
||||
|
||||
Provider 创建 Store 时会捕获 `initialPreferences`。它会一直作为服务端和水合快照;之后改变这个属性不会重置本地偏好。当应用有意切换到另一套偏好身份时,应重新挂载 Provider。
|
||||
|
||||
## 持久化更新 {#persist-updates}
|
||||
|
||||
`onPreferenceChange` 只在值确实发生变化时运行。它可以返回 Promise,但持久化过程不会阻塞本地更新。重试、错误和回滚行为应在应用边界处理。
|
||||
|
||||
## 偏好副作用 {#preference-effects}
|
||||
|
||||
副作用用于连接偏好与 React 之外的 API:
|
||||
|
||||
```tsx
|
||||
const documentEffects = [
|
||||
{
|
||||
layoutEffect: ({ preferences }) => {
|
||||
document.documentElement.dataset.theme = preferences["theme-mode"]
|
||||
},
|
||||
effect: ({ store }) => {
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const listener = () => synchronizeTheme(store.getSnapshot(), media)
|
||||
|
||||
media.addEventListener("change", listener)
|
||||
return () => media.removeEventListener("change", listener)
|
||||
},
|
||||
},
|
||||
] satisfies readonly PreferenceEffect[]
|
||||
```
|
||||
|
||||
必须在绘制前完成的 DOM 变更使用 `layoutEffect`,订阅使用 `effect`。副作用数组的引用应保持稳定,因为快照或数组变化时,回调和清理函数都会重新运行。
|
||||
|
||||
## 校验更新 {#validate-updates}
|
||||
|
||||
在接受无类型边界的数据前,使用相同的 Definitions 创建类型守卫:
|
||||
|
||||
```ts
|
||||
const isPreferenceUpdate = createPreferenceUpdateGuard(definitions)
|
||||
|
||||
if (isPreferenceUpdate(payload)) {
|
||||
await savePreference(payload)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: 主题偏好示例
|
||||
description: 构建完整的亮色、暗色和跟随系统主题偏好流程。
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-theme-mode
|
||||
title: 定义主题模式
|
||||
- id: apply-the-theme
|
||||
title: 应用主题
|
||||
- id: render-a-control
|
||||
title: 渲染控件
|
||||
---
|
||||
|
||||
## 定义主题模式 {#define-theme-mode}
|
||||
|
||||
```ts
|
||||
import "@workspace/preferences"
|
||||
|
||||
declare module "@workspace/preferences" {
|
||||
interface PreferencesCustom {
|
||||
"theme-mode": "light" | "dark" | "system"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在服务端读取 Cookie,并通过 `initialPreferences` 传入解析后的值,使服务端标记和水合过程使用同一个快照。
|
||||
|
||||
## 应用主题 {#apply-the-theme}
|
||||
|
||||
```tsx
|
||||
const themeEffects = [
|
||||
{
|
||||
layoutEffect: ({ preferences }) => {
|
||||
const systemDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches
|
||||
const dark =
|
||||
preferences["theme-mode"] === "dark" ||
|
||||
(preferences["theme-mode"] === "system" && systemDark)
|
||||
|
||||
document.documentElement.classList.toggle("dark", dark)
|
||||
document.documentElement.style.colorScheme = dark ? "dark" : "light"
|
||||
},
|
||||
},
|
||||
] satisfies readonly PreferenceEffect[]
|
||||
```
|
||||
|
||||
## 渲染控件 {#render-a-control}
|
||||
|
||||
```tsx
|
||||
function ThemeModeSelect() {
|
||||
const [themeMode, setThemeMode] = usePreference("theme-mode")
|
||||
|
||||
return (
|
||||
<select
|
||||
value={themeMode}
|
||||
onChange={(event) =>
|
||||
setThemeMode(event.currentTarget.value as typeof themeMode)
|
||||
}
|
||||
>
|
||||
<option value="light">亮色</option>
|
||||
<option value="dark">暗色</option>
|
||||
<option value="system">跟随系统</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: 定义并提供偏好设置
|
||||
description: 声明应用偏好类型,并使用 SSR 安全快照挂载 Provider。
|
||||
order: 10
|
||||
toc:
|
||||
- id: declare-preferences
|
||||
title: 声明偏好设置
|
||||
- id: define-defaults
|
||||
title: 定义默认值
|
||||
- id: mount-the-provider
|
||||
title: 挂载 Provider
|
||||
---
|
||||
|
||||
## 声明偏好设置 {#declare-preferences}
|
||||
|
||||
在应用拥有的 TypeScript 模块中使用模块扩充:
|
||||
|
||||
```ts
|
||||
import "@workspace/preferences"
|
||||
|
||||
declare module "@workspace/preferences" {
|
||||
interface PreferencesCustom {
|
||||
"navigation-density": "comfortable" | "compact"
|
||||
"theme-mode": "light" | "dark" | "system"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该声明会同时更新 `Preferences`、`PreferenceKey`、`PreferenceValue`、`PreferenceUpdate` 和两个偏好 Hooks 的类型。
|
||||
|
||||
## 定义默认值 {#define-defaults}
|
||||
|
||||
Definition 将解析、序列化、默认值和运行时校验放在一起:
|
||||
|
||||
```ts
|
||||
const definitions = {
|
||||
"navigation-density": {
|
||||
cookie: "navigation-density",
|
||||
defaultValue: "comfortable",
|
||||
is: (value) => value === "comfortable" || value === "compact",
|
||||
parse: (value) => (value === "compact" ? "compact" : "comfortable"),
|
||||
serialize: (value) => value,
|
||||
},
|
||||
"theme-mode": {
|
||||
cookie: "theme-mode",
|
||||
defaultValue: "system",
|
||||
is: (value) => value === "light" || value === "dark" || value === "system",
|
||||
parse: (value) =>
|
||||
value === "light" || value === "dark" ? value : "system",
|
||||
serialize: (value) => value,
|
||||
},
|
||||
} satisfies PreferenceDefinitions<Preferences>
|
||||
|
||||
const defaultPreferences = createDefaultPreferences(definitions)
|
||||
```
|
||||
|
||||
## 挂载 Provider {#mount-the-provider}
|
||||
|
||||
将服务端得到的完整快照传给 Provider:
|
||||
|
||||
```tsx
|
||||
<PreferencesProvider
|
||||
initialPreferences={preferences}
|
||||
onPreferenceChange={(update) => savePreference(update)}
|
||||
>
|
||||
<App />
|
||||
</PreferencesProvider>
|
||||
```
|
||||
|
||||
通过类似 `useState` 的 API 读取单个偏好:
|
||||
|
||||
```tsx
|
||||
const [themeMode, setThemeMode] = usePreference("theme-mode")
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 Preferences 包的职责和边界。
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: 包的职责
|
||||
- id: capabilities
|
||||
title: 主要能力
|
||||
- id: design-boundary
|
||||
title: 设计边界
|
||||
---
|
||||
|
||||
## 包的职责 {#package-role}
|
||||
|
||||
`@workspace/preferences` 是一个用于管理应用偏好的轻量 React Store,例如主题模式、导航密度和紧凑布局。偏好字段仍由使用它的应用定义。
|
||||
|
||||
## 主要能力 {#capabilities}
|
||||
|
||||
- 通过 TypeScript 模块扩充扩展偏好类型。
|
||||
- 使用 `useSyncExternalStore` 水合确定的服务端快照。
|
||||
- 通过应用回调持久化类型安全的 `{ key, value }` 更新。
|
||||
- 通过可组合副作用与浏览器 API 同步偏好。
|
||||
- 校验来自 Cookie、请求、表单或消息通道的外部更新。
|
||||
|
||||
## 设计边界 {#design-boundary}
|
||||
|
||||
该包不定义产品特有字段,也不指定持久化服务。应用负责提供完整的初始快照,并决定如何保存已接受的变更。
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Adapter and pagination
|
||||
description: Implement cancellable search requests and return mergeable grouped pages.
|
||||
order: 20
|
||||
toc:
|
||||
- id: implement-adapter
|
||||
title: Implement the adapter
|
||||
- id: group-results
|
||||
title: Group results
|
||||
- id: paginate
|
||||
title: Paginate
|
||||
---
|
||||
|
||||
## Implement the adapter {#implement-adapter}
|
||||
|
||||
`SearchAdapter` is the package's only data dependency:
|
||||
|
||||
```ts
|
||||
const searchAdapter: SearchAdapter = {
|
||||
async search({ cursor, query, signal }) {
|
||||
const response = await fetch(
|
||||
`/api/search?q=${encodeURIComponent(query)}&cursor=${cursor ?? ""}`,
|
||||
{ signal }
|
||||
)
|
||||
|
||||
if (!response.ok) throw new Error("Search request failed")
|
||||
return response.json()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Pass `signal` to the underlying request so an older response cannot replace a newer query.
|
||||
|
||||
## Group results {#group-results}
|
||||
|
||||
Each `SearchPage` contains `SearchResultGroup` values. Keep a group's `id` stable between pages and each item's `id` stable inside its group. Search uses both identifiers to merge results and remove duplicates.
|
||||
|
||||
Use `payload` for application data and `icon` or `image` to customize result presentation.
|
||||
|
||||
## Paginate {#paginate}
|
||||
|
||||
Return `nextCursor` when another page is available; return `null` or omit it at the end. The surface only displays “Load more” while a cursor exists. A cursor can be a string or number and is passed back to the adapter unchanged.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Global search example
|
||||
description: Compose a trigger, dialog, recent searches, and result navigation.
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-results
|
||||
title: Define results
|
||||
- id: handle-selection
|
||||
title: Handle selection
|
||||
- id: control-history
|
||||
title: Control history
|
||||
---
|
||||
|
||||
## Define results {#define-results}
|
||||
|
||||
```ts
|
||||
const searchAdapter: SearchAdapter = {
|
||||
async search({ query }) {
|
||||
return {
|
||||
groups: [
|
||||
{
|
||||
id: "docs",
|
||||
label: "Documentation",
|
||||
items: documents
|
||||
.filter((document) => document.title.includes(query))
|
||||
.map((document) => ({
|
||||
id: document.slug,
|
||||
title: document.title,
|
||||
description: document.description,
|
||||
payload: { href: `/docs/${document.slug}` },
|
||||
})),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Handle selection {#handle-selection}
|
||||
|
||||
```tsx
|
||||
<SearchProvider
|
||||
adapter={searchAdapter}
|
||||
onSelect={({ item }) => {
|
||||
const payload = item.payload as { href: string }
|
||||
router.navigate({ to: payload.href })
|
||||
}}
|
||||
>
|
||||
<HeaderSearchButton />
|
||||
<SearchDialog />
|
||||
</SearchProvider>
|
||||
```
|
||||
|
||||
Code that cannot render `SearchTrigger` can call `searchDialogHandle.open(null)`.
|
||||
|
||||
## Control history {#control-history}
|
||||
|
||||
Recent searches use `workspace-search-history` by default. Supply a product-specific `historyStorageKey`, or set it to `false` to keep history in memory for the current session only.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Installation and setup
|
||||
description: Add Search, register its catalog, and mount the search surface.
|
||||
order: 10
|
||||
toc:
|
||||
- id: add-the-package
|
||||
title: Add the package
|
||||
- id: import-styles
|
||||
title: Import styles
|
||||
- id: mount-search
|
||||
title: Mount search
|
||||
---
|
||||
|
||||
## Add the package {#add-the-package}
|
||||
|
||||
Add Search to the consuming application:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/search": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import styles {#import-styles}
|
||||
|
||||
Import the Search stylesheet once from the application's global stylesheet so Tailwind scans the utilities used by the package:
|
||||
|
||||
```css
|
||||
@import "@workspace/search/globals.css";
|
||||
```
|
||||
|
||||
Also add `@workspace/search/locales/{locale}` to the application's internationalization catalog sources.
|
||||
|
||||
## Mount search {#mount-search}
|
||||
|
||||
```tsx
|
||||
import { SearchDialog, SearchProvider, SearchTrigger } from "@workspace/search"
|
||||
|
||||
;<SearchProvider adapter={searchAdapter} onSelect={openResult}>
|
||||
<SearchTrigger>Search</SearchTrigger>
|
||||
<SearchDialog />
|
||||
</SearchProvider>
|
||||
```
|
||||
|
||||
`SearchDialog` registers `Mod+K` by default. Pass `hotkey={false}` when the application manages shortcuts centrally.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Understand the Search package data boundary, interactions, and use cases.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: capabilities
|
||||
title: Capabilities
|
||||
- id: design-boundary
|
||||
title: Design boundary
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/search` provides an application-search surface and its state management. The host supplies data through `SearchAdapter`; Search owns the query lifecycle, pagination, result presentation, selection interactions, and recent searches.
|
||||
|
||||
## Capabilities {#capabilities}
|
||||
|
||||
- Connect any HTTP API, database, command registry, or local index through an adapter.
|
||||
- Present results in domain groups and merge paginated data.
|
||||
- Cancel stale requests with `AbortSignal`.
|
||||
- Keep recent searches with configurable local persistence.
|
||||
- Provide a `Mod+K` shortcut plus empty, loading, and error states.
|
||||
- Ship `en-US` and `zh-Hans` message catalogs.
|
||||
|
||||
## Design boundary {#design-boundary}
|
||||
|
||||
Search does not choose the data source or perform navigation. The application implements the adapter and interprets each result's `payload` in `onSelect`.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Adapter 与分页
|
||||
description: 实现可取消的搜索请求,并返回可合并的分组分页结果。
|
||||
order: 20
|
||||
toc:
|
||||
- id: implement-adapter
|
||||
title: 实现 Adapter
|
||||
- id: group-results
|
||||
title: 组织结果
|
||||
- id: paginate
|
||||
title: 分页
|
||||
---
|
||||
|
||||
## 实现 Adapter {#implement-adapter}
|
||||
|
||||
`SearchAdapter` 是 Search 唯一的数据依赖:
|
||||
|
||||
```ts
|
||||
const searchAdapter: SearchAdapter = {
|
||||
async search({ cursor, query, signal }) {
|
||||
const response = await fetch(
|
||||
`/api/search?q=${encodeURIComponent(query)}&cursor=${cursor ?? ""}`,
|
||||
{ signal }
|
||||
)
|
||||
|
||||
if (!response.ok) throw new Error("Search request failed")
|
||||
return response.json()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
必须把 `signal` 传给底层请求,避免较早的响应覆盖较新的查询。
|
||||
|
||||
## 组织结果 {#group-results}
|
||||
|
||||
每个 `SearchPage` 包含若干 `SearchResultGroup`。Group 的 `id` 在分页之间应保持稳定;Item 的 `id` 在所属 Group 内应保持稳定。Search 会用这两个标识合并结果并消除重复项。
|
||||
|
||||
可以通过 `payload` 携带应用数据,通过 `icon` 或 `image` 自定义结果外观。
|
||||
|
||||
## 分页 {#paginate}
|
||||
|
||||
还有后续数据时返回 `nextCursor`;没有更多结果时返回 `null` 或省略。界面只在存在游标时显示“加载更多”。游标可以是字符串或数字,并会原样传回 Adapter。
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: 全局搜索示例
|
||||
description: 组合触发器、对话框、历史记录和结果导航。
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-results
|
||||
title: 定义结果
|
||||
- id: handle-selection
|
||||
title: 处理选择
|
||||
- id: control-history
|
||||
title: 控制历史记录
|
||||
---
|
||||
|
||||
## 定义结果 {#define-results}
|
||||
|
||||
```ts
|
||||
const searchAdapter: SearchAdapter = {
|
||||
async search({ query }) {
|
||||
return {
|
||||
groups: [
|
||||
{
|
||||
id: "docs",
|
||||
label: "文档",
|
||||
items: documents
|
||||
.filter((document) => document.title.includes(query))
|
||||
.map((document) => ({
|
||||
id: document.slug,
|
||||
title: document.title,
|
||||
description: document.description,
|
||||
payload: { href: `/docs/${document.slug}` },
|
||||
})),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 处理选择 {#handle-selection}
|
||||
|
||||
```tsx
|
||||
<SearchProvider
|
||||
adapter={searchAdapter}
|
||||
onSelect={({ item }) => {
|
||||
const payload = item.payload as { href: string }
|
||||
router.navigate({ to: payload.href })
|
||||
}}
|
||||
>
|
||||
<HeaderSearchButton />
|
||||
<SearchDialog />
|
||||
</SearchProvider>
|
||||
```
|
||||
|
||||
不方便渲染 `SearchTrigger` 的位置可以调用 `searchDialogHandle.open(null)`。
|
||||
|
||||
## 控制历史记录 {#control-history}
|
||||
|
||||
历史记录默认保存在 `workspace-search-history`。为不同产品传入独立的 `historyStorageKey`,或者设置为 `false`,让历史只存在于当前内存会话中。
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: 安装与接入
|
||||
description: 添加 Search、注册翻译目录并挂载搜索界面。
|
||||
order: 10
|
||||
toc:
|
||||
- id: add-the-package
|
||||
title: 添加包
|
||||
- id: import-styles
|
||||
title: 导入样式
|
||||
- id: mount-search
|
||||
title: 挂载搜索
|
||||
---
|
||||
|
||||
## 添加包 {#add-the-package}
|
||||
|
||||
将 Search 加入应用依赖:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/search": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 导入样式 {#import-styles}
|
||||
|
||||
在应用的全局样式入口导入一次 Search 样式,使 Tailwind 扫描包内使用的 utilities:
|
||||
|
||||
```css
|
||||
@import "@workspace/search/globals.css";
|
||||
```
|
||||
|
||||
同时将 `@workspace/search/locales/{locale}` 加入应用的国际化目录来源。
|
||||
|
||||
## 挂载搜索 {#mount-search}
|
||||
|
||||
```tsx
|
||||
import { SearchDialog, SearchProvider, SearchTrigger } from "@workspace/search"
|
||||
|
||||
;<SearchProvider adapter={searchAdapter} onSelect={openResult}>
|
||||
<SearchTrigger>搜索</SearchTrigger>
|
||||
<SearchDialog />
|
||||
</SearchProvider>
|
||||
```
|
||||
|
||||
`SearchDialog` 默认注册 `Mod+K`。如果应用已经统一管理快捷键,可以传入 `hotkey={false}`。
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 Search 包的数据边界、交互能力和适用场景。
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: 包的职责
|
||||
- id: capabilities
|
||||
title: 主要能力
|
||||
- id: design-boundary
|
||||
title: 设计边界
|
||||
---
|
||||
|
||||
## 包的职责 {#package-role}
|
||||
|
||||
`@workspace/search` 提供应用级搜索界面与状态管理。宿主应用通过 `SearchAdapter` 提供数据,Search 负责查询生命周期、分页、结果展示、选择交互和搜索历史。
|
||||
|
||||
## 主要能力 {#capabilities}
|
||||
|
||||
- 使用 Adapter 连接任意 HTTP API、数据库、命令注册表或本地索引。
|
||||
- 按业务来源分组展示结果,并合并分页数据。
|
||||
- 使用 `AbortSignal` 取消已经过期的查询。
|
||||
- 提供最近搜索记录和可配置的本地持久化。
|
||||
- 内置 `Mod+K` 快捷键、无结果、加载与错误状态。
|
||||
- 提供 `en-US` 和 `zh-Hans` 消息目录。
|
||||
|
||||
## 设计边界 {#design-boundary}
|
||||
|
||||
Search 不决定数据从哪里获取,也不负责路由跳转。应用实现 Adapter,并在 `onSelect` 中解释结果的 `payload`。
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Add responsive behavior
|
||||
description: Use shared breakpoint and interaction hooks when CSS alone cannot express the behavior.
|
||||
order: 20
|
||||
toc:
|
||||
- id: breakpoints
|
||||
title: Breakpoints
|
||||
- id: server-rendering
|
||||
title: Server rendering
|
||||
- id: ripple
|
||||
title: Ripple interaction
|
||||
---
|
||||
|
||||
## Breakpoints {#breakpoints}
|
||||
|
||||
```tsx
|
||||
import { useBreakpoint, useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = useIsMobile()
|
||||
```
|
||||
|
||||
Prefer CSS responsive variants for visual changes. Use these hooks only when component behavior or mounted content must change.
|
||||
|
||||
## Server rendering {#server-rendering}
|
||||
|
||||
The breakpoint store uses a deterministic desktop server snapshot. `getBreakpointInitializationScript` can assign the matching root class before hydration when the application needs CSS and behavioral breakpoints to agree immediately.
|
||||
|
||||
## Ripple interaction {#ripple}
|
||||
|
||||
`useRipple` returns a ref callback that adds pointer-driven feedback to an interactive element. The element should establish a positioned containing block so the generated overlay uses the correct bounds.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Localize UI
|
||||
description: Load only the UI catalog and calendar locale required by the active language.
|
||||
order: 21
|
||||
toc:
|
||||
- id: catalog-source
|
||||
title: Catalog source
|
||||
- id: direct-imports
|
||||
title: Direct imports
|
||||
- id: supported-locales
|
||||
title: Supported locales
|
||||
---
|
||||
|
||||
## Catalog source {#catalog-source}
|
||||
|
||||
Add the UI locale source to the application's internationalization configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/ui/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
The locales root contains metadata and types. It does not import every language catalog.
|
||||
|
||||
## Direct imports {#direct-imports}
|
||||
|
||||
```ts
|
||||
import { calendarLocale, messages } from "@workspace/ui/locales/en-US"
|
||||
```
|
||||
|
||||
Each language entry also exports its matching `react-day-picker` locale.
|
||||
|
||||
## Supported locales {#supported-locales}
|
||||
|
||||
The UI catalogs include American English (`en-US`) and Simplified Chinese (`zh-Hans`).
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Compose a form
|
||||
description: Combine field, input, and button primitives while keeping domain state in the application.
|
||||
order: 30
|
||||
toc:
|
||||
- id: create-the-form
|
||||
title: Create the form
|
||||
- id: validation
|
||||
title: Validation
|
||||
- id: styling
|
||||
title: Styling
|
||||
---
|
||||
|
||||
## Create the form {#create-the-form}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field"
|
||||
import { Input } from "@workspace/ui/components/input"
|
||||
|
||||
;<form onSubmit={saveProfile}>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="display-name">Display name</FieldLabel>
|
||||
<Input id="display-name" name="displayName" />
|
||||
<FieldError>{errors.displayName}</FieldError>
|
||||
</Field>
|
||||
<Button type="submit">Save profile</Button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Validation {#validation}
|
||||
|
||||
The UI package renders validation state but does not select a form library or schema. Connect native form data, React state, or a form framework at the application boundary.
|
||||
|
||||
## Styling {#styling}
|
||||
|
||||
Use component variants for supported semantic changes and `className` for local layout. Use `cn` from `@workspace/ui/lib/utils` when conditional classes or consumer overrides must be merged.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Choose components
|
||||
description: Select primitives by responsibility instead of importing a single monolithic component index.
|
||||
order: 11
|
||||
toc:
|
||||
- id: forms
|
||||
title: Forms and input
|
||||
- id: overlays
|
||||
title: Overlays and menus
|
||||
- id: content
|
||||
title: Content and feedback
|
||||
- id: icons
|
||||
title: Icons
|
||||
---
|
||||
|
||||
## Forms and input {#forms}
|
||||
|
||||
Use `Button`, `Input`, `Textarea`, `Field`, and `Label` for ordinary forms. Add `Checkbox`, `RadioGroup`, `Switch`, or `Slider` for choices, and `Select`, `Combobox`, `Command`, or `Calendar` for richer selection.
|
||||
|
||||
## Overlays and menus {#overlays}
|
||||
|
||||
`Dialog`, `AlertDialog`, `Sheet`, and `Drawer` cover modal surfaces. `Popover`, `HoverCard`, and `Tooltip` provide anchored information, while the menu modules cover context, dropdown, menubar, and navigation patterns.
|
||||
|
||||
## Content and feedback {#content}
|
||||
|
||||
Cards, items, tables, charts, progress, skeletons, spinners, empty states, messages, and toasts provide the common display vocabulary. Accordion, Collapsible, Tabs, Carousel, and Pagination organize larger content sets.
|
||||
|
||||
## Icons {#icons}
|
||||
|
||||
Import named icons from `@workspace/ui/components/icon`. The module centralizes the `@icones/react` vocabulary so applications and packages use the same icon source.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Project setup
|
||||
description: Import the global stylesheet and begin with explicit component subpaths.
|
||||
order: 10
|
||||
toc:
|
||||
- id: import-styles
|
||||
title: Import styles
|
||||
- id: import-a-component
|
||||
title: Import a component
|
||||
- id: entry-points
|
||||
title: Entry points
|
||||
---
|
||||
|
||||
## Import styles {#import-styles}
|
||||
|
||||
Import the global stylesheet once at the application entry:
|
||||
|
||||
```ts
|
||||
import "@workspace/ui/globals.css"
|
||||
```
|
||||
|
||||
It provides the shared Tailwind layers, design variables, font setup, and utilities expected by the components.
|
||||
|
||||
## Import a component {#import-a-component}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export function SaveButton() {
|
||||
return <Button>Save</Button>
|
||||
}
|
||||
```
|
||||
|
||||
## Entry points {#entry-points}
|
||||
|
||||
- `@workspace/ui/components/*` exposes one component module.
|
||||
- `@workspace/ui/hooks/*` exposes reusable interactions and media-query state.
|
||||
- `@workspace/ui/lib/utils` exposes shared class-name composition.
|
||||
- `@workspace/ui/locales/*` exposes language-specific catalogs.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn where the UI package fits and how its explicit entry points support composition.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: tutorial-map
|
||||
title: Tutorial map
|
||||
- id: ui-or-blocks
|
||||
title: UI or Blocks
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/ui` contains reusable React primitives, composite controls, hooks, icons, styles, and locale catalogs. It is the visual foundation shared by applications and higher-level feature packages.
|
||||
|
||||
## Tutorial map {#tutorial-map}
|
||||
|
||||
This tutorial installs the styles, introduces component subpaths, builds a small form through composition, adds responsive behavior, and connects locale catalogs.
|
||||
|
||||
## UI or Blocks {#ui-or-blocks}
|
||||
|
||||
Use UI components when the application owns the workflow. Use `@workspace/blocks` when the interface also needs a complete feature contract such as navigation, media storage, search adapters, or notification queries.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 添加响应式行为
|
||||
description: 当 CSS 无法表达行为变化时,使用共享的断点和交互 Hooks。
|
||||
order: 20
|
||||
toc:
|
||||
- id: breakpoints
|
||||
title: 断点
|
||||
- id: server-rendering
|
||||
title: 服务端渲染
|
||||
- id: ripple
|
||||
title: 波纹交互
|
||||
---
|
||||
|
||||
## 断点 {#breakpoints}
|
||||
|
||||
```tsx
|
||||
import { useBreakpoint, useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = useIsMobile()
|
||||
```
|
||||
|
||||
视觉变化优先使用 CSS 响应式变体。只有组件行为或挂载内容必须变化时才使用这些 Hooks。
|
||||
|
||||
## 服务端渲染 {#server-rendering}
|
||||
|
||||
断点 Store 使用确定的桌面端服务端快照。当应用需要 CSS 与行为断点在水合前保持一致时,可用 `getBreakpointInitializationScript` 提前设置匹配的根节点类名。
|
||||
|
||||
## 波纹交互 {#ripple}
|
||||
|
||||
`useRipple` 返回一个 ref 回调,为交互元素加入指针反馈。元素应创建定位上下文,使生成的覆盖层使用正确边界。
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: UI 本地化
|
||||
description: 只加载当前语言所需的 UI 词典和日历 locale。
|
||||
order: 21
|
||||
toc:
|
||||
- id: catalog-source
|
||||
title: 词典来源
|
||||
- id: direct-imports
|
||||
title: 直接导入
|
||||
- id: supported-locales
|
||||
title: 支持的语言
|
||||
---
|
||||
|
||||
## 词典来源 {#catalog-source}
|
||||
|
||||
在应用国际化配置中加入 UI 词典来源:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalogSources": ["@workspace/ui/locales/{locale}"]
|
||||
}
|
||||
```
|
||||
|
||||
locales 根入口只包含元数据和类型,不会导入所有语言的词典。
|
||||
|
||||
## 直接导入 {#direct-imports}
|
||||
|
||||
```ts
|
||||
import { calendarLocale, messages } from "@workspace/ui/locales/zh-Hans"
|
||||
```
|
||||
|
||||
每个语言入口还会导出对应的 `react-day-picker` locale。
|
||||
|
||||
## 支持的语言 {#supported-locales}
|
||||
|
||||
UI 词典支持简体中文(`zh-Hans`)和美式英语(`en-US`)。
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: 组合表单
|
||||
description: 组合 Field、Input 和 Button,同时让领域状态继续由应用管理。
|
||||
order: 30
|
||||
toc:
|
||||
- id: create-the-form
|
||||
title: 创建表单
|
||||
- id: validation
|
||||
title: 表单验证
|
||||
- id: styling
|
||||
title: 样式
|
||||
---
|
||||
|
||||
## 创建表单 {#create-the-form}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field"
|
||||
import { Input } from "@workspace/ui/components/input"
|
||||
|
||||
;<form onSubmit={saveProfile}>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="display-name">显示名称</FieldLabel>
|
||||
<Input id="display-name" name="displayName" />
|
||||
<FieldError>{errors.displayName}</FieldError>
|
||||
</Field>
|
||||
<Button type="submit">保存资料</Button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## 表单验证 {#validation}
|
||||
|
||||
UI 包负责渲染验证状态,但不会指定表单库或 Schema。请在应用边界连接原生表单数据、React 状态或表单框架。
|
||||
|
||||
## 样式 {#styling}
|
||||
|
||||
受支持的语义变化使用组件变体,局部布局使用 `className`。需要合并条件类名或消费者覆盖时,使用 `@workspace/ui/lib/utils` 中的 `cn`。
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: 选择组件
|
||||
description: 按职责选择基础组件,而不是导入一个庞大的统一组件入口。
|
||||
order: 11
|
||||
toc:
|
||||
- id: forms
|
||||
title: 表单与输入
|
||||
- id: overlays
|
||||
title: 浮层与菜单
|
||||
- id: content
|
||||
title: 内容与反馈
|
||||
- id: icons
|
||||
title: 图标
|
||||
---
|
||||
|
||||
## 表单与输入 {#forms}
|
||||
|
||||
普通表单可使用 `Button`、`Input`、`Textarea`、`Field` 和 `Label`。选择类输入可加入 `Checkbox`、`RadioGroup`、`Switch` 或 `Slider`;更丰富的选择场景可使用 `Select`、`Combobox`、`Command` 或 `Calendar`。
|
||||
|
||||
## 浮层与菜单 {#overlays}
|
||||
|
||||
`Dialog`、`AlertDialog`、`Sheet` 和 `Drawer` 用于模态界面。`Popover`、`HoverCard` 和 `Tooltip` 用于锚定式信息展示,菜单模块则覆盖上下文菜单、下拉菜单、菜单栏和导航模式。
|
||||
|
||||
## 内容与反馈 {#content}
|
||||
|
||||
卡片、列表项、表格、图表、进度条、骨架屏、加载器、空状态、消息和 Toast 构成通用展示词汇。Accordion、Collapsible、Tabs、Carousel 和 Pagination 用于组织更大规模的内容。
|
||||
|
||||
## 图标 {#icons}
|
||||
|
||||
从 `@workspace/ui/components/icon` 导入具名图标。该模块统一了 `@icones/react` 的图标词汇,使应用和各个包使用同一图标来源。
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 项目配置
|
||||
description: 导入全局样式,并从显式组件子路径开始使用。
|
||||
order: 10
|
||||
toc:
|
||||
- id: import-styles
|
||||
title: 导入样式
|
||||
- id: import-a-component
|
||||
title: 导入组件
|
||||
- id: entry-points
|
||||
title: 包入口
|
||||
---
|
||||
|
||||
## 导入样式 {#import-styles}
|
||||
|
||||
在应用入口中导入一次全局样式:
|
||||
|
||||
```ts
|
||||
import "@workspace/ui/globals.css"
|
||||
```
|
||||
|
||||
它提供组件所需的共享 Tailwind 层、设计变量、字体配置和工具类。
|
||||
|
||||
## 导入组件 {#import-a-component}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export function SaveButton() {
|
||||
return <Button>保存</Button>
|
||||
}
|
||||
```
|
||||
|
||||
## 包入口 {#entry-points}
|
||||
|
||||
- `@workspace/ui/components/*` 导出单个组件模块。
|
||||
- `@workspace/ui/hooks/*` 导出可复用的交互和媒体查询状态。
|
||||
- `@workspace/ui/lib/utils` 导出共享的类名组合工具。
|
||||
- `@workspace/ui/locales/*` 导出指定语言的词典。
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 UI 包的定位,以及显式入口如何支持组件组合。
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: 包的职责
|
||||
- id: tutorial-map
|
||||
title: 教程路线
|
||||
- id: ui-or-blocks
|
||||
title: UI 还是 Blocks
|
||||
---
|
||||
|
||||
## 包的职责 {#package-role}
|
||||
|
||||
`@workspace/ui` 包含可复用的 React 基础组件、复合控件、Hooks、图标、样式和本地化词典。它是应用与上层功能包共享的视觉基础。
|
||||
|
||||
## 教程路线 {#tutorial-map}
|
||||
|
||||
本教程会安装样式、介绍组件子路径、通过组合构建一个小型表单、添加响应式行为,并接入本地化词典。
|
||||
|
||||
## UI 还是 Blocks {#ui-or-blocks}
|
||||
|
||||
当应用自己拥有业务流程时使用 UI 组件。当界面还需要导航、媒体存储、搜索适配器或通知查询等完整功能契约时,使用 `@workspace/blocks`。
|
||||
Reference in New Issue
Block a user