feat(docs): add bilingual package documentation site
This commit is contained in:
@@ -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}
|
||||
|
||||
该包不定义产品特有字段,也不指定持久化服务。应用负责提供完整的初始快照,并决定如何保存已接受的变更。
|
||||
Reference in New Issue
Block a user