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,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.