@jacshuo/onyx 0.2.1 → 1.0.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.
Files changed (203) hide show
  1. package/README.md +144 -29
  2. package/README.zh-CN.md +745 -0
  3. package/dist/Accordion.cjs +1 -1
  4. package/dist/Accordion.d.cts +3 -1
  5. package/dist/Accordion.d.ts +3 -1
  6. package/dist/Accordion.js +1 -1
  7. package/dist/Alert.cjs +1 -1
  8. package/dist/Alert.js +1 -1
  9. package/dist/Badge.cjs +1 -1
  10. package/dist/Badge.d.cts +1 -1
  11. package/dist/Badge.d.ts +1 -1
  12. package/dist/Badge.js +1 -1
  13. package/dist/Button.cjs +1 -1
  14. package/dist/Button.js +1 -1
  15. package/dist/Card.cjs +1 -1
  16. package/dist/Card.d.cts +3 -1
  17. package/dist/Card.d.ts +3 -1
  18. package/dist/Card.js +1 -1
  19. package/dist/Checkbox.cjs +1 -1
  20. package/dist/Checkbox.js +1 -1
  21. package/dist/CodeBlock.cjs +1 -1
  22. package/dist/CodeBlock.js +1 -1
  23. package/dist/Dialog.cjs +1 -1
  24. package/dist/Dialog.d.cts +5 -2
  25. package/dist/Dialog.d.ts +5 -2
  26. package/dist/Dialog.js +1 -1
  27. package/dist/Dropdown.cjs +1 -1
  28. package/dist/Dropdown.d.cts +4 -0
  29. package/dist/Dropdown.d.ts +4 -0
  30. package/dist/Dropdown.js +1 -1
  31. package/dist/DropdownButton.cjs +1 -1
  32. package/dist/DropdownButton.d.cts +3 -1
  33. package/dist/DropdownButton.d.ts +3 -1
  34. package/dist/DropdownButton.js +1 -1
  35. package/dist/FileExplorer.cjs +1 -1
  36. package/dist/FileExplorer.js +1 -1
  37. package/dist/Form.cjs +1 -0
  38. package/dist/Form.d.cts +101 -0
  39. package/dist/Form.d.ts +101 -0
  40. package/dist/Form.js +1 -0
  41. package/dist/Header.cjs +1 -1
  42. package/dist/Header.d.cts +7 -1
  43. package/dist/Header.d.ts +7 -1
  44. package/dist/Header.js +1 -1
  45. package/dist/Indicator.cjs +1 -0
  46. package/dist/Indicator.d.cts +19 -0
  47. package/dist/Indicator.d.ts +19 -0
  48. package/dist/Indicator.js +1 -0
  49. package/dist/Input.cjs +1 -1
  50. package/dist/Input.js +1 -1
  51. package/dist/Label.cjs +1 -1
  52. package/dist/Label.js +1 -1
  53. package/dist/List.cjs +1 -1
  54. package/dist/List.d.cts +5 -2
  55. package/dist/List.d.ts +5 -2
  56. package/dist/List.js +1 -1
  57. package/dist/Masonry.cjs +1 -0
  58. package/dist/Masonry.d.cts +41 -0
  59. package/dist/Masonry.d.ts +41 -0
  60. package/dist/Masonry.js +1 -0
  61. package/dist/NavLink.cjs +1 -1
  62. package/dist/NavLink.js +1 -1
  63. package/dist/Panel.cjs +1 -1
  64. package/dist/Panel.js +1 -1
  65. package/dist/ProgressBar.cjs +1 -1
  66. package/dist/ProgressBar.js +1 -1
  67. package/dist/Radio.cjs +1 -1
  68. package/dist/Radio.d.cts +3 -1
  69. package/dist/Radio.d.ts +3 -1
  70. package/dist/Radio.js +1 -1
  71. package/dist/SideNav.cjs +1 -1
  72. package/dist/SideNav.d.cts +14 -1
  73. package/dist/SideNav.d.ts +14 -1
  74. package/dist/SideNav.js +1 -1
  75. package/dist/Spin.cjs +1 -1
  76. package/dist/Spin.js +1 -1
  77. package/dist/Switch.cjs +1 -1
  78. package/dist/Switch.js +1 -1
  79. package/dist/Table.cjs +1 -1
  80. package/dist/Table.d.cts +7 -3
  81. package/dist/Table.d.ts +7 -3
  82. package/dist/Table.js +1 -1
  83. package/dist/Tabs.cjs +1 -1
  84. package/dist/Tabs.d.cts +3 -1
  85. package/dist/Tabs.d.ts +3 -1
  86. package/dist/Tabs.js +1 -1
  87. package/dist/Tooltip.cjs +1 -1
  88. package/dist/Tooltip.d.cts +3 -1
  89. package/dist/Tooltip.d.ts +3 -1
  90. package/dist/Tooltip.js +1 -1
  91. package/dist/Tree.cjs +1 -1
  92. package/dist/Tree.d.cts +3 -1
  93. package/dist/Tree.d.ts +3 -1
  94. package/dist/Tree.js +1 -1
  95. package/dist/chunks/chunk-2JLNRAXS.cjs +1 -0
  96. package/dist/chunks/{chunk-GT56J65P.cjs → chunk-2KVAFCQI.cjs} +1 -1
  97. package/dist/chunks/chunk-3I7Y6FUJ.js +1 -0
  98. package/dist/chunks/chunk-47UMFXDG.js +1 -0
  99. package/dist/chunks/{chunk-GW4C7AV6.cjs → chunk-4D3XBPZX.cjs} +2 -2
  100. package/dist/chunks/{chunk-DCWKY33F.js → chunk-4VFV5U3S.js} +1 -1
  101. package/dist/chunks/{chunk-7DM4FEFY.js → chunk-5FUEJFGY.js} +1 -1
  102. package/dist/chunks/chunk-5XT6TJGF.js +1 -0
  103. package/dist/chunks/{chunk-SJ2HIDEM.cjs → chunk-6BI4QL37.cjs} +1 -1
  104. package/dist/chunks/{chunk-LCLJVRKK.cjs → chunk-6E5ARQBB.cjs} +1 -1
  105. package/dist/chunks/chunk-7CEOIZXK.js +1 -0
  106. package/dist/chunks/chunk-7XPIY2SQ.cjs +1 -0
  107. package/dist/chunks/chunk-A6HIQADJ.cjs +1 -0
  108. package/dist/chunks/{chunk-NFRGBE42.cjs → chunk-AEBULFON.cjs} +1 -1
  109. package/dist/chunks/chunk-AK5IK7ZD.js +1 -0
  110. package/dist/chunks/chunk-AN2R5URJ.js +1 -0
  111. package/dist/chunks/{chunk-3B5OL3PD.cjs → chunk-BTR2N5MO.cjs} +2 -2
  112. package/dist/chunks/{chunk-24YBOQFV.cjs → chunk-BUNOVZ23.cjs} +1 -1
  113. package/dist/chunks/chunk-CEEQE7SY.js +1 -0
  114. package/dist/chunks/chunk-CMHBPMXE.js +1 -0
  115. package/dist/chunks/chunk-DWYAPPDB.cjs +1 -0
  116. package/dist/chunks/chunk-E3DST3QD.cjs +1 -0
  117. package/dist/chunks/{chunk-PC4PVKTK.js → chunk-E4EMAZ6V.js} +2 -2
  118. package/dist/chunks/chunk-E5UKEXJE.js +1 -0
  119. package/dist/chunks/{chunk-A7SZGBY2.cjs → chunk-FDTREGBQ.cjs} +1 -1
  120. package/dist/chunks/chunk-FGUFBTKI.cjs +1 -0
  121. package/dist/chunks/{chunk-K4UGTWDR.js → chunk-FQZX67Z7.js} +1 -1
  122. package/dist/chunks/chunk-G2VO67XY.js +1 -0
  123. package/dist/chunks/{chunk-6DR7FZ4Y.js → chunk-GYFFUCBT.js} +1 -1
  124. package/dist/chunks/chunk-I425OSJL.js +1 -0
  125. package/dist/chunks/chunk-ICDAUJ2G.cjs +1 -0
  126. package/dist/chunks/chunk-IFRKP7M2.js +1 -0
  127. package/dist/chunks/chunk-IHBP6FI4.js +1 -0
  128. package/dist/chunks/{chunk-XOZYC6XB.cjs → chunk-IL5XDMUA.cjs} +1 -1
  129. package/dist/chunks/chunk-IRSGPS7D.cjs +1 -0
  130. package/dist/chunks/chunk-ITWFMFVO.js +1 -0
  131. package/dist/chunks/chunk-JJP23IOG.cjs +1 -0
  132. package/dist/chunks/chunk-JRYYWYGV.cjs +1 -0
  133. package/dist/chunks/chunk-KCIICUZN.cjs +1 -0
  134. package/dist/chunks/chunk-KGRBVUVK.cjs +1 -0
  135. package/dist/chunks/chunk-KY4NDB23.cjs +1 -0
  136. package/dist/chunks/chunk-KZBYFKOH.js +1 -0
  137. package/dist/chunks/chunk-LFJEIO3X.cjs +1 -0
  138. package/dist/chunks/chunk-NY27TTWN.js +1 -0
  139. package/dist/chunks/chunk-OEXZGLB4.js +1 -0
  140. package/dist/chunks/chunk-QC67HOC2.cjs +1 -0
  141. package/dist/chunks/chunk-QLFCH4TD.js +1 -0
  142. package/dist/chunks/chunk-RPBESM5F.cjs +1 -0
  143. package/dist/chunks/{chunk-KG3IXPCM.js → chunk-SC4PNYQT.js} +1 -1
  144. package/dist/chunks/chunk-SLHD7PST.cjs +1 -0
  145. package/dist/chunks/chunk-UEGTVAFV.cjs +1 -0
  146. package/dist/chunks/chunk-V34PT6H4.cjs +1 -0
  147. package/dist/chunks/chunk-VUWT3NFR.js +1 -0
  148. package/dist/chunks/{chunk-A7BECCRP.cjs → chunk-W5UXZVLS.cjs} +1 -1
  149. package/dist/chunks/chunk-WQOSJM7L.js +2 -0
  150. package/dist/chunks/chunk-WRPPKNRG.js +1 -0
  151. package/dist/chunks/chunk-XAOBJSFW.js +1 -0
  152. package/dist/chunks/{chunk-EQXC34N2.cjs → chunk-XK7SMEDO.cjs} +1 -1
  153. package/dist/chunks/chunk-XO6CNALX.js +1 -0
  154. package/dist/chunks/chunk-ZY7GZOBS.js +1 -0
  155. package/dist/index.cjs +1 -1
  156. package/dist/index.d.cts +4 -1
  157. package/dist/index.d.ts +4 -1
  158. package/dist/index.js +1 -1
  159. package/dist/styles/base.css +513 -8
  160. package/dist/styles/tokens.css +159 -0
  161. package/dist/styles.css +607 -8
  162. package/dist/theme.cjs +1 -1
  163. package/dist/theme.d.cts +32 -1
  164. package/dist/theme.d.ts +32 -1
  165. package/dist/theme.js +1 -1
  166. package/package.json +1 -1
  167. package/dist/chunks/chunk-2TTIBHQ3.js +0 -1
  168. package/dist/chunks/chunk-64BRBJ5M.js +0 -1
  169. package/dist/chunks/chunk-7PD2UMAG.cjs +0 -1
  170. package/dist/chunks/chunk-7QV2AV32.cjs +0 -1
  171. package/dist/chunks/chunk-BGUKKSPF.js +0 -1
  172. package/dist/chunks/chunk-GQDGQUKK.js +0 -1
  173. package/dist/chunks/chunk-H35HQKON.js +0 -2
  174. package/dist/chunks/chunk-H6MHL66N.js +0 -1
  175. package/dist/chunks/chunk-HFHOE2PH.cjs +0 -1
  176. package/dist/chunks/chunk-HR4MRHSX.js +0 -1
  177. package/dist/chunks/chunk-HSLG4VVI.cjs +0 -1
  178. package/dist/chunks/chunk-HSWWY3SE.cjs +0 -1
  179. package/dist/chunks/chunk-I327TE7P.js +0 -1
  180. package/dist/chunks/chunk-IPPWOV6D.js +0 -1
  181. package/dist/chunks/chunk-KVV5ZEYV.cjs +0 -1
  182. package/dist/chunks/chunk-LZZTFSBF.js +0 -1
  183. package/dist/chunks/chunk-MSBDCLPM.cjs +0 -1
  184. package/dist/chunks/chunk-MVGDKSBE.js +0 -1
  185. package/dist/chunks/chunk-NI2HVXR2.js +0 -1
  186. package/dist/chunks/chunk-NXWDOFPX.cjs +0 -1
  187. package/dist/chunks/chunk-NYTZZ5ZX.cjs +0 -1
  188. package/dist/chunks/chunk-OMBYGQJE.cjs +0 -1
  189. package/dist/chunks/chunk-P652JDOU.cjs +0 -1
  190. package/dist/chunks/chunk-Q53OOZJ3.cjs +0 -1
  191. package/dist/chunks/chunk-R7ZKMSZ3.js +0 -1
  192. package/dist/chunks/chunk-SCSMM2J4.js +0 -1
  193. package/dist/chunks/chunk-SKZIAHHY.cjs +0 -1
  194. package/dist/chunks/chunk-T3WU6A7R.js +0 -1
  195. package/dist/chunks/chunk-VHOOZBWZ.js +0 -1
  196. package/dist/chunks/chunk-W2JIAB3E.js +0 -1
  197. package/dist/chunks/chunk-WL4AMFUA.cjs +0 -1
  198. package/dist/chunks/chunk-WQA7PVJO.js +0 -1
  199. package/dist/chunks/chunk-X3PUHNVJ.js +0 -1
  200. package/dist/chunks/chunk-X4OS5ODF.cjs +0 -1
  201. package/dist/chunks/chunk-XOO3AGIT.js +0 -1
  202. package/dist/chunks/chunk-YWD2VK35.js +0 -1
  203. package/dist/chunks/chunk-ZEHGTD6Y.cjs +0 -1
@@ -0,0 +1,745 @@
1
+ <p align="center">
2
+ <img src="https://img.shields.io/npm/v/@jacshuo/onyx?color=8b5cf6&style=flat-square" alt="npm version" />
3
+ <img src="https://img.shields.io/npm/l/@jacshuo/onyx?style=flat-square" alt="license" />
4
+ <img src="https://img.shields.io/github/actions/workflow/status/jacshuo/jac-ui/ci.yml?branch=main&style=flat-square&label=CI" alt="CI" />
5
+ <img src="https://img.shields.io/npm/dm/@jacshuo/onyx?color=10b981&style=flat-square" alt="downloads" />
6
+ </p>
7
+
8
+ <p align="right">
9
+ <a href="./README.md">English</a> | <strong>中文</strong>
10
+ </p>
11
+
12
+ # @jacshuo/onyx
13
+
14
+ 基于 Tailwind CSS v4 构建的 **React UI 组件库** —— 同时面向响应式 Web 应用与 Electron 桌面应用。提供支持 Tree-shaking 的 ESM + CJS 双格式产物、按组件拆分的子路径导出、模块化 CSS 以及完整的 TypeScript 类型声明。
15
+
16
+ 源自对**精致跨端体验**的热忱,Onyx 在手机屏幕到 4K 显示器之间提供一致的视觉表现 —— 暗色模式、键盘导航与触摸交互,从第一天起就已内置,绝非事后补丁。
17
+
18
+ > **在线演示 →** [jacshuo.github.io/jac-ui](https://jacshuo.github.io/jac-ui)
19
+
20
+ ---
21
+
22
+ ## 为什么选择 Onyx?
23
+
24
+ - **原生响应式** —— 每个组件都能从手机屏幕自适应到 4K 显示器,无需你多写一行媒体查询。Header 自动折叠为汉堡菜单,侧边栏滑入图标或抽屉模式,Dialog 变身底部弹层 —— 全部内置。`sm` / `md` / `lg` 尺寸变体与语义化 CSS Token 让密度调整变得轻而易举。
25
+ - **桌面与 Electron 一等公民** —— Onyx 以 Electron 和桌面应用为核心使用场景精心打磨,针对键盘导航、指针交互和高密度布局做了专项优化 —— 这正是大多数移动优先的组件库难以胜任的地方。
26
+ - **开箱即用,生产就绪** —— 暗色模式、设计 Token、无障碍访问、触摸手势和键盘快捷键,从第一行代码起就已就绪,而非日后拼凑。
27
+ - **极小体积,完全掌控** —— 无运行时 CSS-in-JS。只有 Tailwind CSS v4 工具类与 CSS 自定义属性。通过覆盖 Token 即可定制任意细节,无需 eject,无需与样式优先级斗争。
28
+ - **丰富的专项组件** —— 除常规的按钮和表单之外,Onyx 还提供 `CinePlayer`、`MiniPlayer`、`FileExplorer` 和 `DataTable` —— 这些在通用组件库中几乎找不到,却是媒体类和桌面级应用不可或缺的存在。
29
+
30
+ ---
31
+
32
+ ## 特性一览
33
+
34
+ - 🎨 **30+ 组件** —— 从 Button → DataTable → CinePlayer → CodeBlock 应有尽有
35
+ - 📱 **默认响应式** —— 内置断点布局、触摸友好的点击区域,以及自适应组件模式(汉堡导航、抽屉侧边栏、底部弹层对话框)
36
+ - 🌗 **深色 / 浅色模式** —— 基于 class 切换,开箱即用
37
+ - 🎯 **CSS 变量设计 Token** —— 通过 CSS 自定义属性覆盖任意设计决策,包括在媒体查询断点处按需调整
38
+ - ⚡ **Tailwind CSS v4** —— 零配置,`@theme` Token,支持 `color-mix()` 强调色
39
+ - 📦 **支持 Tree-shaking** —— 每个组件独立 ESM 入口,按需引入,打包体积精准可控
40
+ - 🗂️ **按需导入** —— 子路径导出(`@jacshuo/onyx/Button`),最大程度控制打包产物
41
+ - 🎨 **模块化 CSS** —— 完整包、仅基础包或按组件单独引入 CSS,随心选择
42
+ - 🖥️ **跨平台** —— 为 Web 与 Electron 桌面应用共同设计
43
+ - ⌨️ **键盘优先** —— CinePlayer、FileExplorer 等组件内置完善的键盘快捷键
44
+ - 👆 **触摸与手势支持** —— tap-to-reveal、focus-visible 状态,以及覆盖所有交互组件的触摸优化体验
45
+ - 🔤 **完整 TypeScript** —— 每个 prop、事件与变体均有类型声明
46
+ - 🧩 **可组合 API** —— 复合组件模式(如 `Dialog` → `DialogContent` + `DialogHeader` + `DialogFooter`),自由拼装所需 UI
47
+
48
+ ---
49
+
50
+ ## 安装
51
+
52
+ ```bash
53
+ npm install @jacshuo/onyx
54
+ # 或
55
+ pnpm add @jacshuo/onyx
56
+ # 或
57
+ yarn add @jacshuo/onyx
58
+ ```
59
+
60
+ ### 对等依赖
61
+
62
+ ```bash
63
+ npm install react react-dom
64
+ ```
65
+
66
+ > 需要 **React ≥ 18.0.0**。
67
+
68
+ ---
69
+
70
+ ## 快速上手
71
+
72
+ **第一步:引入样式表**(在应用入口处引入一次即可):
73
+
74
+ ```tsx
75
+ // main.tsx 或 App.tsx
76
+ import '@jacshuo/onyx/styles.css';
77
+ ```
78
+
79
+ **第二步:使用组件:**
80
+
81
+ ```tsx
82
+ import { Button, Card, CardHeader, CardTitle, CardContent } from '@jacshuo/onyx';
83
+
84
+ function App() {
85
+ return (
86
+ <Card>
87
+ <CardHeader>
88
+ <CardTitle>Hello World</CardTitle>
89
+ </CardHeader>
90
+ <CardContent>
91
+ <Button intent="primary">开始使用</Button>
92
+ </CardContent>
93
+ </Card>
94
+ );
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 导入方式
101
+
102
+ Onyx 支持多种导入风格,按需选择最适合你的打包器和性能要求的方式。
103
+
104
+ ### 整体导入(最简单)
105
+
106
+ 从统一入口导入所有组件。现代打包器(Vite、Next.js、webpack 5)会自动 Tree-shake 掉未使用的组件。
107
+
108
+ ```tsx
109
+ import { Button, Dialog, Tabs } from '@jacshuo/onyx';
110
+ import '@jacshuo/onyx/styles.css';
111
+ ```
112
+
113
+ ### 按组件导入(最彻底的 Tree-shaking)
114
+
115
+ 从各组件的独立子路径导入。即使打包器的 Tree-shaking 能力有限,也能保证只打入实际使用的代码。
116
+
117
+ ```tsx
118
+ import { Button } from '@jacshuo/onyx/Button';
119
+ import { Dialog, DialogContent } from '@jacshuo/onyx/Dialog';
120
+ import { Tabs, TabList, TabTrigger } from '@jacshuo/onyx/Tabs';
121
+ ```
122
+
123
+ ### CSS 引入选项
124
+
125
+ | 导入路径 | 体积 | 说明 |
126
+ |---|---|---|
127
+ | `@jacshuo/onyx/styles.css` | ~102 KB | 完整预编译包 —— 包含所有工具类与组件 CSS,最省心的选择。 |
128
+ | `@jacshuo/onyx/styles/base.css` | ~95 KB | Tailwind 工具类 + 核心设计 Token,不含组件专属关键帧。 |
129
+ | `@jacshuo/onyx/styles/tailwind.css` | ~4 KB | **仅适用于已有 Tailwind CSS v4 项目。** 包含 `@source` 指令、Token 与暗色模式变体。 |
130
+ | `@jacshuo/onyx/styles/tokens.css` | ~4 KB | 仅提供原始 `@theme` Token,不含 `@source` 或 Tailwind 导入。 |
131
+ | `@jacshuo/onyx/styles/CinePlayer.css` | ~2.5 KB | CinePlayer 关键帧及 `--cp-*` 设计 Token |
132
+ | `@jacshuo/onyx/styles/MiniPlayer.css` | ~2.2 KB | MiniPlayer 关键帧及 `--mp-*` 设计 Token |
133
+ | `@jacshuo/onyx/styles/FileExplorer.css` | ~1.6 KB | FileExplorer `--fe-*` 设计 Token |
134
+ | `@jacshuo/onyx/styles/FilmReel.css` | ~0.6 KB | FilmReel 关键帧 |
135
+
136
+ #### 与已有的 Tailwind CSS v4 项目集成
137
+
138
+ 如果你的项目已经运行 Tailwind CSS v4,只想引入 Token 而不需要完整预编译包,**必须**使用 `tailwind.css`,以便 Tailwind 能扫描组件库的 JS 文件中的类名:
139
+
140
+ ```css
141
+ /* 你的应用 CSS 入口 */
142
+ @import "tailwindcss";
143
+ @import "@jacshuo/onyx/styles/tailwind.css";
144
+
145
+ /* 可选:单独引入组件专属 CSS */
146
+ @import "@jacshuo/onyx/styles/CinePlayer.css";
147
+ ```
148
+
149
+ > **为什么这样做?** Onyx 组件在 JavaScript 中(通过 CVA)使用 Tailwind 工具类。没有 `@source` 指令,你的 Tailwind 构建不会扫描这些类名,导致样式缺失。`tailwind.css` 中包含的 `@source ".."` 会告知 Tailwind v4 扫描组件库的编译产物。
150
+ >
151
+ > **不要**单独使用 `tokens.css` —— 它只提供设计 Token,不含 `@source` 指令,组件样式将不完整。
152
+
153
+ **示例 —— 仅使用 CinePlayer 的最小化配置:**
154
+
155
+ ```tsx
156
+ import '@jacshuo/onyx/styles/base.css';
157
+ import '@jacshuo/onyx/styles/CinePlayer.css';
158
+ import { CinePlayer } from '@jacshuo/onyx/CinePlayer';
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 组件列表
164
+
165
+ ### 基础元素
166
+
167
+ | 组件 | 说明 |
168
+ |---|---|
169
+ | **Button** | 六种意图(primary / secondary / danger / warning / ghost / outline)× 三种尺寸(sm / md / lg) |
170
+ | **Input** | 带变体支持的样式化文本输入框 |
171
+ | **Label** | 表单标签,支持尺寸变体 |
172
+ | **Badge** | 内联状态徽标 |
173
+ | **Indicator** | 包裹任意元素并在角落叠加圆点或数字角标 |
174
+ | **Dropdown** | 单选与多选下拉框 |
175
+ | **DropdownButton** | 带下拉菜单的按钮 |
176
+
177
+ ### 布局
178
+
179
+ | 组件 | 说明 |
180
+ |---|---|
181
+ | **Card** | Card / CardHeader / CardTitle / CardDescription / CardContent / CardFooter |
182
+ | **HorizontalCard** | 图片与内容并排的横向卡片 |
183
+ | **ImageCard** | 图片优先的卡片,支持悬停操作区 |
184
+ | **Panel** | 带标题的可折叠面板 |
185
+
186
+ ### 数据展示
187
+
188
+ | 组件 | 说明 |
189
+ |---|---|
190
+ | **Table** | 基础表格原语(Table / TableHeader / TableBody / TableRow 等) |
191
+ | **SortableTable** | 支持点击列头排序 |
192
+ | **DataTable** | 功能完整的数据表格,含排序、多选、分页 |
193
+ | **List / ListItem** | 样式化列表组件 |
194
+ | **Tree / TreeItem** | 可展开的树形视图 |
195
+ | **Chat** | 聊天消息列表,区分发送与接收样式 |
196
+ | **CodeBlock** | 基于 Shiki 的语法高亮代码块,支持 20+ 语言、行号显示与实时编辑模式 |
197
+
198
+ ### 导航
199
+
200
+ | 组件 | 说明 |
201
+ |---|---|
202
+ | **SideNav** | 支持图标、分组和多种折叠模式的可折叠侧边栏 |
203
+ | **Header** | 带导航项与操作按钮的应用顶栏 |
204
+ | **NavLink** | 语义化文字链接(`<a>`),自动识别外链、支持意图 / 尺寸 / 下划线变体 |
205
+ | **Tabs** | 带滑动指示器动画的标签页 |
206
+
207
+ ### 展开收起
208
+
209
+ | 组件 | 说明 |
210
+ |---|---|
211
+ | **Accordion** | 可展开折叠的手风琴区块 |
212
+ | **Tabs** | TabList / TabTrigger / TabPanels / TabContent |
213
+
214
+ ### 浮层
215
+
216
+ | 组件 | 说明 |
217
+ |---|---|
218
+ | **Dialog** | 支持层叠、背景点击关闭、ESC 处理的模态对话框 |
219
+ | **Tooltip** | 可配置位置的悬停提示 |
220
+ | **Alert** | 基于 `useAlert()` Hook 的 Toast 提示系统 |
221
+
222
+ ### 特色组件
223
+
224
+ | 组件 | 说明 |
225
+ |---|---|
226
+ | **FilmReel** | 电影感相册,含灯箱效果 |
227
+ | **MiniPlayer** | 浮动迷你音乐播放器,支持停靠、播放列表、随机与循环 |
228
+ | **CinePlayer** | 全功能视频播放器,含影院模式、播放列表、键盘快捷键 |
229
+ | **FileExplorer** | 科幻风格文件浏览器,支持拖拽、调整尺寸、停靠、多选、Delete 键删除 |
230
+
231
+ ---
232
+
233
+ ## 主题定制
234
+
235
+ ### 暗色模式
236
+
237
+ 组件库采用 Tailwind 的**基于 class** 的暗色模式。将 `class="dark"` 加到 `<html>` 或任意祖先元素即可:
238
+
239
+ ```html
240
+ <html class="dark">
241
+ <!-- 所有 jac-ui 组件将以暗色模式渲染 -->
242
+ </html>
243
+ ```
244
+
245
+ ### 强调色
246
+
247
+ 许多组件接受 `accent` prop(任意 CSS 颜色字符串):
248
+
249
+ ```tsx
250
+ <MiniPlayer accent="#3b82f6" playlist={tracks} />
251
+ <CinePlayer accent="#f43f5e" playlist={videos} />
252
+ <FileExplorer accent="#10b981" files={files} />
253
+ ```
254
+
255
+ ### CSS 自定义属性
256
+
257
+ 所有组件颜色通过 `:root` 和 `.dark` 中的 CSS 自定义属性定义,完全支持外部覆盖:
258
+
259
+ ```css
260
+ /* 覆盖 CinePlayer 颜色 */
261
+ :root {
262
+ --cp-bg: #111;
263
+ --cp-text: rgba(255, 255, 255, 0.8);
264
+ --cp-surface-hover: rgba(255, 255, 255, 0.15);
265
+ }
266
+
267
+ /* 覆盖 MiniPlayer 颜色 */
268
+ :root {
269
+ --mp-bg: rgba(255, 255, 255, 0.95);
270
+ --mp-text: #1e293b;
271
+ }
272
+ .dark {
273
+ --mp-bg: rgba(20, 18, 30, 0.95);
274
+ --mp-text: #ffffff;
275
+ }
276
+
277
+ /* 覆盖 FileExplorer 颜色 */
278
+ :root {
279
+ --fe-bg: linear-gradient(145deg, #fff, #f8f8fc);
280
+ --fe-text: #475569;
281
+ }
282
+ ```
283
+
284
+ <details>
285
+ <summary><strong>完整 Token 参考</strong></summary>
286
+
287
+ #### CinePlayer (`--cp-*`)
288
+ | Token | 默认值 | 说明 |
289
+ |---|---|---|
290
+ | `--cp-bg` | `#000000` | 播放器背景 |
291
+ | `--cp-panel-bg` | `rgba(0,0,0,0.85)` | 播放列表 / 浮层面板 |
292
+ | `--cp-text` | `rgba(255,255,255,0.75)` | 主文字颜色 |
293
+ | `--cp-text-muted` | `rgba(255,255,255,0.50)` | 次级文字颜色 |
294
+ | `--cp-text-strong` | `#ffffff` | 强调文字颜色 |
295
+ | `--cp-border` | `rgba(255,255,255,0.10)` | 边框颜色 |
296
+ | `--cp-surface` | `rgba(255,255,255,0.05)` | 表面背景 |
297
+ | `--cp-surface-hover` | `rgba(255,255,255,0.10)` | 悬停状态 |
298
+ | `--cp-overlay` | `rgba(0,0,0,0.30)` | 遮罩背景 |
299
+ | `--cp-seek-track` | `rgba(255,255,255,0.20)` | 进度条轨道 |
300
+ | `--cp-seek-buffer` | `rgba(255,255,255,0.15)` | 缓冲区域 |
301
+
302
+ #### MiniPlayer (`--mp-*`)
303
+ | Token | 浅色 | 深色 |
304
+ |---|---|---|
305
+ | `--mp-bg` | `rgba(255,255,255,0.90)` | `rgba(26,22,37,0.95)` |
306
+ | `--mp-text` | `primary-900` | `#ffffff` |
307
+ | `--mp-text-muted` | `primary-500` | `rgba(255,255,255,0.50)` |
308
+ | `--mp-border` | `rgba(148,163,184,0.60)` | `rgba(255,255,255,0.10)` |
309
+ | `--mp-surface` | `rgba(148,163,184,0.50)` | `rgba(255,255,255,0.10)` |
310
+ | `--mp-surface-hover` | `rgba(241,245,249,0.60)` | `rgba(255,255,255,0.05)` |
311
+ | `--mp-dock-strip` | `rgba(148,163,184,0.40)` | `rgba(255,255,255,0.20)` |
312
+
313
+ #### FileExplorer (`--fe-*`)
314
+ | Token | 浅色 | 深色 |
315
+ |---|---|---|
316
+ | `--fe-bg` | 渐变白色 | 渐变深色 |
317
+ | `--fe-shadow` | 柔和阴影 | 发光阴影 |
318
+ | `--fe-text` | `primary-600` | `rgba(255,255,255,0.70)` |
319
+ | `--fe-text-strong` | `primary-900` | `#ffffff` |
320
+ | `--fe-text-muted` | `primary-400` | `rgba(255,255,255,0.30)` |
321
+ | `--fe-border` | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.06)` |
322
+ | `--fe-btn-color` | `rgba(0,0,0,0.45)` | `rgba(255,255,255,0.50)` |
323
+
324
+ </details>
325
+
326
+ ---
327
+
328
+ ## 响应式设计
329
+
330
+ Onyx 组件在内部处理响应式行为 —— 你无需自己编写媒体查询,即可获得自适应布局。
331
+
332
+ ### 响应式顶栏
333
+
334
+ `Header` 在移动端会自动将导航项收起为汉堡菜单抽屉,无需额外配置:
335
+
336
+ ```tsx
337
+ import { Header } from '@jacshuo/onyx';
338
+
339
+ // ≥md 断点:完整导航栏 + 操作按钮
340
+ // <md 断点:汉堡菜单(导航)+ 溢出菜单(操作)—— 自动处理
341
+ <Header
342
+ title="我的应用"
343
+ navItems={[
344
+ { label: '首页', href: '/' },
345
+ { label: '文档', href: '/docs' },
346
+ { label: '更新日志', href: '/changelog' },
347
+ ]}
348
+ actions={[
349
+ <Button size="sm" intent="primary">登录</Button>,
350
+ ]}
351
+ />
352
+ ```
353
+
354
+ ### 响应式侧边栏
355
+
356
+ `SideNav` 提供三种折叠模式。通过一个 state 变量即可实现桌面到移动端的无缝过渡:
357
+
358
+ ```tsx
359
+ import { useState } from 'react';
360
+ import { SideNav, type SideNavCollapseMode } from '@jacshuo/onyx';
361
+
362
+ function AppShell() {
363
+ const [mode, setMode] = useState<SideNavCollapseMode>('expanded');
364
+
365
+ return (
366
+ <div className="flex h-screen">
367
+ {/* 移动端隐藏;桌面端可折叠 */}
368
+ <aside className="hidden md:block shrink-0">
369
+ <SideNav
370
+ items={navItems}
371
+ collapsible
372
+ collapseMode={mode}
373
+ onCollapseModeChange={setMode}
374
+ />
375
+ </aside>
376
+
377
+ {/* 移动端滑入抽屉 */}
378
+ <aside className="md:hidden">
379
+ <SideNav items={navItems} />
380
+ </aside>
381
+
382
+ <main className="flex-1 overflow-y-auto p-4 md:p-8">
383
+ {/* 页面内容 */}
384
+ </main>
385
+ </div>
386
+ );
387
+ }
388
+ ```
389
+
390
+ ### Dialog —— 移动端底部弹层
391
+
392
+ `Dialog` 在小屏幕上会自动渲染为从底部滑入的底部弹层,符合移动端用户的操作习惯:
393
+
394
+ ```tsx
395
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, Button } from '@jacshuo/onyx';
396
+
397
+ // ≥md 断点:居中模态框
398
+ // <md 断点:从底部滑入的全宽弹层 —— 无需额外 props
399
+ <Dialog open={open} onOpenChange={setOpen}>
400
+ <DialogContent size="sm">
401
+ <DialogHeader>
402
+ <DialogTitle>确认操作</DialogTitle>
403
+ </DialogHeader>
404
+ <p>确定要继续执行此操作吗?</p>
405
+ <div className="flex justify-end gap-2">
406
+ <Button intent="ghost" onClick={() => setOpen(false)}>取消</Button>
407
+ <Button intent="primary" onClick={() => setOpen(false)}>确认</Button>
408
+ </div>
409
+ </DialogContent>
410
+ </Dialog>
411
+ ```
412
+
413
+ ### 尺寸变体控制密度
414
+
415
+ 所有组件都提供 `size` prop(`sm` / `md` / `lg`),用于在移动端紧凑布局与桌面端宽松仪表盘之间灵活切换:
416
+
417
+ ```tsx
418
+ import { DataTable, Tabs, TabList, TabTrigger } from '@jacshuo/onyx';
419
+
420
+ // 移动端紧凑模式
421
+ <DataTable columns={columns} data={rows} size="sm" />
422
+
423
+ // 桌面端舒适模式
424
+ <DataTable columns={columns} data={rows} size="lg" />
425
+
426
+ // 混搭尺寸以匹配布局密度
427
+ <Tabs defaultValue="a">
428
+ <TabList size="sm"> {/* 紧凑标签页 */}
429
+ <TabTrigger value="a">标签 A</TabTrigger>
430
+ <TabTrigger value="b">标签 B</TabTrigger>
431
+ </TabList>
432
+ </Tabs>
433
+ ```
434
+
435
+ ### 在断点处覆盖 Token
436
+
437
+ 所有尺寸与间距值均为 CSS 自定义属性。在任意断点处覆盖它们,即可实现精准的响应式调整:
438
+
439
+ ```css
440
+ /* 默认(移动端优先)表单布局 */
441
+ :root {
442
+ --form-label-w-md: 5rem;
443
+ --form-item-gap-md: 0.5rem;
444
+ --form-row-gap-md: 0.75rem;
445
+ }
446
+
447
+ /* 桌面端:更宽的标签列 + 更大的间距 */
448
+ @media (min-width: 768px) {
449
+ :root {
450
+ --form-label-w-md: 7rem;
451
+ --form-item-gap-md: 0.75rem;
452
+ --form-row-gap-md: 1.25rem;
453
+ }
454
+ }
455
+ ```
456
+
457
+ ---
458
+
459
+ ## 使用示例
460
+
461
+ ### Button
462
+
463
+ ```tsx
464
+ import { Button } from '@jacshuo/onyx';
465
+
466
+ <Button intent="primary" size="lg">保存</Button>
467
+ <Button intent="danger">删除</Button>
468
+ <Button intent="ghost">取消</Button>
469
+ <Button intent="outline">设置</Button>
470
+ ```
471
+
472
+ ### Dialog
473
+
474
+ ```tsx
475
+ import { useState } from 'react';
476
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Button } from '@jacshuo/onyx';
477
+
478
+ function ConfirmDialog() {
479
+ const [open, setOpen] = useState(false);
480
+
481
+ return (
482
+ <>
483
+ <Button onClick={() => setOpen(true)}>打开对话框</Button>
484
+ <Dialog open={open} onOpenChange={setOpen}>
485
+ <DialogContent size="sm">
486
+ <DialogHeader>
487
+ <DialogTitle>确认操作</DialogTitle>
488
+ </DialogHeader>
489
+ <p>确定要继续执行此操作吗?</p>
490
+ <DialogFooter>
491
+ <Button intent="ghost" onClick={() => setOpen(false)}>取消</Button>
492
+ <Button intent="primary" onClick={() => setOpen(false)}>确认</Button>
493
+ </DialogFooter>
494
+ </DialogContent>
495
+ </Dialog>
496
+ </>
497
+ );
498
+ }
499
+ ```
500
+
501
+ ### DataTable
502
+
503
+ ```tsx
504
+ import { DataTable, type ColumnDef } from '@jacshuo/onyx';
505
+
506
+ type User = { id: number; name: string; email: string };
507
+
508
+ const columns: ColumnDef<User>[] = [
509
+ { key: 'id', header: 'ID', width: 60 },
510
+ { key: 'name', header: '姓名', sortable: true },
511
+ { key: 'email', header: '邮箱', sortable: true },
512
+ ];
513
+
514
+ <DataTable columns={columns} data={users} selectionMode="multi" pageSize={10} />
515
+ ```
516
+
517
+ ### Tabs
518
+
519
+ ```tsx
520
+ import { Tabs, TabList, TabTrigger, TabPanels, TabContent } from '@jacshuo/onyx';
521
+
522
+ <Tabs defaultValue="overview">
523
+ <TabList>
524
+ <TabTrigger value="overview">概览</TabTrigger>
525
+ <TabTrigger value="settings">设置</TabTrigger>
526
+ </TabList>
527
+ <TabPanels>
528
+ <TabContent value="overview">概览内容……</TabContent>
529
+ <TabContent value="settings">设置内容……</TabContent>
530
+ </TabPanels>
531
+ </Tabs>
532
+ ```
533
+
534
+ ### Alert(Toast 提示)
535
+
536
+ ```tsx
537
+ import { useAlert, Button } from '@jacshuo/onyx';
538
+
539
+ function NotifyButton() {
540
+ const alert = useAlert();
541
+
542
+ return (
543
+ <Button onClick={() => alert({ title: '已保存!', description: '你的更改已成功保存。', variant: 'success' })}>
544
+ 保存
545
+ </Button>
546
+ );
547
+ }
548
+ ```
549
+
550
+ ### NavLink
551
+
552
+ ```tsx
553
+ import { NavLink } from '@jacshuo/onyx';
554
+
555
+ {/* 内部链接 */}
556
+ <NavLink href="/about">关于</NavLink>
557
+
558
+ {/* 自动识别外链 —— 显示图标并自动设置 target="_blank" */}
559
+ <NavLink href="https://github.com">GitHub</NavLink>
560
+
561
+ {/* 禁用外链图标 */}
562
+ <NavLink href="https://example.com" external={false}>示例</NavLink>
563
+
564
+ {/* 变体 */}
565
+ <NavLink href="/docs" intent="secondary" size="lg" underline="always">文档</NavLink>
566
+ ```
567
+
568
+ ### CodeBlock
569
+
570
+ ```tsx
571
+ import { CodeBlock } from '@jacshuo/onyx';
572
+
573
+ {/* 基础语法高亮 */}
574
+ <CodeBlock code={`const x = 42;`} language="typescript" />
575
+
576
+ {/* 显示行号 */}
577
+ <CodeBlock code={sourceCode} language="tsx" lineNumbers />
578
+
579
+ {/* 实时可编辑模式 */}
580
+ function Editor() {
581
+ const [code, setCode] = useState('console.log("hello")');
582
+ return (
583
+ <CodeBlock
584
+ code={code}
585
+ language="typescript"
586
+ editable
587
+ onCodeChange={setCode}
588
+ lineNumbers
589
+ />
590
+ );
591
+ }
592
+ ```
593
+
594
+ ### MiniPlayer
595
+
596
+ ```tsx
597
+ import { MiniPlayer } from '@jacshuo/onyx';
598
+
599
+ const tracks = [
600
+ { title: 'Midnight City', artist: 'M83', src: '/audio/midnight.mp3', cover: '/covers/m83.jpg' },
601
+ { title: 'Intro', artist: 'The xx', src: '/audio/intro.mp3' },
602
+ ];
603
+
604
+ <MiniPlayer
605
+ playlist={tracks}
606
+ position="bottom-right"
607
+ accent="#8b5cf6"
608
+ shuffle
609
+ autoPlay
610
+ />
611
+ ```
612
+
613
+ ### CinePlayer
614
+
615
+ ```tsx
616
+ import { CinePlayer } from '@jacshuo/onyx';
617
+
618
+ const videos = [
619
+ { title: '大雄兔', src: 'https://example.com/bunny.mp4', subtitle: '开源动画' },
620
+ ];
621
+
622
+ <CinePlayer
623
+ playlist={videos}
624
+ accent="#f43f5e"
625
+ onPlayChange={(playing, index) => console.log(playing, index)}
626
+ />
627
+ ```
628
+
629
+ ### FileExplorer
630
+
631
+ ```tsx
632
+ import { FileExplorer, type FileExplorerItem } from '@jacshuo/onyx';
633
+
634
+ const files: FileExplorerItem[] = [
635
+ { name: 'src', path: '/src', type: 'directory' },
636
+ { name: 'index.ts', path: '/src/index.ts', type: 'file', size: 2048, extension: '.ts' },
637
+ ];
638
+
639
+ <FileExplorer
640
+ files={files}
641
+ accent="#10b981"
642
+ dockable
643
+ onFileOpen={(f) => console.log('打开', f.name)}
644
+ onDelete={(items) => console.log('删除', items)}
645
+ />
646
+ ```
647
+
648
+ ---
649
+
650
+ ## 键盘快捷键
651
+
652
+ ### FileExplorer
653
+ | 按键 | 操作 |
654
+ |---|---|
655
+ | `点击` | 选中文件 |
656
+ | `Ctrl+点击` | 多选 |
657
+ | `Ctrl+A` | 全选 |
658
+ | `Delete` | 删除选中项(含确认对话框) |
659
+ | `Escape` | 清除选中 |
660
+ | `双击` | 打开文件 / 进入目录 |
661
+
662
+ ### CinePlayer
663
+ | 按键 | 操作 |
664
+ |---|---|
665
+ | `空格` | 播放 / 暂停 |
666
+ | `←` / `→` | 快退 / 快进 ±5 秒 |
667
+ | `↑` / `↓` | 音量 ±5% |
668
+ | `F` | 切换全屏 |
669
+ | `C` | 切换影院模式 |
670
+ | `L` | 切换播放列表 |
671
+ | `M` | 静音 / 取消静音 |
672
+ | `N` | 下一曲 |
673
+ | `P` | 上一曲 |
674
+ | `S` | 切换随机播放 |
675
+
676
+ ---
677
+
678
+ ## 本地开发
679
+
680
+ ```bash
681
+ # 安装依赖
682
+ npm install
683
+
684
+ # 启动演示站开发服务器(http://localhost:8080)
685
+ npm run dev
686
+
687
+ # 构建组件库产物(dist/)
688
+ npm run dist
689
+
690
+ # 构建演示站(dist-demo/)
691
+ npm run build:demo
692
+
693
+ # 类型检查
694
+ npm run typecheck
695
+ ```
696
+
697
+ ---
698
+
699
+ ## 项目结构
700
+
701
+ ```
702
+ jac-ui/
703
+ ├── src/
704
+ │ ├── components/ # 所有 React 组件
705
+ │ ├── lib/utils.ts # cn() 工具函数(clsx + tailwind-merge)
706
+ │ └── styles/
707
+ │ ├── index.css # 完整 CSS 入口(Tailwind + 所有 Token + 所有组件 CSS)
708
+ │ ├── base.css # Tailwind + 核心 Token
709
+ │ ├── tokens.css # @theme 语义 Token 与核心关键帧
710
+ │ ├── theme.ts # CVA 变体定义
711
+ │ └── components/ # 按组件单独的 CSS(关键帧与设计 Token)
712
+ │ ├── CinePlayer.css
713
+ │ ├── MiniPlayer.css
714
+ │ ├── FileExplorer.css
715
+ │ └── FilmReel.css
716
+ ├── demo/ # 演示站(GitHub Pages)
717
+ │ ├── App.tsx
718
+ │ ├── main.tsx
719
+ │ └── pages/ # 各组件演示页
720
+ ├── .github/workflows/
721
+ │ ├── ci.yml # PR/push:类型检查 + 构建
722
+ │ └── release.yml # 手动触发:版本号更新 → npm → GitHub Release → Pages
723
+ ├── dist/ # 组件库构建产物(ESM + CJS + DTS + CSS)
724
+ │ ├── *.js / *.cjs # 按组件拆分的入口文件
725
+ │ ├── chunks/ # 共享代码(tsup 自动提取)
726
+ │ ├── styles.css # 完整预编译 CSS 包
727
+ │ └── styles/ # 模块化 CSS 文件
728
+ └── dist-demo/ # 演示站构建产物
729
+ ```
730
+
731
+ ---
732
+
733
+ ## 参与贡献
734
+
735
+ 1. Fork 本仓库
736
+ 2. 创建功能分支(`git checkout -b feature/my-feature`)
737
+ 3. 提交你的更改(`git commit -m 'feat: 添加新组件'`)
738
+ 4. 推送到分支(`git push origin feature/my-feature`)
739
+ 5. 发起 Pull Request
740
+
741
+ ---
742
+
743
+ ## 许可证
744
+
745
+ [MIT](./LICENSE) © Shuo Wang