@maiyunnet/kebab 9.0.2 → 9.1.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 (166) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/bin/kebab.js +0 -0
  4. package/doc/kebab-rag.md +604 -604
  5. package/index.d.ts +1 -1
  6. package/index.js +1 -1
  7. package/lib/ai.d.ts +0 -0
  8. package/lib/ai.js +0 -0
  9. package/lib/buffer.d.ts +0 -0
  10. package/lib/buffer.js +0 -0
  11. package/lib/captcha/zcool-addict-italic.ttf +0 -0
  12. package/lib/captcha.d.ts +0 -0
  13. package/lib/captcha.js +0 -0
  14. package/lib/consistent.d.ts +0 -0
  15. package/lib/consistent.js +0 -0
  16. package/lib/core.d.ts +0 -0
  17. package/lib/core.js +0 -0
  18. package/lib/cron.d.ts +0 -0
  19. package/lib/cron.js +0 -0
  20. package/lib/crypto.d.ts +0 -0
  21. package/lib/crypto.js +0 -0
  22. package/lib/db/conn.d.ts +0 -0
  23. package/lib/db/conn.js +0 -0
  24. package/lib/db/pool.d.ts +0 -0
  25. package/lib/db/pool.js +0 -0
  26. package/lib/db/tran.d.ts +0 -0
  27. package/lib/db/tran.js +8 -8
  28. package/lib/db.d.ts +0 -0
  29. package/lib/db.js +0 -0
  30. package/lib/dns.d.ts +0 -0
  31. package/lib/dns.js +0 -0
  32. package/lib/fs.d.ts +0 -0
  33. package/lib/fs.js +0 -0
  34. package/lib/kv.d.ts +0 -0
  35. package/lib/kv.js +0 -0
  36. package/lib/lan.d.ts +0 -0
  37. package/lib/lan.js +0 -0
  38. package/lib/lang.d.ts +0 -0
  39. package/lib/lang.js +0 -0
  40. package/lib/net/cacert.pem +0 -0
  41. package/lib/net/formdata.d.ts +0 -0
  42. package/lib/net/formdata.js +0 -0
  43. package/lib/net/request.d.ts +0 -0
  44. package/lib/net/request.js +0 -0
  45. package/lib/net/response.d.ts +0 -0
  46. package/lib/net/response.js +0 -0
  47. package/lib/net.d.ts +0 -0
  48. package/lib/net.js +0 -0
  49. package/lib/ratelimit.d.ts +0 -0
  50. package/lib/ratelimit.js +0 -0
  51. package/lib/s3.d.ts +0 -0
  52. package/lib/s3.js +0 -0
  53. package/lib/scan.d.ts +0 -0
  54. package/lib/scan.js +0 -0
  55. package/lib/session.d.ts +0 -0
  56. package/lib/session.js +0 -0
  57. package/lib/socket.d.ts +0 -0
  58. package/lib/socket.js +0 -0
  59. package/lib/sql.d.ts +0 -0
  60. package/lib/sql.js +0 -0
  61. package/lib/ssh/sftp.d.ts +0 -0
  62. package/lib/ssh/sftp.js +0 -0
  63. package/lib/ssh/shell.d.ts +0 -0
  64. package/lib/ssh/shell.js +0 -0
  65. package/lib/ssh.d.ts +0 -0
  66. package/lib/ssh.js +0 -0
  67. package/lib/text/tld.json +0 -0
  68. package/lib/text.d.ts +0 -0
  69. package/lib/text.js +0 -0
  70. package/lib/time.d.ts +0 -0
  71. package/lib/time.js +0 -0
  72. package/lib/turnstile.d.ts +0 -0
  73. package/lib/turnstile.js +0 -0
  74. package/lib/vector.d.ts +0 -0
  75. package/lib/vector.js +0 -0
  76. package/lib/ws.d.ts +0 -0
  77. package/lib/ws.js +0 -0
  78. package/lib/zip.d.ts +0 -0
  79. package/lib/zip.js +0 -0
  80. package/lib/zlib.d.ts +0 -0
  81. package/lib/zlib.js +0 -0
  82. package/main.d.ts +0 -0
  83. package/main.js +0 -0
  84. package/package.json +9 -2
  85. package/sys/child.d.ts +0 -0
  86. package/sys/child.js +0 -0
  87. package/sys/cmd.d.ts +0 -0
  88. package/sys/cmd.js +69 -42
  89. package/sys/ctr.d.ts +8 -0
  90. package/sys/ctr.js +86 -36
  91. package/sys/master.d.ts +0 -0
  92. package/sys/master.js +0 -0
  93. package/sys/mod.d.ts +0 -0
  94. package/sys/mod.js +0 -0
  95. package/sys/monitor/watchdog.d.ts +0 -0
  96. package/sys/monitor/watchdog.js +0 -0
  97. package/sys/monitor.d.ts +0 -0
  98. package/sys/monitor.js +1 -1
  99. package/sys/route.d.ts +0 -0
  100. package/sys/route.js +0 -0
  101. package/www/example/ctr/main.d.ts +0 -0
  102. package/www/example/ctr/main.js +0 -0
  103. package/www/example/ctr/middle.d.ts +0 -0
  104. package/www/example/ctr/middle.js +0 -0
  105. package/www/example/ctr/test.d.ts +4 -0
  106. package/www/example/ctr/test.js +12 -3
  107. package/www/example/data/locale/en.test.json +0 -0
  108. package/www/example/data/locale/index.html +0 -0
  109. package/www/example/data/locale/ja.test.json +0 -0
  110. package/www/example/data/locale/sc.test.json +0 -0
  111. package/www/example/data/locale/tc.test.json +0 -0
  112. package/www/example/data/test.zip +0 -0
  113. package/www/example/kebab.json +0 -0
  114. package/www/example/mod/test.d.ts +0 -0
  115. package/www/example/mod/test.js +0 -0
  116. package/www/example/mod/testdata.d.ts +0 -0
  117. package/www/example/mod/testdata.js +0 -0
  118. package/www/example/route.json +0 -0
  119. package/www/example/stc/chunk-YJ3GYATF.js +81 -0
  120. package/www/example/stc/lib/ui/checkbox.d.ts +9 -0
  121. package/www/example/stc/lib/ui/checkbox.js +11 -0
  122. package/www/example/stc/lib/ui/checkbox.tsx +30 -0
  123. package/www/example/stc/lib/ui/input.d.ts +3 -0
  124. package/www/example/stc/lib/ui/input.js +10 -0
  125. package/www/example/stc/lib/ui/input.tsx +24 -0
  126. package/www/example/stc/lib/ui/label.d.ts +11 -0
  127. package/www/example/stc/lib/ui/label.js +13 -0
  128. package/www/example/stc/lib/ui/label.tsx +24 -0
  129. package/www/example/stc/lib/ui/switch.d.ts +9 -0
  130. package/www/example/stc/lib/ui/switch.js +11 -0
  131. package/www/example/stc/lib/ui/switch.tsx +31 -0
  132. package/www/example/stc/lib/utils.d.ts +7 -0
  133. package/www/example/stc/lib/utils.js +10 -0
  134. package/www/example/stc/view/hello.page.bundle.js +1 -0
  135. package/www/example/stc/view/hello.page.css +2 -0
  136. package/www/example/stc/view/hello.page.d.ts +17 -0
  137. package/www/example/stc/view/hello.page.js +15 -0
  138. package/www/example/stc/view/hello.page.tsx +49 -0
  139. package/www/example/stc/view/react-router.page.bundle.js +1 -0
  140. package/www/example/stc/view/react-router.page.css +2 -0
  141. package/www/example/stc/view/{react-router-page.d.ts → react-router.page.d.ts} +1 -1
  142. package/www/example/stc/view/{react-router-page.js → react-router.page.js} +1 -1
  143. package/www/example/stc/view/{react-router-page.tsx → react-router.page.tsx} +1 -1
  144. package/www/example/stc/view/react.page.bundle.js +26 -0
  145. package/www/example/stc/view/react.page.css +2 -0
  146. package/www/example/stc/view/{react-page.d.ts → react.page.d.ts} +16 -18
  147. package/www/example/stc/view/react.page.js +181 -0
  148. package/www/example/stc/view/{react-page.tsx → react.page.tsx} +259 -111
  149. package/www/example/view/test.ejs +0 -0
  150. package/www/example/ws/handler.d.ts +0 -0
  151. package/www/example/ws/handler.js +0 -0
  152. package/www/example/ws/main.d.ts +0 -0
  153. package/www/example/ws/main.js +0 -0
  154. package/www/example/ws/mproxy.d.ts +0 -0
  155. package/www/example/ws/mproxy.js +0 -0
  156. package/www/example/ws/rproxy.d.ts +0 -0
  157. package/www/example/ws/rproxy.js +0 -0
  158. package/www/example/ws/rsocket.d.ts +0 -0
  159. package/www/example/ws/rsocket.js +0 -0
  160. package/www/example/ws/test.d.ts +0 -0
  161. package/www/example/ws/test.js +0 -0
  162. package/www/example/stc/view/react-page.bundle.js +0 -97
  163. package/www/example/stc/view/react-page.css +0 -2
  164. package/www/example/stc/view/react-page.js +0 -136
  165. package/www/example/stc/view/react-router-page.bundle.js +0 -81
  166. package/www/example/stc/view/react-router-page.css +0 -2
@@ -1,33 +1,50 @@
1
1
  /**
2
2
  * --- Kebab React 全页面示例组件 ---
3
3
  *
4
+ * node ./source/main build -d source/www/example/stc
5
+ *
6
+ * 【文件命名约定】
7
+ * 文件名以 .page.tsx 结尾的为页面入口(本文件),框架会将其打包为 .page.bundle.js。
8
+ * 其余 .tsx 均视为组件(如 stc/lib/ui/button.tsx),不会被单独打包。
9
+ *
4
10
  * 【使用方式】
5
- * 在 Ctr 方法中调用 _loadReactPage('view/react-page', { ...props }),
6
- * 框架自动注入 _importMapJson / _hydrateScript / _propsJson 三个内部 props,
7
- * 组件在 <head> 和 <body> 末尾渲染对应的 <script> 标签即可,其余部分与普通 React 组件无异。
11
+ * 在 Ctr 方法中调用 _loadReactPage('view/react', { ...props }),
12
+ * 框架自动将 import map、props JSON、水合脚本注入 HTML,组件只写业务逻辑,无需任何框架样板代码。
8
13
  *
9
14
  * 【多页面 / 共用组件】
10
- * 将公共 UI(如 Button/Card/Badge)放在 stc/lib/ 目录,各页面 import 进来。
15
+ * 将公共 UI(如 Label/Input/Checkbox)放在 stc/lib/ui/ 目录,各页面 import 进来。
16
+ * 这和 shadcn/ui 推荐的 components/ui/ 目录结构完全一致。
11
17
  * tsc watch 会自动将所有 .tsx 编译为同路径的 .js,无需打包工具。
12
18
  *
13
19
  * 【编译方式】
14
20
  * 本文件 (.tsx) 已集成到 source/tsconfig.json 的 include 中,
15
21
  * 开启 `tsc: watch - source/tsconfig.json` 即可自动编译,无需额外命令。
16
- * 编译输出:stc/view/react-page.js(由 tsc 覆盖写入,勿手动编辑 .js 文件)
22
+ * 编译输出:stc/view/react.page.js(由 tsc 覆盖写入,勿手动编辑 .js 文件)
17
23
  *
18
24
  * 【运行时机】
19
25
  * - 服务端(Node.js):_loadReactPage() 调用 renderToString(),产出完整 HTML 字符串。
20
26
  * - 客户端(浏览器):import map 将 bare import 解析到 esm.sh,hydrateRoot 接管 document。
21
27
  * - 两端使用同一份 JS 文件:服务端从磁盘读,浏览器从静态 URL 下载。
22
28
  *
29
+ * 【第三方包 import map 自动解析】
30
+ * 开发模式下,框架会自动扫描入口 JS 及其相对引用中的所有 bare specifier,
31
+ * 并通过 esm.sh CDN 自动生成 import map 条目,无需手动配置。
32
+ *
23
33
  * 【限制】
24
34
  * - 不能包含 Node.js 专属代码(fs/path/lDb 等),数据必须通过 props 传入。
25
35
  * - 等价于 Next.js 的 Client Component,而非 Server Component。
26
36
  */
27
37
 
28
- import { useState, useEffect, type ReactNode } from 'react';
38
+ // --- React / React Router ---
39
+ import { useState, useEffect, useId, type ReactNode } from 'react';
29
40
  import { MemoryRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
30
41
 
42
+ // --- shadcn/ui 控件(从 stc/lib/ui/ 引入,与行业标准 components/ui/ 目录结构一致)---
43
+ import { Label } from '../lib/ui/label.js';
44
+ import { Input } from '../lib/ui/input.js';
45
+ import { Checkbox } from '../lib/ui/checkbox.js';
46
+ import { Switch } from '../lib/ui/switch.js';
47
+
31
48
  // --- 组件接收的 props 接口,_urlXxx/_staticVer 由框架自动注入 ---
32
49
  interface IProps {
33
50
  'title': string;
@@ -37,24 +54,9 @@ interface IProps {
37
54
  '_urlStc': string;
38
55
  '_urlFull': string;
39
56
  '_staticVer': string;
40
- /** --- 框架注入:import map JSON 字符串,<head> 中以 type="importmap" <script> 渲染 --- */
41
- '_importMapJson'?: string;
42
- /**
43
- * --- 框架注入:水合 JS 代码字符串,</body> 前以 type="module" <script> 渲染 ---
44
- * --- 内容:import hydrateRoot + import App + hydrateRoot(document, createElement(App, p)) ---
45
- */
46
- '_hydrateScript'?: string;
47
- /**
48
- * --- 框架注入:fullProps 的 JSON 序列化(不含 _propsJson 本身)---
49
- * --- 客户端水合脚本读取此值重建 props,suppressHydrationWarning 处理服务端/客户端内容差异 ---
50
- */
51
- '_propsJson'?: string;
52
57
  }
53
58
 
54
- // ─── shadcn/ui 风格基础控件 ───────────────────────────────────────────────────
55
- // 以下组件是 Tailwind + React 的轻量实现,与 shadcn/ui 源码结构完全一致。
56
- // 生产环境可直接替换为 shadcn/ui 官方组件,import map 会通过 esm.sh 自动
57
- // 解析 Radix UI 等所有依赖,无需本地 npm install 也无需打包工具。
59
+ // ─── 页面内部组件(仅本页演示使用,无需提取到 lib/)────────────────────────────
58
60
 
59
61
  /** --- 卡片容器 --- */
60
62
  function Card({ children, className = '' }: { 'children': ReactNode; 'className'?: string }) {
@@ -110,7 +112,7 @@ function Btn({ children, onClick, disabled = false, outline = false }: {
110
112
  function RouterHome() {
111
113
  return (
112
114
  <div>
113
- <p className="text-slate-700 text-sm font-medium mb-3">当前路由:<code className="bg-slate-100 px-1 rounded">/</code></p>
115
+ <p className="text-slate-700 text-sm font-medium mb-3">Current route: <code className="bg-slate-100 px-1 rounded">/</code></p>
114
116
  <div className="flex gap-2 flex-wrap">
115
117
  <Link to="/about" className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-500 hover:bg-blue-600 text-white text-xs font-medium">
116
118
  → /about
@@ -131,14 +133,14 @@ function RouterAbout() {
131
133
  const navigate = useNavigate();
132
134
  return (
133
135
  <div>
134
- <p className="text-slate-700 text-sm font-medium mb-3">当前路由:<code className="bg-slate-100 px-1 rounded">/about</code></p>
135
- <p className="text-slate-500 text-xs mb-3">useNavigate() 演示编程导航(不使用 Link 组件):</p>
136
+ <p className="text-slate-700 text-sm font-medium mb-3">Current route: <code className="bg-slate-100 px-1 rounded">/about</code></p>
137
+ <p className="text-slate-500 text-xs mb-3">useNavigate() programmatic navigation (without Link component):</p>
136
138
  <div className="flex gap-2">
137
139
  <button
138
140
  onClick={() => navigate('/')}
139
141
  className="inline-flex items-center px-3 py-1.5 rounded-lg border border-slate-300 hover:bg-slate-50 text-slate-700 text-xs font-medium cursor-pointer"
140
142
  >
141
- 返回 /
143
+ Back to /
142
144
  </button>
143
145
  <button
144
146
  onClick={() => navigate('/user/99')}
@@ -158,29 +160,202 @@ function RouterUser() {
158
160
  return (
159
161
  <div>
160
162
  <p className="text-slate-700 text-sm font-medium mb-1">
161
- 当前路由:<code className="bg-slate-100 px-1 rounded">/user/:id</code>
163
+ Current route: <code className="bg-slate-100 px-1 rounded">/user/:id</code>
162
164
  </p>
163
165
  <p className="text-slate-500 text-xs mb-3">
164
- useParams() 读取动态段:<code className="bg-slate-100 px-1 rounded">id = &quot;{id}&quot;</code>
166
+ useParams() dynamic segment: <code className="bg-slate-100 px-1 rounded">id = &quot;{id}&quot;</code>
165
167
  </p>
166
168
  <button
167
169
  onClick={() => navigate('/')}
168
170
  className="inline-flex items-center px-3 py-1.5 rounded-lg border border-slate-300 hover:bg-slate-50 text-slate-700 text-xs font-medium cursor-pointer"
169
171
  >
170
- 返回 /
172
+ Back to /
171
173
  </button>
172
174
  </div>
173
175
  );
174
176
  }
175
177
 
178
+ // ─── shadcn/ui 演示区 ────────────────────────────────────────────────────────
179
+
180
+ /**
181
+ * --- 展示 Label/Input/Checkbox/Switch 组合使用 ---
182
+ * 控件定义在 stc/lib/ui/ 目录,本组件只做 import + 组合。
183
+ */
184
+ function ShadcnDemo() {
185
+ // --- 表单受控状态 ---
186
+ const [name, setName] = useState('');
187
+ const [email, setEmail] = useState('');
188
+ const [agree, setAgree] = useState<boolean | 'indeterminate'>(false);
189
+ const [newsletter, setNewsletter] = useState(false);
190
+ const [submitted, setSubmitted] = useState(false);
191
+
192
+ // --- useId 生成无障碍访问用的稳定 id(SSR + 水合均确保一致)---
193
+ const nameId = useId();
194
+ const emailId = useId();
195
+ const agreeId = useId();
196
+ const newsletterId = useId();
197
+
198
+ function handleSubmit(e: React.FormEvent<HTMLFormElement>): void {
199
+ e.preventDefault();
200
+ setSubmitted(true);
201
+ }
202
+
203
+ return (
204
+ <div className="space-y-4">
205
+
206
+ {/* ── 说明 ── */}
207
+ <Card>
208
+ <h2 className="font-semibold text-slate-900 mb-2">shadcn/ui Components</h2>
209
+ <p className="text-slate-500 text-xs leading-relaxed">
210
+ All components below are imported from <code className="bg-slate-100 px-1 rounded">stc/lib/ui/</code>,
211
+ following the same <code className="bg-slate-100 px-1 rounded">components/ui/</code> structure recommended by shadcn/ui.
212
+ Built on Radix UI primitives, styled with Tailwind CSS.
213
+ In dev mode, the framework auto-scans all bare imports and generates the import map automatically.
214
+ </p>
215
+ </Card>
216
+
217
+ {/* ── Label + Input 演示 ── */}
218
+ <Card>
219
+ <h2 className="font-semibold text-slate-900 mb-4">Label + Input</h2>
220
+ {/* Label 使用 @radix-ui/react-label,htmlFor 关联 Input 的 id,点击 Label 可聚焦 Input */}
221
+ <div className="grid gap-4">
222
+ <div className="grid gap-1.5">
223
+ <Label htmlFor={nameId}>Username</Label>
224
+ <Input
225
+ id={nameId}
226
+ type="text"
227
+ placeholder="Enter your username"
228
+ value={name}
229
+ onChange={e => setName(e.target.value)}
230
+ />
231
+ </div>
232
+ <div className="grid gap-1.5">
233
+ <Label htmlFor={emailId}>Email</Label>
234
+ <Input
235
+ id={emailId}
236
+ type="email"
237
+ placeholder="you@example.com"
238
+ value={email}
239
+ onChange={e => setEmail(e.target.value)}
240
+ />
241
+ </div>
242
+ </div>
243
+ <p className="text-xs text-slate-400 mt-3">Click the Label text to focus the corresponding input (Radix UI a11y semantics)</p>
244
+ </Card>
245
+
246
+ {/* ── Checkbox 演示 ── */}
247
+ <Card>
248
+ <h2 className="font-semibold text-slate-900 mb-4">Checkbox</h2>
249
+ {/* Checkbox 使用 @radix-ui/react-checkbox,支持 checked / indeterminate 三态 */}
250
+ <div className="flex items-center gap-2">
251
+ <Checkbox
252
+ id={agreeId}
253
+ checked={agree}
254
+ onCheckedChange={setAgree}
255
+ />
256
+ <Label htmlFor={agreeId}>I have read and agree to the Terms of Service</Label>
257
+ </div>
258
+ <p className="text-xs text-slate-400 mt-3">
259
+ Value: <code className="bg-slate-100 px-1 rounded">{String(agree)}</code>
260
+ &nbsp;(supports <code className="bg-slate-100 px-1 rounded">true | false | &apos;indeterminate&apos;</code> tri-state)
261
+ </p>
262
+ </Card>
263
+
264
+ {/* ── Switch 演示 ── */}
265
+ <Card>
266
+ <h2 className="font-semibold text-slate-900 mb-4">Switch</h2>
267
+ {/* Switch 使用 @radix-ui/react-switch,onCheckedChange 接收 boolean */}
268
+ <div className="flex items-center gap-2">
269
+ <Switch
270
+ id={newsletterId}
271
+ checked={newsletter}
272
+ onCheckedChange={setNewsletter}
273
+ />
274
+ <Label htmlFor={newsletterId}>Subscribe to product updates</Label>
275
+ </div>
276
+ <p className="text-xs text-slate-400 mt-3">
277
+ Value: <code className="bg-slate-100 px-1 rounded">{newsletter ? 'true' : 'false'}</code>
278
+ </p>
279
+ </Card>
280
+
281
+ {/* ── 综合表单提交 ── */}
282
+ <Card>
283
+ <h2 className="font-semibold text-slate-900 mb-4">Combined Form (Submit Demo)</h2>
284
+ {submitted ? (
285
+ <div className="space-y-2 text-sm">
286
+ <p className="text-green-700 font-medium">✓ Submitted! Received data:</p>
287
+ <pre className="bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-700 overflow-auto">{JSON.stringify(
288
+ { name, email, agree: Boolean(agree), newsletter },
289
+ null,
290
+ 2
291
+ )}</pre>
292
+ <button
293
+ onClick={() => setSubmitted(false)}
294
+ className="text-xs text-blue-500 hover:underline cursor-pointer"
295
+ >Reset</button>
296
+ </div>
297
+ ) : (
298
+ <form onSubmit={handleSubmit} className="space-y-4">
299
+ <div className="grid gap-1.5">
300
+ <Label htmlFor={`${nameId}-form`}>Username <span className="text-red-500">*</span></Label>
301
+ <Input
302
+ id={`${nameId}-form`}
303
+ required
304
+ placeholder="At least 2 characters"
305
+ value={name}
306
+ onChange={e => setName(e.target.value)}
307
+ />
308
+ </div>
309
+ <div className="grid gap-1.5">
310
+ <Label htmlFor={`${emailId}-form`}>Email <span className="text-red-500">*</span></Label>
311
+ <Input
312
+ id={`${emailId}-form`}
313
+ type="email"
314
+ required
315
+ placeholder="you@example.com"
316
+ value={email}
317
+ onChange={e => setEmail(e.target.value)}
318
+ />
319
+ </div>
320
+ <div className="flex items-center gap-2">
321
+ <Checkbox
322
+ id={`${agreeId}-form`}
323
+ checked={agree}
324
+ onCheckedChange={setAgree}
325
+ required
326
+ />
327
+ <Label htmlFor={`${agreeId}-form`}>Agree to terms</Label>
328
+ </div>
329
+ <div className="flex items-center gap-2">
330
+ <Switch
331
+ id={`${newsletterId}-form`}
332
+ checked={newsletter}
333
+ onCheckedChange={setNewsletter}
334
+ />
335
+ <Label htmlFor={`${newsletterId}-form`}>Subscribe</Label>
336
+ </div>
337
+ <button
338
+ type="submit"
339
+ className="inline-flex items-center px-4 py-2 rounded-lg bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium cursor-pointer"
340
+ >
341
+ Submit
342
+ </button>
343
+ </form>
344
+ )}
345
+ </Card>
346
+
347
+ </div>
348
+ );
349
+ }
350
+
176
351
  // ─── 页面主组件 ────────────────────────────────────────────────────────────────
177
352
 
178
353
  /** --- Kebab React 全页面演示 --- */
179
- export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc, _importMapJson, _hydrateScript, _propsJson }: IProps) {
354
+ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc, _staticVer }: IProps) {
180
355
 
181
356
  // --- useState 在 SSR 阶段使用初始值渲染,客户端水合后变为可交互状态 ---
182
357
  const [count, setCount] = useState(0);
183
- const [tab, setTab] = useState<'overview' | 'routing' | 'fetch'>('overview');
358
+ const [tab, setTab] = useState<'overview' | 'routing' | 'fetch' | 'shadcn'>('overview');
184
359
  const [fetchResult, setFetchResult] = useState<string | null>(null);
185
360
  const [isFetching, setIsFetching] = useState(false);
186
361
  /** --- 仅客户端为 true,用于展示水合已完成 --- */
@@ -214,28 +389,12 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
214
389
  // --- 组件渲染完整 HTML 文档,无需外部 EJS 模板 ---
215
390
  <html lang="en">
216
391
  <head>
217
- {/*
218
- meta/title 放在 <head> 最前,与 renderToString 实际输出顺序保持一致,
219
- 避免客户端水合时虚拟 DOM 与浏览器 DOM 的顺序不匹配(#418)。
220
- */}
221
392
  <meta charSet="UTF-8" />
222
393
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
223
394
  <title>{title}</title>
224
- {/*
225
- import map _loadReactPage 通过 props 传入,使 bare import 在浏览器端通过
226
- esm.sh 自动解析。必须在所有 <script type="module"> 之前出现。
227
- 服务端:_importMapJson 有值 → 正常渲染。
228
- 客户端:同一值读自 __kebab_props__ → 渲染相同 → 水合匹配。
229
- */}
230
- {_importMapJson && (
231
- <script type="importmap" dangerouslySetInnerHTML={{ '__html': _importMapJson }} />
232
- )}
233
- {/*
234
- Tailwind CSS CDN:开发和演示可直接使用。
235
- 生产环境建议用 `npx tailwindcss build` 输出 purged CSS,
236
- 然后改为:<link rel="stylesheet" href="/stc/css/app.css" />
237
- */}
238
- <script src="https://cdn.tailwindcss.com"></script>
395
+ {/* import map 由框架自动注入在此标签前,无需手动添加 */}
396
+ {/* dev: Tailwind Play CDN;prod: 将此行替换为 kebab build 编译的 CSS 文件 */}
397
+ <link href={`${_urlStc}view/react.page.css?v=${_staticVer}`} rel="stylesheet" />
239
398
  </head>
240
399
  <body className="bg-slate-50 min-h-screen font-sans">
241
400
  <div className="max-w-3xl mx-auto px-4 py-10 space-y-6">
@@ -255,7 +414,7 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
255
414
 
256
415
  {/* ── Tab 导航(客户端状态路由,无页面刷新) ── */}
257
416
  <div className="flex gap-1 bg-slate-100 p-1 rounded-lg w-fit">
258
- {(['overview', 'routing', 'fetch'] as const).map(t => (
417
+ {(['overview', 'routing', 'fetch', 'shadcn'] as const).map(t => (
259
418
  <button
260
419
  key={t}
261
420
  onClick={() => setTab(t)}
@@ -277,8 +436,8 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
277
436
  <Card>
278
437
  <h2 className="font-semibold text-slate-900 mb-1">Counter(useState + hydration)</h2>
279
438
  <p className="text-slate-500 text-xs mb-4">
280
- SSR 渲染初始值 0props 序列化为内联 JSON
281
- 水合完成后 state 变为可交互,点击按钮测试:
439
+ SSR renders with initial value 0; props are serialized as inline JSON.
440
+ After hydration the state becomes interactive — click the buttons to test:
282
441
  </p>
283
442
  <div className="flex items-center gap-4">
284
443
  <button
@@ -295,12 +454,12 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
295
454
 
296
455
  {/* Server Props 展示 */}
297
456
  <Card>
298
- <h2 className="font-semibold text-slate-900 mb-3">Server Props(来自 Ctr 方法)</h2>
457
+ <h2 className="font-semibold text-slate-900 mb-3">Server Props (from Ctr method)</h2>
299
458
  <p className="text-slate-500 text-xs mb-3">
300
- 通过{' '}
301
- <code className="bg-slate-100 px-1 rounded">_loadReactPage(path, props)</code>{' '}
302
- 传入,框架自动注入 _urlBase 等常量,
303
- 整体序列化为内联 JSON,客户端水合时直接复用,无需二次请求。
459
+ Passed via{' '}
460
+ <code className="bg-slate-100 px-1 rounded">_loadReactPage(path, props)</code>.
461
+ The framework auto-injects constants like _urlBase and serializes
462
+ all props as inline JSON, reused directly during client hydration — no extra request needed.
304
463
  </p>
305
464
  <div className="space-y-2">
306
465
  {([
@@ -317,20 +476,24 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
317
476
  </div>
318
477
  </Card>
319
478
 
320
- {/* shadcn/ui 兼容说明 */}
479
+ {/* 项目结构说明 */}
321
480
  <Card>
322
- <h2 className="font-semibold text-slate-900 mb-2">shadcn/ui 兼容性</h2>
323
- <p className="text-slate-500 text-xs mb-3">
324
- shadcn/ui = Tailwind + Radix UI。本页的 Card/Badge/Btn 是等效的轻量实现。
325
- 使用官方 shadcn/ui 组件时,其 Radix UI 依赖由 esm.sh 递归解析,
326
- 与打包工具的处理结果完全等价,无需本地 npm install。
481
+ <h2 className="font-semibold text-slate-900 mb-2">Recommended Directory Structure</h2>
482
+ <pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs leading-relaxed text-slate-700">{`stc/
483
+ lib/
484
+ utils.ts # cn() utility
485
+ ui/
486
+ label.tsx # shadcn Label
487
+ input.tsx # shadcn Input
488
+ checkbox.tsx # shadcn Checkbox
489
+ switch.tsx # shadcn Switch
490
+ view/
491
+ react-page.tsx # Page component (import + compose)`}
492
+ </pre>
493
+ <p className="text-slate-500 text-xs mt-3">
494
+ Mirrors the shadcn/ui <code className="bg-slate-100 px-1 rounded">components/ui/</code> convention.
495
+ In dev mode, the framework auto-scans imports — no manual import map configuration needed.
327
496
  </p>
328
- <div className="flex flex-wrap gap-2">
329
- <Badge variant="success">Card ✓</Badge>
330
- <Badge variant="success">Badge ✓</Badge>
331
- <Badge variant="success">Btn ✓</Badge>
332
- <Badge>Radix UI Dialog 同样支持</Badge>
333
- </div>
334
497
  </Card>
335
498
 
336
499
  </div>
@@ -345,11 +508,11 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
345
508
  */
346
509
  <MemoryRouter>
347
510
  <Card>
348
- <h2 className="font-semibold text-slate-900 mb-1">React Router 演示</h2>
511
+ <h2 className="font-semibold text-slate-900 mb-1">React Router Demo</h2>
349
512
  <p className="text-slate-500 text-xs mb-4">
350
- 使用 <code className="bg-slate-100 px-1 rounded">MemoryRouter</code>
351
- {' '}演示路由跳转、动态参数(useParams)和编程导航(useNavigate),
352
- 服务端 SSR 与客户端水合均可运行,无需额外服务端配置。
513
+ Uses <code className="bg-slate-100 px-1 rounded">MemoryRouter</code>
514
+ {' '}to demonstrate route navigation, dynamic params (useParams), and programmatic navigation (useNavigate).
515
+ Works on both server SSR and client hydration with no extra server configuration.
353
516
  </p>
354
517
  {/* ── 路由定义 ── */}
355
518
  <Routes>
@@ -361,39 +524,44 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
361
524
 
362
525
  {/* 整页 BrowserRouter 配置说明 */}
363
526
  <Card className="mt-4">
364
- <h2 className="font-semibold text-slate-900 mb-2">整页 BrowserRouter 配置</h2>
527
+ <h2 className="font-semibold text-slate-900 mb-2">Full-Page BrowserRouter Setup</h2>
365
528
  <p className="text-slate-500 text-xs mb-3">
366
- 若要让 React Router 接管整个页面的真实 URL(如 /app、/app/about),
367
- MemoryRouter 替换为 BrowserRouter,并在服务端将所有子路径路由到同一 Ctr 方法:
529
+ To let React Router manage the real URL (e.g. /app, /app/about),
530
+ replace MemoryRouter with BrowserRouter and route all sub-paths to the same Ctr method on the server:
368
531
  </p>
369
- <pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs overflow-auto leading-relaxed text-slate-700">{`// 1. route.json:将所有子路径指向同一 Ctr 方法
532
+ <pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs overflow-auto leading-relaxed text-slate-700">{`// 1. route.json: 将所有子路径路由到同一 Ctr 方法
370
533
  {
371
534
  "app": "ctr/app@reactPage",
372
535
  "app\\/.*": "ctr/app@reactPage"
373
536
  }
374
537
 
375
- // 2. 客户端(浏览器):BrowserRouter 自动读取当前 URL
376
- import { BrowserRouter } from 'react-router-dom';
377
- hydrateRoot(document,
378
- createElement(BrowserRouter, { basename: '/app' },
379
- createElement(App, props)));
380
-
381
- // 3. 服务端 SSR:StaticRouter 使用请求 URL,避免水合差异
382
- import { StaticRouter } from 'react-router-dom/server';
383
- renderToString(
384
- createElement(StaticRouter, { location: reqPath },
385
- createElement(App, props)));`}
538
+ // 2. Ctr 方法:传入 router: 'browser',框架自动处理 SSR(StaticRouter)和客户端(BrowserRouter)
539
+ await this._loadReactPage('view/my', { ...props }, {
540
+ router: 'browser',
541
+ routerBase: 'app', // basename,相对于 urlBase
542
+ });
543
+
544
+ // 3. 组件:直接使用 Routes/Route/Link,不需要包裹任何 Router
545
+ <Routes>
546
+ <Route path="/" element={<Home />} />
547
+ <Route path="/user/:id" element={<User />} />
548
+ </Routes>`}
386
549
  </pre>
387
550
  </Card>
388
551
  </MemoryRouter>
389
552
  )}
390
553
 
554
+ {/* ══ shadcn/ui Tab ══ */}
555
+ {tab === 'shadcn' && (
556
+ <ShadcnDemo />
557
+ )}
558
+
391
559
  {/* ══ Fetch Tab ══ */}
392
560
  {tab === 'fetch' && (
393
561
  <Card>
394
562
  <h2 className="font-semibold text-slate-900 mb-3">Client Fetch Demo</h2>
395
563
  <p className="text-slate-500 text-xs mb-4">
396
- 水合完成后,点击按钮发起 GET 请求。setState 触发局部重渲染,无页面刷新。
564
+ After hydration, click a button to send a GET request. setState triggers a partial re-render — no page refresh.
397
565
  </p>
398
566
  <div className="flex gap-3 flex-wrap">
399
567
  <Btn
@@ -421,27 +589,7 @@ renderToString(
421
589
  )}
422
590
 
423
591
  </div>
424
- {/*
425
- __kebab_props__:存储 fullProps JSON 供水合脚本读取。放在 <body> 末尾以确保
426
- JSX 树结构与 renderToString 输出、浏览器 DOM 三者一致,避免水合错误 #418。
427
- 服务端:_propsJson 有值(fullProps 的完整 JSON)。
428
- 客户端:_propsJson 为 undefined(本身不在 JSON 中),渲染空串。
429
- suppressHydrationWarning:跳过此元素的文本内容比对。
430
- */}
431
- <script
432
- id="__kebab_props__"
433
- type="application/json"
434
- suppressHydrationWarning
435
- dangerouslySetInnerHTML={{ '__html': _propsJson ?? '' }}
436
- />
437
- {/*
438
- 水合脚本:读取 __kebab_props__ → hydrateRoot(document, createElement(App, p))。
439
- 服务端:_hydrateScript 有值 → 渲染此 script 元素。
440
- 客户端:同一值读自 __kebab_props__ → 渲染相同 → 水合匹配。
441
- */}
442
- {_hydrateScript && (
443
- <script type="module" dangerouslySetInnerHTML={{ '__html': _hydrateScript }} />
444
- )}
592
+ {/* props JSON 与水合脚本由框架自动注入在此标签前,无需手动添加 */}
445
593
  </body>
446
594
  </html>
447
595
  );
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes