feat(docs): add bilingual package documentation site

This commit is contained in:
Maofeng
2026-09-20 15:53:28 +08:00
parent 60f6f1fb1a
commit dc80bd8ae0
111 changed files with 6531 additions and 0 deletions
@@ -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.
+33
View File
@@ -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。
+33
View File
@@ -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.
+44
View File
@@ -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.
+48
View File
@@ -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`.
+35
View File
@@ -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` 暴露生成的加载器。
+35
View File
@@ -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.
+26
View File
@@ -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.
+29
View File
@@ -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}`。
+29
View File
@@ -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`。
+31
View File
@@ -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.
+39
View File
@@ -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.
+24
View File
@@ -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` 的图标词汇,使应用和各个包使用同一图标来源。
+39
View File
@@ -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/*` 导出指定语言的词典。
+24
View File
@@ -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`。