@linhey/rhine-design 0.0.0-stage → 0.2.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 +16 -0
- package/CONTRIBUTING.md +44 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +144 -2
- package/components/Actions.tsx +40 -0
- package/components/COMMON_COMPONENTS.md +70 -0
- package/components/Data.tsx +42 -0
- package/components/Forms.tsx +64 -0
- package/components/Layout.tsx +13 -0
- package/components/Logo.tsx +34 -0
- package/components/MOTION.md +72 -0
- package/components/Overlays.tsx +53 -0
- package/components/README.md +137 -0
- package/components/STARTUP.md +34 -0
- package/components/Select.tsx +22 -0
- package/components/TYPOGRAPHY.md +60 -0
- package/components/Toast.tsx +25 -0
- package/components/Toolbar.tsx +14 -0
- package/components/Typography.tsx +13 -0
- package/components/assets/logo.svg +5 -0
- package/components/common.css +69 -0
- package/components/components.tsx +151 -0
- package/components/index.ts +24 -0
- package/components/styles.css +109 -0
- package/components/tokens.css +58 -0
- package/components/typography.css +31 -0
- package/design/README.md +14 -0
- package/docs/DESIGN_SYSTEM.md +83 -0
- package/docs/DESIGN_SYSTEM_AUDIT.md +63 -0
- package/docs/RELEASING.md +27 -0
- package/index.html +3 -0
- package/package.json +76 -4
- package/preview/CommonGallery.tsx +35 -0
- package/preview/MotionGallery.tsx +89 -0
- package/preview/StartupGallery.tsx +73 -0
- package/preview/brand-assembly.css +18 -0
- package/preview/main.tsx +100 -0
- package/preview/motion.css +25 -0
- package/preview/preview.css +27 -0
- package/preview/startup.css +33 -0
- package/scripts/check-build.mjs +15 -0
- package/skills/rhine-design/SKILL.md +37 -0
- package/skills/rhine-design/agents/openai.yaml +4 -0
- package/skills/rhine-design/references/API_MAP.md +34 -0
- package/skills/rhine-design/references/TASK_MAP.md +20 -0
- package/skills/rhine-design/references/integration.md +40 -0
- package/tests/CommonComponents.test.tsx +71 -0
- package/tests/DesignSystem.test.tsx +100 -0
- package/tests/MotionGallery.test.tsx +32 -0
- package/tests/StartupGallery.test.tsx +55 -0
- package/tests/setup.ts +4 -0
- package/tsconfig.json +32 -0
- package/vite.config.ts +3 -0
- package/vitest.config.ts +3 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 版本记录
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- 定位为个人通用设计系统 linhay/rhine-design,独立迭代。
|
|
6
|
+
- 包名由 @tod-ai/design-kit 改为 @linhey/rhine-design;更新安装记录、导入语句与固定版本,不提供旧名别名。
|
|
7
|
+
- 通用规范与演示移除特定产品定位;原产品页面、历史图稿和专属规范移回消费项目,Git历史保留。
|
|
8
|
+
- Logo默认字标与可访问名称改为Rhine Design,新增wordmark配置;自定义文字须显式传入。
|
|
9
|
+
- 保留三角几何、动画配方与tod-样式命名空间。
|
|
10
|
+
- 配置公开 npm 分发,随包提供 rhine-design skill、任务/API 索引与接入指引;发布状态以 registry 为准。
|
|
11
|
+
- GitHub 仓库公开,安装文档改用无需认证的 HTTPS 克隆方式。
|
|
12
|
+
- 采用 Apache-2.0 许可证,补齐 LICENSE、NOTICE 和贡献指南;README 面向外部开发者组织接入文档。
|
|
13
|
+
|
|
14
|
+
## 0.1.0
|
|
15
|
+
|
|
16
|
+
首次独立提取:40组件、字体规范、10组动效与启动演示,独立测试与构建。旧产品专属定位已由0.2.0取代。
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 参与 Rhine Design
|
|
2
|
+
|
|
3
|
+
欢迎通过 Issue 或 Pull Request 改进组件、设计规范、文档和示例。
|
|
4
|
+
|
|
5
|
+
## 本地开发
|
|
6
|
+
|
|
7
|
+
使用 Node 24 与 npm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone https://github.com/linhay/rhine-design.git
|
|
11
|
+
cd rhine-design
|
|
12
|
+
npm ci
|
|
13
|
+
npm run dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
展台地址为 `http://127.0.0.1:18309/`。`components/` 是组件源码与契约,`preview/` 使用同一组件演示交互,`tests/` 验证组件行为,`skills/rhine-design/` 提供 AI 助手的检索入口。
|
|
17
|
+
|
|
18
|
+
## 修改组件
|
|
19
|
+
|
|
20
|
+
优先组合已有组件和 tokens。新增能力时,同步更新组件契约与展台示例,展示相关的默认、禁用、加载、错误和长文本状态。使用语义化 HTML,保证键盘操作和焦点行为,并提供减少动态效果。
|
|
21
|
+
|
|
22
|
+
运行检查:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm run check
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
它包含测试、类型检查、展台构建和 CSS/SVG 产物检查。视觉变化还需检查 400/520/880 宽度、200% 缩放和相关交互。纯文档修改检查链接与示例即可。
|
|
29
|
+
|
|
30
|
+
`dist/` 是展台构建结果;包导出源码。修改打包配置时,请在仓库外安装实际 tarball 验证。不要删除入口和 CSS 的 `sideEffects`;Logo 依赖 SVG `?no-inline`。本地链接消费项目时,Vite 使用 `resolve.dedupe: ['react', 'react-dom']` 避免重复 React。
|
|
31
|
+
|
|
32
|
+
## 文档与 AI skill
|
|
33
|
+
|
|
34
|
+
README 面向首次接触项目的开发者,优先解释用途、安装、示例、兼容性和文档入口。维护者操作放在专门文档中。
|
|
35
|
+
|
|
36
|
+
组件 API 变更时,更新契约、示例以及 skill 的 `references/API_MAP.md` 或 `TASK_MAP.md`。索引指向真实源码,不复制第二份 API 定义;skill 的版本信息随包版本更新。
|
|
37
|
+
|
|
38
|
+
## 提交 Pull Request
|
|
39
|
+
|
|
40
|
+
说明解决的问题、变更后的行为,以及已执行的验证。涉及视觉或动效时附上截图或录屏;涉及破坏性 API 变更时提供迁移说明并更新 [CHANGELOG](CHANGELOG.md)。
|
|
41
|
+
|
|
42
|
+
问题反馈请提供包版本、组件名、预期与实际行为、最小复现。删除复现中的私人数据和凭据。
|
|
43
|
+
|
|
44
|
+
维护者发布步骤见 [RELEASING](docs/RELEASING.md)。项目采用 [Apache-2.0](LICENSE) 许可证。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,145 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="components/assets/logo.svg" width="96" height="96" alt="Rhine Design 三角构架标志" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">莱茵设计 · Rhine Design</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">暖白与苔绿,三角与细线。让界面拥有清晰的秩序与克制的节奏。</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="#quick-start">快速接入</a> ·
|
|
11
|
+
<a href="#components">组件</a> ·
|
|
12
|
+
<a href="#documentation">设计规范</a> ·
|
|
13
|
+
<a href="#ai-skills">AI Skills</a> ·
|
|
14
|
+
<a href="CONTRIBUTING.md">参与贡献</a> ·
|
|
15
|
+
<a href="CHANGELOG.md">版本记录</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
Rhine Design 是面向 React 应用的开源设计系统,适合桌面工具、管理界面和数据密集型应用。它将设计规范、40 个组件和动效示例放在一起,帮助团队建立一致、可复用的界面语言。
|
|
19
|
+
|
|
20
|
+
- **一致的视觉语言**:色彩、字体、文字层级、间距、几何与焦点规则。
|
|
21
|
+
- **40 个可组合组件**:覆盖布局、表单、数据、导航、浮层和反馈。
|
|
22
|
+
- **可验证的动效**:10 组播放 / 重播 / 复位演示,以及三角构架启动编排。
|
|
23
|
+
- **面向开发协作**:组件契约、交互展台和 AI skill 帮助设计与实现保持一致。
|
|
24
|
+
|
|
25
|
+
基于 React 与 Radix UI,采用 [Apache-2.0](LICENSE) 许可证。
|
|
26
|
+
|
|
27
|
+
<a id="quick-start"></a>
|
|
28
|
+
|
|
29
|
+
## 快速接入
|
|
30
|
+
|
|
31
|
+
需要 **React 19**。当前已验证的构建环境为 **Vite**,开发建议使用 **Node 24**。
|
|
32
|
+
|
|
33
|
+
首次 npm 发布尚未完成,目前可在你的 React 项目中通过公开仓库安装:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install git+https://github.com/linhay/rhine-design.git
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
包的导入名称为 `@linhey/rhine-design`。正式项目建议在 Git URL 后附加 `#<commit>` 固定源码版本,并提交 lockfile。
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Button, Stack, Surface, Text } from '@linhey/rhine-design';
|
|
43
|
+
|
|
44
|
+
export function Welcome() {
|
|
45
|
+
return (
|
|
46
|
+
<div className="tod-theme">
|
|
47
|
+
<Surface>
|
|
48
|
+
<Stack gap={4}>
|
|
49
|
+
<Text as="h1" variant="title">开始新的工作</Text>
|
|
50
|
+
<Text tone="muted">用统一的组件,构建属于你的工作空间。</Text>
|
|
51
|
+
<Button onClick={() => { /* 在这里处理点击 */ }}>继续</Button>
|
|
52
|
+
</Stack>
|
|
53
|
+
</Surface>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
入口自动加载样式,用 `.tod-theme` 包裹页面即可使用默认主题。布局和颜色变量使用 `--tod-*` 前缀;完整定义见 [tokens.css](components/tokens.css)。
|
|
60
|
+
|
|
61
|
+
### 兼容范围
|
|
62
|
+
|
|
63
|
+
- 包导出 TS/TSX、CSS 和 SVG 源码,构建器需支持 CSS 导入与 Vite 的 SVG `?no-inline` 引用;目前未验证其他构建器和 SSR。
|
|
64
|
+
- 浮层挂载到最近的 `.tod-theme`。请将主题根放在页面根部,避免对它使用 `transform`、裁剪或 `contain`。
|
|
65
|
+
- 组件提供呈现与交互,应用负责数据请求、搜索、分页和操作结果。
|
|
66
|
+
- 当前提供默认暖白主题;Logo 支持独立的深浅配色,完整组件深色主题尚未提供。
|
|
67
|
+
|
|
68
|
+
<a id="components"></a>
|
|
69
|
+
|
|
70
|
+
## 组件
|
|
71
|
+
|
|
72
|
+
| 类别 | 组件 |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| 品牌与文字 | Logo · Text |
|
|
75
|
+
| 操作 | Button · Icon · IconButton · Link · CopyButton |
|
|
76
|
+
| 布局 | Surface · Toolbar · Divider · Stack · Inline · Grid |
|
|
77
|
+
| 表单与选择 | TextField · Textarea · Select · Checkbox · RadioGroup · Switch · SearchField · Combobox · SelectionCard |
|
|
78
|
+
| 导航与展开 | Tabs · SegmentedControl · Disclosure |
|
|
79
|
+
| 数据 | Metric · KeyValue · DataTable · Pagination |
|
|
80
|
+
| 浮层 | Dialog · ConfirmDialog · Popover · Menu · Tooltip |
|
|
81
|
+
| 状态与反馈 | Badge · Notice · Toast · Progress · Skeleton · EmptyState |
|
|
82
|
+
|
|
83
|
+
参数、状态和组合示例见[组件 API](components/README.md)与[常用组件契约](components/COMMON_COMPONENTS.md)。
|
|
84
|
+
|
|
85
|
+
<a id="documentation"></a>
|
|
86
|
+
|
|
87
|
+
## 设计规范与展台
|
|
88
|
+
|
|
89
|
+
| 主题 | 文档 | 本地展台锚点 |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| 视觉基础与组合原则 | [设计规范](docs/DESIGN_SYSTEM.md) | `/#foundation` |
|
|
92
|
+
| 字体、文字层级与数值 | [Typography](components/TYPOGRAPHY.md) | `/#typography` |
|
|
93
|
+
| 品牌几何与 Logo | [Logo 契约](components/README.md#品牌标志) | `/#brand` |
|
|
94
|
+
| 动效、交互与减少动态 | [Motion](components/MOTION.md) | `/#motion` |
|
|
95
|
+
| 启动动画与就绪衔接 | [Startup](components/STARTUP.md) | `/#startup` |
|
|
96
|
+
| 系统覆盖与演进方向 | [组件系统对照](docs/DESIGN_SYSTEM_AUDIT.md) | — |
|
|
97
|
+
|
|
98
|
+
本地展台包含组件状态、键盘操作、长文本、加载与错误示例。动效可播放、重播、复位和慢放,并支持减少动态模式。
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
git clone https://github.com/linhay/rhine-design.git
|
|
102
|
+
cd rhine-design
|
|
103
|
+
npm ci
|
|
104
|
+
npm run dev
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
打开 `http://127.0.0.1:18309/`,使用上表中的锚点访问对应章节。
|
|
108
|
+
|
|
109
|
+
<a id="ai-skills"></a>
|
|
110
|
+
|
|
111
|
+
## AI Skills
|
|
112
|
+
|
|
113
|
+
[`rhine-design` skill](skills/rhine-design/SKILL.md) 帮助 AI 编程助手查找组件 API、遵循字体与动效规范,并优先复用已有组件。它适用于支持 `SKILL.md` 的工具;Codex 可通过 `$rhine-design` 调用。
|
|
114
|
+
|
|
115
|
+
安装组件包后,在你的项目根目录执行:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
mkdir -p .agents/skills
|
|
119
|
+
cp -R node_modules/@linhey/rhine-design/skills/rhine-design .agents/skills/
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
然后向助手提出具体任务,例如:
|
|
123
|
+
|
|
124
|
+
> 使用 $rhine-design 设计一个设置页面,包含搜索、选项开关和保存反馈;复用现有组件,并提供键盘操作与减少动态效果。
|
|
125
|
+
|
|
126
|
+
skill 中提供以下参考入口:
|
|
127
|
+
|
|
128
|
+
| 文件 | 用途 |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| [SKILL.md](skills/rhine-design/SKILL.md) | 使用范围、设计先行流程、版本与源码定位 |
|
|
131
|
+
| [TASK_MAP.md](skills/rhine-design/references/TASK_MAP.md) | 按字体、表单、数据、浮层、动效等任务选择规范 |
|
|
132
|
+
| [API_MAP.md](skills/rhine-design/references/API_MAP.md) | 按组件名定位真实实现和公开入口 |
|
|
133
|
+
| [integration.md](skills/rhine-design/references/integration.md) | 主题、构建、数据所有权与迁移注意事项 |
|
|
134
|
+
|
|
135
|
+
升级组件时请同步更新 skill;如果已做本地定制,先比较差异再合并。其他工具请将同一 skill 目录放入其支持的技能路径。
|
|
136
|
+
|
|
137
|
+
## 参与贡献
|
|
138
|
+
|
|
139
|
+
欢迎提交问题、文档改进和组件提案。遇到问题时,请在 [Issues](https://github.com/linhay/rhine-design/issues) 提供组件名、版本、预期行为及最小复现。
|
|
140
|
+
|
|
141
|
+
开发环境、验证命令和 PR 说明见 [贡献指南](CONTRIBUTING.md)。版本变化和升级说明见 [CHANGELOG](CHANGELOG.md)。
|
|
142
|
+
|
|
143
|
+
## 许可证
|
|
144
|
+
|
|
145
|
+
[Apache License 2.0](LICENSE)。版权声明见 [NOTICE](NOTICE)。第三方依赖遵循各自的许可证。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useRef, useState, type AnchorHTMLAttributes, type ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import { Button } from './components';
|
|
3
|
+
|
|
4
|
+
const paths = {
|
|
5
|
+
close: 'm5 5 10 10M15 5 5 15',
|
|
6
|
+
chevronDown: 'm5 8 5 5 5-5',
|
|
7
|
+
chevronLeft: 'm12 5-5 5 5 5',
|
|
8
|
+
chevronRight: 'm8 5 5 5-5 5',
|
|
9
|
+
copy: 'M7 7h9v10H7zM4 13H3V3h9v1',
|
|
10
|
+
refresh: 'M16 7A6 6 0 1 0 16 13M16 3v4h-4',
|
|
11
|
+
search: 'M8.5 3a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11M13 13l4 4',
|
|
12
|
+
info: 'M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16M10 9v5M10 6v.5',
|
|
13
|
+
external: 'M11 3h6v6M17 3 9 11M7 3H3v14h14v-4',
|
|
14
|
+
check: 'm4 10 4 4 8-8',
|
|
15
|
+
more: 'M4 10h.1M10 10h.1M16 10h.1',
|
|
16
|
+
} as const;
|
|
17
|
+
export type IconName = keyof typeof paths;
|
|
18
|
+
export function Icon({ name, size = 20 }: { name: IconName; size?: 16 | 20 }) {
|
|
19
|
+
return <svg className="tod-icon" width={size} height={size} viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d={paths[name]} /></svg>;
|
|
20
|
+
}
|
|
21
|
+
export const IconButton = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement> & { label: string; icon: IconName }>(function IconButton({ label, icon, className = '', type = 'button', ...props }, ref) {
|
|
22
|
+
return <button {...props} ref={ref} type={type} aria-label={label} className={`tod-button tod-button--quiet tod-icon-button ${className}`}><Icon name={icon} /></button>;
|
|
23
|
+
});
|
|
24
|
+
export function Link({ children, className = '', target, rel, ...props }: AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) {
|
|
25
|
+
return <a {...props} target={target} rel={target === '_blank' ? `${rel || ''} noopener noreferrer`.trim() : rel} className={`tod-link ${className}`}>{children}{target === '_blank' && <><Icon name="external" size={16} /><span className="tod-sr-only">(新窗口打开)</span></>}</a>;
|
|
26
|
+
}
|
|
27
|
+
export function CopyButton({ value, label = '复制', copy = text => navigator.clipboard.writeText(text) }: { value: string; label?: string; copy?: (value: string) => Promise<void> }) {
|
|
28
|
+
const [status, setStatus] = useState<'idle' | 'busy' | 'success' | 'error'>('idle');
|
|
29
|
+
const pending = useRef(false);
|
|
30
|
+
const epoch = useRef(0);
|
|
31
|
+
useEffect(() => { epoch.current++; pending.current = false; setStatus('idle'); return () => { epoch.current++; }; }, [value]);
|
|
32
|
+
const run = async () => {
|
|
33
|
+
if (pending.current) return;
|
|
34
|
+
pending.current = true; const request = epoch.current; setStatus('busy');
|
|
35
|
+
try { await copy(value); if (request === epoch.current) setStatus('success'); }
|
|
36
|
+
catch { if (request === epoch.current) setStatus('error'); }
|
|
37
|
+
finally { if (request === epoch.current) pending.current = false; }
|
|
38
|
+
};
|
|
39
|
+
return <span className="tod-copy"><Button variant="secondary" busy={status === 'busy'} busyLabel="正在复制" onClick={run}><Icon name="copy" size={16} /> {label}</Button><span role={status === 'error' ? 'alert' : 'status'} className={status === 'error' ? 'tod-text--error' : 'tod-text--muted'}>{status === 'success' ? '已复制' : status === 'error' ? '复制失败,请重试' : ''}</span></span>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 莱茵设计 · 常用组件契约 V1.4
|
|
2
|
+
|
|
3
|
+
本文件定义[莱茵设计](../README.md)的常用组件契约。先固定契约和组件展台,再验证;默认优先复用本目录,页面先改设计稿,再按授权接入App。具体业务由宿主定义,不进入通用契约。
|
|
4
|
+
|
|
5
|
+
## 范围与契约
|
|
6
|
+
|
|
7
|
+
| 组件 | 最小职责与边界 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Metric | 标题、值、单位、说明、loading/unknown;不计算或格式化金额 |
|
|
10
|
+
| KeyValue | 原生dl键值信息;值允许换行,不隐藏长标识 |
|
|
11
|
+
| DataTable | 原生表格、caption、行键、列定义、加载/空/失败、排序事件;不本地筛选排序分页 |
|
|
12
|
+
| Pagination | 受控页码、总数、每页大小、加载禁用;总数未知时只依赖hasNext |
|
|
13
|
+
| Link | 原生链接,区分跳转与按钮动作,外部新窗口标识 |
|
|
14
|
+
| Icon / IconButton | 少量常用16/20px线性图标、44px操作目标、可访问名称 |
|
|
15
|
+
| CopyButton | 显式点击后复制;等待真实Promise成功才提示,失败可重试;可注入原生clipboard适配器 |
|
|
16
|
+
| Checkbox | 原生复选框、混合态、label/hint;选中由调用方控制 |
|
|
17
|
+
| RadioGroup | fieldset/legend与原生radio,独立组名、禁用项、键盘导航 |
|
|
18
|
+
| Switch | 原生checkbox加switch语义;只用于二元偏好,不替代账号选择 |
|
|
19
|
+
| Textarea | 可见label、hint/error、只读/禁用;允许垂直调整,不做富文本 |
|
|
20
|
+
| SearchField | 受控输入;提交/清空事件;中文输入法组合中不提交,不触发隐式全量本地过滤 |
|
|
21
|
+
| Combobox | 受控查询与选中值;候选由调用方返回;方向键/Enter/Escape,IME保护,加载/无结果/错误 |
|
|
22
|
+
| Tooltip | 鼠标与键盘说明、Escape、可悬停;不可放交互控件或关键信息 |
|
|
23
|
+
| Toast | 单条通知、关闭;成功可定时、警告错误持久;hover/focus/页面隐藏暂停计时,无全局队列 |
|
|
24
|
+
| Dialog / ConfirmDialog | 标题描述、焦点限制及归还、Escape、滚动;确认默认聚焦取消,异步失败保留内容 |
|
|
25
|
+
| Popover | 按钮触发非模态详情、边缘避让、Escape与焦点归还;不替代Tooltip |
|
|
26
|
+
| Menu | 操作项/禁用/危险项;方向键/Home/End/输入字符跳转;不是表单选择器 |
|
|
27
|
+
| Divider / Stack / Inline / Grid | 语义分隔线、纵排、可换行横排、自适应网格;只消费间距tokens |
|
|
28
|
+
|
|
29
|
+
共24个新增组件,与已有16个合计40个。复杂浮层使用Radix无样式基础,避免维护重复焦点/菜单/碰撞实现;样式由Rhine Design定义。原生表单、数据与布局不引入额外框架。
|
|
30
|
+
|
|
31
|
+
## 动效与状态
|
|
32
|
+
|
|
33
|
+
补充参考:[Transitions.dev UX与动效映射](./MOTION.md)。方向感知菜单、内容衔接与结果淡入已在`#motion`展台提供播放/重播候选;尚未推广到通用默认或App,以下为现有组件基线。
|
|
34
|
+
|
|
35
|
+
- 输入/按钮/选择器沿用120ms交互;Switch滑块120ms,不延迟checked;焦点即时。
|
|
36
|
+
- 浮层进入160ms,opacity与最多3px位移;模态缩放不作用于文字。关闭立即恢复焦点,不等待退出动画。Tooltip延迟300ms,focus无需等待;减少动态去除视觉过渡。
|
|
37
|
+
- Toast进入180ms,成功默认5秒后关闭;计时与视觉动画独立,减少动态不使通知瞬间消失。
|
|
38
|
+
- 指标不滚动计数,表格不做逐行飞入。加载保留已有数据,未知不显示0;分页动作不移动键盘焦点。
|
|
39
|
+
- Portal浮层保持同一字体、颜色与减少动态偏好;在嵌套Dialog内的浮层不得被底层页面遮挡。
|
|
40
|
+
|
|
41
|
+
## 接入契约
|
|
42
|
+
|
|
43
|
+
统一从 套件的`components/index.ts` 导入,并在页面根部使用 `.tod-theme`。主题根不要放在带transform、overflow裁剪或contain的局部面板;浮层挂到最近主题根,保持token和减少动态偏好。Dialog、Popover、Menu的trigger与Tooltip的children必须是单个可聚焦、可接收ref和事件的元素;使用Button/IconButton即可。Dialog受控时必须同时传open与onOpenChange。
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<Stack gap={4}>
|
|
47
|
+
<Grid minWidth={180}>
|
|
48
|
+
<Metric label="请求次数" value={stats?.requests ?? null} />
|
|
49
|
+
<Metric label="预估费用" value={formattedCost} description="包含未计价请求" />
|
|
50
|
+
</Grid>
|
|
51
|
+
<DataTable caption="请求明细" rows={rows} rowKey={row => row.id}
|
|
52
|
+
columns={columns} loading={loading} error={error}
|
|
53
|
+
sort={sort} onSortChange={requestSort} onRetry={reload} />
|
|
54
|
+
<Pagination page={page} pageSize={10} total={total}
|
|
55
|
+
loading={loading} onPageChange={requestPage} />
|
|
56
|
+
</Stack>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
示例中的数据与回调由业务层提供。DataTable的columns使用`key/header/render`,可指定`align`与`sortable`;排序回传`{key, direction: 'asc' | 'desc'}`,不会改动rows。Pagination的page从1开始,pageSize须为正整数;total未知时省略并传hasNext,不臆造页数。KeyValue使用`items[{key,label,value}]`;Metric的null/undefined显示未知,0原样保留。
|
|
60
|
+
|
|
61
|
+
- SearchField传`value/onValueChange/onSearch`;Enter或搜索按钮提交,清空回传空查询。Combobox传`query/onQueryChange/value/onValueChange/options`;options为`{value,label,disabled?}`,查询输入与选中ID分开管理。业务层负责服务端搜索、防抖、取消与过期结果保护,组件不拉全量过滤。
|
|
62
|
+
- Checkbox接受原生input属性及indeterminate;混合状态在点击后应由调用方重算,不使用readOnly模拟锁定(原生checkbox无只读行为)。RadioGroup与Switch都是受控,Textarea沿用原生readOnly和disabled语义。
|
|
63
|
+
- CopyButton接受`value/label/copy?`,copy适配器返回Promise。等待成功后提示;value变化后忽略旧请求结果,错误不暴露异常正文。宿主原生平台接入应提供平台适配器并做真实剪贴板验收。
|
|
64
|
+
- ConfirmDialog的`onConfirm`返回void或Promise;等待时阻止再次确认、取消和Escape,成功后关闭,失败保留内容供重试。任务超时/取消能力由业务回调负责,不让Promise无限悬挂。默认焦点在取消按钮。
|
|
65
|
+
- Toast由`open/onOpenChange/title/description/tone/duration`控制;默认5秒,duration≤0常驻,warning/error始终常驻。重复打开同一条已打开通知不会重置计时;要作为新通知应更换key或先关闭。不要把凭证、异常正文等敏感数据放进通知。
|
|
66
|
+
- Stack/Inline/Grid的gap取`1/2/3/4/6/8/12`,对应间距token;Grid的minWidth以px计,窄容器自动收缩。Icon的name使用导出IconName类型,不以自由文本插入SVG。
|
|
67
|
+
|
|
68
|
+
## 设计展台验收
|
|
69
|
+
|
|
70
|
+
展示40组件的真实实现;新增数据、表单、操作/布局、反馈/浮层四组。合成数据只演示事件与状态;Clipboard仅复制明确的演示字符串。测试覆盖分页边界、请求状态、异步复制失败、IME、候选列表、通知计时、浮层焦点/Escape与窄窗;原生宿主原生平台和屏幕阅读器仍需App接入后单独验收。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Button, EmptyState, Notice, Skeleton } from './components';
|
|
3
|
+
import { Text } from './Typography';
|
|
4
|
+
|
|
5
|
+
export function Metric({ label, value, unit, description, loading = false }: { label: string; value: ReactNode; unit?: string; description?: string; loading?: boolean }) {
|
|
6
|
+
return <div className="tod-metric" aria-busy={loading}><Text variant="caption" tone="muted">{label}</Text>{loading ? <Skeleton lines={1} label={`正在读取${label}`} /> : <Text variant="metric">{value ?? '—'}{unit && <span className="tod-metric__unit"> {unit}</span>}</Text>}{description && <Text variant="caption" tone="muted">{description}</Text>}</div>;
|
|
7
|
+
}
|
|
8
|
+
export function KeyValue({ items }: { items: { key: string; label: string; value: ReactNode }[] }) {
|
|
9
|
+
return <dl className="tod-key-value">{items.map(item => <div key={item.key}><dt>{item.label}</dt><dd>{item.value ?? '—'}</dd></div>)}</dl>;
|
|
10
|
+
}
|
|
11
|
+
export interface DataColumn<T> { key: string; header: string; render: (row: T) => ReactNode; align?: 'left' | 'right'; sortable?: boolean }
|
|
12
|
+
export interface TableSort { key: string; direction: 'asc' | 'desc' }
|
|
13
|
+
export interface DataTableProps<T> {
|
|
14
|
+
caption: string; columns: DataColumn<T>[]; rows: T[]; rowKey: (row: T) => string;
|
|
15
|
+
loading?: boolean; error?: string; emptyTitle?: string; sort?: TableSort;
|
|
16
|
+
onSortChange?: (sort: TableSort) => void; onRetry?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export function DataTable<T>({ caption, columns, rows, rowKey, loading = false, error, emptyTitle = '暂无记录', sort, onSortChange, onRetry }: DataTableProps<T>) {
|
|
19
|
+
return <div className="tod-data" aria-busy={loading}>
|
|
20
|
+
{error && <Notice tone="error" title="读取失败" action={onRetry && <Button variant="secondary" onClick={onRetry} disabled={loading}>重试</Button>}>{error}{rows.length > 0 && ' · 以下保留上次读取的记录。'}</Notice>}
|
|
21
|
+
{loading && <p role="status" className="tod-text tod-text--caption tod-text--muted">{rows.length ? '正在刷新,保留当前记录…' : '正在读取…'}</p>}
|
|
22
|
+
<div className="tod-table-scroll" role="region" aria-label={`${caption},可横向滚动`} tabIndex={0}>
|
|
23
|
+
<table className="tod-table"><caption>{caption}</caption><thead><tr>{columns.map(column => <th scope="col" key={column.key} data-align={column.align} aria-sort={column.sortable ? sort?.key === column.key ? sort.direction === 'asc' ? 'ascending' : 'descending' : 'none' : undefined}>{column.sortable && onSortChange ? <button type="button" disabled={loading} onClick={() => onSortChange({ key: column.key, direction: sort?.key === column.key && sort.direction === 'asc' ? 'desc' : 'asc' })}>{column.header}<span aria-hidden="true">{sort?.key === column.key ? sort.direction === 'asc' ? ' ↑' : ' ↓' : ' ↕'}</span></button> : column.header}</th>)}</tr></thead>
|
|
24
|
+
<tbody>{rows.map(row => <tr key={rowKey(row)}>{columns.map(column => <td key={column.key} data-align={column.align}>{column.render(row) ?? '—'}</td>)}</tr>)}</tbody></table>
|
|
25
|
+
</div>
|
|
26
|
+
{!rows.length && !loading && !error && <EmptyState title={emptyTitle} description="当前条件下没有可展示的数据。" />}
|
|
27
|
+
</div>;
|
|
28
|
+
}
|
|
29
|
+
export function Pagination({ page, pageSize, total, hasNext = false, loading = false, onPageChange }: {
|
|
30
|
+
page: number; pageSize: number; total?: number; hasNext?: boolean; loading?: boolean; onPageChange: (page: number) => void;
|
|
31
|
+
}) {
|
|
32
|
+
const size = Math.max(1, Math.floor(pageSize));
|
|
33
|
+
const count = total === undefined ? undefined : Math.max(0, Math.floor(total));
|
|
34
|
+
const pages = count === undefined ? undefined : Math.max(1, Math.ceil(count / size));
|
|
35
|
+
const current = Math.max(1, Math.floor(page));
|
|
36
|
+
const next = pages === undefined ? hasNext : current < pages;
|
|
37
|
+
return <nav className="tod-pagination" aria-label="分页">
|
|
38
|
+
<span className="tod-text tod-text--caption tod-text--numeric">{count === undefined ? `第 ${current} 页` : `第 ${current} / ${pages} 页 · 共 ${count} 条`}</span>
|
|
39
|
+
<Button variant="secondary" disabled={loading || current <= 1} onClick={() => onPageChange(current - 1)}>上一页</Button>
|
|
40
|
+
<Button variant="secondary" disabled={loading || !next} onClick={() => onPageChange(current + 1)}>下一页</Button>
|
|
41
|
+
</nav>;
|
|
42
|
+
}
|