@linhey/rhine-design 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +63 -14
- package/components/Actions.tsx +2 -0
- package/components/AdvancedForms.tsx +184 -0
- package/components/AppShell.tsx +71 -0
- package/components/Background.tsx +29 -0
- package/components/COMMON_COMPONENTS.md +5 -1
- package/components/Charts.tsx +94 -0
- package/components/Data.tsx +98 -18
- package/components/FormWorkflow.tsx +25 -0
- package/components/Forms.tsx +14 -8
- package/components/MOTION.md +6 -2
- package/components/Media.tsx +82 -0
- package/components/Navigation.tsx +42 -0
- package/components/Overlays.tsx +10 -3
- package/components/README.md +20 -5
- package/components/SPACING.md +82 -0
- package/components/StatusTimeline.tsx +67 -0
- package/components/TYPOGRAPHY.md +34 -2
- package/components/background.css +43 -0
- package/components/charts.css +27 -0
- package/components/common.css +17 -8
- package/components/data.css +6 -0
- package/components/drawer-placement.css +4 -0
- package/components/extensions.css +70 -0
- package/components/index.ts +18 -2
- package/components/media-parity.css +3 -0
- package/components/patterns.css +39 -0
- package/components/styles.css +15 -8
- package/components/theme-arknights.css +306 -0
- package/components/theme-neo-brutalism.css +90 -0
- package/components/theme.tsx +58 -0
- package/components/timeline.css +23 -0
- package/components/tokens.css +49 -1
- package/docs/ANTD_MUI_AUDIT.md +38 -0
- package/docs/APPLICATION_COMPONENTS.md +175 -0
- package/docs/DESIGN_SYSTEM.md +12 -2
- package/docs/DESIGN_SYSTEM_AUDIT.md +6 -4
- package/docs/INTERACTION_PATTERNS.md +129 -0
- package/docs/ISSUE_4_VALIDATION.md +21 -0
- package/docs/ISSUE_VALIDATION.md +52 -0
- package/docs/REFERENCE_RESEARCH.md +38 -0
- package/docs/THEME_NEO_BRUTALISM.md +37 -0
- package/docs/THEMING.md +145 -0
- package/docs/assets/application-shell.png +0 -0
- package/docs/assets/issue4-shell.png +0 -0
- package/docs/assets/media-viewer-mobile.png +0 -0
- package/docs/assets/monitoring-desktop.png +0 -0
- package/docs/audits/DATA_ANTD_MUI.md +75 -0
- package/docs/audits/FORMS_ANTD_MUI.md +88 -0
- package/docs/audits/MONITORING_ANTD_MUI.md +44 -0
- package/docs/audits/SHELL_MEDIA_ANTD_MUI.md +91 -0
- package/package.json +5 -1
- package/preview/BackgroundGallery.tsx +46 -0
- package/preview/CommonGallery.tsx +8 -8
- package/preview/ExtensionsGallery.tsx +95 -0
- package/preview/GalleryShell.tsx +107 -0
- package/preview/LineHeightGallery.tsx +27 -0
- package/preview/MonitoringGallery.tsx +27 -0
- package/preview/MotionGallery.tsx +6 -2
- package/preview/PatternGallery.tsx +52 -0
- package/preview/SpacingGallery.tsx +32 -0
- package/preview/SubscriptionGallery.tsx +84 -0
- package/preview/backgrounds.css +10 -0
- package/preview/extensions.css +6 -0
- package/preview/line-height.css +2 -0
- package/preview/main.tsx +48 -25
- package/preview/patterns.css +7 -0
- package/preview/preview.css +2 -8
- package/preview/shell.css +35 -0
- package/preview/spacing.css +2 -0
- package/preview/subscriptions.css +27 -0
- package/preview/theme-arknights.css +234 -0
- package/preview/theme-neo-brutalism.css +11 -0
- package/skills/rhine-design/SKILL.md +26 -3
- package/skills/rhine-design/references/API_MAP.md +11 -3
- package/skills/rhine-design/references/TASK_MAP.md +5 -1
- package/skills/rhine-design/references/integration.md +7 -3
- package/tests/Background.test.tsx +22 -0
- package/tests/DataParity.test.tsx +148 -0
- package/tests/Extensions.test.tsx +145 -0
- package/tests/FormParity.test.tsx +185 -0
- package/tests/GalleryShell.test.tsx +98 -0
- package/tests/Monitoring.test.tsx +38 -0
- package/tests/MonitoringParity.test.tsx +125 -0
- package/tests/Patterns.test.tsx +75 -0
- package/tests/ShellMediaParity.test.tsx +180 -0
- package/tests/StatusTimeline.test.tsx +15 -0
- package/tests/Theme.test.tsx +112 -0
package/components/README.md
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
# 莱茵设计 · 通用组件
|
|
1
|
+
# 莱茵设计 · 通用组件
|
|
2
2
|
|
|
3
3
|
本目录提供[莱茵设计 · Rhine Design](../README.md)的 React 组件、设计变量和 API 契约。组件接收应用传入的数据与动作,不绑定特定后端或原生平台。包名为`@linhey/rhine-design`;样式变量和类名使用`rhine-`命名空间。
|
|
4
4
|
|
|
5
|
-
共40
|
|
5
|
+
0.5.0 共59个组件;0.4.0为48个,0.3.0为40个。新增导航、表单流程及健康时间轴组件见[场景与交互规范](../docs/INTERACTION_PATTERNS.md),原24个扩展组件见[常用组件契约](./COMMON_COMPONENTS.md)。[字体与文字规范](./TYPOGRAPHY.md)与[主流系统对照与缺口](../docs/DESIGN_SYSTEM_AUDIT.md)共同约束复用。静态页面样稿也使用同源Toolbar/Select CSS,不运行React。
|
|
6
6
|
|
|
7
7
|
页面改动遵循:优先复用组件 → 先在设计稿/展台修改并验证 → 再按任务授权实现到App。缺组件先补组件与展台,不在业务页另写同用途样式;同一任务已授权设计和实现时连续推进,无需重复确认。
|
|
8
8
|
|
|
9
|
+
## 间距组合
|
|
10
|
+
|
|
11
|
+
[间距规范](SPACING.md)定义尺度、分组与卡片配方:Surface负责24px内边距,嵌套Stack提供8px标题/说明和16px内容/操作间隔,Inline提供8px按钮间隔;Surface不自动给子元素加间距。展台`#spacing`可检查默认、加载、错误和长文案。
|
|
12
|
+
|
|
9
13
|
## 使用
|
|
10
14
|
|
|
11
15
|
先按[套件接入说明](../README.md#quick-start)安装组件包,再导入:
|
|
12
16
|
|
|
13
17
|
```tsx
|
|
14
|
-
import { Button, Notice, Progress, Surface } from '@linhey/rhine-design';
|
|
18
|
+
import { Button, Inline, Notice, Progress, Stack, Surface } from '@linhey/rhine-design';
|
|
15
19
|
|
|
16
20
|
export function Example() {
|
|
17
21
|
return (
|
|
18
22
|
<div className="rhine-theme">
|
|
19
23
|
<Surface>
|
|
24
|
+
<Stack gap={4}>
|
|
20
25
|
<Notice tone="warning" title="需要处理">请检查后重试。</Notice>
|
|
21
26
|
<Progress label="任务进度" value={null} />
|
|
22
|
-
<Button variant="secondary" onClick={() => { /* 业务动作 */ }}>重试</Button>
|
|
27
|
+
<Inline gap={2}><Button variant="secondary" onClick={() => { /* 业务动作 */ }}>重试</Button></Inline>
|
|
28
|
+
</Stack>
|
|
23
29
|
</Surface>
|
|
24
30
|
</div>
|
|
25
31
|
);
|
|
@@ -28,6 +34,8 @@ export function Example() {
|
|
|
28
34
|
|
|
29
35
|
入口 `index.ts` 自动导入组件 CSS。使用一个 `.rhine-theme` 包裹组件树,获得字体、盒模型、焦点和动效降级规则。主题根放在页面根部,不加transform、裁剪或contain;浮层Portal挂在最近的主题根以继承变量和减少动态偏好。独立运行与跨项目导入方式见[套件入口](../README.md);跨仓复用核对React19与套件package.json中的5个Radix依赖,不导入展台。
|
|
30
36
|
|
|
37
|
+
0.4.0 新增主题入口 `@linhey/rhine-design/theme`,导出 `ThemeProvider`、`useTheme`、`themes`、`ThemeName` 和 `ThemeProviderProps`(自 0.4.0 提供)。Provider 自动生成主题根并加载内置主题;用 `setTheme('arknights')` 切换,受控偏好与存储由宿主管理。支持并列 Provider,暂不支持嵌套覆盖。完整契约及代码见[主题 API](../docs/THEMING.md)。
|
|
38
|
+
|
|
31
39
|
- `tokens.css`:唯一色彩、尺寸与动画变量来源。
|
|
32
40
|
- `styles.css`:`rhine-` 前缀组件样式,业务页面可组合布局但不覆盖组件状态。
|
|
33
41
|
- `components.tsx`:原有基础组件;`Actions/Data/Forms/Layout/Overlays/Toast.tsx`承载新增组件。表单与业务数据由调用方控制,浮层开关、通知计时及复制结果只管理局部交互。
|
|
@@ -38,6 +46,8 @@ export function Example() {
|
|
|
38
46
|
|
|
39
47
|
## 组件契约
|
|
40
48
|
|
|
49
|
+
0.5.0 的 Background、AppShell、MediaCard、MediaViewer、DateField、DateRangePicker、NumberField、TreeSelect、RemoteMultiSelect、TrendChart、StackedBar,以及 DataTable/Pagination 和 StatusTimeline 详情扩展见[应用组件契约](../docs/APPLICATION_COMPONENTS.md)。新增 11 个,总数 59;npm 0.4.0 为 48 个。
|
|
50
|
+
|
|
41
51
|
| 组件 | 关键输入 | 状态与边界 |
|
|
42
52
|
| --- | --- | --- |
|
|
43
53
|
| Button | variant=primary/secondary/quiet/danger;busy;busyLabel;原生按钮属性 | 默认 type=button;busy 保留焦点并拦截再次 click,不自动把业务标记成功;disabled 使用原生禁用 |
|
|
@@ -47,6 +57,7 @@ export function Example() {
|
|
|
47
57
|
| Toolbar | title;description;children;HTML div属性 | 自适应控件组合,role=group与普通Tab顺序;不伪装为需要方向键导航的ARIA toolbar;不含业务状态 |
|
|
48
58
|
| Badge | tone=neutral/success/warning/error;children | 只读短状态,颜色必须配文字,不做可点击按钮 |
|
|
49
59
|
| Surface | HTML div 属性 | 静态分区,无点击语义;不是弹窗或导航 |
|
|
60
|
+
| Background(0.5.0) | roleTone=canvas/content/sidebar/surface/overlay/media;decoration=auto/theme/none;appearance={color,textColor,texture,textureSize};HTML div 属性 | 默认正文纯色;仅画布默认使用主题纹理;局部覆盖不创建主题根或定位上下文;完整配置见[场景背景](../docs/THEMING.md#场景背景050) |
|
|
50
61
|
| SelectionCard | label;selected;onSelectedChange;disabled;children | 原生 checkbox 可取消;是否单选由上层控制;内部不嵌套按钮/链接/其他输入 |
|
|
51
62
|
| TextField | label;hint;error;原生 input 属性 | label 与 id 自动关联;错误与提示组成 aria-describedby;输入值由调用方管理 |
|
|
52
63
|
| Tabs | label;items[{value,label,content,disabled?}];value;onValueChange | value 必须指向有效可用项;唯一 value;左右/Home/End,跳过禁用项;隐藏面板保留挂载但退出可访问树 |
|
|
@@ -67,7 +78,7 @@ export function Example() {
|
|
|
67
78
|
|
|
68
79
|
启动动画候选见[三角构架归位](./STARTUP.md)与`/#startup`。专属分镜不改变通用Logo入场参数,也不作为真实初始化等待门槛。
|
|
69
80
|
|
|
70
|
-
用户指定的Transitions.dev参考与莱茵设计取舍见[UX与动效补充](./MOTION.md)。`/#motion`已提供
|
|
81
|
+
用户指定的Transitions.dev参考与莱茵设计取舍见[UX与动效补充](./MOTION.md)。`/#motion`已提供12组播放、重播、复位与4倍慢放示例,可逐例切换减少动态。方向展开、内容淡入与结果反馈仅在实验室作为候选实现;通用默认实现仍以下表和实际组件为准。
|
|
71
82
|
|
|
72
83
|
### 品牌标志
|
|
73
84
|
|
|
@@ -135,3 +146,7 @@ export function Example() {
|
|
|
135
146
|
在仓库根执行npm run check。浏览器核对400/520/880/1200宽度、200%缩放、键盘/焦点、展开反向、减少动态中断与加载宽度。JSDOM不代替视觉或原生辅助技术验收。
|
|
136
147
|
|
|
137
148
|
本仓构建产物是独立展台;消费项目只导入组件入口,避免把preview中的模拟流程打入业务产物。版本变化和迁移见[CHANGELOG](../CHANGELOG.md)。
|
|
149
|
+
|
|
150
|
+
## 背景与外壳(0.5.0)
|
|
151
|
+
|
|
152
|
+
新增 `Background` 与 `AppShell`,0.5.0 共 59 个组件;npm 0.4.0 仍为 48 个。Background 支持六类 roleTone、auto/theme/none 装饰策略及 appearance 局部覆盖;AppShell 提供组合展台使用的固定顶部操作区与响应式侧栏。完整 API、最小接入、背景滚动及 Portal 边界见[背景与应用外壳契约](../docs/APPLICATION_COMPONENTS.md)。
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# 间距与组合
|
|
2
|
+
|
|
3
|
+
间距表达信息关系:同组紧、组间松、区域之间留白。颜色和边框不能代替分组。此规范适用于所有消费项目,真实示例见展台 `#spacing`。
|
|
4
|
+
|
|
5
|
+
## 尺度与用途
|
|
6
|
+
|
|
7
|
+
唯一尺寸来源是 [tokens.css](tokens.css)。`Stack / Inline / Grid` 的 `gap` 是 token 后缀,不是 px:`gap={4}` 表示16px。
|
|
8
|
+
|
|
9
|
+
| Token / gap | 尺寸 | 默认用途 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `--rhine-space-1` / 1 | 4px | 标签与紧随其后的短提示、图标微间隔 |
|
|
12
|
+
| `--rhine-space-2` / 2 | 8px | 标题与说明、字段标签与控件、同组按钮 |
|
|
13
|
+
| `--rhine-space-3` / 3 | 12px | 同组信息行、密集工具栏项目 |
|
|
14
|
+
| `--rhine-space-4` / 4 | 16px | 内容与操作、表单字段、普通列表/网格项 |
|
|
15
|
+
| `--rhine-space-6` / 6 | 24px | Surface内边距、卡片之间、模块内部的独立信息组 |
|
|
16
|
+
| `--rhine-space-8` / 8 | 32px | 页面主要章节之间、宽屏页面边距 |
|
|
17
|
+
| `--rhine-space-12` / 12 | 48px | 独立大区域或品牌留白,不用于普通表单 |
|
|
18
|
+
|
|
19
|
+
0只表示无间隔,不新增token。1px边线、控件高度、焦点环偏移不属于间距尺度。宿主既有窄窗20px边距可作为明确记录的布局例外,不能变成随处使用的新gap。
|
|
20
|
+
|
|
21
|
+
## 视觉间距与操作范围
|
|
22
|
+
|
|
23
|
+
分别定义可见图形、文字行盒、可点击范围与焦点环。视觉对齐按可见内容和行盒测量,操作范围不得为压缩列表而缩小;允许透明命中区向外扩展,但不能覆盖相邻按钮、链接或输入。不能同时叠加父gap、条目padding和控件内部透明留白来表达同一段距离。
|
|
24
|
+
|
|
25
|
+
有分隔线的重复条目,分隔线前后使用同一组间距;首项不重复叠加区域顶部间距,末项为按需详情与焦点环留出空间。不要在分隔线后立刻贴标题、却把全部留白放到前一条末尾。
|
|
26
|
+
|
|
27
|
+
密集健康列表:名称/额度行与20px健康条间隔8px,条目之间分隔线两侧各16px;首项不额外叠加顶部内距。compact时间轴的44px命中区在可见条上下各扩展12px,不占据文档流留白;调用方须保留这段安全空间,避免与相邻可点击控件重叠。不要再把44px命中框的内部空白叠加到视觉gap上。
|
|
28
|
+
|
|
29
|
+
这是紧凑健康列表的配方,不要求所有列表都采用20px图形或16px内距。浮层的自然高度和单层滚动规则见[交互规范](../docs/INTERACTION_PATTERNS.md#弹窗高度与滚动归属)。
|
|
30
|
+
|
|
31
|
+
## 默认卡片配方:24 / 8 / 16
|
|
32
|
+
|
|
33
|
+
- 外层 `Surface` 负责24px内边距与边界,不自动给任意子节点添加margin。`SelectionCard`同样使用24px内边距,组件自身提供标题与内容间的8px;内容内部仍由调用方分组。
|
|
34
|
+
- 标题与说明放进 `Stack gap={2}`,保持8px间隔。
|
|
35
|
+
- 内容组与操作组放进 `Stack gap={4}`,保持16px间隔。
|
|
36
|
+
- 操作组使用 `Inline gap={2}`,按钮间隔8px、允许换行、保持内容宽度。
|
|
37
|
+
- 卡片之间用 `Stack gap={6}` 或 `Grid gap={6}`,保持24px间隔。不要同时设置父级gap与子级margin来叠加同一段距离。
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<Surface>
|
|
41
|
+
<Stack gap={4}>
|
|
42
|
+
<Stack gap={2}>
|
|
43
|
+
<Text as="h2" variant="heading">应用更新</Text>
|
|
44
|
+
<Text tone="muted">当前版本已是最新。</Text>
|
|
45
|
+
</Stack>
|
|
46
|
+
<Inline gap={2}>
|
|
47
|
+
<Button variant="secondary" onClick={checkUpdate}>检查更新</Button>
|
|
48
|
+
</Inline>
|
|
49
|
+
</Stack>
|
|
50
|
+
</Surface>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
页面已有h1时卡片通常用h2;标题等级由结构决定,不能用字号决定。不要为布局引入空Text、空段落或连续br。
|
|
54
|
+
|
|
55
|
+
行高与间距分别管理:正文14/22px、标题16/24px,独立正文段落默认12px段间距;不是在每一行之间再加gap。详见[行高规范](TYPOGRAPHY.md#行高段落与控件)。
|
|
56
|
+
|
|
57
|
+
## 常见组合
|
|
58
|
+
|
|
59
|
+
| 场景 | 内部 | 组间 / 区域 | 组件选择 |
|
|
60
|
+
| --- | --- | --- | --- |
|
|
61
|
+
| 信息与设置卡片 | 标题/说明8px | 内容/动作16px;卡片24px | Surface +嵌套Stack +Inline |
|
|
62
|
+
| 表单 | 标签/控件由字段组件负责 | 字段16px;字段组24px;提交区24px | TextField/Fieldset +Stack |
|
|
63
|
+
| 工具栏 | 同组按钮8px | 独立组16px,窄屏换行 | Toolbar/Inline |
|
|
64
|
+
| 数据列表 | 同项说明4–8px | 列表项16px;筛选/表格/分页16–24px | Stack +DataTable/Pagination |
|
|
65
|
+
| 浮层 | 标题/说明由浮层组件负责 | 正文组16px;操作区24px | Dialog/Drawer +Stack/Inline |
|
|
66
|
+
| 反馈 | 提示内部由Notice负责 | 与前后内容16px | Notice放入Stack,不覆盖其内部样式 |
|
|
67
|
+
|
|
68
|
+
这些是组合默认值,不重新覆盖现有组件内部的padding、gap或状态样式。要改变组件密度,先在库中定义并验证,不能在业务页用后代选择器临时压缩。
|
|
69
|
+
|
|
70
|
+
## 长文本、窄窗与状态变化
|
|
71
|
+
|
|
72
|
+
保持8/16/24的关系,不因窄屏把所有间距归零。长文本自然换行、卡片随内容增高;多列转单列,按钮换行,不缩小字号或点击区域。保持至少44px操作目标及焦点环空间。
|
|
73
|
+
|
|
74
|
+
加载保留已有文案和按钮位置,只改变真实状态;额外进度或错误信息作为独立组加入16px间距。错误文案常驻直至处理,不用固定卡高或裁剪限制。状态元素移除后不保留空白占位组;不要用空的Inline或Stack充当spacer。
|
|
75
|
+
|
|
76
|
+
间距本身不动画。状态变化复用现有组件动效,不为留白增加缩放、弹跳或循环;减少动态保留同样布局与操作。
|
|
77
|
+
|
|
78
|
+
## 验收
|
|
79
|
+
|
|
80
|
+
检查默认、加载、错误、长文案;400/520/880px及200%等效重排;文字、焦点与按钮不裁切;不存在父gap叠加子margin或按钮意外撑满。以真实边界间距为准,行高内部的字形留白不当成额外margin测量。
|
|
81
|
+
|
|
82
|
+
本规范与展台随main更新;现有gap API在npm0.3.0可用,文档章节不代表已发布新版本。
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useId, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as PopoverBase from '@radix-ui/react-popover';
|
|
3
|
+
import { Text } from './Typography';
|
|
4
|
+
import { KeyValue } from './Data';
|
|
5
|
+
import { Button } from './components';
|
|
6
|
+
|
|
7
|
+
export type TimelineStatus = 'healthy' | 'recovered' | 'degraded' | 'failed' | 'unknown';
|
|
8
|
+
export interface TimelineDetail {
|
|
9
|
+
start: string; end: string; reason?: string;
|
|
10
|
+
counts?: { id: string; label: string; value: number | null; unit?: string }[];
|
|
11
|
+
methodology?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TimelineSegment { id: string; label: string; status: TimelineStatus; detail?: TimelineDetail }
|
|
14
|
+
export interface StatusTimelineProps {
|
|
15
|
+
label: string; segments: TimelineSegment[]; startLabel: string; endLabel: string; compact?: boolean; disabled?: boolean;
|
|
16
|
+
activeId?: string; defaultActiveId?: string; onActiveChange?: (id: string) => void;
|
|
17
|
+
open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
export const timelineLabels: Record<TimelineStatus, string> = { healthy: '有输出·未见异常', recovered: '重试已恢复', degraded: '异常·结果待确认', failed: '存在最终失败', unknown: '未知 / 无数据' };
|
|
20
|
+
|
|
21
|
+
/** Host supplies ordered, validated evidence; this component never computes a health score. */
|
|
22
|
+
export function StatusTimeline({ label, segments, startLabel, endLabel, compact = false, disabled = false, activeId, defaultActiveId, onActiveChange, open: controlledOpen, defaultOpen = false, onOpenChange }: StatusTimelineProps) {
|
|
23
|
+
const [localId, setLocalId] = useState(defaultActiveId);
|
|
24
|
+
const [localOpen, setLocalOpen] = useState(defaultOpen), [container, setContainer] = useState<HTMLElement>();
|
|
25
|
+
const open = controlledOpen ?? localOpen;
|
|
26
|
+
const setOpen = (next: boolean) => { if (disabled && next) return; if (controlledOpen === undefined) setLocalOpen(next); if (next !== open) onOpenChange?.(next); };
|
|
27
|
+
const root = useRef<HTMLDivElement>(null), slider = useRef<HTMLInputElement>(null), popup = useRef<HTMLDivElement>(null), returning = useRef(false), focusDetail = useRef(false);
|
|
28
|
+
useLayoutEffect(() => { setContainer(root.current?.closest<HTMLElement>('.rhine-theme') ?? document.body); }, []);
|
|
29
|
+
const detailId = useId();
|
|
30
|
+
const matched = segments.findIndex(item => item.id === (activeId ?? localId));
|
|
31
|
+
const selected = matched < 0 ? segments.length - 1 : matched;
|
|
32
|
+
const select = (index: number) => { const next = segments[index]?.id; if (!next || disabled) return; if (activeId === undefined) setLocalId(next); if (next !== segments[selected]?.id) onActiveChange?.(next); };
|
|
33
|
+
const segment = segments[selected];
|
|
34
|
+
const summary = segment ? `${segment.label} · ${timelineLabels[segment.status]}` : '暂无记录';
|
|
35
|
+
const hasPopup = !disabled && !!segment && (compact || !!segment.detail);
|
|
36
|
+
return <div ref={root} className={`rhine-timeline${compact ? ' rhine-timeline--compact' : ''}`}>
|
|
37
|
+
<PopoverBase.Root open={open && hasPopup} onOpenChange={setOpen}><PopoverBase.Anchor asChild><div className="rhine-timeline__track">
|
|
38
|
+
<div className="rhine-timeline__bars" aria-hidden="true">{segments.map((item, i) => <span key={item.id} data-status={item.status} data-selected={i === selected} />)}</div>
|
|
39
|
+
<input ref={slider} type="range" min={0} max={Math.max(0, segments.length - 1)} value={Math.max(0, selected)} disabled={disabled || !segments.length}
|
|
40
|
+
aria-label={label} aria-valuetext={segment ? `${segment.label} · ${timelineLabels[segment.status]}` : '暂无记录'} aria-describedby={detailId}
|
|
41
|
+
aria-details={open && hasPopup ? `${detailId}-popup` : undefined} aria-controls={open && hasPopup ? `${detailId}-popup` : undefined} aria-haspopup={hasPopup ? 'dialog' : undefined}
|
|
42
|
+
onFocus={() => { if (!returning.current) setOpen(true); returning.current = false; }}
|
|
43
|
+
onKeyDown={event => {
|
|
44
|
+
if (event.key === 'Escape' && open && hasPopup) { event.preventDefault(); event.stopPropagation(); setOpen(false); }
|
|
45
|
+
if ((event.key === 'Enter' || event.key === ' ') && hasPopup) { event.preventDefault(); if (open) popup.current?.querySelector<HTMLButtonElement>('button')?.focus(); else { focusDetail.current = true; setOpen(true); } }
|
|
46
|
+
}}
|
|
47
|
+
onChange={event => { select(Number(event.target.value)); setOpen(true); }}
|
|
48
|
+
onPointerDown={() => setOpen(true)}
|
|
49
|
+
onPointerMove={event => { if (disabled || event.pointerType !== 'mouse' || !segments.length) return; const rect = event.currentTarget.getBoundingClientRect(); if (rect.width) select(Math.min(segments.length - 1, Math.max(0, Math.floor((event.clientX - rect.left) / rect.width * segments.length)))); setOpen(true); }} />
|
|
50
|
+
</div></PopoverBase.Anchor><PopoverBase.Portal container={container}><PopoverBase.Content ref={popup} id={`${detailId}-popup`} className="rhine-timeline-popover" side="top" sideOffset={8} collisionPadding={12} aria-label={`${label}时段详情`}
|
|
51
|
+
onOpenAutoFocus={event => { event.preventDefault(); if (focusDetail.current) { focusDetail.current = false; popup.current?.querySelector<HTMLButtonElement>('button')?.focus(); } }} onCloseAutoFocus={event => { event.preventDefault(); focusDetail.current = false; if (document.activeElement === document.body || popup.current?.contains(document.activeElement)) { returning.current = true; slider.current?.focus(); } }}
|
|
52
|
+
onInteractOutside={event => { if (root.current?.contains(event.target as Node)) event.preventDefault(); }}>
|
|
53
|
+
<div className="rhine-dialog__head"><strong>{summary}</strong><PopoverBase.Close asChild><Button variant="quiet" aria-label="关闭时段详情">关闭</Button></PopoverBase.Close></div>
|
|
54
|
+
{segment?.detail && <><KeyValue items={[
|
|
55
|
+
{ key: 'start', label: '开始时间', value: segment.detail.start }, { key: 'end', label: '结束时间', value: segment.detail.end },
|
|
56
|
+
{ key: 'reason', label: '状态原因', value: segment.detail.reason || '未提供' },
|
|
57
|
+
...(segment.detail.counts ?? []).map(item => ({ key: `count-${item.id}`, label: item.label, value: item.value === null || !Number.isFinite(item.value) ? '未知' : `${item.value}${item.unit ? ` ${item.unit}` : ''}` })),
|
|
58
|
+
]} /><p className="rhine-text rhine-text--caption">统计口径:{segment.detail.methodology || '未提供,需向数据来源确认。'}</p></>}
|
|
59
|
+
</PopoverBase.Content></PopoverBase.Portal></PopoverBase.Root>
|
|
60
|
+
<div className={compact ? 'rhine-sr-only' : 'rhine-timeline__range'}><Text variant="caption" tone="muted">{startLabel}</Text><Text variant="caption" tone="muted">{endLabel}</Text></div>
|
|
61
|
+
<Text id={detailId} className={compact && segment ? 'rhine-sr-only' : 'rhine-timeline__summary'} variant="caption">{summary}</Text>
|
|
62
|
+
</div>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function TimelineLegend() {
|
|
66
|
+
return <ul className="rhine-timeline__legend" aria-label="健康记录图例">{Object.entries(timelineLabels).map(([status, label]) => <li key={status}><span data-status={status} aria-hidden="true"/>{label}</li>)}</ul>;
|
|
67
|
+
}
|
package/components/TYPOGRAPHY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 莱茵设计 · 字体与文字规范 V1.
|
|
1
|
+
# 莱茵设计 · 字体与文字规范 V1.4
|
|
2
2
|
|
|
3
|
-
2026-10-
|
|
3
|
+
2026-10-23。适用于使用莱茵设计的中文界面与中英文混排。变量定义在 [tokens.css](./tokens.css),样式与 React 入口分别为 [typography.css](./typography.css)、[Typography.tsx](./Typography.tsx)。归属与接入见[套件入口](../README.md)。
|
|
4
4
|
|
|
5
5
|
## 字体与字重
|
|
6
6
|
|
|
@@ -34,6 +34,38 @@
|
|
|
34
34
|
<Text as="code" variant="code">gpt-6.1-sol</Text>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## 行高、段落与控件
|
|
38
|
+
|
|
39
|
+
字号与行高成对使用,`Text`按variant同时设置两者;不在业务页单改字号而继承父级行高,不使用`normal`或固定容器高度猜测文本尺寸。上表是100%缩放的CSS尺寸,缩放后保持比例与自然换行,不以压低行高塞回原高度。
|
|
40
|
+
|
|
41
|
+
| 内容 | 字号 / 行高 | Token | 多行规则 |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| 大标题 | 30 / 38px | `--rhine-leading-title` | 换行仍为38px,不压成单行 |
|
|
44
|
+
| 分区标题 | 18 / 26px | `--rhine-leading-section` | 行距保持,标题之后的间隔另计 |
|
|
45
|
+
| 卡片标题 | 16 / 24px | `--rhine-leading-heading` | 中英文混排同一行高 |
|
|
46
|
+
| 正文、表单标签、按钮 | 14 / 22px | `--rhine-leading-body` | 两行正文占44px,三行占66px |
|
|
47
|
+
| 时间、来源、辅助说明 | 12 / 18px | `--rhine-leading-caption` | 两行36px;重要正文不要降为辅助字号 |
|
|
48
|
+
| 代码、路径 | 12 / 20px | `--rhine-leading-code` | 长标识可换行,行距不随字体回退变化 |
|
|
49
|
+
| 指标 | 26 / 32px;紧凑20 / 26px | `--rhine-leading-metric` / `--rhine-leading-metric-compact` | 字号与行高一起切换,单位不得把整行撑乱 |
|
|
50
|
+
|
|
51
|
+
- 行高是行框高度,包含字形上下留白,不等于两行字形之间的空白;不能把行高22px理解为额外22px的gap。
|
|
52
|
+
- 一个段落自然换行时不增加margin;两个独立段落默认用`Stack gap={3}`(12px)。标题与说明仍为8px,正文组与操作为16px,遵循[间距规范](SPACING.md)。标题/段落默认无浏览器自带margin。
|
|
53
|
+
- 按钮文字采用14/22,最小操作目标44px通过padding与min-height保证;不要把line-height设置为44px来垂直居中。长按钮文案可换行并增高,禁止固定height裁切。
|
|
54
|
+
- 现有紧凑组件的例外:Badge与Notice说明为12/20px,数据表普通文本为12/22px;这些是组件内部的既有密度,不用后代CSS临时覆盖。数据表中的技术标识仍使用Text code的12/20px。Logo字标是单行品牌几何,line-height=1仅限该品牌组件,不用于正文。
|
|
55
|
+
- 在行内混用不同字号时,行框可能由最大的子元素决定;需要固定对齐的指标、单位和标签使用组件既有组合,不承诺所有混排整行都等于单个Text的行高。
|
|
56
|
+
- 行高、字距与字号不做动画;状态文字立即更新,减少动态不改变排版。
|
|
57
|
+
|
|
58
|
+
展台`#typography`的「多行行高与段落」使用真实Text和Stack,可显示行框辅助线;辅助线只标注每行区域,不模拟字体基线,也不进入产品。
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<Stack gap={3}>
|
|
62
|
+
<Text>第一段说明自然换行,每行22px;段内不添加额外gap。</Text>
|
|
63
|
+
<Text>第二段由Stack与前一段保持12px距离。</Text>
|
|
64
|
+
</Stack>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
验收:400/520/880px与200%等效重排下检查标题、长正文、路径、操作按钮;计算样式与token一致,文字不裁切,长文增高不挤压操作。系统纯文本放大和原生辅助技术另行验收。此补充随main更新,不代表新npm版本已发布。
|
|
68
|
+
|
|
37
69
|
## 混排、数字与单位
|
|
38
70
|
|
|
39
71
|
- 中文正文不增加字距,不做两端强制分散;段落左对齐,长说明建议不超过60ch。居中仅用于简短空态与品牌展示。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Each container resets its paint so local overrides never leak into nested scenes.
|
|
2
|
+
Background paint does not create a containing block or intercept pointer events. */
|
|
3
|
+
.rhine-background {
|
|
4
|
+
--rhine-background-color: var(--rhine-content-bg);
|
|
5
|
+
--rhine-background-foreground: var(--rhine-content-text);
|
|
6
|
+
--rhine-background-image: var(--rhine-content-texture);
|
|
7
|
+
--rhine-background-size: var(--rhine-background-scale);
|
|
8
|
+
min-width: 0;
|
|
9
|
+
color: var(--rhine-background-foreground);
|
|
10
|
+
background-color: var(--rhine-background-color);
|
|
11
|
+
background-image: var(--rhine-background-image);
|
|
12
|
+
background-size: var(--rhine-background-size);
|
|
13
|
+
background-position: 0 0;
|
|
14
|
+
background-attachment: scroll;
|
|
15
|
+
}
|
|
16
|
+
.rhine-background[data-tone=canvas] {
|
|
17
|
+
--rhine-background-color: var(--rhine-canvas-bg);
|
|
18
|
+
--rhine-background-foreground: var(--rhine-canvas-text);
|
|
19
|
+
--rhine-background-image: var(--rhine-canvas-texture);
|
|
20
|
+
}
|
|
21
|
+
.rhine-background[data-tone=sidebar] {
|
|
22
|
+
--rhine-background-color: var(--rhine-sidebar-bg);
|
|
23
|
+
--rhine-background-foreground: var(--rhine-sidebar-text);
|
|
24
|
+
--rhine-background-image: var(--rhine-sidebar-texture);
|
|
25
|
+
}
|
|
26
|
+
.rhine-background[data-tone=surface] {
|
|
27
|
+
--rhine-background-color: var(--rhine-surface-bg);
|
|
28
|
+
--rhine-background-foreground: var(--rhine-surface-text);
|
|
29
|
+
--rhine-background-image: var(--rhine-surface-texture);
|
|
30
|
+
}
|
|
31
|
+
.rhine-background[data-tone=overlay] {
|
|
32
|
+
--rhine-background-color: var(--rhine-overlay-bg);
|
|
33
|
+
--rhine-background-foreground: var(--rhine-overlay-text);
|
|
34
|
+
--rhine-background-image: var(--rhine-overlay-texture);
|
|
35
|
+
}
|
|
36
|
+
.rhine-background[data-tone=media] {
|
|
37
|
+
--rhine-background-color: var(--rhine-media-bg);
|
|
38
|
+
--rhine-background-foreground: var(--rhine-media-text);
|
|
39
|
+
--rhine-background-image: var(--rhine-media-texture);
|
|
40
|
+
color-scheme: dark;
|
|
41
|
+
}
|
|
42
|
+
.rhine-background[data-decoration=theme] { --rhine-background-image: var(--rhine-background-texture); }
|
|
43
|
+
.rhine-background[data-decoration=none] { background-image: none; }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.rhine-chart { min-width:0; margin:0; display:grid; gap:16px; }
|
|
2
|
+
.rhine-chart figcaption { display:grid; gap:4px; overflow-wrap:anywhere; }
|
|
3
|
+
.rhine-chart figcaption strong { font-size:16px; line-height:24px; }
|
|
4
|
+
.rhine-chart figcaption>span,.rhine-chart figcaption p { font-size:12px; line-height:20px; color:var(--rhine-muted); margin:0; }
|
|
5
|
+
.rhine-chart__plot { width:100%; height:180px; overflow:visible; }
|
|
6
|
+
.rhine-chart__scale,.rhine-chart__range { display:flex; justify-content:space-between; gap:16px; font-size:12px; line-height:20px; color:var(--rhine-muted); overflow-wrap:anywhere; }
|
|
7
|
+
.rhine-chart__range>* { min-width:0; }
|
|
8
|
+
.rhine-chart__range>:last-child { text-align:right; }
|
|
9
|
+
.rhine-chart__grid { fill:none; stroke:var(--rhine-chart-grid); stroke-width:1; }
|
|
10
|
+
.rhine-chart__highlight { fill:var(--rhine-selected); stroke:var(--rhine-green); stroke-width:1; vector-effect:non-scaling-stroke; }
|
|
11
|
+
.rhine-chart__inspection { margin:0; min-height:44px; font-size:14px; line-height:22px; overflow-wrap:anywhere; }
|
|
12
|
+
.rhine-chart__line { fill:none; stroke:var(--rhine-chart-series-1); stroke-width:2; }
|
|
13
|
+
.rhine-chart__bar,.rhine-chart__point { fill:var(--rhine-chart-series-1); }
|
|
14
|
+
.rhine-chart__missing { fill:none; stroke:var(--rhine-chart-missing); stroke-width:2; }
|
|
15
|
+
.rhine-chart__legend { margin:0; font-size:12px; line-height:20px; }
|
|
16
|
+
.rhine-chart__legend>span { display:inline-block; width:12px; height:12px; background:var(--rhine-chart-series-1); margin-right:8px; }
|
|
17
|
+
.rhine-chart__data { min-width:0; }
|
|
18
|
+
.rhine-chart__data summary { min-height:44px; padding:12px 4px; cursor:pointer; }
|
|
19
|
+
.rhine-chart__stack { height:32px; display:flex; gap:2px; border:1px solid var(--rhine-line); }
|
|
20
|
+
.rhine-chart__stack>span { flex-basis:0; min-width:0; }
|
|
21
|
+
.rhine-chart [data-tone="1"] { background:var(--rhine-chart-series-1); }
|
|
22
|
+
.rhine-chart [data-tone="2"] { background:var(--rhine-chart-series-2); }
|
|
23
|
+
.rhine-chart [data-tone="3"] { background:var(--rhine-chart-series-3); }
|
|
24
|
+
.rhine-chart [data-tone="4"] { background:var(--rhine-chart-series-4); }
|
|
25
|
+
.rhine-chart__parts { list-style:none; padding:0; margin:0; display:flex; flex-wrap:wrap; gap:8px 16px; font-size:14px; line-height:22px; }
|
|
26
|
+
.rhine-chart__parts li { min-width:0; overflow-wrap:anywhere; }
|
|
27
|
+
.rhine-chart__parts li>span { display:inline-block; width:12px; height:12px; margin-right:8px; border:1px solid var(--rhine-control); }
|
package/components/common.css
CHANGED
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
.rhine-metric__unit { font-size:var(--rhine-text-caption); font-weight:400; color:var(--rhine-muted); }
|
|
15
15
|
.rhine-key-value { margin:0; display:grid; gap:12px; }
|
|
16
16
|
.rhine-key-value>div { display:grid; grid-template-columns:minmax(80px,1fr) minmax(0,2fr); gap:16px; }
|
|
17
|
-
.rhine-key-value dt { color:var(--rhine-muted); font-size:
|
|
17
|
+
.rhine-key-value dt { color:var(--rhine-muted); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); }.rhine-key-value dd { margin:0; overflow-wrap:anywhere; }
|
|
18
18
|
.rhine-data { display:grid; gap:12px; min-width:0; }
|
|
19
19
|
.rhine-table-scroll { overflow:auto; max-width:100%; }
|
|
20
20
|
.rhine-table { width:100%; min-width:500px; border-collapse:collapse; text-align:left; font-size:12px; }
|
|
21
|
-
.rhine-table caption { text-align:left; font-size:
|
|
21
|
+
.rhine-table caption { text-align:left; font-size:var(--rhine-text-heading); line-height:var(--rhine-leading-heading); font-weight:var(--rhine-weight-heading); margin-bottom:var(--rhine-space-4); }
|
|
22
22
|
.rhine-table th,.rhine-table td { padding:12px; border-bottom:1px solid var(--rhine-line); max-width:240px; overflow-wrap:anywhere; }
|
|
23
23
|
.rhine-table th { color:var(--rhine-muted); font-weight:500; }.rhine-table td { font-variant-numeric:tabular-nums; }
|
|
24
|
-
.rhine-table [data-align=right] { text-align:right; }.rhine-table tbody tr:hover { background:var(--rhine-
|
|
24
|
+
.rhine-table [data-align=right] { text-align:right; }.rhine-table tbody tr:hover { background:var(--rhine-table-hover-bg); }
|
|
25
25
|
.rhine-table th button { font:inherit; color:inherit; background:none; border:0; min-height:44px; cursor:pointer; padding:0; }
|
|
26
26
|
.rhine-table th button:disabled { cursor:default; }
|
|
27
27
|
.rhine-pagination { display:flex; flex-wrap:wrap; justify-content:flex-end; align-items:center; gap:12px; }
|
|
@@ -43,18 +43,27 @@
|
|
|
43
43
|
.rhine-combobox { position:relative; }
|
|
44
44
|
.rhine-combobox__input { position:relative; }.rhine-combobox__input input { padding-right:40px; }
|
|
45
45
|
.rhine-combobox__arrow { position:absolute; right:12px; top:50%; transform:translateY(-50%); pointer-events:none; }
|
|
46
|
-
.rhine-combobox__list { z-index:50; width:var(--radix-popover-trigger-width); max-width:calc(100vw - 24px); max-height:min(240px,var(--radix-popover-content-available-height)); overflow:auto; background:var(--rhine-
|
|
46
|
+
.rhine-combobox__list { z-index:50; width:var(--radix-popover-trigger-width); max-width:calc(100vw - 24px); max-height:min(240px,var(--radix-popover-content-available-height)); overflow:auto; background:var(--rhine-overlay-bg); color:var(--rhine-overlay-text); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2818; }
|
|
47
47
|
.rhine-combobox__list [role=option],.rhine-combobox__empty { padding:12px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
|
|
48
48
|
.rhine-combobox__list [role=option][data-active=true],.rhine-combobox__list [role=option][aria-selected=true] { background:var(--rhine-selected); }
|
|
49
49
|
.rhine-combobox__list [role=option][aria-disabled=true],.rhine-combobox__empty { color:var(--rhine-muted); cursor:default; }
|
|
50
50
|
.rhine-overlay-anchor { display:inline-block; vertical-align:middle; max-width:100%; }
|
|
51
|
-
.rhine-overlay { position:fixed; inset:0; background
|
|
52
|
-
.rhine-dialog { position:fixed; z-index:41; top:50%; left:50%; transform:translate(-50%,-50%); width:min(480px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); overflow:auto; padding:24px; border:1px solid var(--rhine-line); border-radius:4px; background:var(--rhine-
|
|
53
|
-
.rhine-dialog>p { margin-top:12px; }.rhine-dialog>.rhine-notice { margin-top:16px; }
|
|
51
|
+
.rhine-overlay { position:fixed; inset:0; background:var(--rhine-backdrop-bg); z-index:40; }
|
|
52
|
+
.rhine-dialog { position:fixed; z-index:41; top:50%; left:50%; transform:translate(-50%,-50%); width:min(480px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); overflow:auto; padding:24px; border:1px solid var(--rhine-line); border-radius:4px; background:var(--rhine-overlay-bg); color:var(--rhine-overlay-text); box-shadow:0 16px 48px #242b2826; }
|
|
53
|
+
.rhine-dialog>p,.rhine-dialog__main>p { margin-top:12px; }.rhine-dialog>.rhine-notice { margin-top:16px; }
|
|
54
|
+
.rhine-dialog__main { min-width:0; }
|
|
55
|
+
.rhine-dialog--split { width:min(960px,calc(100vw - 32px)); display:grid; grid-template-columns:minmax(240px,0.8fr) minmax(0,1.2fr); gap:24px; }
|
|
56
|
+
.rhine-dialog--split .rhine-dialog__head { padding-right:24px; }
|
|
57
|
+
.rhine-dialog--split .rhine-dialog__head>.rhine-icon-button { position:absolute; top:16px; right:16px; }
|
|
58
|
+
.rhine-dialog__side { min-width:0; border-left:1px solid var(--rhine-line); padding:40px 0 0 24px; }
|
|
59
|
+
.rhine-dialog--side-navigation .rhine-dialog__side { padding-top:0; }
|
|
60
|
+
.rhine-dialog--side-navigation .rhine-dialog__main>.rhine-dialog__head { min-height:44px; padding-right:0; }
|
|
61
|
+
@media(max-width:720px) { .rhine-dialog--split { grid-template-columns:minmax(0,1fr); }.rhine-dialog__side { border-left:0; border-top:1px solid var(--rhine-line); padding:24px 0 0; } }
|
|
62
|
+
@media(max-width:720px) { .rhine-dialog--side-navigation .rhine-dialog__side { padding-top:16px; } }
|
|
54
63
|
.rhine-dialog__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
|
|
55
64
|
.rhine-dialog__head>:first-child { min-width:0; overflow-wrap:anywhere; }
|
|
56
65
|
.rhine-dialog__body { margin-top:24px; }.rhine-dialog__actions { display:flex; justify-content:flex-end; flex-wrap:wrap; gap:12px; margin-top:24px; }
|
|
57
|
-
.rhine-popover,.rhine-menu { z-index:50; background:var(--rhine-
|
|
66
|
+
.rhine-popover,.rhine-menu { z-index:50; background:var(--rhine-overlay-bg); color:var(--rhine-overlay-text); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2820; max-width:calc(100vw - 24px); overflow:auto; }
|
|
58
67
|
.rhine-popover { width:300px; padding:16px; max-height:var(--radix-popover-content-available-height); }
|
|
59
68
|
.rhine-menu { min-width:180px; max-height:var(--radix-dropdown-menu-content-available-height); padding:4px; }
|
|
60
69
|
.rhine-menu__item { min-height:44px; padding:10px 12px; cursor:pointer; outline:none; overflow-wrap:anywhere; }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
.rhine-data .rhine-table tbody th[scope=row] { position:static; top:auto; z-index:auto; color:var(--rhine-ink); background:var(--rhine-surface); font-weight:var(--rhine-weight-label); }
|
|
2
|
+
.rhine-data .rhine-table tbody th[scope=row][data-sticky] { position:sticky; z-index:1; }
|
|
3
|
+
.rhine-data .rhine-table tr[data-selected=true] th[scope=row] { background:var(--rhine-table-selected-bg); }
|
|
4
|
+
.rhine-table__radio { display:flex; align-items:center; gap:8px; min-height:44px; cursor:pointer; }
|
|
5
|
+
.rhine-table__radio input { flex:none; width:18px; height:18px; accent-color:var(--rhine-green); }
|
|
6
|
+
.rhine-table__radio:has(input:disabled) { color:var(--rhine-muted); cursor:default; }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
.rhine-dialog.rhine-drawer[data-side=left] { left:0; right:auto; animation-name:rhine-drawer-enter-left; }
|
|
2
|
+
.rhine-dialog.rhine-drawer[data-side=right] { left:auto; right:0; }
|
|
3
|
+
.rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-drawer[data-side=left] { border-radius:0 24px 24px 0; box-shadow:5px 0 0 var(--rhine-ink); }
|
|
4
|
+
@keyframes rhine-drawer-enter-left { from { opacity:0; transform:translateX(-3px); } to { opacity:1; transform:translateX(0); } }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.rhine-shell { height:100%; min-height:0; min-width:0; display:flex; flex-direction:column; border:1px solid var(--rhine-line); background:var(--rhine-canvas-bg); }
|
|
2
|
+
.rhine-shell__header { flex:none; display:flex; flex-wrap:wrap; align-items:center; gap:8px; padding:12px; border-bottom:1px solid var(--rhine-line); background:var(--rhine-surface); }
|
|
3
|
+
.rhine-shell__body { display:flex; flex:1; min-height:0; min-width:0; }
|
|
4
|
+
.rhine-shell__nav, .rhine-shell__secondary { flex:0 0 224px; min-width:0; overflow:auto; padding:16px; background:var(--rhine-sidebar-bg); border-right:1px solid var(--rhine-line); overflow-wrap:anywhere; }
|
|
5
|
+
.rhine-shell__secondary { flex-basis:256px; }
|
|
6
|
+
.rhine-shell[data-layout=left-right] .rhine-shell__secondary { order:2; border-right:0; border-left:1px solid var(--rhine-line); }
|
|
7
|
+
.rhine-shell__content { flex:1; min-width:0; min-height:0; overflow:auto; padding:24px; overflow-wrap:anywhere; }
|
|
8
|
+
.rhine-date-range { margin:0; min-width:0; padding:16px; border:1px solid var(--rhine-line); background:var(--rhine-field-group-bg); }
|
|
9
|
+
.rhine-date-range legend { padding:0 4px; font-weight:600; }
|
|
10
|
+
.rhine-date-range__fields { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr)); gap:16px; margin-bottom:16px; }
|
|
11
|
+
.rhine-date-range input { max-width:100%; min-width:0; }
|
|
12
|
+
.rhine-number-field { display:grid; gap:8px; min-width:0; }
|
|
13
|
+
.rhine-multi-select { min-width:0; }
|
|
14
|
+
.rhine-multi-select__tags { display:flex; flex-wrap:wrap; gap:8px; list-style:none; padding:0; margin:8px 0; }
|
|
15
|
+
.rhine-multi-select__tags li { display:flex; align-items:center; max-width:100%; padding-left:12px; border:1px solid var(--rhine-line); background:var(--rhine-selected); border-radius:var(--rhine-radius-control); }
|
|
16
|
+
.rhine-multi-select__tags li>span { min-width:0; overflow-wrap:anywhere; }
|
|
17
|
+
.rhine-multi-select__tags .rhine-button { flex:none; }
|
|
18
|
+
.rhine-tree { min-width:0; padding:8px; border:1px solid var(--rhine-line); background:var(--rhine-field-group-bg); }
|
|
19
|
+
.rhine-tree [role=group] { padding-left:var(--rhine-tree-indent); }
|
|
20
|
+
.rhine-tree__row { display:flex; align-items:center; gap:8px; min-height:44px; padding:4px; overflow-wrap:anywhere; cursor:pointer; }
|
|
21
|
+
.rhine-tree__row>span:last-of-type { min-width:0; flex:1; }
|
|
22
|
+
.rhine-tree__row[data-selected=true] { background:var(--rhine-selected); }
|
|
23
|
+
.rhine-tree [aria-disabled=true]>.rhine-tree__row { color:var(--rhine-muted); cursor:default; }
|
|
24
|
+
.rhine-tree__row button { min-height:44px; min-width:44px; border:1px solid var(--rhine-line); background:var(--rhine-surface); color:var(--rhine-ink); font:inherit; cursor:pointer; }
|
|
25
|
+
.rhine-tree__spacer { flex:0 0 12px; }
|
|
26
|
+
.rhine-data[data-density=compact] { --rhine-table-cell-padding:6px 12px; }
|
|
27
|
+
.rhine-data .rhine-table-scroll { max-width:100%; overflow:auto; scroll-padding:48px; }
|
|
28
|
+
.rhine-data .rhine-table :is(th,td) { padding:var(--rhine-table-cell-padding); overflow-wrap:anywhere; font-size:14px; }
|
|
29
|
+
.rhine-table th { background:var(--rhine-table-header-bg); }
|
|
30
|
+
.rhine-table td { background:var(--rhine-surface); }
|
|
31
|
+
.rhine-table tbody tr:hover:not([data-selected=true]) > :is(td,th[scope=row]) { background:var(--rhine-table-hover-bg); }
|
|
32
|
+
.rhine-table tr[data-selected=true] td { background:var(--rhine-table-selected-bg); }
|
|
33
|
+
.rhine-table[data-sticky-header=true] th { position:sticky; top:0; z-index:2; }
|
|
34
|
+
.rhine-table :is(th,td)[data-sticky] { position:sticky; z-index:1; box-shadow:0 0 0 1px var(--rhine-line); }
|
|
35
|
+
.rhine-table[data-sticky-header=true] th[data-sticky] { z-index:3; }
|
|
36
|
+
.rhine-table__depth { display:block; white-space:nowrap; color:var(--rhine-muted); font-size:12px; }
|
|
37
|
+
.rhine-table .rhine-check label { min-height:44px; }
|
|
38
|
+
.rhine-pagination .rhine-select { width:auto; }
|
|
39
|
+
.rhine-media-card { min-width:0; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); }
|
|
40
|
+
.rhine-media-card[data-selected=true] { border:2px solid var(--rhine-green); }
|
|
41
|
+
.rhine-media-card__open { display:block; width:100%; min-width:0; padding:0; border:0; background:none; color:inherit; font:inherit; text-align:left; cursor:pointer; }
|
|
42
|
+
.rhine-media-card__open:disabled { cursor:default; opacity:.6; }
|
|
43
|
+
.rhine-media-card__preview { position:relative; display:flex; align-items:center; justify-content:center; width:100%; overflow:hidden; background:var(--rhine-media-canvas); color:var(--rhine-media-text); border-radius:var(--rhine-radius-card) var(--rhine-radius-card) 0 0; }
|
|
44
|
+
.rhine-media-card__preview :is(img,video) { width:100%; height:100%; object-fit:contain; }
|
|
45
|
+
.rhine-media-card__kind { position:absolute; top:8px; left:8px; padding:4px 8px; background:var(--rhine-media-overlay); color:var(--rhine-media-text); border:1px solid var(--rhine-media-border); font-size:12px; }
|
|
46
|
+
.rhine-media-card__caption { display:grid; gap:4px; padding:16px; overflow-wrap:anywhere; }
|
|
47
|
+
.rhine-media-card__caption>span { font-size:12px; color:var(--rhine-muted); }
|
|
48
|
+
.rhine-media-card>.rhine-check { padding:0 16px 12px; overflow-wrap:anywhere; }
|
|
49
|
+
.rhine-media-card>.rhine-check label { min-height:44px; }
|
|
50
|
+
.rhine-media-viewer { position:fixed; inset:0; z-index:41; width:100%; height:100dvh; display:flex; flex-direction:column; background:var(--rhine-media-canvas); color:var(--rhine-media-text); color-scheme:dark; overflow:auto; }
|
|
51
|
+
.rhine-media-viewer__header { flex:none; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px; padding:16px; background:var(--rhine-media-overlay); border-bottom:1px solid var(--rhine-media-border); }
|
|
52
|
+
.rhine-media-viewer__header>div { min-width:0; }
|
|
53
|
+
.rhine-media-viewer__header h2 { font-size:18px; line-height:26px; overflow-wrap:anywhere; margin:0; }
|
|
54
|
+
.rhine-media-viewer__header p { color:var(--rhine-media-muted); font-size:12px; margin:4px 0 0; }
|
|
55
|
+
.rhine-media-viewer__actions { display:flex; flex-wrap:wrap; gap:8px; }
|
|
56
|
+
.rhine-media-viewer :is(button,select,a) { min-height:44px; min-width:44px; max-width:100%; padding:8px 12px; border:1px solid var(--rhine-media-border); border-radius:4px; background:var(--rhine-media-overlay); color:var(--rhine-media-text); font:inherit; overflow-wrap:anywhere; }
|
|
57
|
+
.rhine-media-viewer button { cursor:pointer; }
|
|
58
|
+
.rhine-media-viewer button:disabled { color:var(--rhine-media-muted); opacity:.5; cursor:default; }
|
|
59
|
+
.rhine-theme .rhine-media-viewer.rhine-media-viewer :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:3px solid var(--rhine-media-focus); outline-offset:-3px; }
|
|
60
|
+
.rhine-media-viewer__body { display:grid; grid-template-columns:var(--rhine-media-columns); flex:1; min-height:0; min-width:0; }
|
|
61
|
+
.rhine-media-viewer__canvas { display:flex; align-items:center; justify-content:center; min-height:0; min-width:0; overflow:auto; padding:16px; }
|
|
62
|
+
.rhine-media-viewer__canvas :is(img,video) { display:block; max-width:100%; max-height:100%; object-fit:contain; }
|
|
63
|
+
.rhine-media-viewer__metadata { min-width:0; overflow:auto; padding:24px; border-left:1px solid var(--rhine-media-border); background:var(--rhine-media-overlay); overflow-wrap:anywhere; }
|
|
64
|
+
@media(max-width:760px) {
|
|
65
|
+
.rhine-media-viewer__body { display:flex; flex-direction:column; overflow:auto; }
|
|
66
|
+
.rhine-media-viewer__canvas { flex:1 0 220px; }
|
|
67
|
+
.rhine-media-viewer__canvas :is(img,video) { max-height:50dvh; }
|
|
68
|
+
.rhine-media-viewer__metadata { flex:none; overflow:visible; border-left:0; border-top:1px solid var(--rhine-media-border); }
|
|
69
|
+
.rhine-shell__content { padding:16px; }
|
|
70
|
+
}
|
package/components/index.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import './styles.css';
|
|
2
|
+
export { Background } from './Background';
|
|
3
|
+
export type { BackgroundProps, BackgroundRole, BackgroundDecoration, BackgroundAppearance } from './Background';
|
|
4
|
+
export { AppShell } from './AppShell';
|
|
5
|
+
export type { AppShellProps, ShellPanel } from './AppShell';
|
|
6
|
+
export { MediaCard, MediaViewer } from './Media';
|
|
7
|
+
export type { MediaCardProps, MediaViewerProps } from './Media';
|
|
8
|
+
export { DateField, DateRangePicker, NumberField, RemoteMultiSelect, TreeSelect } from './AdvancedForms';
|
|
9
|
+
export type { DateFieldProps, DateRange, DateRangePickerProps, NumberFieldProps, RemoteMultiSelectProps, TreeNode, TreeSelectProps } from './AdvancedForms';
|
|
10
|
+
export { StatusTimeline, TimelineLegend } from './StatusTimeline';
|
|
11
|
+
export type { TimelineSegment, TimelineStatus, TimelineDetail, StatusTimelineProps } from './StatusTimeline';
|
|
12
|
+
export { TrendChart, StackedBar } from './Charts';
|
|
13
|
+
export type { ChartPoint, ChartPart, TrendChartProps, StackedBarProps } from './Charts';
|
|
2
14
|
|
|
3
15
|
export { Logo } from './Logo';
|
|
4
16
|
export type { LogoProps } from './Logo';
|
|
@@ -13,12 +25,16 @@ export type { IconName } from './Actions';
|
|
|
13
25
|
export { Divider, Stack, Inline, Grid } from './Layout';
|
|
14
26
|
export type { Spacing } from './Layout';
|
|
15
27
|
export { Metric, KeyValue, DataTable, Pagination } from './Data';
|
|
16
|
-
export type { DataColumn, DataTableProps, TableSort } from './Data';
|
|
28
|
+
export type { DataColumn, DataTableProps, TableSort, PaginationProps } from './Data';
|
|
17
29
|
export { Checkbox, RadioGroup, Switch, Textarea, SearchField, Combobox } from './Forms';
|
|
18
30
|
export type { ComboboxProps } from './Forms';
|
|
19
|
-
export { Dialog, ConfirmDialog, Popover, Menu, Tooltip } from './Overlays';
|
|
31
|
+
export { Dialog, Drawer, ConfirmDialog, Popover, Menu, Tooltip } from './Overlays';
|
|
20
32
|
export type { DialogProps, MenuItem } from './Overlays';
|
|
21
33
|
export { Toast } from './Toast';
|
|
34
|
+
export { SideNav, Breadcrumbs, Steps } from './Navigation';
|
|
35
|
+
export type { NavItem, NavGroup, SideNavProps, BreadcrumbItem, BreadcrumbsProps, StepItem, StepsProps } from './Navigation';
|
|
36
|
+
export { Fieldset, ErrorSummary } from './FormWorkflow';
|
|
37
|
+
export type { FieldsetProps, FieldError, ErrorSummaryProps } from './FormWorkflow';
|
|
22
38
|
|
|
23
39
|
export { Button, Badge, Surface, SelectionCard, TextField, Tabs, SegmentedControl, Notice, Progress, Skeleton, Disclosure, EmptyState } from './components';
|
|
24
40
|
export type { ButtonProps, Tone, Choice, TabItem } from './components';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.rhine-side-nav__group { margin-top:var(--rhine-space-4); }
|
|
2
|
+
.rhine-side-nav__group:first-child { margin-top:0; }
|
|
3
|
+
.rhine-side-nav__group>p { margin:0 var(--rhine-space-3) var(--rhine-space-2); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); color:var(--rhine-muted); }
|
|
4
|
+
.rhine-side-nav ul,.rhine-breadcrumbs ol,.rhine-steps { margin:0; padding:0; list-style:none; }
|
|
5
|
+
.rhine-side-nav ul { display:grid; gap:var(--rhine-space-1); }
|
|
6
|
+
.rhine-side-nav a { min-height:44px; padding:var(--rhine-space-2) var(--rhine-space-3); display:flex; align-items:center; gap:var(--rhine-space-3); border-inline-start:2px solid transparent; border-radius:var(--rhine-radius-control); text-decoration:none; font-size:var(--rhine-text-body); line-height:var(--rhine-leading-body); color:var(--rhine-ink); transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard); overflow-wrap:anywhere; }
|
|
7
|
+
.rhine-side-nav__label { min-width:0; }
|
|
8
|
+
.rhine-side-nav .rhine-icon { flex-shrink:0; color:var(--rhine-muted); }
|
|
9
|
+
.rhine-side-nav a:hover { background:var(--rhine-bg); }
|
|
10
|
+
.rhine-side-nav a[aria-current=page] { background:var(--rhine-selected); border-color:var(--rhine-green); color:var(--rhine-green); font-weight:600; }
|
|
11
|
+
.rhine-side-nav a[aria-current=page] .rhine-icon { color:var(--rhine-green); }
|
|
12
|
+
.rhine-breadcrumbs ol { display:flex; flex-wrap:wrap; align-items:center; gap:4px 12px; }
|
|
13
|
+
.rhine-breadcrumbs li { display:flex; align-items:center; gap:12px; min-width:0; overflow-wrap:anywhere; font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); color:var(--rhine-muted); }
|
|
14
|
+
.rhine-breadcrumbs a { display:inline-flex; align-items:center; min-height:44px; color:var(--rhine-green); text-underline-offset:3px; }
|
|
15
|
+
.rhine-breadcrumbs [aria-current=page] { color:var(--rhine-ink); font-weight:600; }
|
|
16
|
+
.rhine-breadcrumbs__separator { color:var(--rhine-control); }
|
|
17
|
+
.rhine-steps { display:grid; gap:0; }
|
|
18
|
+
.rhine-steps li { border-inline-start:2px solid var(--rhine-line); }
|
|
19
|
+
.rhine-steps li>div,.rhine-steps button { display:flex; gap:16px; align-items:flex-start; padding:16px; width:100%; text-align:start; color:var(--rhine-muted); }
|
|
20
|
+
.rhine-steps button { border:0; background:none; font:inherit; cursor:pointer; }
|
|
21
|
+
.rhine-steps button:hover { background:var(--rhine-selected); }
|
|
22
|
+
.rhine-steps strong,.rhine-steps small { display:block; overflow-wrap:anywhere; }
|
|
23
|
+
.rhine-steps small { margin-top:4px; font-size:12px; font-weight:400; }
|
|
24
|
+
.rhine-steps__number { flex:0 0 28px; font:14px/24px var(--rhine-mono); }
|
|
25
|
+
.rhine-steps li[data-state=current],.rhine-steps li[data-state=complete] { border-color:var(--rhine-green); }
|
|
26
|
+
.rhine-steps li[data-state=current]>div { background:var(--rhine-selected); color:var(--rhine-green); animation:rhine-pattern-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
27
|
+
.rhine-steps li[data-state=error] { border-color:var(--rhine-error); }
|
|
28
|
+
.rhine-steps li[data-state=error]>div { color:var(--rhine-error); background:var(--rhine-error-bg); }
|
|
29
|
+
.rhine-fieldset { background:var(--rhine-surface-bg); color:var(--rhine-surface-text); min-width:0; margin:0; padding:20px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); }
|
|
30
|
+
.rhine-fieldset legend { padding:0 8px; font-weight:600; }
|
|
31
|
+
.rhine-fieldset>p { margin:0 0 20px; color:var(--rhine-muted); font-size:12px; }
|
|
32
|
+
.rhine-fieldset__body { display:grid; gap:16px; }
|
|
33
|
+
.rhine-error-summary { padding:20px; border:1px solid var(--rhine-error); border-inline-start:3px solid var(--rhine-error); background:var(--rhine-error-bg); }
|
|
34
|
+
.rhine-error-summary h3 { margin:0 0 8px; font-size:16px; }
|
|
35
|
+
.rhine-error-summary ul { padding-inline-start:20px; margin:0; }
|
|
36
|
+
.rhine-error-summary a { display:inline-block; padding:10px 0; color:var(--rhine-error); text-underline-offset:3px; overflow-wrap:anywhere; }
|
|
37
|
+
.rhine-dialog.rhine-drawer { inset-block:0; inset-inline-end:0; inset-inline-start:auto; width:min(440px,100%); max-height:100dvh; margin:0; transform:none; border-radius:0; animation:rhine-drawer-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
38
|
+
@keyframes rhine-drawer-enter { from { opacity:0; transform:translateX(3px); } to { opacity:1; transform:translateX(0); } }
|
|
39
|
+
@keyframes rhine-pattern-enter { from { opacity:.65; transform:translateY(3px); } to { opacity:1; transform:translateY(0); } }
|