This commit is contained in:
oneao committed 2026-10-09 22:05:02 +08:00
1 parent 0be0b0767a
commit 5f28a10d89
852 files changed
+8962 -111755

No files matched your search

@@ -0,0 +1,100 @@
---
title: 双向布局(RTL)
description: 让组件跟随从右到左的书写方向,以及哪些地方刻意不镜像
---
# 双向布局(RTL)
组件库同时支持 LTR / RTL。镜像的原则只有一条:**跟随行内方向**(inline direction),
而不是「左边换成右边」的粗暴翻转。
## 怎么开启
**整站(推荐)**:宿主在 `<html>` 上声明方向,组件零配置跟随:
```html
<html lang="ar" dir="rtl">
```
视觉镜像完全由 CSS 的 `[dir='rtl']` 选择器驱动;需要方向语义的 JS 逻辑
(浮层对齐轴、键盘左右键)由 `useDirection()` 读取 —— 它会观察
`documentElement` 的 `dir` / `class` / `style`,运行时切换也能跟上。
**局部子树**:同一页里既有 LTR 又有 RTL 时,用 `G3ConfigProvider`:
```vue
<G3ConfigProvider direction="rtl">
<G3Form>…</G3Form>
</G3ConfigProvider>
```
它会把 `dir` 挂到子树根,同时通过 `provide` 下发。这条路径**在 SSR 首帧就生效**,
不受「挂载后才能探测 DOM」影响,是服务端渲染场景的首选。
**组件树外**:`G3Message` / `G3Notification` 这类拿不到 `inject` 的实例走全局兜底:
```ts
G3UI.config({ direction: 'rtl' })
```
优先级:`ConfigProvider` → 全局配置 → 文档方向(`<html dir>`)→ `ltr`。
## 写组件样式的规矩
镜像靠**逻辑属性**实现,组件 `<style>` 里不要用 `left` / `right`:
| 场景 | 用什么 |
|---|---|
| 缩进、内边距、边框 | `padding-inline-start` / `border-inline-start` |
| 定位到某一侧 | `inset-inline-start` / `inset-inline-end` |
| 圆角 | `border-start-start-radius` 等逻辑角 |
| 外边距偏移(栅格 offset) | `margin-inline-start` |
| 带符号的位移 | token `--g3-dir-sign`(LTR `1` / RTL `-1`) |
```scss
/* 滑块位移:RTL 下自动反向 */
transform: translateX(calc(#{v('dir-sign', 1)} * 12px));
```
⚠️ **不要用 `scaleX(-1)` 做整块镜像**,文字会变成反字。只有方向性图标(Chevron 系列)
该翻面,统一在 `packages/ui/src/_styles/rtl.scss` 里按 lucide 自带的 `lucide-*` 类名处理。
## 刻意不镜像的地方
这些是**物理**语义,`dir="rtl"` 下不会翻,属设计约定:
| 位置 | 原因 |
|---|---|
| `G3Drawer` / `G3Modal` / `G3Tabs` 的 `placement="left \| right"` | 字面 API,指定的是物理边 |
| 浮层(Popover / Tooltip / Dropdown / Select / DatePicker)的主方向 `left` / `right` | 与 floating-ui 一致:只镜像 `*-start` / `*-end` 的**对齐轴** |
| 浮层箭头、`left: 50%` 居中、`G3Notification` 的停靠边 | 物理锚定,没有行内语义 |
另外 `G3Grid` 的 `push` / `pull` 已改为**沿行内方向位移**(跟随方向),
与 Bootstrap 的物理语义**不同**,迁移时留意。
## 键盘
水平方向键按行内方向解释:LTR 里 `ArrowRight` 是「下一个」,RTL 里反过来是 `ArrowLeft`。
已覆盖:
- `G3Tabs` 标签切换
- `G3Tree` 展开 / 收起 / 进子节点 / 回父节点
- `G3DatePicker` 日期面板的左右移动
- `G3Splitter` 分隔条左右调整
`G3Radio` / `G3Segmented` 内部是原生 `<input type="radio">`,键盘行为交给浏览器。
## 自测
- playground 右上角 `dir: ltr / rtl` 开关;也可用 `?dir=rtl` 直接以 RTL 进入
- `pnpm --filter @g3soft/ui test:e2e` 里的 `e2e/rtl.spec.ts` 锁住几何镜像
(滑块、滚动条轨道、树缩进、按钮组圆角、浮层对齐、键盘语义)
## 已知边界
- **横向 `G3Scrollbar` 的拖拽**在 RTL 下依赖浏览器 `scrollLeft` 的符号约定。
新版 Chrome / Edge / Safari 已一致;Firefox 未实测。
- 运行时用脚本直接翻 `<html dir>` 时,CSS 立即生效,JS 侧靠 `MutationObserver` 跟随
(同一帧内可能读到旧值,定位类行为会在下一次展开 / 重算时纠正)。
`G3ConfigProvider` 路径没有这个问题。
- 语言包目前只有 `zhCN` / `enUS`,**没有** `ar`;文案需自行接入。