@openrails/contentkit-ui 0.0.0-stage → 0.68.1

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 (367) hide show
  1. package/README.md +470 -2
  2. package/dist/appearance.d.ts +38 -0
  3. package/dist/appearance.js +15 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
  6. package/dist/client/aspect.d.ts +6 -0
  7. package/dist/client/aspect.js +17 -0
  8. package/dist/client/aspect.js.map +1 -0
  9. package/dist/client/client.d.ts +53 -0
  10. package/dist/client/client.js +36 -0
  11. package/dist/client/client.js.map +1 -0
  12. package/dist/client/codes.d.ts +12 -0
  13. package/dist/client/codes.js +19 -0
  14. package/dist/client/codes.js.map +1 -0
  15. package/dist/client/content/bans.d.ts +15 -0
  16. package/dist/client/content/bans.js +40 -0
  17. package/dist/client/content/bans.js.map +1 -0
  18. package/dist/client/content/comments.d.ts +36 -0
  19. package/dist/client/content/comments.js +95 -0
  20. package/dist/client/content/comments.js.map +1 -0
  21. package/dist/client/content/favorites.d.ts +14 -0
  22. package/dist/client/content/favorites.js +42 -0
  23. package/dist/client/content/favorites.js.map +1 -0
  24. package/dist/client/content/moderation.d.ts +18 -0
  25. package/dist/client/content/moderation.js +37 -0
  26. package/dist/client/content/moderation.js.map +1 -0
  27. package/dist/client/content/polls.d.ts +47 -0
  28. package/dist/client/content/polls.js +151 -0
  29. package/dist/client/content/polls.js.map +1 -0
  30. package/dist/client/content/posts.d.ts +44 -0
  31. package/dist/client/content/posts.js +92 -0
  32. package/dist/client/content/posts.js.map +1 -0
  33. package/dist/client/content/reactions.d.ts +12 -0
  34. package/dist/client/content/reactions.js +36 -0
  35. package/dist/client/content/reactions.js.map +1 -0
  36. package/dist/client/content/types.d.ts +91 -0
  37. package/dist/client/content/types.js +8 -0
  38. package/dist/client/content/types.js.map +1 -0
  39. package/dist/client/crop.d.ts +32 -0
  40. package/dist/client/crop.js +103 -0
  41. package/dist/client/crop.js.map +1 -0
  42. package/dist/client/errors.d.ts +54 -0
  43. package/dist/client/errors.js +80 -0
  44. package/dist/client/errors.js.map +1 -0
  45. package/dist/client/gallery.d.ts +43 -0
  46. package/dist/client/gallery.js +86 -0
  47. package/dist/client/gallery.js.map +1 -0
  48. package/dist/client/generated/error-codes.d.ts +5 -0
  49. package/dist/client/generated/error-codes.js +131 -0
  50. package/dist/client/generated/error-codes.js.map +1 -0
  51. package/dist/client/generated/routes.d.ts +1086 -0
  52. package/dist/client/generated/routes.js +1400 -0
  53. package/dist/client/generated/routes.js.map +1 -0
  54. package/dist/client/generated/wire.d.ts +630 -0
  55. package/dist/client/generated/wire.js +6 -0
  56. package/dist/client/generated/wire.js.map +1 -0
  57. package/dist/client/http.d.ts +83 -0
  58. package/dist/client/http.js +82 -0
  59. package/dist/client/http.js.map +1 -0
  60. package/dist/client/image.d.ts +24 -0
  61. package/dist/client/image.js +65 -0
  62. package/dist/client/image.js.map +1 -0
  63. package/dist/client/index.d.ts +33 -0
  64. package/dist/client/media/api.d.ts +36 -0
  65. package/dist/client/media/api.js +61 -0
  66. package/dist/client/media/api.js.map +1 -0
  67. package/dist/client/media/client.d.ts +247 -0
  68. package/dist/client/media/client.js +407 -0
  69. package/dist/client/media/client.js.map +1 -0
  70. package/dist/client/media/encode.d.ts +3 -0
  71. package/dist/client/media/encode.js +8 -0
  72. package/dist/client/media/encode.js.map +1 -0
  73. package/dist/client/media/hash-stream.d.ts +6 -0
  74. package/dist/client/media/hash-stream.js +16 -0
  75. package/dist/client/media/hash-stream.js.map +1 -0
  76. package/dist/client/media/hash.d.ts +12 -0
  77. package/dist/client/media/hash.js +48 -0
  78. package/dist/client/media/hash.js.map +1 -0
  79. package/dist/client/media/hash.worker-inline.js +18 -0
  80. package/dist/client/media/hash.worker-inline.js.map +1 -0
  81. package/dist/client/media/pacer.d.ts +21 -0
  82. package/dist/client/media/pacer.js +25 -0
  83. package/dist/client/media/pacer.js.map +1 -0
  84. package/dist/client/media/queue.d.ts +112 -0
  85. package/dist/client/media/queue.js +274 -0
  86. package/dist/client/media/queue.js.map +1 -0
  87. package/dist/client/media/ref.d.ts +6 -0
  88. package/dist/client/media/ref.js +10 -0
  89. package/dist/client/media/ref.js.map +1 -0
  90. package/dist/client/media/rules.d.ts +54 -0
  91. package/dist/client/media/rules.js +143 -0
  92. package/dist/client/media/rules.js.map +1 -0
  93. package/dist/client/media/transport.d.ts +13 -0
  94. package/dist/client/media/transport.js +33 -0
  95. package/dist/client/media/transport.js.map +1 -0
  96. package/dist/client/media/windows.d.ts +15 -0
  97. package/dist/client/media/windows.js +39 -0
  98. package/dist/client/media/windows.js.map +1 -0
  99. package/dist/client/playback.d.ts +200 -0
  100. package/dist/client/playback.js +142 -0
  101. package/dist/client/playback.js.map +1 -0
  102. package/dist/client/player.d.ts +102 -0
  103. package/dist/client/player.js +155 -0
  104. package/dist/client/player.js.map +1 -0
  105. package/dist/client/public.d.ts +25 -0
  106. package/dist/client/public.js +18 -0
  107. package/dist/client/public.js.map +1 -0
  108. package/dist/client/rendition.d.ts +19 -0
  109. package/dist/client/rendition.js +16 -0
  110. package/dist/client/rendition.js.map +1 -0
  111. package/dist/client/route.d.ts +39 -0
  112. package/dist/client/route.js +31 -0
  113. package/dist/client/route.js.map +1 -0
  114. package/dist/client/taxonomy.d.ts +30 -0
  115. package/dist/client/taxonomy.js +77 -0
  116. package/dist/client/taxonomy.js.map +1 -0
  117. package/dist/client.js +33 -0
  118. package/dist/components/encode-progress.d.ts +15 -0
  119. package/dist/components/encode-progress.js +49 -0
  120. package/dist/components/encode-progress.js.map +1 -0
  121. package/dist/components/hover-preview.d.ts +29 -0
  122. package/dist/components/hover-preview.js +49 -0
  123. package/dist/components/hover-preview.js.map +1 -0
  124. package/dist/components/image-crop-dialog.d.ts +48 -0
  125. package/dist/components/image-crop-dialog.js +359 -0
  126. package/dist/components/image-crop-dialog.js.map +1 -0
  127. package/dist/components/lazy-mount.d.ts +10 -0
  128. package/dist/components/lazy-mount.js +22 -0
  129. package/dist/components/lazy-mount.js.map +1 -0
  130. package/dist/components/media-folder-editor.d.ts +59 -0
  131. package/dist/components/media-folder-editor.js +611 -0
  132. package/dist/components/media-folder-editor.js.map +1 -0
  133. package/dist/components/media-gallery.d.ts +54 -0
  134. package/dist/components/media-gallery.js +597 -0
  135. package/dist/components/media-gallery.js.map +1 -0
  136. package/dist/components/media-readiness-notice.d.ts +24 -0
  137. package/dist/components/media-readiness-notice.js +61 -0
  138. package/dist/components/media-readiness-notice.js.map +1 -0
  139. package/dist/components/player-controls.d.ts +71 -0
  140. package/dist/components/player-controls.js +264 -0
  141. package/dist/components/player-controls.js.map +1 -0
  142. package/dist/components/progress.d.ts +5 -0
  143. package/dist/components/progress.js +14 -0
  144. package/dist/components/progress.js.map +1 -0
  145. package/dist/components/rendition-img.d.ts +34 -0
  146. package/dist/components/rendition-img.js +78 -0
  147. package/dist/components/rendition-img.js.map +1 -0
  148. package/dist/components/slot-editor.d.ts +95 -0
  149. package/dist/components/slot-editor.js +182 -0
  150. package/dist/components/slot-editor.js.map +1 -0
  151. package/dist/components/slot-image.d.ts +23 -0
  152. package/dist/components/slot-image.js +48 -0
  153. package/dist/components/slot-image.js.map +1 -0
  154. package/dist/components/slot-upload.d.ts +41 -0
  155. package/dist/components/slot-upload.js +215 -0
  156. package/dist/components/slot-upload.js.map +1 -0
  157. package/dist/components/social/bans.d.ts +42 -0
  158. package/dist/components/social/bans.js +300 -0
  159. package/dist/components/social/bans.js.map +1 -0
  160. package/dist/components/social/comments.d.ts +42 -0
  161. package/dist/components/social/comments.js +517 -0
  162. package/dist/components/social/comments.js.map +1 -0
  163. package/dist/components/social/engagement.d.ts +45 -0
  164. package/dist/components/social/engagement.js +129 -0
  165. package/dist/components/social/engagement.js.map +1 -0
  166. package/dist/components/social/index.d.ts +6 -0
  167. package/dist/components/social/moderation.d.ts +32 -0
  168. package/dist/components/social/moderation.js +383 -0
  169. package/dist/components/social/moderation.js.map +1 -0
  170. package/dist/components/social/parts.d.ts +50 -0
  171. package/dist/components/social/parts.js +106 -0
  172. package/dist/components/social/parts.js.map +1 -0
  173. package/dist/components/social/poll-editor.d.ts +34 -0
  174. package/dist/components/social/poll-editor.js +567 -0
  175. package/dist/components/social/poll-editor.js.map +1 -0
  176. package/dist/components/social/poll.d.ts +30 -0
  177. package/dist/components/social/poll.js +252 -0
  178. package/dist/components/social/poll.js.map +1 -0
  179. package/dist/components/sortable-list.d.ts +29 -0
  180. package/dist/components/sortable-list.js +121 -0
  181. package/dist/components/sortable-list.js.map +1 -0
  182. package/dist/components/video-mini-player.d.ts +35 -0
  183. package/dist/components/video-mini-player.js +236 -0
  184. package/dist/components/video-mini-player.js.map +1 -0
  185. package/dist/components/video-player.d.ts +146 -0
  186. package/dist/components/video-player.js +630 -0
  187. package/dist/components/video-player.js.map +1 -0
  188. package/dist/components/video-poster-picker.d.ts +59 -0
  189. package/dist/components/video-poster-picker.js +335 -0
  190. package/dist/components/video-poster-picker.js.map +1 -0
  191. package/dist/components/video-poster.d.ts +24 -0
  192. package/dist/components/video-poster.js +46 -0
  193. package/dist/components/video-poster.js.map +1 -0
  194. package/dist/i18n/context.d.ts +4 -0
  195. package/dist/i18n/context.js +11 -0
  196. package/dist/i18n/context.js.map +1 -0
  197. package/dist/i18n/messages.d.ts +40 -0
  198. package/dist/i18n/messages.js +94 -0
  199. package/dist/i18n/messages.js.map +1 -0
  200. package/dist/index.d.ts +23 -0
  201. package/dist/index.js +41 -0
  202. package/dist/locales/de.d.ts +2 -0
  203. package/dist/locales/de.js +271 -0
  204. package/dist/locales/de.js.map +1 -0
  205. package/dist/locales/en.d.ts +457 -0
  206. package/dist/locales/en.js +271 -0
  207. package/dist/locales/en.js.map +1 -0
  208. package/dist/locales/es.d.ts +2 -0
  209. package/dist/locales/es.js +271 -0
  210. package/dist/locales/es.js.map +1 -0
  211. package/dist/locales/ja.d.ts +2 -0
  212. package/dist/locales/ja.js +271 -0
  213. package/dist/locales/ja.js.map +1 -0
  214. package/dist/locales/ko.d.ts +2 -0
  215. package/dist/locales/ko.js +271 -0
  216. package/dist/locales/ko.js.map +1 -0
  217. package/dist/locales/social/de.d.ts +2 -0
  218. package/dist/locales/social/de.js +198 -0
  219. package/dist/locales/social/de.js.map +1 -0
  220. package/dist/locales/social/en.d.ts +195 -0
  221. package/dist/locales/social/en.js +198 -0
  222. package/dist/locales/social/en.js.map +1 -0
  223. package/dist/locales/social/es.d.ts +2 -0
  224. package/dist/locales/social/es.js +198 -0
  225. package/dist/locales/social/es.js.map +1 -0
  226. package/dist/locales/social/ja.d.ts +2 -0
  227. package/dist/locales/social/ja.js +198 -0
  228. package/dist/locales/social/ja.js.map +1 -0
  229. package/dist/locales/social/ko.d.ts +2 -0
  230. package/dist/locales/social/ko.js +198 -0
  231. package/dist/locales/social/ko.js.map +1 -0
  232. package/dist/locales/social/zh.d.ts +2 -0
  233. package/dist/locales/social/zh.js +198 -0
  234. package/dist/locales/social/zh.js.map +1 -0
  235. package/dist/locales/zh.d.ts +2 -0
  236. package/dist/locales/zh.js +271 -0
  237. package/dist/locales/zh.js.map +1 -0
  238. package/dist/provider.d.ts +22 -0
  239. package/dist/provider.js +59 -0
  240. package/dist/provider.js.map +1 -0
  241. package/dist/react/canonical.d.ts +33 -0
  242. package/dist/react/canonical.js +78 -0
  243. package/dist/react/canonical.js.map +1 -0
  244. package/dist/react/comments.d.ts +82 -0
  245. package/dist/react/comments.js +155 -0
  246. package/dist/react/comments.js.map +1 -0
  247. package/dist/react/config.d.ts +13 -0
  248. package/dist/react/config.js +14 -0
  249. package/dist/react/config.js.map +1 -0
  250. package/dist/react/context.d.ts +55 -0
  251. package/dist/react/context.js +42 -0
  252. package/dist/react/context.js.map +1 -0
  253. package/dist/react/crop.d.ts +49 -0
  254. package/dist/react/crop.js +107 -0
  255. package/dist/react/crop.js.map +1 -0
  256. package/dist/react/engagement.d.ts +38 -0
  257. package/dist/react/engagement.js +88 -0
  258. package/dist/react/engagement.js.map +1 -0
  259. package/dist/react/folder.d.ts +103 -0
  260. package/dist/react/folder.js +240 -0
  261. package/dist/react/folder.js.map +1 -0
  262. package/dist/react/gallery.d.ts +140 -0
  263. package/dist/react/gallery.js +477 -0
  264. package/dist/react/gallery.js.map +1 -0
  265. package/dist/react/index.d.ts +21 -0
  266. package/dist/react/inline-preview.d.ts +24 -0
  267. package/dist/react/inline-preview.js +76 -0
  268. package/dist/react/inline-preview.js.map +1 -0
  269. package/dist/react/player.d.ts +70 -0
  270. package/dist/react/player.js +214 -0
  271. package/dist/react/player.js.map +1 -0
  272. package/dist/react/polls.d.ts +74 -0
  273. package/dist/react/polls.js +176 -0
  274. package/dist/react/polls.js.map +1 -0
  275. package/dist/react/posts.d.ts +59 -0
  276. package/dist/react/posts.js +149 -0
  277. package/dist/react/posts.js.map +1 -0
  278. package/dist/react/provider.d.ts +27 -0
  279. package/dist/react/provider.js +49 -0
  280. package/dist/react/provider.js.map +1 -0
  281. package/dist/react/public.d.ts +39 -0
  282. package/dist/react/public.js +70 -0
  283. package/dist/react/public.js.map +1 -0
  284. package/dist/react/read.d.ts +52 -0
  285. package/dist/react/read.js +144 -0
  286. package/dist/react/read.js.map +1 -0
  287. package/dist/react/resources.d.ts +73 -0
  288. package/dist/react/resources.js +247 -0
  289. package/dist/react/resources.js.map +1 -0
  290. package/dist/react/slot.d.ts +104 -0
  291. package/dist/react/slot.js +185 -0
  292. package/dist/react/slot.js.map +1 -0
  293. package/dist/react/store.d.ts +51 -0
  294. package/dist/react/store.js +165 -0
  295. package/dist/react/store.js.map +1 -0
  296. package/dist/react/upload.d.ts +86 -0
  297. package/dist/react/upload.js +155 -0
  298. package/dist/react/upload.js.map +1 -0
  299. package/dist/react/use-resource.d.ts +44 -0
  300. package/dist/react/use-resource.js +74 -0
  301. package/dist/react/use-resource.js.map +1 -0
  302. package/dist/react/video.d.ts +118 -0
  303. package/dist/react/video.js +184 -0
  304. package/dist/react/video.js.map +1 -0
  305. package/dist/react/viewport.d.ts +19 -0
  306. package/dist/react/viewport.js +30 -0
  307. package/dist/react/viewport.js.map +1 -0
  308. package/dist/react.js +21 -0
  309. package/dist/scope.d.ts +14 -0
  310. package/dist/scope.js +30 -0
  311. package/dist/scope.js.map +1 -0
  312. package/dist/styles.css +3 -0
  313. package/dist/ui/alert-dialog.d.ts +18 -0
  314. package/dist/ui/alert-dialog.js +86 -0
  315. package/dist/ui/alert-dialog.js.map +1 -0
  316. package/dist/ui/alert.d.ts +10 -0
  317. package/dist/ui/alert.js +38 -0
  318. package/dist/ui/alert.js.map +1 -0
  319. package/dist/ui/avatar.d.ts +11 -0
  320. package/dist/ui/avatar.js +31 -0
  321. package/dist/ui/avatar.js.map +1 -0
  322. package/dist/ui/badge.d.ts +7 -0
  323. package/dist/ui/badge.js +31 -0
  324. package/dist/ui/badge.js.map +1 -0
  325. package/dist/ui/button.d.ts +8 -0
  326. package/dist/ui/button.js +46 -0
  327. package/dist/ui/button.js.map +1 -0
  328. package/dist/ui/dialog.d.ts +17 -0
  329. package/dist/ui/dialog.js +91 -0
  330. package/dist/ui/dialog.js.map +1 -0
  331. package/dist/ui/dropdown-menu.d.ts +15 -0
  332. package/dist/ui/dropdown-menu.js +105 -0
  333. package/dist/ui/dropdown-menu.js.map +1 -0
  334. package/dist/ui/input.d.ts +3 -0
  335. package/dist/ui/input.js +17 -0
  336. package/dist/ui/input.js.map +1 -0
  337. package/dist/ui/label.d.ts +3 -0
  338. package/dist/ui/label.js +15 -0
  339. package/dist/ui/label.js.map +1 -0
  340. package/dist/ui/progress.d.ts +7 -0
  341. package/dist/ui/progress.js +31 -0
  342. package/dist/ui/progress.js.map +1 -0
  343. package/dist/ui/select.d.ts +15 -0
  344. package/dist/ui/select.js +98 -0
  345. package/dist/ui/select.js.map +1 -0
  346. package/dist/ui/skeleton.d.ts +2 -0
  347. package/dist/ui/skeleton.js +14 -0
  348. package/dist/ui/skeleton.js.map +1 -0
  349. package/dist/ui/slider.d.ts +3 -0
  350. package/dist/ui/slider.js +35 -0
  351. package/dist/ui/slider.js.map +1 -0
  352. package/dist/ui/switch.d.ts +5 -0
  353. package/dist/ui/switch.js +20 -0
  354. package/dist/ui/switch.js.map +1 -0
  355. package/dist/ui/tabs.d.ts +10 -0
  356. package/dist/ui/tabs.js +47 -0
  357. package/dist/ui/tabs.js.map +1 -0
  358. package/dist/ui/textarea.d.ts +3 -0
  359. package/dist/ui/textarea.js +15 -0
  360. package/dist/ui/textarea.js.map +1 -0
  361. package/dist/ui/toggle-group.d.ts +5 -0
  362. package/dist/ui/toggle-group.js +23 -0
  363. package/dist/ui/toggle-group.js.map +1 -0
  364. package/dist/urls/index.d.ts +100 -0
  365. package/dist/urls.js +118 -0
  366. package/dist/urls.js.map +1 -0
  367. package/package.json +120 -3
package/README.md CHANGED
@@ -1,3 +1,471 @@
1
- # Temporary Holding Version
1
+ # @openrails/contentkit-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ContentKit in the browser: a framework-free client, content URLs, React hooks
4
+ and styled components (uploads with cropping, video posters, galleries, HLS
5
+ playback, comments, reactions, favorites, polls and their staff screens).
6
+ `@openrails/contentkit-ui@X.Y.Z` speaks ContentKit `vX.Y.Z`.
7
+
8
+ ```sh
9
+ pnpm add @openrails/contentkit-ui@X.Y.Z
10
+ ```
11
+
12
+ | Import | Contents | Needs |
13
+ | --- | --- | --- |
14
+ | `@openrails/contentkit-ui` | styled components, `ContentKitUiProvider`, i18n | `react`, `react-dom` |
15
+ | `@openrails/contentkit-ui/react` | `ContentKitProvider` and every hook | `react` |
16
+ | `@openrails/contentkit-ui/client` | `createContentKitClient`, `ContentKitError`, generated wire types, crop/aspect/rendition/ABR/gallery math | nothing |
17
+ | `@openrails/contentkit-ui/urls` | content codes, `[/{lang}]/{route}/{CODE}[/{slug}]` paths, `canonical` | nothing |
18
+ | `@openrails/contentkit-ui/locales/{de,es,ja,ko,zh}` | message bundles (English is built in) | |
19
+ | `@openrails/contentkit-ui/styles.css` | the stylesheet, scoped under `.ckui` (the root entry also installs it) | |
20
+
21
+ `react` and `react-dom` are optional peers, so `/client` and `/urls` work
22
+ without React. hls.js loads on first play.
23
+
24
+ ## Client and providers
25
+
26
+ ```tsx
27
+ import { createContentKitClient } from "@openrails/contentkit-ui/client";
28
+ import { ContentKitProvider } from "@openrails/contentkit-ui/react";
29
+ import { ContentKitUiProvider } from "@openrails/contentkit-ui";
30
+ import { createContentURLs } from "@openrails/contentkit-ui/urls";
31
+
32
+ const contentkit = createContentKitClient({
33
+ baseUrl: "/api/v1/contentkit", // where the host mounts ContentKit
34
+ mounts: { upload: "/api/v1/user-uploads" }, // optional: modules mounted elsewhere (a string, or (ref) => string)
35
+ fetch: auth.authFetch, // an authenticating transport
36
+ token: () => auth.getAccessToken(), // HLS playlists on the API origin (hls.js uses XHR)
37
+ language: () => lang, // Accept-Language
38
+ });
39
+ const urls = createContentURLs({ routes: { video: "watch", gallery: "g" }, languages: ["en", "es"], origin: "https://example.com" });
40
+
41
+ <ContentKitProvider
42
+ client={contentkit}
43
+ viewer={user?.id ?? null} // the signed-in user as ContentKit sees it
44
+ language={lang} // same value as the client's language callback
45
+ accessRevision={accessRevision} // increment when permissions/entitlements change
46
+ onSignIn={() => openSignIn()} // asks signed-out visitors to sign in where the server refuses them
47
+ urls={urls}
48
+ navigate={(to, o) => navigate(to, o)}
49
+ onChange={(change) => queryClient.invalidateQueries({ queryKey: ["media", change.ref.id] })}
50
+ onError={(error, { operation }) => report(operation, error)}
51
+ >
52
+ <ContentKitUiProvider appearance={{ theme: "inherit" }} language={lang} density={[2, 3]}>
53
+ <App />
54
+ </ContentKitUiProvider>
55
+ </ContentKitProvider>;
56
+ ```
57
+
58
+ Keep `viewer`, `language` and `accessRevision` current on the provider. A change
59
+ starts fresh media and social reads without remounting the children. Old requests
60
+ are abandoned when their last reader leaves; their results cannot fill the new
61
+ scope. `accessRevision` is optional when the host has no changing access state.
62
+ It is a cache signal, not an authorization decision. Without a provider, replace
63
+ the client when its session or request language changes. Host-supplied `read` and
64
+ `initial` data must also belong to the current viewer and language.
65
+
66
+ Writes retain the cache scope they started in. After a scope change, a late
67
+ notification refetches affected reads instead of copying its response into them;
68
+ `onChange` still runs. Use a separate client for each independent session or
69
+ request-language context, not one client across differently scoped providers.
70
+ The framework-free client exposes `setReadScope(key)` and passes that key as
71
+ the second `subscribe` / `onChange` argument, or `null` when the scope changed during the
72
+ operation. This does not change the operation's result or its authentication.
73
+
74
+ - **Mounts.** Without `mounts`, modules live under `baseUrl`: content at the
75
+ root, `/media` (reads, HLS), `/media/upload`, `/codes`, `/taxonomy`.
76
+ - **`client.media`**: `upload`, `put` (upload, commit, wait), `commit`, `read`,
77
+ `waitFor`, `editorView`, `getFrame`, `hlsBase(ref, dir)`, `discard`, and
78
+ `xhrSetup` for players (`<MediaGallery xhrSetup={contentkit.media.xhrSetup} />`).
79
+ Uploads hash in a Web Worker, send one checksum-bound PUT up to 64 MiB and
80
+ resumable multipart above (`onState`/`resume`). The bucket must allow CORS
81
+ `PUT` from the app's origin with `Content-Type` and `x-amz-checksum-sha256`.
82
+ - **Content modules**, one per resource, each method one route of the
83
+ generated route table with its generated types: `posts`, `comments`,
84
+ `reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`),
85
+ `moderation`, `taxonomy`, `codes`. Lists take `limit`/`offset` (the review
86
+ queue a `cursor`); staff lists are `posts.adminList` (`q` searches,
87
+ `deleted: true` lists deleted posts for `posts.restore`), `polls.adminList`,
88
+ `comments.adminList`.
89
+ - **Images of posts and polls** go to the item's server-named upload path:
90
+ `media.uploadNamed(ref, file)` resolves the name content routes take;
91
+ `media.uploadInline(postId, file)` resolves a body image's public URL;
92
+ `posts.uploadCover`, `polls.uploadImage` and `polls.uploadOptionImage` do
93
+ both steps. `folders` renames the post and poll media kinds. A post's
94
+ public image files exist only while it is published; `usePost` shows an
95
+ unpublished post's images to its editors (below).
96
+ - **`client.config()`** says what the content module allows: which
97
+ interactions signed-out visitors may make (`content.Options.Anonymous`,
98
+ none by default) and the longest comment (`CommentMaxLength`, 2,200 by
99
+ default; longer is `comment_too_long` with `details.max`).
100
+ - **`client.subscribe(listener)`** receives every successful mutation
101
+ (`media.committed`, `media.processed`, `comment.created`, `reaction.changed`,
102
+ `poll.updated`, `ban.saved`, …); `ContentKitProvider onChange` is the same
103
+ stream, for the host's cache.
104
+ - **`ContentKitProvider`** holds the client, URL config, `navigate`,
105
+ `onChange`, `onError` and the read store hooks share: one request per item
106
+ and options while any hook shows it; `set()` and processed uploads update
107
+ every reader in place.
108
+ - **`ContentKitUiProvider`**: `appearance` (`theme`
109
+ `light`/`dark`/`auto`/`inherit`, `variables` as `--ckui-*`), `language`
110
+ (loads its built-in bundle), `messages` (bundles over it), `t` (host
111
+ translate hook), `density` (default `[2, 3]`), `inlinePreview` (default true).
112
+
113
+ ## Hooks
114
+
115
+ Hooks read the client from `ContentKitProvider`; a `client` option overrides it.
116
+
117
+ ```tsx
118
+ const q = useUploadQueue({ ref, path: "originals/{name}" });
119
+ q.add(input.files!); q.move(id, 0); await q.commit();
120
+
121
+ const one = useUpload();
122
+ await one.upload(file, { ref, path: "inline/x.png", put: {} });
123
+
124
+ const img = useSlotImage({ ref, path: "avatar", image });
125
+ const crop = useSlotCrop({ ref, path: "avatar", file: img.file, aspect: "1:1", onSaved: img.set });
126
+ ```
127
+
128
+ Content hooks share one store per client: one request per item while any
129
+ hook shows it, kept per `viewer`, with every change made through the client
130
+ applied in place. Reactions, favorites and votes apply at once and roll back
131
+ if the server refuses them.
132
+
133
+ ```tsx
134
+ const thread = useComments(ref, { sort: "best" });
135
+ await thread.post("Nice!", { replyTo: parentId });
136
+ await thread.react(comment, 1);
137
+ const { counts, toggle } = useReaction(ref);
138
+ const { favorited, count, toggle: fav } = useFavorite(ref);
139
+ const { poll, vote } = usePoll(null, { language }); // the newest live poll
140
+ const editor = usePollEditor(pollId); // staff
141
+ ```
142
+
143
+ A post editor puts `editorBody` in its rich-text field and inserts what
144
+ `uploadImage(file)` resolves: URLs that show the images now (the file for a
145
+ fresh upload, signed editor views of an unpublished post's images, read and
146
+ refreshed as needed). `update({ body })` stores the images' public URLs
147
+ (`storedBody(html)` does the same for a host's own save); `imageSrc(url)` shows
148
+ the cover.
149
+
150
+ ```tsx
151
+ const editor = usePost(postId);
152
+ <RichText value={editor.editorBody} onImage={editor.uploadImage} onSave={(html) => editor.update({ body: html })} />
153
+ <img src={editor.imageSrc(editor.post?.cover_url)} alt="" />
154
+ ```
155
+
156
+ Also `useContentConfig` (`client.config()`, read once), `useCommentReplies`,
157
+ `useCanComment` (may the caller comment, may signed-out visitors, the ban
158
+ that stops it, and what it may do to others' comments), `useLatestComments`,
159
+ `usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`,
160
+ `useModerationQueue`, `useCommentBans`.
161
+
162
+ Also: `useCrop`, `useVideoImages`, `useFrameStrip`, `useVideoFrame`,
163
+ `useVideoPoster`, `useEncodeProgress`, `useHlsPlayer`, `useCarousel`,
164
+ `useGalleryView`, `useInlinePreview`, `useRefreshBeforeExpiry`, `usePresets`,
165
+ `useContentKitClient`, `useContentURLs`, `useErrorReporter`.
166
+
167
+ ### useMediaRead
168
+
169
+ ```tsx
170
+ const { read, loading, error, processing, reload, refresh, set } =
171
+ useMediaRead(ref, { prefix: "low-res/", editor: true, window: { start: 0, end: 120 } });
172
+ ```
173
+
174
+ One read per item and options, shared by every hook showing it. It reads
175
+ again shortly before the URLs expire, waits out `rate_limited`, polls while
176
+ uploads process (editor reads, every 2.5 s; `poll`), and reloads after any
177
+ commit through the same client. `window` splits a long item into reads of
178
+ `chunk` files (default 50) merged into one result; windows once read stay
179
+ loaded. `refresh()` joins a read in flight (a player's grant refresh);
180
+ `reload()` restarts it. `read` shows a host's read until `reload()`.
181
+
182
+ ### useMediaFolder
183
+
184
+ ```tsx
185
+ const folder = useMediaFolder(ref, { paths: ["images/{name}", "videos/{name}"], commit: "manual" });
186
+ folder.add(files); // screened and queued; refusals in folder.refused
187
+ await folder.commit(); // the uploaded files, in queue order
188
+ await folder.move(path, 0); await folder.rename(path, "cover");
189
+ await folder.edit(path, edit); await folder.replace(path, file); await folder.remove([path]);
190
+ folder.discard(); // stop uploads and empty the queue
191
+ ```
192
+
193
+ An item's folder for its editors: the editor read (all windows), an upload
194
+ queue, and the kind's upload rules from the editor read, which screen type,
195
+ size and file caps before anything uploads and name files around names
196
+ taken. `groups` lists each path's uploads; `commit: "auto"` commits each
197
+ upload once those before it have (a draft). Files added before the editor
198
+ read arrives wait in `waiting` and are screened when its rules do (or
199
+ refused if the read fails or states no rules). `ref` may be null until the item exists,
200
+ so a composer can create its draft on the first drop. `busy` is true while
201
+ work is in flight (files waiting, uploads queued or running, a commit or
202
+ update); `fileCount` counts the uploads and the files on their way in. A
203
+ committed file leaves the queue once the read lists it. Failures go to the provider's
204
+ `onError` with an operation (`upload`, `folder.commit`, `folder.update`,
205
+ `folder.process` for uploads the worker fails after the editor opened) and
206
+ the file's name.
207
+
208
+ ### useEditorCrop
209
+
210
+ ```tsx
211
+ const crop = useEditorCrop(ref, cropping); // a path, or null
212
+ // crop.status: idle | loading | cropping (source, edit) | saving | done | error
213
+ await crop.save(edit);
214
+ ```
215
+
216
+ Re-crops a kept upload: while the path is set, its editor view (the
217
+ original, oriented, unedited) and current edit load; `save` commits an edit
218
+ op. Nothing is uploaded.
219
+
220
+ ### usePublicImage
221
+
222
+ ```tsx
223
+ const { image, rule, isDefault } = usePublicImage("user", userId, "avatar");
224
+ <SlotImage image={image} fallback={<Initials name={name} />} />
225
+ ```
226
+
227
+ An item's image for a public preset: the exact renditions from a read's
228
+ `public`, or from a host listing passed as `image` (no read), else the
229
+ kind's default image. Published files carry a generation, so a URL built
230
+ from a preset template only ever names the default; `rule` (from
231
+ `GET /media/presets`) gives the shape, widths and narrowest edit.
232
+
233
+ ### useCanonicalContent
234
+
235
+ ```tsx
236
+ useCanonicalContent(video.link, { title: video.title, image: posterURL, location: pathname + search, defaultLanguage: "en" });
237
+ ```
238
+
239
+ Once a content page's link arrives, replaces the address with its canonical
240
+ path (code spelling, merged code, current slug) through the provider's
241
+ `navigate`, else `history.replaceState`, and keeps `<link rel=canonical>`,
242
+ `og:url`, `og:title`, `og:image` and hreflang alternates (each language's
243
+ own slug, plus `x-default`) in the head while mounted. Unmounting removes
244
+ them, server-rendered ones included, so the next page sets its own. Needs the provider's
245
+ `urls`; `/urls` also exports `hreflang()` and `canonicalURL()` (drops
246
+ tracking parameters) for server rendering.
247
+
248
+ ### useNearViewport
249
+
250
+ ```tsx
251
+ const [ref, near] = useNearViewport({ margin: "800px 0px", leaveMargin: "2400px 0px" });
252
+ ```
253
+
254
+ Whether an element is near the viewport, to mount media as it approaches;
255
+ with `leaveMargin`, false again far away.
256
+
257
+ ## Components
258
+
259
+ `AvatarUpload`, `CoverUpload` (complete form fields); `SlotEditor` with
260
+ `SlotEditMenu`, `SlotEditError` and `useSlotEditor()` (the same pick, crop,
261
+ save and re-crop flow inside a host layout); `SlotImage`; `ImageCropDialog`;
262
+ `VideoPosterPicker`, `VideoPoster`; `EncodeProgress`; `RenditionImg`;
263
+ `MediaGallery`, `VideoPlayer` and `VideoMiniPlayer` (HLS with ABR, inline
264
+ muted previews, a grant refresh before `expires`, and a reason, Retry and
265
+ support code for every failure; see below). A `PublicPreset` (`{ preset, aspect, renditions }`)
266
+ is what a slot or poster shows; a read's `public` lists `PublicImage`s. A
267
+ slot crops at its image's aspect, else its public preset's
268
+ (`GET /media/presets`), so an item without an image yet still crops a 3:1
269
+ cover at 3:1.
270
+
271
+ ### MediaFolderEditor
272
+
273
+ ```tsx
274
+ <MediaFolderEditor item={post} paths={["images/{name}", "videos/{name}"]} commit="auto" ref={handle}
275
+ toolbar={<ZipImport />} rowActions={(f) => <SetCover file={f} />} footer={note} />
276
+ handle.current.discard();
277
+
278
+ // Or the host owns the folder and renders with its state:
279
+ const folder = useMediaFolder(draft, { paths, commit: "auto" }); // draft: null until created
280
+ <MediaFolderEditor folder={folder} />
281
+ <Button disabled={folder.busy || (!title && !text && !folder.fileCount)}>Publish</Button>
282
+ ```
283
+
284
+ `useMediaFolder` with its UI: a drop zone stating the rules, a queue with
285
+ progress, retry and remove, and each path's uploads as a sortable list with
286
+ thumbnail, name, edited badge, encode progress and failure; crop (with
287
+ `aspects` when the path has no preset shape), rename, replace and bulk
288
+ remove. `commit="manual"` adds the queue with "Add N"; `"auto"` commits as
289
+ files finish. `confirmRemove` replaces the browser's confirm.
290
+
291
+ ### SortableList
292
+
293
+ ```tsx
294
+ <SortableList items={rows} id={(r) => r.id} name={(r) => r.title} onMove={(from, to) => move(from, to)} layout="grid">
295
+ {(row, handle) => <>{handle}<Row row={row} /></>}
296
+ </SortableList>
297
+ ```
298
+
299
+ Reorders by a drag handle with a mouse, a finger or the keyboard (Space or
300
+ Enter lifts, arrows move, Space or Enter drops, Escape cancels), with
301
+ localized screen reader announcements. `onMove` fires once, on drop.
302
+
303
+ ### ImageCropDialog shapes
304
+
305
+ `aspects={["", "1:1", "4:5", "16:9"]}` adds a shape chooser (`""` is the
306
+ original's, turning with the image); a preview of the result and its size
307
+ sit under the controls (`preview={false}` hides them), and Reset returns to
308
+ the starting shape and edit.
309
+
310
+ ### MediaGallery item
311
+
312
+ `<MediaGallery item={post} prefix="low-res/" renderLocked={unlock} />` reads
313
+ through the client: the read (refreshed, reloaded after commits), HLS bases,
314
+ playlist auth and the grant refresh. Passing `read`, `hlsBase`, `xhrSetup`
315
+ and `refresh` yourself still works. A locked item's public teaser carries
316
+ the locked count and `renderLocked`, so the unlock is on the first slide;
317
+ the stage is never taller than `maxHeight` (default `80svh`).
318
+
319
+ ### VideoPosterPicker frames from another video
320
+
321
+ `frames={{ item: otherVideo, path: "source" }}` browses another item's video;
322
+ the chosen frame (cropped or whole) is uploaded to the poster path as an
323
+ image. Which video to offer is the host's choice.
324
+
325
+ ### HoverPreview
326
+
327
+ ```tsx
328
+ <div className="card relative"><Thumbnail /><HoverPreview item={video} duration={video.duration} /></div>
329
+ ```
330
+
331
+ A card's muted inline preview over the client's HLS base, fading in on hover
332
+ (or while it is the most visible card on touch screens), one page-wide, off
333
+ with reduced motion or Save-Data.
334
+
335
+ ### Images: fallback, skeleton, fit and LazyMount
336
+
337
+ `RenditionImg`, `SlotImage` and `VideoPoster` take `fallback` (shown without
338
+ renditions or after a failed load, e.g. a 404), and `RenditionImg` and
339
+ `SlotImage` take `skeleton` (a pulsing fill until loaded) and `fit`
340
+ (`cover` or `contain`). `<LazyMount placeholder={box}>` mounts its children
341
+ as it nears the viewport (`useNearViewport` options).
342
+
343
+ ### MediaReadinessNotice
344
+
345
+ `<MediaReadinessNotice item={post} description="Only editors see it until then." />`
346
+ renders nothing once every upload is processed; while some process, a
347
+ notice with the first video's encode progress; when uploads failed, which
348
+ ones.
349
+
350
+ ## Video player
351
+
352
+ `VideoPlayer` plays a ladder's HLS folder (`client.media.hlsBase(ref, dir)`)
353
+ with its own controls: seek bar with sprite preview, volume, subtitles,
354
+ audio, quality (Auto or a fixed rung), speed, picture in picture, theater,
355
+ fullscreen and a mini player.
356
+
357
+ ```tsx
358
+ <VideoPlayer
359
+ base={contentkit.media.hlsBase(ref, "hls/")}
360
+ xhrSetup={contentkit.media.xhrSetup}
361
+ refresh={reload} expires={read.expires} // re-grant before the token expires
362
+ width={video.w} height={video.h} duration={video.dur} poster={posterPreset}
363
+ autoPlay startAt={resumePoint} // resume; never a muted autoplay
364
+ keyboard="global" // a watch page; default "focus"
365
+ audioLanguage={lang} subtitleLanguage={null} // until the viewer chooses
366
+ onProgress={(p) => saveResume(p.time, p.played)} // interval, pause, seek, end, page hide, unload
367
+ onEvent={(e) => analytics(e)} // play, pause, seek, quality, audio, subtitles, fullscreen, error…
368
+ theater={theater} onTheaterChange={setTheater}
369
+ onMiniPlayer={setHandoff}
370
+ renderDownloads={({ className, container }) => <Downloads className={className} container={container} />}
371
+ menuItems={[{ label: "Version", value, options, onChange }, { label: "Report a problem", onSelect }]}
372
+ />
373
+ {handoff && <VideoMiniPlayer handoff={handoff} xhrSetup={contentkit.media.xhrSetup} onExpand={openWatchPage} onClose={() => setHandoff(null)} />}
374
+ ```
375
+
376
+ - **Shortcuts:** Space/K play, ←/→ 5 s, J/L 10 s, ↑/↓ volume, M mute, C
377
+ subtitles, F fullscreen, T theater, I mini player, 0–9 a tenth of the way,
378
+ Home/End, `<`/`>` speed. Never while typing or in a menu.
379
+ - **Touch:** a tap shows or hides the controls; a double tap on either side
380
+ seeks 10 s, and further taps keep seeking. A double click is fullscreen.
381
+ - **Remembered per browser:** volume and mute (`ckui.player.volume`), the
382
+ audio and subtitle languages (`ckui.player.tracks`) and a fixed quality
383
+ (`ckui.player.quality`); each `*Key` option renames or disables one.
384
+ - **Mini player:** `onMiniPlayer` gets a `PlayerHandoff` (position, playing,
385
+ languages, size, poster). `VideoMiniPlayer` docks it in a corner and calls
386
+ `onExpand({ time, playing })` or `onClose({ time })`; where it lives and
387
+ what expand opens are the host's.
388
+ - **Theming:** `appearance.variables.playerAccent` colors the played range
389
+ and pressed toggles (default white). Menus are always dark.
390
+
391
+ ## Comments, reactions and polls
392
+
393
+ `Comments` (threads with one-level replies, tombstones, held and rejected
394
+ states shown to their author, reactions, edit, delete, ban and rate-limit
395
+ notices, the server's longest comment), `ReactionButtons`, `FavoriteButton` (with the server's count)
396
+ and `Poll` (a final vote with results scaled to the leading option, or a
397
+ free-text answer and its groups; `results="always"` shows results before
398
+ voting). Signed-out visitors comment under a name, react and vote where the
399
+ server allows it (`content.Options.Anonymous`, read from the comment standing
400
+ and `client.config()`); elsewhere they are asked to sign in (`onSignIn`, the
401
+ provider's or their own) or, without one, told to.
402
+
403
+ Staff: `CommentModeration` (every comment, the review queue, the site's
404
+ bans), `CommentBans` with `CommentBanDialog`, and `PollEditor`. They show what
405
+ the caller's permissions allow and say so when a route refuses; the host
406
+ decides who sees them.
407
+
408
+ ```tsx
409
+ <Comments item={{ kind: "video", id }} count={video.comment_count} userHref={(u) => `/u/${u.username}`} />
410
+ <Poll language={lang} />
411
+ <CommentModeration contentKinds={["video", "post"]} itemHref={(i) => `/${i.kind}/${i.id}`} />
412
+ ```
413
+
414
+ ## Errors
415
+
416
+ Every call rejects with `ContentKitError`: `code` (the server's, or `network`,
417
+ `storage`, `aborted`, `resume_mismatch`, `decode`, `render_timeout`),
418
+ `status`, `retryAfter`, `details` (image and video limits), `blobs`
419
+ (`not_uploaded`), `ban` (`comment_banned`) and `action` (`rate_limited`),
420
+ plus `refusal`, `isLimit`, `isCeiling`, `blocksQueue` and `transient`. Every
421
+ code has a message in every bundle (`useMessages().error(e)`).
422
+
423
+ ## Migrating from the two old packages
424
+
425
+ | Before | Now |
426
+ | --- | --- |
427
+ | `@openrails/contentkit-upload` | `@openrails/contentkit-ui/client` |
428
+ | `@openrails/contentkit-upload/react` | `@openrails/contentkit-ui/react` |
429
+ | `@openrails/contentkit-upload/ui` | `@openrails/contentkit-ui` |
430
+ | `@openrails/contentkit-upload/locales/*`, `/styles.css` | `@openrails/contentkit-ui/locales/*`, `/styles.css` |
431
+ | `@openrails/contentkit-urls` | `@openrails/contentkit-ui/urls` |
432
+ | `createUploadClient({ endpoint, readEndpoint })` | `createContentKitClient({ baseUrl, mounts: { upload, media } }).media` |
433
+ | `UploadClient`, `UploadApi` | `MediaClient`, `MediaApi` |
434
+ | `UploadUiProvider client onError …` | `ContentKitProvider client onError` + `ContentKitUiProvider appearance messages t density inlinePreview` |
435
+ | `UploadError`, `UploadErrorCode` | `ContentKitError`, `ContentKitErrorCode` |
436
+ | `useUploadQueue(client, o)`, `useUpload(client)` | `useUploadQueue(o)`, `useUpload()` |
437
+ | `useRead(client, ref, o)` | `useMediaRead(ref, o)` |
438
+ | `useSlotImage(client, o)`, `useSlotCrop(client, o)` | `useSlotImage(o)`, `useSlotCrop(o)` |
439
+ | `useVideoImages`, `useFrameStrip`, `useVideoFrame`, `useVideoPoster` `(client, o)` | the same `(o)` |
440
+ | `PublicImage` (preset at an item) | `PublicPreset` |
441
+ | `SlotImage`, `VideoPoster` `placeholder` | `fallback` |
442
+ | `UploadUi*` types, `UploadUiRoot` | `ContentKitUi*`, `ContentKitUiRoot` |
443
+
444
+ ## Development
445
+
446
+ ```sh
447
+ pnpm check # typecheck, lint, unit tests (pure math and client-free components), build
448
+ pnpm build
449
+ pnpm test:e2e # the integration project and the browser specs against a real ContentKit
450
+ pnpm test:integration # or either alone
451
+ pnpm test:browser # (demo/ in Chromium, light/dark × desktop/mobile screenshots)
452
+ pnpm demo # the stack with seeded demo pages, until Ctrl-C
453
+ ```
454
+
455
+ The suites need Docker, Go and ffmpeg. `e2e/server` composes the real
456
+ `contentkit.Runtime` (content, taxonomy, codes, media upload and read) with
457
+ AuthKit for identity over PGroonga PostgreSQL 18, MinIO, and media-worker and
458
+ media-gateway built from this checkout (`e2e/compose.yaml`). Each run brings
459
+ the stack up under its own project name and takes it down; the server exits
460
+ with its runner. Pages sign in with auth-ui and pass `auth.authFetch`. Tests
461
+ set up through `/__test`: accounts (`staff`, `moderator`, `editor` roles),
462
+ items the host's resolver knows, stored-object checks, and faults (no CORS,
463
+ 429 and expired tokens at the gateway, dropped or held bucket requests, API
464
+ error replies).
465
+
466
+ The wire types, route table and error codes in `src/client/generated/` are
467
+ generated from ContentKit's route catalog (`pnpm contract`; CI runs
468
+ `pnpm contract:check`). UI primitives come from
469
+ `pnpm dlx shadcn@4.21.0 add …` (`components.json`); local edits are marked
470
+ `// Local:`. The `release` workflow publishes the package to npm for every
471
+ published `v*` release.
@@ -0,0 +1,38 @@
1
+ import { CSSProperties } from 'react';
2
+ /**
3
+ * `light`/`dark` force a palette, `auto` follows the OS, `inherit` reads the
4
+ * host's shadcn tokens (`--primary`, …) and its `.dark` class.
5
+ */
6
+ export type ContentKitUiTheme = "light" | "dark" | "auto" | "inherit";
7
+ /** Raw CSS values applied as `--ckui-*` custom properties on every root. */
8
+ export interface ContentKitUiVariables {
9
+ background?: string;
10
+ foreground?: string;
11
+ card?: string;
12
+ cardForeground?: string;
13
+ popover?: string;
14
+ popoverForeground?: string;
15
+ primary?: string;
16
+ primaryForeground?: string;
17
+ secondary?: string;
18
+ secondaryForeground?: string;
19
+ muted?: string;
20
+ mutedForeground?: string;
21
+ accent?: string;
22
+ accentForeground?: string;
23
+ destructive?: string;
24
+ warning?: string;
25
+ border?: string;
26
+ input?: string;
27
+ ring?: string;
28
+ radius?: string;
29
+ fontFamily?: string;
30
+ /** The video player's played range, seek thumb and pressed toggles. Default white. */
31
+ playerAccent?: string;
32
+ }
33
+ export interface ContentKitUiAppearance {
34
+ theme?: ContentKitUiTheme;
35
+ variables?: ContentKitUiVariables;
36
+ }
37
+ export declare function appearanceStyle(a: ContentKitUiAppearance | undefined): CSSProperties | undefined;
38
+ export declare function appearanceTheme(a: ContentKitUiAppearance | undefined): ContentKitUiTheme;
@@ -0,0 +1,15 @@
1
+ //#region src/appearance.ts
2
+ var e = (e) => e.replace(/[A-Z]/g, (e) => "-" + e.toLowerCase());
3
+ function t(t) {
4
+ if (!t?.variables) return;
5
+ let n = {};
6
+ for (let [r, i] of Object.entries(t.variables)) i && (n[r === "fontFamily" ? "--ckui-font" : `--ckui-${e(r)}`] = i);
7
+ return n;
8
+ }
9
+ function n(e) {
10
+ return e?.theme ?? "auto";
11
+ }
12
+ //#endregion
13
+ export { t as appearanceStyle, n as appearanceTheme };
14
+
15
+ //# sourceMappingURL=appearance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance.js","names":[],"sources":["../src/appearance.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\n\n/**\n * `light`/`dark` force a palette, `auto` follows the OS, `inherit` reads the\n * host's shadcn tokens (`--primary`, …) and its `.dark` class.\n */\nexport type ContentKitUiTheme = \"light\" | \"dark\" | \"auto\" | \"inherit\";\n\n/** Raw CSS values applied as `--ckui-*` custom properties on every root. */\nexport interface ContentKitUiVariables {\n background?: string;\n foreground?: string;\n card?: string;\n cardForeground?: string;\n popover?: string;\n popoverForeground?: string;\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n muted?: string;\n mutedForeground?: string;\n accent?: string;\n accentForeground?: string;\n destructive?: string;\n warning?: string;\n border?: string;\n input?: string;\n ring?: string;\n radius?: string;\n fontFamily?: string;\n /** The video player's played range, seek thumb and pressed toggles. Default white. */\n playerAccent?: string;\n}\n\nexport interface ContentKitUiAppearance {\n theme?: ContentKitUiTheme;\n variables?: ContentKitUiVariables;\n}\n\nconst kebab = (k: string) => k.replace(/[A-Z]/g, (c) => \"-\" + c.toLowerCase());\n\nexport function appearanceStyle(a: ContentKitUiAppearance | undefined): CSSProperties | undefined {\n if (!a?.variables) return undefined;\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(a.variables)) {\n if (value) style[key === \"fontFamily\" ? \"--ckui-font\" : `--ckui-${kebab(key)}`] = value;\n }\n return style as CSSProperties;\n}\n\nexport function appearanceTheme(a: ContentKitUiAppearance | undefined): ContentKitUiTheme {\n return a?.theme ?? \"auto\";\n}\n"],"mappings":";AAwCA,IAAM,KAAS,MAAc,EAAE,QAAQ,WAAW,MAAM,MAAM,EAAE,YAAY,CAAC;AAE7E,SAAgB,EAAgB,GAAkE;CAChG,IAAI,CAAC,GAAG,WAAW;CACnB,IAAM,IAAgC,CAAC;CACvC,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,EAAE,SAAS,GACnD,AAAI,MAAO,EAAM,MAAQ,eAAe,gBAAgB,UAAU,EAAM,CAAG,OAAO;CAEpF,OAAO;AACT;AAEA,SAAgB,EAAgB,GAA0D;CACxF,OAAO,GAAG,SAAS;AACrB"}