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 下手动检查交互。
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: 锚定式个人菜单
|
||||
description: 使用 Popover 的连续尾巴、滚动视口和 Base UI render 组合个人菜单。
|
||||
order: 31
|
||||
toc:
|
||||
- id: example
|
||||
title: 示例
|
||||
- id: why-these-props
|
||||
title: 参数说明
|
||||
- id: accessibility
|
||||
title: 可访问性
|
||||
---
|
||||
|
||||
## 示例 {#example}
|
||||
|
||||
```tsx
|
||||
import { Avatar, AvatarFallback } from "@workspace/ui/components/avatar"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { SettingsIcon } from "@workspace/ui/components/icon"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@workspace/ui/components/popover"
|
||||
|
||||
export function AccountMenu() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={<Button variant="ghost" size="icon" aria-label="账户菜单" />}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarFallback>JF</AvatarFallback>
|
||||
</Avatar>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
align="end"
|
||||
side="bottom"
|
||||
showArrow={{ cornerRadius: "2xl", tailSize: "sm" }}
|
||||
className="w-80 gap-0 p-0"
|
||||
viewportClassName="max-h-[min(28rem,var(--available-height))] p-4"
|
||||
>
|
||||
<PopoverTitle>Jaydon Frankie</PopoverTitle>
|
||||
<PopoverDescription>demo@minimals.cc</PopoverDescription>
|
||||
<Button variant="ghost" className="mt-4 w-full justify-start">
|
||||
<SettingsIcon />
|
||||
界面外观
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 参数说明 {#why-these-props}
|
||||
|
||||
`align="end"` 让面板尾部与触发器尾部对齐,逻辑方向会自动适配 RTL。`showArrow` 启用本地连续尾巴。外层 `className` 控制整个表面,内部 `viewportClassName` 控制滚动区域;这种分离能让长菜单滚动而不裁剪尾巴。
|
||||
|
||||
## 可访问性 {#accessibility}
|
||||
|
||||
触发按钮使用图标或头像时必须提供 `aria-label`。面板使用 `PopoverTitle` 与 `PopoverDescription`,关闭与焦点返回交给 Base UI Root 管理。若内容本质上是一组选项或命令,应改用 DropdownMenu 或 Command,而不是手工添加错误的 menu role。
|
||||
@@ -1,37 +1,96 @@
|
||||
---
|
||||
title: 组合表单
|
||||
description: 组合 Field、Input 和 Button,同时让领域状态继续由应用管理。
|
||||
title: 组合资料表单
|
||||
description: 用 Field、Input、Select 和 Button 组合可访问表单,同时把状态留在应用层。
|
||||
order: 30
|
||||
toc:
|
||||
- id: create-the-form
|
||||
title: 创建表单
|
||||
- id: complete-example
|
||||
title: 完整示例
|
||||
- id: validation
|
||||
title: 表单验证
|
||||
title: 验证状态
|
||||
- id: controlled-select
|
||||
title: 受控 Select
|
||||
- id: styling
|
||||
title: 样式
|
||||
title: 样式覆盖
|
||||
---
|
||||
|
||||
## 创建表单 {#create-the-form}
|
||||
## 完整示例 {#complete-example}
|
||||
|
||||
```tsx
|
||||
import * as React from "react"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
} from "@workspace/ui/components/field"
|
||||
import { Input } from "@workspace/ui/components/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
|
||||
;<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>
|
||||
export function ProfileForm() {
|
||||
const [role, setRole] = React.useState("editor")
|
||||
const [error, setError] = React.useState("")
|
||||
|
||||
function submit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
const data = new FormData(event.currentTarget)
|
||||
setError(data.get("displayName") ? "" : "请输入显示名称")
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={submit} className="max-w-md space-y-6">
|
||||
<FieldGroup>
|
||||
<Field data-invalid={Boolean(error)}>
|
||||
<FieldLabel htmlFor="display-name">显示名称</FieldLabel>
|
||||
<Input
|
||||
id="display-name"
|
||||
name="displayName"
|
||||
aria-invalid={Boolean(error)}
|
||||
aria-describedby="display-name-error"
|
||||
/>
|
||||
<FieldDescription>其他成员会看到这个名称。</FieldDescription>
|
||||
<FieldError id="display-name-error">{error}</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel htmlFor="role">角色</FieldLabel>
|
||||
<Select
|
||||
value={role}
|
||||
onValueChange={(value) => setRole(value ?? "editor")}
|
||||
>
|
||||
<SelectTrigger id="role">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="viewer">只读</SelectItem>
|
||||
<SelectItem value="editor">编辑者</SelectItem>
|
||||
<SelectItem value="admin">管理员</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<Button type="submit">保存资料</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 表单验证 {#validation}
|
||||
## 验证状态 {#validation}
|
||||
|
||||
UI 包负责渲染验证状态,但不会指定表单库或 Schema。请在应用边界连接原生表单数据、React 状态或表单框架。
|
||||
UI 包不选择验证库。应用负责产生错误,再把同一状态连接到 `Field data-invalid`、输入控件的 `aria-invalid` 和 `FieldError`。多个 Schema 错误可通过 `FieldError errors={errors}` 传入,它会去重后渲染列表。
|
||||
|
||||
## 样式 {#styling}
|
||||
## 受控 Select {#controlled-select}
|
||||
|
||||
受支持的语义变化使用组件变体,局部布局使用 `className`。需要合并条件类名或消费者覆盖时,使用 `@workspace/ui/lib/utils` 中的 `cn`。
|
||||
本地 Select 基于 Base UI,使用 `value` 与 `onValueChange`。`SelectTrigger` 的箭头由组件自己渲染,只需放入 `SelectValue`;不要再添加第二个 Chevron。弹层默认与 trigger 对齐并使用其宽度,可通过 `SelectContent` 的 `side`、`align`、`sideOffset` 与 `alignItemWithTrigger` 调整。
|
||||
|
||||
## 样式覆盖 {#styling}
|
||||
|
||||
先使用组件已有的 `variant`、`size` 和结构部件,再用 `className` 处理页面布局。组件内部使用 `cn` 合并 class,因此消费端的 Tailwind class 可以覆盖默认 utility。跨多个页面重复的覆盖应沉淀为新 variant 或复合组件,而不是复制长 class 字符串。
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: 消息线程
|
||||
description: 组合滚动容器、消息布局与气泡,同时保护用户阅读历史消息的位置。
|
||||
order: 32
|
||||
toc:
|
||||
- id: structure
|
||||
title: 组件结构
|
||||
- id: messages
|
||||
title: 渲染消息
|
||||
- id: ownership
|
||||
title: 状态归属
|
||||
---
|
||||
|
||||
## 组件结构 {#structure}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
MessageScroller,
|
||||
MessageScrollerButton,
|
||||
MessageScrollerContent,
|
||||
MessageScrollerItem,
|
||||
MessageScrollerProvider,
|
||||
MessageScrollerViewport,
|
||||
} from "@workspace/ui/components/message-scroller"
|
||||
|
||||
export function Thread({ messages }: { messages: ChatMessage[] }) {
|
||||
return (
|
||||
<MessageScrollerProvider>
|
||||
<MessageScroller className="h-[32rem]">
|
||||
<MessageScrollerViewport>
|
||||
<MessageScrollerContent className="gap-5 p-4">
|
||||
{messages.map((message, index) => (
|
||||
<MessageScrollerItem
|
||||
key={message.id}
|
||||
messageId={message.id}
|
||||
scrollAnchor={index === messages.length - 1}
|
||||
>
|
||||
<ChatMessageRow message={message} />
|
||||
</MessageScrollerItem>
|
||||
))}
|
||||
</MessageScrollerContent>
|
||||
</MessageScrollerViewport>
|
||||
<MessageScrollerButton />
|
||||
</MessageScroller>
|
||||
</MessageScrollerProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 渲染消息 {#messages}
|
||||
|
||||
在 `ChatMessageRow` 中使用 `Message`、`MessageAvatar`、`MessageContent`、`MessageHeader` 和 `MessageFooter` 表达参与者结构,再在 Content 内组合一个或多个 `Bubble`。发送者消息可使用 `align="end"`,但不要根据文本语言交换消息归属。
|
||||
|
||||
附件放入相同的 `MessageContent`。如果一条消息没有气泡背景,使用 `Bubble variant="ghost"`,Header 与 Footer 会自动移除气泡对应的水平 padding。
|
||||
|
||||
## 状态归属 {#ownership}
|
||||
|
||||
`MessageScroller` 管理跟随底部、可见性与跳转按钮;应用管理消息数组、分页、加载、已读和重试。加载更早消息时应保持稳定 ID,并使用 scroller API 维持视觉锚点,而不是在组件内复制一套滚动监听。
|
||||
@@ -1,30 +1,101 @@
|
||||
---
|
||||
title: 选择组件
|
||||
description: 按职责选择基础组件,而不是导入一个庞大的统一组件入口。
|
||||
title: 组件目录
|
||||
description: 按职责查找组件入口,并理解本地 Base UI 的组合方式。
|
||||
order: 11
|
||||
toc:
|
||||
- id: forms
|
||||
title: 表单与输入
|
||||
- id: overlays
|
||||
title: 浮层与菜单
|
||||
- id: content
|
||||
- id: composition-model
|
||||
title: 组合模型
|
||||
- id: actions-and-forms
|
||||
title: 操作与表单
|
||||
- id: overlays-and-navigation
|
||||
title: 浮层与导航
|
||||
- id: content-and-feedback
|
||||
title: 内容与反馈
|
||||
- id: layout-and-data
|
||||
title: 布局与数据
|
||||
- id: icons
|
||||
title: 图标
|
||||
---
|
||||
|
||||
## 表单与输入 {#forms}
|
||||
## 组合模型 {#composition-model}
|
||||
|
||||
普通表单可使用 `Button`、`Input`、`Textarea`、`Field` 和 `Label`。选择类输入可加入 `Checkbox`、`RadioGroup`、`Switch` 或 `Slider`;更丰富的选择场景可使用 `Select`、`Combobox`、`Command` 或 `Calendar`。
|
||||
组件采用 shadcn/ui 的“复制源码并组合 primitives”模型,但本仓库选择的是 Base UI 实现。组件通常由 Root、Trigger、Content、Item 等部分组成,状态和可访问性由底层 primitive 管理,外观通过 Tailwind class 与 `data-slot` 暴露。
|
||||
|
||||
## 浮层与菜单 {#overlays}
|
||||
Base UI 使用 `render` 组合自定义元素,而不是 Radix 版本常见的 `asChild`:
|
||||
|
||||
`Dialog`、`AlertDialog`、`Sheet` 和 `Drawer` 用于模态界面。`Popover`、`HoverCard` 和 `Tooltip` 用于锚定式信息展示,菜单模块则覆盖上下文菜单、下拉菜单、菜单栏和导航模式。
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Dialog, DialogTrigger } from "@workspace/ui/components/dialog"
|
||||
|
||||
## 内容与反馈 {#content}
|
||||
;<Dialog>
|
||||
<DialogTrigger render={<Button variant="outline" />}>编辑资料</DialogTrigger>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
卡片、列表项、表格、图表、进度条、骨架屏、加载器、空状态、消息和 Toast 构成通用展示词汇。Accordion、Collapsible、Tabs、Carousel 和 Pagination 用于组织更大规模的内容。
|
||||
只在需要替换底层元素时使用 `render`。普通内容直接作为 children 传入。
|
||||
|
||||
## 操作与表单 {#actions-and-forms}
|
||||
|
||||
| 目标 | 组件入口 |
|
||||
| -------------- | -------------------------------------------------- |
|
||||
| 按钮与成组操作 | `button`、`button-group`、`toggle`、`toggle-group` |
|
||||
| 文本输入 | `input`、`textarea`、`input-group`、`input-otp` |
|
||||
| 字段结构 | `field`、`label` |
|
||||
| 布尔与单选 | `checkbox`、`switch`、`radio-group` |
|
||||
| 范围与进度 | `slider`、`progress` |
|
||||
| 选项与命令 | `native-select`、`select`、`combobox`、`command` |
|
||||
| 日期 | `calendar` |
|
||||
|
||||
简单、移动端优先且不需要自定义浮层的选项优先用 `NativeSelect`。需要搜索、多选或自定义项时,分别选择 `Combobox`、`Select` 或 `Command`。
|
||||
|
||||
## 浮层与导航 {#overlays-and-navigation}
|
||||
|
||||
| 目标 | 组件入口 |
|
||||
| -------------- | -------------------------------------------------------- |
|
||||
| 模态确认与编辑 | `alert-dialog`、`dialog`、`sheet`、`drawer` |
|
||||
| 锚定内容 | `popover`、`hover-card`、`tooltip`、`anchored-surface` |
|
||||
| 菜单 | `dropdown-menu`、`context-menu`、`menubar` |
|
||||
| 页面导航 | `breadcrumb`、`navigation-menu`、`pagination`、`sidebar` |
|
||||
|
||||
Dialog、Sheet 与 Drawer 都提供受控状态和组合部件。重要破坏性操作使用 `AlertDialog`,短暂说明使用 `Tooltip`,需要交互控件的锚定面板使用 `Popover`。
|
||||
|
||||
## 内容与反馈 {#content-and-feedback}
|
||||
|
||||
| 目标 | 组件入口 |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| 容器与状态 | `card`、`item`、`empty`、`alert`、`badge`、`marker` |
|
||||
| 加载与通知 | `skeleton`、`spinner`、`toast` |
|
||||
| 消息界面 | `message`、`bubble`、`attachment`、`message-scroller` |
|
||||
| 分段内容 | `accordion`、`collapsible`、`tabs`、`carousel` |
|
||||
|
||||
`Message` 负责参与者、对齐和元数据;`Bubble` 负责消息正文表面;`Attachment` 表达上传生命周期;`MessageScroller` 管理自动跟随和回到底部按钮。它们都是展示原语,不拥有会话数据。
|
||||
|
||||
## 布局与数据 {#layout-and-data}
|
||||
|
||||
| 目标 | 组件入口 |
|
||||
| -------- | ------------------------------------------------------- |
|
||||
| 数据展示 | `table`、`chart` |
|
||||
| 布局 | `separator`、`aspect-ratio`、`resizable`、`scroll-area` |
|
||||
| 方向 | `direction` |
|
||||
| 键盘提示 | `kbd` |
|
||||
| 头像 | `avatar` |
|
||||
|
||||
图表模块封装 Recharts 的容器、Tooltip、Legend 与样式映射,不替应用决定数据模型。`DirectionProvider` 直接转发 Base UI 的方向上下文;页面仍应同步设置 HTML 的 `dir` 属性。
|
||||
|
||||
## 图标 {#icons}
|
||||
|
||||
从 `@workspace/ui/components/icon` 导入具名图标。该模块统一了 `@icones/react` 的图标词汇,使应用和各个包使用同一图标来源。
|
||||
从本地 Icones 适配层导入具名图标:
|
||||
|
||||
```tsx
|
||||
import { SearchIcon, SettingsIcon } from "@workspace/ui/components/icon"
|
||||
```
|
||||
|
||||
组件会为没有显式尺寸 class 的 SVG 提供默认尺寸。图标按钮仍要提供可访问名称:
|
||||
|
||||
```tsx
|
||||
<Button size="icon" aria-label="搜索">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
不要在新组件中重新引入 `lucide-react`;需要新增图标时,在 `components/icon.tsx` 中用 `createIcon` 统一注册。
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: 反馈与消息
|
||||
description: 配置 Toast,并用 Message、Bubble、Attachment 和 MessageScroller 组合消息界面。
|
||||
order: 13
|
||||
toc:
|
||||
- id: toast-provider
|
||||
title: Toast Provider
|
||||
- id: send-toast
|
||||
title: 发送 Toast
|
||||
- id: message-composition
|
||||
title: 消息组合
|
||||
- id: attachments
|
||||
title: 附件状态
|
||||
- id: scrolling
|
||||
title: 消息滚动
|
||||
---
|
||||
|
||||
## Toast Provider {#toast-provider}
|
||||
|
||||
在应用根节点附近挂载一次 `Toaster`:
|
||||
|
||||
```tsx
|
||||
import { Toaster } from "@workspace/ui/components/toast"
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<Routes />
|
||||
<Toaster />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
默认 viewport 固定在小屏底部与大屏右下角。Toast 支持堆叠、展开、滑动退出、键盘焦点和 `success`、`info`、`warning`、`error`、`loading` 类型图标。
|
||||
|
||||
## 发送 Toast {#send-toast}
|
||||
|
||||
使用模块导出的 manager:
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { toast } from "@workspace/ui/components/toast"
|
||||
|
||||
;<Button
|
||||
onClick={() =>
|
||||
toast.add({
|
||||
title: "已保存",
|
||||
description: "个人资料已经更新。",
|
||||
type: "success",
|
||||
})
|
||||
}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
```
|
||||
|
||||
需要隔离通知区域时,用 `createToastManager()` 创建 manager,并同时传给 `Toaster` 与调用方。关闭按钮的默认可访问名称来自 UI 本地化上下文。
|
||||
|
||||
## 消息组合 {#message-composition}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Message,
|
||||
MessageContent,
|
||||
MessageFooter,
|
||||
MessageGroup,
|
||||
} from "@workspace/ui/components/message"
|
||||
import { Bubble, BubbleContent } from "@workspace/ui/components/bubble"
|
||||
|
||||
;<MessageGroup>
|
||||
<Message align="end">
|
||||
<MessageContent>
|
||||
<Bubble align="end" variant="tinted">
|
||||
<BubbleContent>明天下午三点可以。</BubbleContent>
|
||||
</Bubble>
|
||||
<MessageFooter>已读 · 14:32</MessageFooter>
|
||||
</MessageContent>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
```
|
||||
|
||||
把消息 ID、作者、发送状态与操作保留在业务层。UI primitives 只从 props 和 `data-*` 状态决定布局。
|
||||
|
||||
## 附件状态 {#attachments}
|
||||
|
||||
`Attachment` 支持 `idle`、`uploading`、`processing`、`error` 和 `done`。`AttachmentMedia`、`AttachmentTitle`、`AttachmentDescription` 与 `AttachmentActions` 会根据状态调整外观;上传进度和重试逻辑仍由应用负责。
|
||||
|
||||
横向附件使用默认 `orientation="horizontal"`,缩略图网格可使用 `orientation="vertical"`。多个附件放入 `AttachmentGroup` 后会获得横向滚动和 snap 行为。
|
||||
|
||||
## 消息滚动 {#scrolling}
|
||||
|
||||
`MessageScrollerProvider`、`MessageScroller`、`MessageScrollerViewport` 与 `MessageScrollerContent` 应保持层级。每条消息放在 `MessageScrollerItem` 中;需要作为自动跟随目标的项传 `scrollAnchor`。`MessageScrollerButton direction="end"` 会根据当前可滚动状态自动显示或隐藏。
|
||||
|
||||
业务层追加消息时不要直接设置 `scrollTop`。使用这些 primitives 提供的 follow 和 visibility 状态,避免用户向上阅读历史内容时被强制拉回底部。
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: 浮层与定位
|
||||
description: 使用 Base UI 的 render 组合 Dialog、Popover 与本地 AnchoredSurface。
|
||||
order: 12
|
||||
toc:
|
||||
- id: dialog
|
||||
title: Dialog
|
||||
- id: popover
|
||||
title: Popover
|
||||
- id: anchored-surface
|
||||
title: 连续尾巴
|
||||
- id: positioning
|
||||
title: 自定义定位
|
||||
- id: choosing-an-overlay
|
||||
title: 选择浮层
|
||||
---
|
||||
|
||||
## Dialog {#dialog}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@workspace/ui/components/dialog"
|
||||
|
||||
export function ProfileDialog() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger render={<Button variant="outline" />}>
|
||||
编辑资料
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑资料</DialogTitle>
|
||||
<DialogDescription>更新公开显示的信息。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>表单内容</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`DialogContent` 已包含 Portal、Overlay 和默认关闭按钮。标题与说明会连接到底层可访问性属性,不要只用无语义的 `div` 替代。
|
||||
|
||||
## Popover {#popover}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@workspace/ui/components/popover"
|
||||
|
||||
;<Popover>
|
||||
<PopoverTrigger render={<Button variant="outline" />}>
|
||||
打开设置
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end">
|
||||
<PopoverTitle>显示设置</PopoverTitle>
|
||||
<PopoverDescription>选择列表密度。</PopoverDescription>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
```
|
||||
|
||||
本地 API 继续转发 Base UI Popup 与 Positioner 的 props。需要受控状态时给 `Popover` 传入 `open` 和 `onOpenChange`。
|
||||
|
||||
## 连续尾巴 {#anchored-surface}
|
||||
|
||||
本仓库的 `showArrow` 不是普通三角 SVG。它让 `PopoverContent` 用 `AnchoredSurface` 渲染一个圆角表面与连续曲线尾巴:
|
||||
|
||||
```tsx
|
||||
<PopoverContent
|
||||
side="bottom"
|
||||
showArrow={{ cornerRadius: "2xl", tailSize: "sm" }}
|
||||
className="w-80 p-0"
|
||||
viewportClassName="max-h-72 p-4"
|
||||
>
|
||||
{content}
|
||||
</PopoverContent>
|
||||
```
|
||||
|
||||
外层表面保持 `overflow: visible`,避免尾巴被裁剪;滚动发生在内部 `data-slot="popover-viewport"`。因此要限制内容高度或设置 padding 时,优先使用 `viewportClassName`。
|
||||
|
||||
`AnchoredSurface` 也可以独立使用,但它只负责视觉表面,不负责测量锚点、碰撞检测、焦点或 Portal。
|
||||
|
||||
## 自定义定位 {#positioning}
|
||||
|
||||
`PopoverContent` 额外暴露 `anchor`、`positionMethod`、`arrowPadding`、`alignOffset` 和 `sideOffset`。虚拟锚点或自定义锚点应通过这些参数传给共享组件,不需要绕过它重新拼装 Base UI primitives。
|
||||
|
||||
需要从非 React 代码控制弹层时,可使用 `createPopoverHandle<Payload>()` 创建 handle,再按 Base UI 的 handle 模式传给 Root。
|
||||
|
||||
## 选择浮层 {#choosing-an-overlay}
|
||||
|
||||
- 必须阻止背景交互:`Dialog` 或 `AlertDialog`。
|
||||
- 从屏幕边缘进入的任务面板:`Sheet`;强调触摸拖拽的移动面板:`Drawer`。
|
||||
- 与一个触发点关联的交互内容:`Popover`。
|
||||
- 只在悬停或聚焦时补充预览:`HoverCard`。
|
||||
- 简短、非交互说明:`Tooltip`。
|
||||
|
||||
基础用法可与 [shadcn Popover 文档](https://ui.shadcn.com/docs/components/base/popover)对照;组合语法必须使用 Base UI 版本的 `render`。
|
||||
@@ -1,39 +1,91 @@
|
||||
---
|
||||
title: 项目配置
|
||||
description: 导入全局样式,并从显式组件子路径开始使用。
|
||||
description: 配置 workspace 依赖、全局样式、Tailwind 扫描和 shadcn CLI。
|
||||
order: 10
|
||||
toc:
|
||||
- id: import-styles
|
||||
title: 导入样式
|
||||
- id: import-a-component
|
||||
title: 导入组件
|
||||
- id: entry-points
|
||||
title: 包入口
|
||||
- id: dependency
|
||||
title: 添加依赖
|
||||
- id: global-styles
|
||||
title: 全局样式
|
||||
- id: imports
|
||||
title: 显式导入
|
||||
- id: shadcn-cli
|
||||
title: shadcn CLI
|
||||
- id: browser-components
|
||||
title: 浏览器组件
|
||||
- id: verify
|
||||
title: 验证配置
|
||||
---
|
||||
|
||||
## 导入样式 {#import-styles}
|
||||
## 添加依赖 {#dependency}
|
||||
|
||||
在应用入口中导入一次全局样式:
|
||||
应用通过 workspace 协议引用 UI 包:
|
||||
|
||||
```ts
|
||||
import "@workspace/ui/globals.css"
|
||||
```
|
||||
|
||||
它提供组件所需的共享 Tailwind 层、设计变量、字体配置和工具类。
|
||||
|
||||
## 导入组件 {#import-a-component}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export function SaveButton() {
|
||||
return <Button>保存</Button>
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/ui": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 包入口 {#entry-points}
|
||||
仓库中的应用已经完成此配置。这个 package 当前是私有 workspace package,不需要从 npm 安装。
|
||||
|
||||
- `@workspace/ui/components/*` 导出单个组件模块。
|
||||
- `@workspace/ui/hooks/*` 导出可复用的交互和媒体查询状态。
|
||||
- `@workspace/ui/lib/utils` 导出共享的类名组合工具。
|
||||
- `@workspace/ui/locales/*` 导出指定语言的词典。
|
||||
## 全局样式 {#global-styles}
|
||||
|
||||
在应用的全局 CSS 中导入一次:
|
||||
|
||||
```css
|
||||
@import "@workspace/ui/globals.css";
|
||||
```
|
||||
|
||||
该入口会加载 Tailwind CSS、shadcn 的 Tailwind 扩展、动画工具、Inter Variable 字体、语义颜色、圆角变量和基础样式。主题通过根节点上的 `.ui:light` 与 `.ui:dark` class 切换;组件代码不要自行复制这些变量。
|
||||
|
||||
`globals.css` 中的 `@source` 已覆盖本仓库的 `apps` 与组件目录。如果将 package 移到另一种目录结构,应同步调整扫描路径,否则生产构建可能缺少组件使用的 utility。
|
||||
|
||||
## 显式导入 {#imports}
|
||||
|
||||
package 没有聚合式 barrel。始终从具体子路径导入,便于追踪依赖并避免意外扩大客户端 bundle:
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { SaveIcon } from "@workspace/ui/components/icon"
|
||||
import { useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
```
|
||||
|
||||
公开入口包括:
|
||||
|
||||
- `@workspace/ui/components/*`:组件与局部类型。
|
||||
- `@workspace/ui/hooks/*`:断点和波纹交互。
|
||||
- `@workspace/ui/locales`:支持的 locale 与消息 ID 类型。
|
||||
- `@workspace/ui/locales/en-US`、`@workspace/ui/locales/zh-Hans`:消息与日历 locale。
|
||||
- `@workspace/ui/globals.css`:全局样式。
|
||||
|
||||
## shadcn CLI {#shadcn-cli}
|
||||
|
||||
仓库根目录运行:
|
||||
|
||||
```sh
|
||||
bunx shadcn@latest add button -c apps/web
|
||||
```
|
||||
|
||||
应用和 UI package 的 `components.json` 使用同一套 `base-vega`、neutral、CSS variables、Lucide 词汇和 RTL 配置;别名会把生成文件放到 `packages/ui/src/components`。这与 shadcn 官方的 [monorepo 工作流](https://ui.shadcn.com/docs/monorepo)一致。
|
||||
|
||||
CLI 生成完成后必须检查 diff。生成器不知道本仓库的 Icones、国际化、波纹和 Popover 扩展,更新已有组件时可能覆盖本地适配。`components.json` 各字段的含义可参考[官方说明](https://ui.shadcn.com/docs/components-json)。
|
||||
|
||||
## 浏览器组件 {#browser-components}
|
||||
|
||||
多数交互组件依赖事件、Portal 或浏览器 API。使用 React Server Components 的消费端应从自身的客户端边界导入它们。本仓库配置为 `rsc: false`,组件文件不会统一插入框架专用指令;少数组件因自身需要显式包含 `"use client"`。
|
||||
|
||||
## 验证配置 {#verify}
|
||||
|
||||
修改组件或配置后运行:
|
||||
|
||||
```sh
|
||||
bun run --cwd packages/ui typecheck
|
||||
bun run lint
|
||||
bun run format:check
|
||||
```
|
||||
|
||||
如果改动涉及消费方式,再运行应用或文档的构建,确认 Tailwind 能扫描到新 class,并检查浅色、深色与 RTL。
|
||||
|
||||
@@ -1,24 +1,53 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 UI 包的定位,以及显式入口如何支持组件组合。
|
||||
description: 了解 @workspace/ui 的职责、上游基线和本仓库的扩展。
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: 包的职责
|
||||
- id: tutorial-map
|
||||
title: 教程路线
|
||||
- id: ui-or-blocks
|
||||
title: UI 还是 Blocks
|
||||
- id: source-model
|
||||
title: 源码模型
|
||||
- id: local-adaptations
|
||||
title: 本地适配
|
||||
- id: learning-path
|
||||
title: 学习路线
|
||||
- id: package-boundaries
|
||||
title: 包边界
|
||||
---
|
||||
|
||||
## 包的职责 {#package-role}
|
||||
|
||||
`@workspace/ui` 包含可复用的 React 基础组件、复合控件、Hooks、图标、样式和本地化词典。它是应用与上层功能包共享的视觉基础。
|
||||
`@workspace/ui` 是整个工作区的视觉基础。它提供可编辑的 React 组件源码、Tailwind CSS 主题变量、响应式与交互 Hooks、统一图标入口,以及组件自身使用的本地化消息。
|
||||
|
||||
## 教程路线 {#tutorial-map}
|
||||
这个包只负责通用界面能力。路由、数据请求、持久化和业务流程仍由应用或领域 package 管理。
|
||||
|
||||
本教程会安装样式、介绍组件子路径、通过组合构建一个小型表单、添加响应式行为,并接入本地化词典。
|
||||
## 源码模型 {#source-model}
|
||||
|
||||
## UI 还是 Blocks {#ui-or-blocks}
|
||||
组件以 [shadcn/ui Base UI 版本](https://ui.shadcn.com/docs/components/base)和其 [GitHub registry 源码](https://github.com/shadcn-ui/ui/tree/main/apps/v4/registry/bases/base/ui)为基线。与 shadcn/ui 一样,它们不是不可修改的第三方黑盒:源码位于 `packages/ui/src`,可以直接审查、组合和演进。
|
||||
|
||||
当应用自己拥有业务流程时使用 UI 组件。导航和通知等完整应用外壳功能使用 `@workspace/blocks`,媒体与搜索等领域工作流使用 `@workspace/media` 或 `@workspace/search` 等专用包。
|
||||
本仓库使用 `base-vega` 风格、CSS 变量、Tailwind CSS v4、Base UI primitives 和 RTL 输出。`components.json` 只控制生成方式;运行时 API 以本仓库源码为准。
|
||||
|
||||
## 本地适配 {#local-adaptations}
|
||||
|
||||
本仓库在上游基础上做了这些有意保留的调整:
|
||||
|
||||
- 使用 `@icones/react` 封装统一的 `components/icon` 入口,不直接依赖 `lucide-react`。
|
||||
- `Button` 默认接入指针波纹反馈。
|
||||
- 增加 `I18nProvider`,让 Breadcrumb、Calendar、Carousel、Pagination、Sidebar、Toast 等组件使用应用的翻译器。
|
||||
- 只发布 `en-US` 和 `zh-Hans` 两套 UI 词典与日历 locale。
|
||||
- 增加 mobile、tablet、desktop 三段式断点 Hooks 和根节点 class 同步。
|
||||
- 扩展 `PopoverContent` 的自定义锚点、定位方式、连续尾巴和独立滚动视口。
|
||||
- 增加 `AnchoredSurface`,并保留消息、气泡、附件和消息滚动器等组合组件。
|
||||
|
||||
升级 shadcn/ui 组件时不要盲目覆盖这些差异。完整清单见“本仓库适配”。
|
||||
|
||||
## 学习路线 {#learning-path}
|
||||
|
||||
先完成“项目配置”,再通过“组件目录”确定入口;浮层和反馈类组件有独立指南。需要 SSR、响应式行为或多语言时,再阅读进阶章节。示例章节展示可直接组合进应用的完整结构。
|
||||
|
||||
## 包边界 {#package-boundaries}
|
||||
|
||||
- 使用 `@workspace/ui` 构建按钮、表单、菜单、弹层、列表和反馈等基础界面。
|
||||
- 使用 `@workspace/blocks` 组合应用外壳、导航、聊天和通知等完整区域。
|
||||
- 使用 `@workspace/media`、`@workspace/search`、`@workspace/lexical` 等 package 承载领域交互。
|
||||
- 使用 `@workspace/appearance` 与 `@workspace/preferences` 聚合主题偏好和持久化。
|
||||
|
||||
Reference in New Issue
Block a user