docs(ui): expand bilingual component guides
This commit is contained in:
@@ -1,31 +1,98 @@
|
||||
---
|
||||
title: 添加响应式行为
|
||||
description: 当 CSS 无法表达行为变化时,使用共享的断点和交互 Hooks。
|
||||
title: 响应式与交互 Hooks
|
||||
description: 在行为必须随视口改变时同步断点,并为自定义交互元素添加波纹。
|
||||
order: 20
|
||||
toc:
|
||||
- id: breakpoints
|
||||
title: 断点
|
||||
- id: breakpoint-contract
|
||||
title: 断点约定
|
||||
- id: reading-breakpoints
|
||||
title: 读取断点
|
||||
- id: root-classes
|
||||
title: 根节点 class
|
||||
- id: server-rendering
|
||||
title: 服务端渲染
|
||||
- id: ripple
|
||||
title: 波纹交互
|
||||
---
|
||||
|
||||
## 断点 {#breakpoints}
|
||||
## 断点约定 {#breakpoint-contract}
|
||||
|
||||
共享断点把视口分成三段:
|
||||
|
||||
| 返回值 | 范围 |
|
||||
| --------- | --------------- |
|
||||
| `mobile` | 小于 768px |
|
||||
| `tablet` | 768px 至 1023px |
|
||||
| `desktop` | 大于等于 1024px |
|
||||
|
||||
仅视觉变化应优先使用 CSS。只有挂载内容、交互方式或数据量必须变化时,才在渲染逻辑中读取断点。
|
||||
|
||||
## 读取断点 {#reading-breakpoints}
|
||||
|
||||
```tsx
|
||||
import { useBreakpoint, useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
import {
|
||||
useBreakpoint,
|
||||
useIsDesktop,
|
||||
useIsMobile,
|
||||
useIsTablet,
|
||||
useOnBreakpoint,
|
||||
} from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = useIsMobile()
|
||||
```
|
||||
|
||||
视觉变化优先使用 CSS 响应式变体。只有组件行为或挂载内容必须变化时才使用这些 Hooks。
|
||||
每个 hook 通过 `useSyncExternalStore` 订阅同一组 `matchMedia` 查询。`useOnBreakpoint(listener)` 只在断点发生变化后调用 listener,不会把初始值作为一次变化上报。
|
||||
|
||||
## 根节点 class {#root-classes}
|
||||
|
||||
挂载 `BreakpointReactor` 后,文档根节点会且只会拥有 `.ui:mobile`、`.ui:tablet`、`.ui:desktop` 中的一个:
|
||||
|
||||
```tsx
|
||||
import { BreakpointReactor } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
;<>
|
||||
<BreakpointReactor />
|
||||
<App />
|
||||
</>
|
||||
```
|
||||
|
||||
`globals.css` 把它们注册为 `mobile:`、`tablet:`、`desktop:` 自定义变体。这样组件可以让 CSS 与行为层使用同一份三段式定义。
|
||||
|
||||
## 服务端渲染 {#server-rendering}
|
||||
|
||||
断点 Store 使用确定的桌面端服务端快照。当应用需要 CSS 与行为断点在水合前保持一致时,可用 `getBreakpointInitializationScript` 提前设置匹配的根节点类名。
|
||||
服务端快照固定为 `desktop`,保证 SSR 可重复。为了在水合前写入真实 class,可把 `getBreakpointInitializationScript()` 的结果作为内联脚本放到文档头部:
|
||||
|
||||
```tsx
|
||||
import { getBreakpointInitializationScript } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
;<script
|
||||
dangerouslySetInnerHTML={{ __html: getBreakpointInitializationScript() }}
|
||||
/>
|
||||
```
|
||||
|
||||
在严格 CSP 下为脚本传入应用生成的 nonce。脚本只同步 class,不替代 `BreakpointReactor` 后续监听变化。
|
||||
|
||||
## 波纹交互 {#ripple}
|
||||
|
||||
`useRipple` 返回一个 ref 回调,为交互元素加入指针反馈。元素应创建定位上下文,使生成的覆盖层使用正确边界。
|
||||
`Button` 已自动使用 `useRippleRef`,普通按钮无需重复配置。自定义交互表面可直接使用 hook:
|
||||
|
||||
```tsx
|
||||
import { useRipple } from "@workspace/ui/hooks/use-ripple"
|
||||
|
||||
export function Tile() {
|
||||
const ref = useRipple<HTMLButtonElement>({
|
||||
duration: 500,
|
||||
opacity: 0.2,
|
||||
rippleClassName: "bg-primary",
|
||||
})
|
||||
|
||||
return (
|
||||
<button ref={ref} className="relative">
|
||||
打开项目
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
元素必须建立定位上下文,通常使用 `relative`。hook 会忽略 disabled、`aria-disabled="true"` 和非主鼠标键,并在 ref 变化或卸载时移除监听器和动画节点。需要合并外部 ref 时使用 `useRippleRef(ref, options)`。
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
---
|
||||
title: UI 本地化
|
||||
description: 只加载当前语言所需的 UI 词典和日历 locale。
|
||||
description: 将应用翻译器桥接到 UI 组件,并加载当前语言的日历 locale。
|
||||
order: 21
|
||||
toc:
|
||||
- id: catalog-source
|
||||
title: 词典来源
|
||||
- id: direct-imports
|
||||
title: 直接导入
|
||||
- id: supported-locales
|
||||
title: 支持的语言
|
||||
- id: two-layers
|
||||
title: 两层职责
|
||||
- id: catalogs
|
||||
title: 合并词典
|
||||
- id: bridge-provider
|
||||
title: 桥接 Provider
|
||||
- id: fallback
|
||||
title: 回退行为
|
||||
- id: direction
|
||||
title: 文本方向
|
||||
---
|
||||
|
||||
## 词典来源 {#catalog-source}
|
||||
## 两层职责 {#two-layers}
|
||||
|
||||
在应用国际化配置中加入 UI 词典来源:
|
||||
`@workspace/i18n` 管理应用 locale、词典加载、格式化和切换;`@workspace/ui/components/i18n` 是轻量适配层,让 UI primitives 不绑定某个国际化框架。后者接收一个兼容的翻译函数、语言标签和 `react-day-picker` locale。
|
||||
|
||||
这种拆分允许 UI 包独立复用,也允许应用继续使用工作区的完整国际化运行时。
|
||||
|
||||
## 合并词典 {#catalogs}
|
||||
|
||||
在应用的 `i18n.config.json` 中加入 UI 词典来源:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -21,16 +31,50 @@ toc:
|
||||
}
|
||||
```
|
||||
|
||||
locales 根入口只包含元数据和类型,不会导入所有语言的词典。
|
||||
根入口 `@workspace/ui/locales` 只导出 `uiCatalogLocales`、`uiMessageIds` 和类型,不会静态导入所有语言。当前仅支持 `en-US` 与 `zh-Hans`。
|
||||
|
||||
## 直接导入 {#direct-imports}
|
||||
## 桥接 Provider {#bridge-provider}
|
||||
|
||||
```ts
|
||||
import { calendarLocale, messages } from "@workspace/ui/locales/zh-Hans"
|
||||
从当前应用 Provider 中读取翻译器,再传给 UI Provider:
|
||||
|
||||
```tsx
|
||||
import { useLocaleDefinition, useTranslate } from "@workspace/i18n"
|
||||
import {
|
||||
I18nProvider as UiI18nProvider,
|
||||
type CalendarLocale,
|
||||
} from "@workspace/ui/components/i18n"
|
||||
import { calendarLocale as enUS } from "@workspace/ui/locales/en-US"
|
||||
import { calendarLocale as zhHans } from "@workspace/ui/locales/zh-Hans"
|
||||
|
||||
const calendarLocales: Record<string, CalendarLocale> = {
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
}
|
||||
|
||||
export function AppUiI18nProvider({ children }: React.PropsWithChildren) {
|
||||
const locale = useLocaleDefinition()
|
||||
const t = useTranslate()
|
||||
|
||||
return (
|
||||
<UiI18nProvider
|
||||
calendarLocale={calendarLocales[locale.locale]}
|
||||
locale={locale.languageTag ?? locale.locale}
|
||||
t={t}
|
||||
>
|
||||
{children}
|
||||
</UiI18nProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
每个语言入口还会导出对应的 `react-day-picker` locale。
|
||||
`Calendar` 会优先使用组件自身的 `locale` prop,其次使用 Provider 的 `calendarLocale`;月份格式化与 `lang` 默认使用 Provider 的语言标签。
|
||||
|
||||
## 支持的语言 {#supported-locales}
|
||||
## 回退行为 {#fallback}
|
||||
|
||||
UI 词典支持简体中文(`zh-Hans`)和美式英语(`en-US`)。
|
||||
没有 Provider 时,UI 层使用 `en-US` 和消息描述符中的英文原文。因此独立测试和 Story 不会因为缺少 Provider 崩溃,但正式应用仍应桥接 Provider,保证 aria-label 与可见文本一致。
|
||||
|
||||
自定义组件可以使用 `uiMessages`、`useMessage`、`useTranslate`、`LocalizedText`、`useLocale` 和 `useCalendarLocale`。产品业务文案应定义在应用或领域 package 中,不要追加到 UI 词典。
|
||||
|
||||
## 文本方向 {#direction}
|
||||
|
||||
翻译语言与布局方向是两件事。切换 locale 时,应用应同步设置 `document.documentElement.lang` 与 `dir`,并在需要时使用 `DirectionProvider`。本地组件使用 `start`、`end`、`ps`、`pe` 等逻辑方向 utility,`components.json` 也开启了 `rtl`。
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: 本仓库适配
|
||||
description: 记录相对 shadcn/ui Base UI 源码必须保留的实现差异与升级步骤。
|
||||
order: 22
|
||||
toc:
|
||||
- id: baseline
|
||||
title: 上游基线
|
||||
- id: icons-and-ripple
|
||||
title: 图标与波纹
|
||||
- id: localization
|
||||
title: 本地化
|
||||
- id: popover
|
||||
title: Popover 扩展
|
||||
- id: select
|
||||
title: Select 修正
|
||||
- id: upgrade-checklist
|
||||
title: 升级清单
|
||||
---
|
||||
|
||||
## 上游基线 {#baseline}
|
||||
|
||||
本地组件最初来自 shadcn/ui 的 Base UI registry,配置风格为 `base-vega`。上游可参考 [组件文档](https://ui.shadcn.com/docs/components/base)和 [GitHub 源码目录](https://github.com/shadcn-ui/ui/tree/main/apps/v4/registry/bases/base/ui),但上游当前版本不等于本仓库锁定的实现。
|
||||
|
||||
判断行为时按以下优先级:本地源码、本文档、本地 `components.json`,最后才是上游最新示例。
|
||||
|
||||
## 图标与波纹 {#icons-and-ripple}
|
||||
|
||||
`components/icon.tsx` 把 Lucide 图标名映射到 `@icones/react` 组件,并保留一批与上游同名的导出。已有组件已改为从该入口导入。CLI 新增或更新的源码如果出现 `lucide-react`,应转换到这个入口。
|
||||
|
||||
`Button` 合并传入 ref 与 `useRippleRef`。修改 Button 时必须保留 `relative` 定位、波纹 ref 合并和 disabled 行为。
|
||||
|
||||
## 本地化 {#localization}
|
||||
|
||||
Breadcrumb、Calendar、Carousel、Dialog、Pagination、Sheet、Sidebar、Spinner 与 Toast 等组件已移除部分硬编码英文,改用 `components/i18n.tsx` 中的消息描述符。更新这些文件时要检查 aria-label、sr-only 文本和默认说明,不能只比较可见 UI。
|
||||
|
||||
词典对外只发布 `en-US` 和 `zh-Hans`。新增消息时要同步:
|
||||
|
||||
1. `uiMessages` 描述符。
|
||||
2. `uiMessageIds` 类型列表。
|
||||
3. 两个 locale 的 `messages`。
|
||||
4. 使用该消息的组件。
|
||||
|
||||
## Popover 扩展 {#popover}
|
||||
|
||||
本地 `PopoverContent` 比上游包装层多出:
|
||||
|
||||
- `anchor` 与 `positionMethod` 转发。
|
||||
- `showArrow`,支持 `AnchoredSurface` 配置对象。
|
||||
- `viewportClassName`,把可滚动内容与必须 overflow-visible 的外层表面分开。
|
||||
- 按锚点尺寸计算的默认 side offset 与连续曲线尾巴。
|
||||
- `createPopoverHandle` 与 `PopoverHandle` 导出。
|
||||
|
||||
不要用上游文件整体替换它。特别要测试长内容滚动、四个 side、碰撞翻转、窄触发器和 RTL。
|
||||
|
||||
## Select 修正 {#select}
|
||||
|
||||
`SelectTrigger` 把 `ChevronDownIcon` 作为 `SelectPrimitive.Icon` 的 child 渲染,而不是把图标组件交给 `render`。这是为当前 Base UI 与 Icones 组合保留的修正。升级 Select 时要确认 trigger 只出现一个图标,并验证 SSR 与 hydration。
|
||||
|
||||
## 升级清单 {#upgrade-checklist}
|
||||
|
||||
1. 先查看 shadcn 文档与对应 registry 源码的提交差异,不直接执行覆盖。
|
||||
2. 通过 CLI 生成到临时分支或逐文件暂存,保留本地 public exports。
|
||||
3. 将直接图标依赖迁回 `components/icon`。
|
||||
4. 恢复翻译消息、逻辑方向 utility 与本地 theme class。
|
||||
5. 对 Button、Popover、Select、Calendar、Toast 和 Sidebar 做重点回归。
|
||||
6. 运行 UI typecheck、全仓 lint/format,再构建实际消费应用。
|
||||
7. 在 light、dark、LTR、RTL、mobile、tablet、desktop 下手动检查交互。
|
||||
Reference in New Issue
Block a user