opacacms 0.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 (399) hide show
  1. package/bun.lock +34 -0
  2. package/dist/admin/api-client.d.ts +8 -0
  3. package/dist/admin/auth-client.d.ts +940 -0
  4. package/dist/admin/custom-field.d.ts +71 -0
  5. package/dist/admin/index.d.ts +11 -0
  6. package/dist/admin/react.d.ts +3 -0
  7. package/dist/admin/router.d.ts +7 -0
  8. package/dist/admin/stores/admin-queries.d.ts +32 -0
  9. package/dist/admin/stores/auth.d.ts +33 -0
  10. package/dist/admin/stores/column-visibility.d.ts +21 -0
  11. package/dist/admin/stores/config.d.ts +7 -0
  12. package/dist/admin/stores/media.d.ts +44 -0
  13. package/dist/admin/stores/query.d.ts +4 -0
  14. package/dist/admin/stores/ui.d.ts +11 -0
  15. package/dist/admin/ui/admin-client.d.ts +7 -0
  16. package/dist/admin/ui/admin-layout.d.ts +14 -0
  17. package/dist/admin/ui/components/ColumnVisibilityToggle.d.ts +10 -0
  18. package/dist/admin/ui/components/DataDetailSheet.d.ts +13 -0
  19. package/dist/admin/ui/components/DataDetailView.d.ts +9 -0
  20. package/dist/admin/ui/components/Table.d.ts +10 -0
  21. package/dist/admin/ui/components/fields/ArrayField.d.ts +13 -0
  22. package/dist/admin/ui/components/fields/BlocksField.d.ts +17 -0
  23. package/dist/admin/ui/components/fields/BooleanField.d.ts +13 -0
  24. package/dist/admin/ui/components/fields/CollapsibleField.d.ts +16 -0
  25. package/dist/admin/ui/components/fields/DateField.d.ts +13 -0
  26. package/dist/admin/ui/components/fields/FileField.d.ts +23 -0
  27. package/dist/admin/ui/components/fields/GroupField.d.ts +13 -0
  28. package/dist/admin/ui/components/fields/JoinField.d.ts +15 -0
  29. package/dist/admin/ui/components/fields/NumberField.d.ts +14 -0
  30. package/dist/admin/ui/components/fields/RadioField.d.ts +17 -0
  31. package/dist/admin/ui/components/fields/RelationshipField.d.ts +16 -0
  32. package/dist/admin/ui/components/fields/RowField.d.ts +12 -0
  33. package/dist/admin/ui/components/fields/SelectField.d.ts +18 -0
  34. package/dist/admin/ui/components/fields/TabsField.d.ts +15 -0
  35. package/dist/admin/ui/components/fields/TextAreaField.d.ts +14 -0
  36. package/dist/admin/ui/components/fields/TextField.d.ts +14 -0
  37. package/dist/admin/ui/components/fields/VirtualField.d.ts +8 -0
  38. package/dist/admin/ui/components/fields/index.d.ts +28 -0
  39. package/dist/admin/ui/components/fields/richtext-editor/index.d.ts +10 -0
  40. package/dist/admin/ui/components/fields/richtext-editor/nodes/ImageComponent.d.ts +7 -0
  41. package/dist/admin/ui/components/fields/richtext-editor/nodes/ImageNode.d.ts +27 -0
  42. package/dist/admin/ui/components/fields/richtext-editor/plugins/ComponentPickerPlugin.d.ts +1 -0
  43. package/dist/admin/ui/components/fields/richtext-editor/plugins/EditableSyncPlugin.d.ts +5 -0
  44. package/dist/admin/ui/components/fields/richtext-editor/plugins/NotionToolbarPlugin.d.ts +1 -0
  45. package/dist/admin/ui/components/fields/richtext-editor/plugins/SimpleToolbarPlugin.d.ts +1 -0
  46. package/dist/admin/ui/components/fields/richtext-editor/plugins/ValueSyncPlugin.d.ts +5 -0
  47. package/dist/admin/ui/components/fields/utils.d.ts +1 -0
  48. package/dist/admin/ui/components/link.d.ts +8 -0
  49. package/dist/admin/ui/components/media/AssetManagerModal.d.ts +17 -0
  50. package/dist/admin/ui/components/toast.d.ts +10 -0
  51. package/dist/admin/ui/components/ui/accordion.d.ts +11 -0
  52. package/dist/admin/ui/components/ui/alert-dialog.d.ts +12 -0
  53. package/dist/admin/ui/components/ui/blocks.d.ts +5 -0
  54. package/dist/admin/ui/components/ui/breadcrumbs.d.ts +7 -0
  55. package/dist/admin/ui/components/ui/button.d.ts +7 -0
  56. package/dist/admin/ui/components/ui/collapsible.d.ts +16 -0
  57. package/dist/admin/ui/components/ui/dialog.d.ts +27 -0
  58. package/dist/admin/ui/components/ui/group.d.ts +6 -0
  59. package/dist/admin/ui/components/ui/index.d.ts +17 -0
  60. package/dist/admin/ui/components/ui/input.d.ts +5 -0
  61. package/dist/admin/ui/components/ui/join.d.ts +7 -0
  62. package/dist/admin/ui/components/ui/label.d.ts +3 -0
  63. package/dist/admin/ui/components/ui/radio-group.d.ts +13 -0
  64. package/dist/admin/ui/components/ui/relationship-detail-sheet.d.ts +9 -0
  65. package/dist/admin/ui/components/ui/relationship.d.ts +8 -0
  66. package/dist/admin/ui/components/ui/scroll-area.d.ts +7 -0
  67. package/dist/admin/ui/components/ui/select.d.ts +37 -0
  68. package/dist/admin/ui/components/ui/separator.d.ts +8 -0
  69. package/dist/admin/ui/components/ui/sheet.d.ts +28 -0
  70. package/dist/admin/ui/components/ui/tabs.d.ts +17 -0
  71. package/dist/admin/ui/components/ui/utils.d.ts +1 -0
  72. package/dist/admin/ui/hooks/use-debounce.d.ts +1 -0
  73. package/dist/admin/ui/views/collection-list-view.d.ts +5 -0
  74. package/dist/admin/ui/views/dashboard-view.d.ts +10 -0
  75. package/dist/admin/ui/views/document-edit-view.d.ts +7 -0
  76. package/dist/admin/ui/views/global-edit-view.d.ts +19 -0
  77. package/dist/admin/ui/views/init-view.d.ts +4 -0
  78. package/dist/admin/ui/views/login-view.d.ts +4 -0
  79. package/dist/admin/ui/views/media-registry-view.d.ts +7 -0
  80. package/dist/admin/ui/views/settings-view.d.ts +7 -0
  81. package/dist/admin/webcomponent.d.ts +1 -0
  82. package/dist/api.d.ts +6 -0
  83. package/dist/auth/index.d.ts +2107 -0
  84. package/dist/auth/migrations.d.ts +5 -0
  85. package/dist/auth/premissions.d.ts +6 -0
  86. package/dist/chunk-16vgcf3k.js +88 -0
  87. package/dist/chunk-2zm8cy1w.js +9482 -0
  88. package/dist/chunk-5gvbp2qa.js +167 -0
  89. package/dist/chunk-62ev8gnc.js +41 -0
  90. package/dist/chunk-6dhs73zq.js +126 -0
  91. package/dist/chunk-6ew02s0c.js +472 -0
  92. package/dist/chunk-7a9kn0np.js +116 -0
  93. package/dist/chunk-8gkhn1d4.js +309 -0
  94. package/dist/chunk-8sqjbsgt.js +42 -0
  95. package/dist/chunk-9kxpbcb1.js +85 -0
  96. package/dist/chunk-cvdd4eqh.js +110 -0
  97. package/dist/chunk-d3ffeqp9.js +87 -0
  98. package/dist/chunk-dy5t83hr.js +261 -0
  99. package/dist/chunk-f3nvxn63.js +17 -0
  100. package/dist/chunk-hmhcense.js +1352 -0
  101. package/dist/chunk-j4d50hrx.js +20 -0
  102. package/dist/chunk-jwjk85ze.js +15 -0
  103. package/dist/chunk-kwp83w8b.js +250 -0
  104. package/dist/chunk-s8mqwnm1.js +14 -0
  105. package/dist/chunk-srsac177.js +85 -0
  106. package/dist/chunk-v521d72w.js +10 -0
  107. package/dist/chunk-xa7rjsn2.js +20 -0
  108. package/dist/chunk-xg35h5a3.js +15 -0
  109. package/dist/chunk-ybbbqj63.js +130 -0
  110. package/dist/chunk-zvwb67nd.js +332 -0
  111. package/dist/cli/commands/generate-types.d.ts +1 -0
  112. package/dist/cli/commands/init.d.ts +1 -0
  113. package/dist/cli/commands/migrate-commands.d.ts +5 -0
  114. package/dist/cli/commands/seed-command.d.ts +2 -0
  115. package/dist/cli/d1-mock.d.ts +30 -0
  116. package/dist/cli/index.d.ts +5 -0
  117. package/dist/cli/index.test.d.ts +1 -0
  118. package/dist/cli/r2-mock.d.ts +46 -0
  119. package/dist/cli/seeding.d.ts +17 -0
  120. package/dist/client.d.ts +51 -0
  121. package/dist/config-utils.d.ts +6 -0
  122. package/dist/config.d.ts +10 -0
  123. package/dist/db/adapter.d.ts +34 -0
  124. package/dist/db/better-sqlite.d.ts +40 -0
  125. package/dist/db/bun-sqlite.d.ts +40 -0
  126. package/dist/db/d1.d.ts +42 -0
  127. package/dist/db/kysely/data-mapper.d.ts +6 -0
  128. package/dist/db/kysely/field-mapper.d.ts +22 -0
  129. package/dist/db/kysely/migration-generator.d.ts +9 -0
  130. package/dist/db/kysely/query-builder.d.ts +9 -0
  131. package/dist/db/kysely/schema-builder.d.ts +15 -0
  132. package/dist/db/kysely/sql-utils.d.ts +1 -0
  133. package/dist/db/postgres.d.ts +51 -0
  134. package/dist/db/sqlite.d.ts +41 -0
  135. package/dist/db/system-schema.d.ts +2 -0
  136. package/dist/index.d.ts +6 -0
  137. package/dist/runtimes/bun.d.ts +17 -0
  138. package/dist/runtimes/cloudflare-workers.d.ts +10 -0
  139. package/dist/runtimes/next.d.ts +16 -0
  140. package/dist/runtimes/node.d.ts +18 -0
  141. package/dist/schema/collection.d.ts +100 -0
  142. package/dist/schema/fields/base.d.ts +83 -0
  143. package/dist/schema/fields/index.d.ts +135 -0
  144. package/dist/schema/global.d.ts +82 -0
  145. package/dist/schema/index.d.ts +4 -0
  146. package/dist/schema/infer.d.ts +55 -0
  147. package/dist/server/admin-router.d.ts +9 -0
  148. package/dist/server/admin.d.ts +18 -0
  149. package/dist/server/assets.d.ts +47 -0
  150. package/dist/server/collection-router.d.ts +14 -0
  151. package/dist/server/handlers.d.ts +76 -0
  152. package/dist/server/middlewares/admin.d.ts +6 -0
  153. package/dist/server/middlewares/auth.d.ts +16 -0
  154. package/dist/server/middlewares/context.d.ts +9 -0
  155. package/dist/server/middlewares/cors.d.ts +3 -0
  156. package/dist/server/middlewares/database-init.d.ts +11 -0
  157. package/dist/server/middlewares/rate-limit.d.ts +3 -0
  158. package/dist/server/router.d.ts +7 -0
  159. package/dist/server/setup-middlewares.d.ts +17 -0
  160. package/dist/server/system-router.d.ts +9 -0
  161. package/dist/server.d.ts +6 -0
  162. package/dist/src/admin/index.css +47 -0
  163. package/dist/src/admin/index.js +176 -0
  164. package/dist/src/admin/webcomponent.js +19 -0
  165. package/dist/src/api.js +27 -0
  166. package/dist/src/cli/index.js +157 -0
  167. package/dist/src/client.js +9 -0
  168. package/dist/src/db/bun-sqlite.js +523 -0
  169. package/dist/src/db/d1.js +568 -0
  170. package/dist/src/db/postgres.js +520 -0
  171. package/dist/src/db/sqlite.js +534 -0
  172. package/dist/src/index.js +20 -0
  173. package/dist/src/runtimes/bun.js +36 -0
  174. package/dist/src/runtimes/cloudflare-workers.js +29 -0
  175. package/dist/src/runtimes/next.js +26 -0
  176. package/dist/src/runtimes/node.js +38 -0
  177. package/dist/src/server.js +27 -0
  178. package/dist/src/storage/index.js +355 -0
  179. package/dist/storage/adapters/cloudflare-r2.d.ts +6 -0
  180. package/dist/storage/adapters/local.d.ts +6 -0
  181. package/dist/storage/adapters/s3.d.ts +13 -0
  182. package/dist/storage/errors.d.ts +12 -0
  183. package/dist/storage/index.d.ts +5 -0
  184. package/dist/storage/types.d.ts +31 -0
  185. package/dist/types.d.ts +484 -0
  186. package/dist/utils/lexical.d.ts +5 -0
  187. package/dist/utils/logger.d.ts +35 -0
  188. package/dist/validation.d.ts +300 -0
  189. package/dist/validator.d.ts +9 -0
  190. package/global.d.ts +11 -0
  191. package/package.json +151 -0
  192. package/src/admin/api-client.ts +63 -0
  193. package/src/admin/auth-client.ts +40 -0
  194. package/src/admin/custom-field.ts +179 -0
  195. package/src/admin/index.ts +15 -0
  196. package/src/admin/react.tsx +72 -0
  197. package/src/admin/router.ts +9 -0
  198. package/src/admin/stores/admin-queries.ts +121 -0
  199. package/src/admin/stores/auth.ts +61 -0
  200. package/src/admin/stores/column-visibility.ts +67 -0
  201. package/src/admin/stores/config.ts +15 -0
  202. package/src/admin/stores/media.ts +95 -0
  203. package/src/admin/stores/query.ts +13 -0
  204. package/src/admin/stores/ui.ts +29 -0
  205. package/src/admin/ui/admin-client.tsx +283 -0
  206. package/src/admin/ui/admin-layout.tsx +276 -0
  207. package/src/admin/ui/components/ColumnVisibilityToggle.tsx +141 -0
  208. package/src/admin/ui/components/DataDetailSheet.tsx +141 -0
  209. package/src/admin/ui/components/DataDetailView.tsx +175 -0
  210. package/src/admin/ui/components/Table.tsx +67 -0
  211. package/src/admin/ui/components/fields/ArrayField.tsx +166 -0
  212. package/src/admin/ui/components/fields/BlocksField.tsx +202 -0
  213. package/src/admin/ui/components/fields/BooleanField.tsx +50 -0
  214. package/src/admin/ui/components/fields/CollapsibleField.tsx +75 -0
  215. package/src/admin/ui/components/fields/DateField.tsx +45 -0
  216. package/src/admin/ui/components/fields/FileField.tsx +322 -0
  217. package/src/admin/ui/components/fields/GroupField.tsx +50 -0
  218. package/src/admin/ui/components/fields/JoinField.tsx +23 -0
  219. package/src/admin/ui/components/fields/NumberField.tsx +46 -0
  220. package/src/admin/ui/components/fields/RadioField.tsx +62 -0
  221. package/src/admin/ui/components/fields/RelationshipField.tsx +278 -0
  222. package/src/admin/ui/components/fields/RowField.tsx +40 -0
  223. package/src/admin/ui/components/fields/SelectField.tsx +59 -0
  224. package/src/admin/ui/components/fields/TabsField.tsx +101 -0
  225. package/src/admin/ui/components/fields/TextAreaField.tsx +54 -0
  226. package/src/admin/ui/components/fields/TextField.tsx +49 -0
  227. package/src/admin/ui/components/fields/VirtualField.tsx +53 -0
  228. package/src/admin/ui/components/fields/index.tsx +371 -0
  229. package/src/admin/ui/components/fields/richtext-editor/index.tsx +211 -0
  230. package/src/admin/ui/components/fields/richtext-editor/nodes/ImageComponent.tsx +142 -0
  231. package/src/admin/ui/components/fields/richtext-editor/nodes/ImageNode.tsx +95 -0
  232. package/src/admin/ui/components/fields/richtext-editor/plugins/ComponentPickerPlugin.tsx +226 -0
  233. package/src/admin/ui/components/fields/richtext-editor/plugins/EditableSyncPlugin.tsx +16 -0
  234. package/src/admin/ui/components/fields/richtext-editor/plugins/NotionToolbarPlugin.tsx +184 -0
  235. package/src/admin/ui/components/fields/richtext-editor/plugins/SimpleToolbarPlugin.tsx +240 -0
  236. package/src/admin/ui/components/fields/richtext-editor/plugins/ValueSyncPlugin.tsx +40 -0
  237. package/src/admin/ui/components/fields/utils.ts +1 -0
  238. package/src/admin/ui/components/link.tsx +41 -0
  239. package/src/admin/ui/components/media/AssetManagerModal.tsx +334 -0
  240. package/src/admin/ui/components/toast.tsx +72 -0
  241. package/src/admin/ui/components/ui/accordion.tsx +51 -0
  242. package/src/admin/ui/components/ui/alert-dialog.tsx +98 -0
  243. package/src/admin/ui/components/ui/blocks.tsx +32 -0
  244. package/src/admin/ui/components/ui/breadcrumbs.tsx +59 -0
  245. package/src/admin/ui/components/ui/button.tsx +26 -0
  246. package/src/admin/ui/components/ui/collapsible.tsx +124 -0
  247. package/src/admin/ui/components/ui/dialog.tsx +79 -0
  248. package/src/admin/ui/components/ui/group.tsx +20 -0
  249. package/src/admin/ui/components/ui/index.ts +17 -0
  250. package/src/admin/ui/components/ui/input.tsx +12 -0
  251. package/src/admin/ui/components/ui/join.tsx +53 -0
  252. package/src/admin/ui/components/ui/label.tsx +11 -0
  253. package/src/admin/ui/components/ui/radio-group.tsx +75 -0
  254. package/src/admin/ui/components/ui/relationship-detail-sheet.tsx +122 -0
  255. package/src/admin/ui/components/ui/relationship.tsx +58 -0
  256. package/src/admin/ui/components/ui/scroll-area.tsx +19 -0
  257. package/src/admin/ui/components/ui/select.tsx +187 -0
  258. package/src/admin/ui/components/ui/separator.tsx +21 -0
  259. package/src/admin/ui/components/ui/sheet.tsx +106 -0
  260. package/src/admin/ui/components/ui/tabs.tsx +116 -0
  261. package/src/admin/ui/components/ui/utils.ts +3 -0
  262. package/src/admin/ui/hooks/use-debounce.ts +15 -0
  263. package/src/admin/ui/styles/_locale-switcher.scss +33 -0
  264. package/src/admin/ui/styles/accordion.scss +60 -0
  265. package/src/admin/ui/styles/animations.scss +41 -0
  266. package/src/admin/ui/styles/asset-manager.scss +547 -0
  267. package/src/admin/ui/styles/badge.scss +13 -0
  268. package/src/admin/ui/styles/base.scss +22 -0
  269. package/src/admin/ui/styles/button.scss +161 -0
  270. package/src/admin/ui/styles/card.scss +13 -0
  271. package/src/admin/ui/styles/collapsible.scss +75 -0
  272. package/src/admin/ui/styles/data-detail.scss +92 -0
  273. package/src/admin/ui/styles/dialog.scss +102 -0
  274. package/src/admin/ui/styles/empty-state.scss +22 -0
  275. package/src/admin/ui/styles/group.scss +19 -0
  276. package/src/admin/ui/styles/index.scss +33 -0
  277. package/src/admin/ui/styles/input.scss +80 -0
  278. package/src/admin/ui/styles/label.scss +12 -0
  279. package/src/admin/ui/styles/layout.scss +56 -0
  280. package/src/admin/ui/styles/lexical.scss +469 -0
  281. package/src/admin/ui/styles/loading.scss +102 -0
  282. package/src/admin/ui/styles/media-registry.scss +597 -0
  283. package/src/admin/ui/styles/pagination.scss +20 -0
  284. package/src/admin/ui/styles/radio-group.scss +66 -0
  285. package/src/admin/ui/styles/row.scss +17 -0
  286. package/src/admin/ui/styles/scrollbar.scss +36 -0
  287. package/src/admin/ui/styles/select.scss +121 -0
  288. package/src/admin/ui/styles/separator.scss +14 -0
  289. package/src/admin/ui/styles/sheet.scss +152 -0
  290. package/src/admin/ui/styles/sidebar.scss +148 -0
  291. package/src/admin/ui/styles/switch.scss +59 -0
  292. package/src/admin/ui/styles/table.scss +207 -0
  293. package/src/admin/ui/styles/tabs.scss +62 -0
  294. package/src/admin/ui/styles/toast.scss +45 -0
  295. package/src/admin/ui/styles/variables.scss +24 -0
  296. package/src/admin/ui/views/collection-list-view.tsx +720 -0
  297. package/src/admin/ui/views/dashboard-view.tsx +263 -0
  298. package/src/admin/ui/views/document-edit-view.tsx +384 -0
  299. package/src/admin/ui/views/global-edit-view.tsx +226 -0
  300. package/src/admin/ui/views/init-view.tsx +182 -0
  301. package/src/admin/ui/views/login-view.tsx +123 -0
  302. package/src/admin/ui/views/media-registry-view.tsx +1104 -0
  303. package/src/admin/ui/views/settings-view.tsx +729 -0
  304. package/src/admin/webcomponent.tsx +15 -0
  305. package/src/api.ts +9 -0
  306. package/src/auth/index.ts +194 -0
  307. package/src/auth/migrations.ts +87 -0
  308. package/src/auth/premissions.ts +46 -0
  309. package/src/cli/commands/generate-types.ts +116 -0
  310. package/src/cli/commands/init.ts +95 -0
  311. package/src/cli/commands/migrate-commands.ts +160 -0
  312. package/src/cli/commands/seed-command.ts +11 -0
  313. package/src/cli/d1-mock.ts +101 -0
  314. package/src/cli/index.test.ts +84 -0
  315. package/src/cli/index.ts +183 -0
  316. package/src/cli/r2-mock.ts +217 -0
  317. package/src/cli/seeding.ts +405 -0
  318. package/src/client.ts +181 -0
  319. package/src/config-utils.ts +102 -0
  320. package/src/config.ts +49 -0
  321. package/src/db/adapter.ts +53 -0
  322. package/src/db/better-sqlite.ts +630 -0
  323. package/src/db/bun-sqlite.ts +646 -0
  324. package/src/db/d1.ts +711 -0
  325. package/src/db/kysely/data-mapper.ts +142 -0
  326. package/src/db/kysely/field-mapper.ts +148 -0
  327. package/src/db/kysely/migration-generator.ts +223 -0
  328. package/src/db/kysely/query-builder.ts +92 -0
  329. package/src/db/kysely/schema-builder.ts +439 -0
  330. package/src/db/kysely/sql-utils.ts +13 -0
  331. package/src/db/postgres.ts +621 -0
  332. package/src/db/sqlite.ts +658 -0
  333. package/src/db/system-schema.ts +121 -0
  334. package/src/index.ts +13 -0
  335. package/src/runtimes/README.md +59 -0
  336. package/src/runtimes/bun.ts +49 -0
  337. package/src/runtimes/cloudflare-workers.ts +38 -0
  338. package/src/runtimes/next.ts +26 -0
  339. package/src/runtimes/node.ts +52 -0
  340. package/src/schema/collection.ts +184 -0
  341. package/src/schema/fields/base.ts +164 -0
  342. package/src/schema/fields/index.ts +427 -0
  343. package/src/schema/global.ts +145 -0
  344. package/src/schema/index.ts +4 -0
  345. package/src/schema/infer.ts +72 -0
  346. package/src/server/admin-router.ts +20 -0
  347. package/src/server/admin.ts +142 -0
  348. package/src/server/assets.ts +306 -0
  349. package/src/server/collection-router.ts +55 -0
  350. package/src/server/handlers.ts +722 -0
  351. package/src/server/middlewares/admin.ts +27 -0
  352. package/src/server/middlewares/auth.ts +89 -0
  353. package/src/server/middlewares/context.ts +17 -0
  354. package/src/server/middlewares/cors.ts +24 -0
  355. package/src/server/middlewares/database-init.ts +74 -0
  356. package/src/server/middlewares/rate-limit.ts +71 -0
  357. package/src/server/router.ts +47 -0
  358. package/src/server/setup-middlewares.ts +58 -0
  359. package/src/server/system-router.ts +35 -0
  360. package/src/server.ts +9 -0
  361. package/src/storage/adapters/cloudflare-r2.ts +136 -0
  362. package/src/storage/adapters/local.ts +146 -0
  363. package/src/storage/adapters/s3.ts +186 -0
  364. package/src/storage/errors.ts +46 -0
  365. package/src/storage/index.ts +5 -0
  366. package/src/storage/types.ts +39 -0
  367. package/src/types.ts +577 -0
  368. package/src/utils/lexical.ts +37 -0
  369. package/src/utils/logger.ts +73 -0
  370. package/src/validation.ts +429 -0
  371. package/src/validator.ts +179 -0
  372. package/test/admin-custom-field.test.ts +162 -0
  373. package/test/admin-react-field.test.tsx +134 -0
  374. package/test/api-features.test.ts +78 -0
  375. package/test/api.test.ts +178 -0
  376. package/test/auth.test.ts +62 -0
  377. package/test/cli-integration.test.ts +146 -0
  378. package/test/cli.test.ts +25 -0
  379. package/test/db/postgres.test.ts +95 -0
  380. package/test/db/sqlite-filter.test.ts +53 -0
  381. package/test/db/sqlite.test.ts +82 -0
  382. package/test/engine-features.test.ts +79 -0
  383. package/test/globals.test.ts +74 -0
  384. package/test/integration-tmp/db-app/opacacms.config.ts +15 -0
  385. package/test/integration-tmp/my-sqlite-app/opacacms.config.ts +25 -0
  386. package/test/integration-tmp/my-test-app/index.ts +8 -0
  387. package/test/integration-tmp/my-test-app/opacacms.config.ts +16 -0
  388. package/test/integration-tmp/my-test-app/package.json +12 -0
  389. package/test/populate.test.ts +79 -0
  390. package/test/runtimes.test.ts +43 -0
  391. package/test/schema-builder.test.ts +107 -0
  392. package/test/schema-features.test.ts +63 -0
  393. package/test/seeding.test.ts +68 -0
  394. package/test/storage/local.test.ts +72 -0
  395. package/test/storage/s3.test.ts +60 -0
  396. package/test/structural-data.test.ts +100 -0
  397. package/test/test-setup.ts +11 -0
  398. package/test/validation.test.ts +162 -0
  399. package/tsconfig.json +42 -0
@@ -0,0 +1,334 @@
1
+ import { useStore } from '@nanostores/react';
2
+ import {
3
+ ChevronRight,
4
+ File,
5
+ FileText,
6
+ FolderPlus,
7
+ Image as ImageIcon,
8
+ Loader2,
9
+ Upload,
10
+ X,
11
+ } from 'lucide-react';
12
+ import React, { useMemo, useRef, useState } from 'react';
13
+ import { api, getCurrentBaseURL } from '../../../api-client';
14
+ import { $config } from '../../../stores/config';
15
+ import {
16
+ $assets,
17
+ $mediaCurrentFolder,
18
+ $mediaSelectedBucket,
19
+ $mediaViewMode,
20
+ type AssetDoc,
21
+ setMediaBucket,
22
+ setMediaFolder,
23
+ } from '../../../stores/media';
24
+ import '../../styles/asset-manager.scss';
25
+ import {
26
+ Select,
27
+ SelectContent,
28
+ SelectItem,
29
+ SelectLabel,
30
+ SelectSeparator,
31
+ SelectTrigger,
32
+ SelectValue,
33
+ } from '../ui';
34
+
35
+ interface AssetManagerProps {
36
+ onSelect: (asset: {
37
+ assetId: string;
38
+ url: string;
39
+ filename: string;
40
+ mimeType: string;
41
+ filesize: number;
42
+ }) => void;
43
+ onClose: () => void;
44
+ allowedmime_types?: string[];
45
+ maxFileSize?: number;
46
+ bucket?: string;
47
+ }
48
+
49
+ export const AssetManagerModal: React.FC<AssetManagerProps> = ({
50
+ onSelect,
51
+ onClose,
52
+ allowedmime_types,
53
+ maxFileSize,
54
+ bucket = 'default',
55
+ }) => {
56
+ const { data, loading: isLoading } = useStore($assets);
57
+ const selectedBucket = useStore($mediaSelectedBucket);
58
+ const currentFolder = useStore($mediaCurrentFolder);
59
+ const viewMode = useStore($mediaViewMode);
60
+ const config = useStore($config);
61
+
62
+ const [isUploading, setIsUploading] = useState<boolean>(false);
63
+ const [isDragActive, setIsDragActive] = useState<boolean>(false);
64
+
65
+ const fileInputRef = useRef<HTMLInputElement>(null);
66
+
67
+ const buckets = useMemo(() => {
68
+ return Object.keys(config?.storages || {});
69
+ }, [config?.storages]);
70
+
71
+ const assets = data?.docs || [];
72
+ const folders = data?.folders || [];
73
+
74
+ const handleUpload = async (file: File) => {
75
+ // ... same logic but use selectedBucket and currentFolder
76
+ if (allowedmime_types && !allowedmime_types.includes(file.type)) {
77
+ alert(`Invalid file type. Allowed: ${allowedmime_types.join(', ')}`);
78
+ return;
79
+ }
80
+ if (maxFileSize && file.size > maxFileSize) {
81
+ alert(`File too large. Max size: ${maxFileSize / 1024 / 1024}MB`);
82
+ return;
83
+ }
84
+
85
+ setIsUploading(true);
86
+
87
+ const formData = new FormData();
88
+ formData.append('file', file);
89
+
90
+ try {
91
+ const bucketToUse = selectedBucket === 'all' ? 'default' : selectedBucket;
92
+ const resp = await api
93
+ .post(
94
+ `api/__system/assets/upload?bucket=${bucketToUse}&folder=${currentFolder}`,
95
+ {
96
+ body: formData,
97
+ onDownloadProgress: (_progress: unknown) => {
98
+ // Ky uses onDownloadProgress, but xhr.upload.onprogress is for upload.
99
+ },
100
+ },
101
+ )
102
+ .json<AssetDoc>();
103
+
104
+ setIsUploading(false);
105
+ $assets.revalidate(); // Refresh the list
106
+ // Auto select the new file
107
+ onSelect({
108
+ assetId: resp.id,
109
+ url: resp.url || `${getCurrentBaseURL()}/api/assets/${resp.id}/view`,
110
+ filename: resp.filename,
111
+ mimeType: resp.mimeType || resp.mime_type || '',
112
+ filesize: resp.filesize,
113
+ });
114
+ } catch (err: unknown) {
115
+ alert(
116
+ `Upload failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
117
+ );
118
+ setIsUploading(false);
119
+ }
120
+ };
121
+
122
+ const handleDrop = (e: React.DragEvent) => {
123
+ e.preventDefault();
124
+ setIsDragActive(false);
125
+ if (
126
+ e.dataTransfer.files &&
127
+ e.dataTransfer.files.length > 0 &&
128
+ e.dataTransfer.files[0]
129
+ ) {
130
+ handleUpload(e.dataTransfer.files[0]);
131
+ }
132
+ };
133
+
134
+ const getFileIcon = (mime: string) => {
135
+ if (!mime) return <File size={40} className="text-gray-400" />;
136
+ if (mime.startsWith('image/'))
137
+ return <ImageIcon size={40} className="text-blue-400" />;
138
+ if (mime.startsWith('video/'))
139
+ return <FileText size={40} className="text-purple-400" />;
140
+ if (mime.includes('pdf'))
141
+ return <FileText size={40} className="text-red-400" />;
142
+ return <File size={40} className="text-gray-400" />;
143
+ };
144
+
145
+ return (
146
+ <div className="asset-manager-overlay">
147
+ <div className="asset-manager-container">
148
+ {/* Header */}
149
+ <div className="asset-manager-header">
150
+ <div>
151
+ <h2>Media Library</h2>
152
+ <div className="asset-manager-breadcrumbs">
153
+ <button type="button" onClick={() => setMediaFolder('')}>
154
+ Home
155
+ </button>
156
+ {currentFolder
157
+ .split('/')
158
+ .filter(Boolean)
159
+ .map((part: string, i: number, arr: string[]) => (
160
+ <React.Fragment key={part || i}>
161
+ <ChevronRight size={14} className="breadcrumb-separator" />
162
+ <button
163
+ type="button"
164
+ onClick={() =>
165
+ setMediaFolder(arr.slice(0, i + 1).join('/'))
166
+ }
167
+ >
168
+ {part}
169
+ </button>
170
+ </React.Fragment>
171
+ ))}
172
+ </div>
173
+ </div>
174
+ <div className="header-actions">
175
+ <Select
176
+ value={selectedBucket}
177
+ onValueChange={(val: string) => setMediaBucket(val)}
178
+ >
179
+ <SelectTrigger className="bucket-selector">
180
+ <SelectValue placeholder="Bucket" />
181
+ </SelectTrigger>
182
+ <SelectContent>
183
+ <SelectItem value="all">All Buckets</SelectItem>
184
+ <SelectSeparator />
185
+ <SelectLabel>Storage</SelectLabel>
186
+ {buckets.map((b) => (
187
+ <SelectItem key={b} value={b}>
188
+ {b.toUpperCase()}
189
+ </SelectItem>
190
+ ))}
191
+ </SelectContent>
192
+ </Select>
193
+ <button type="button" onClick={onClose} className="close-button">
194
+ <X size={20} />
195
+ </button>
196
+ </div>
197
+ </div>
198
+
199
+ {/* Upload Zone */}
200
+ <button
201
+ type="button"
202
+ onDragOver={(e) => {
203
+ e.preventDefault();
204
+ setIsDragActive(true);
205
+ }}
206
+ onDragLeave={() => setIsDragActive(false)}
207
+ onDrop={handleDrop}
208
+ onClick={() => !isUploading && fileInputRef.current?.click()}
209
+ aria-label="Upload file"
210
+ className={`asset-manager-upload-zone ${isDragActive ? 'is-drag-active' : ''} ${isUploading ? 'uploading' : ''}`}
211
+ >
212
+ <input
213
+ type="file"
214
+ ref={fileInputRef}
215
+ style={{ display: 'none' }}
216
+ accept={allowedmime_types?.join(',')}
217
+ onChange={(e) => {
218
+ if (
219
+ e.target.files &&
220
+ e.target.files.length > 0 &&
221
+ e.target.files[0]
222
+ ) {
223
+ handleUpload(e.target.files[0]);
224
+ }
225
+ }}
226
+ />
227
+ {isUploading ? (
228
+ <div className="uploading-status">
229
+ <Loader2 className="opaca-spin" size={24} />
230
+ <span className="status-text">Uploading...</span>
231
+ </div>
232
+ ) : (
233
+ <>
234
+ <Upload size={24} className="upload-icon" />
235
+ <p className="upload-prompt">
236
+ Drag & drop a file here, or <span>click to browse</span>.
237
+ </p>
238
+ </>
239
+ )}
240
+ </button>
241
+
242
+ {/* Grid */}
243
+ <div className="asset-manager-grid-container">
244
+ {isLoading ? (
245
+ <div className="loading-assets">
246
+ <Loader2 className="opaca-spin" size={32} />
247
+ <p>Loading assets...</p>
248
+ </div>
249
+ ) : assets.length === 0 && folders.length === 0 ? (
250
+ <div className="no-assets">
251
+ <ImageIcon size={48} className="empty-icon" />
252
+ <p>No assets found here.</p>
253
+ </div>
254
+ ) : (
255
+ <div className={`asset-manager-grid mode-${viewMode}`}>
256
+ {/* Folders */}
257
+ {folders.map((folder: any) => (
258
+ <button
259
+ type="button"
260
+ key={`folder-${folder.name}`}
261
+ className="asset-manager-card folder-card"
262
+ onClick={() =>
263
+ setMediaFolder(
264
+ currentFolder
265
+ ? `${currentFolder}/${folder.name}`
266
+ : folder.name,
267
+ )
268
+ }
269
+ >
270
+ <div className="asset-thumb">
271
+ <FolderPlus size={40} className="folder-icon" />
272
+ </div>
273
+ <div className="asset-info">
274
+ <span className="filename">{folder.name}</span>
275
+ <span className="file-meta">Folder</span>
276
+ </div>
277
+ </button>
278
+ ))}
279
+
280
+ {/* Assets */}
281
+ {assets.map((asset: AssetDoc) => (
282
+ <button
283
+ type="button"
284
+ key={asset.id}
285
+ className="asset-manager-card asset-card"
286
+ onClick={() => {
287
+ const baseUrl = getCurrentBaseURL();
288
+ onSelect({
289
+ assetId: asset.id,
290
+ url: `${baseUrl}/api/assets/${asset.id}/view`,
291
+ filename: asset.filename,
292
+ mimeType: asset.mimeType || asset.mime_type || '',
293
+ filesize: asset.filesize,
294
+ });
295
+ }}
296
+ >
297
+ <div className="asset-thumb">
298
+ {(() => {
299
+ const mime = asset.mimeType || asset.mime_type;
300
+ return mime?.startsWith('image/') ? (
301
+ <img
302
+ src={`${getCurrentBaseURL()}/api/assets/${asset.id}/view`}
303
+ alt={asset.filename}
304
+ />
305
+ ) : (
306
+ getFileIcon(mime || '')
307
+ );
308
+ })()}
309
+ </div>
310
+
311
+ <div className="asset-info">
312
+ <span className="filename" title={asset.filename}>
313
+ {asset.filename}
314
+ </span>
315
+ <span className="file-meta">
316
+ {(asset.mimeType || asset.mime_type || '')
317
+ .split('/')[1]
318
+ ?.toUpperCase() || 'FILE'}{' '}
319
+ • {((asset.filesize || 0) / 1024).toFixed(1)} KB
320
+ </span>
321
+ </div>
322
+
323
+ <div className="selection-overlay">
324
+ <div className="select-badge">Select</div>
325
+ </div>
326
+ </button>
327
+ ))}
328
+ </div>
329
+ )}
330
+ </div>
331
+ </div>
332
+ </div>
333
+ );
334
+ };
@@ -0,0 +1,72 @@
1
+ import { CheckCircle, Info, X, XCircle } from "lucide-react";
2
+ import { useEffect, useState } from "react";
3
+ import type { ToastItem, ToastType } from "../../stores/ui";
4
+
5
+ interface ToastProps extends ToastItem {
6
+ onClear: (id: string) => void;
7
+ }
8
+
9
+ export function Toast({ id, message, type, onClear }: ToastProps) {
10
+ const [isExiting, setIsExiting] = useState(false);
11
+
12
+ useEffect(() => {
13
+ const timer = setTimeout(() => {
14
+ setIsExiting(true);
15
+ setTimeout(() => onClear(id), 200);
16
+ }, 4000);
17
+ return () => clearTimeout(timer);
18
+ }, [id, onClear]);
19
+
20
+ const Icon = type === "success" ? CheckCircle : type === "error" ? XCircle : Info;
21
+ const iconColor =
22
+ type === "success"
23
+ ? "var(--opaca-success)"
24
+ : type === "error"
25
+ ? "var(--opaca-error)"
26
+ : "var(--opaca-accent)";
27
+
28
+ return (
29
+ <div className={`opaca-toast opaca-toast-${type} ${isExiting ? "exit" : ""}`}>
30
+ <Icon size={18} style={{ color: iconColor }} />
31
+ <span className="opaca-toast-message">{message}</span>
32
+ <button
33
+ type="button"
34
+ onClick={() => {
35
+ setIsExiting(true);
36
+ setTimeout(() => onClear(id), 200);
37
+ }}
38
+ style={{
39
+ background: "none",
40
+ border: "none",
41
+ padding: "4px",
42
+ marginLeft: "auto",
43
+ cursor: "pointer",
44
+ color: "var(--opaca-text-dim)",
45
+ display: "flex",
46
+ alignItems: "center",
47
+ justifyContent: "center",
48
+ }}
49
+ >
50
+ <X size={14} />
51
+ </button>
52
+ </div>
53
+ );
54
+ }
55
+
56
+ export function ToastContainer({
57
+ toasts,
58
+ onClear,
59
+ }: {
60
+ toasts: ToastItem[];
61
+ onClear: (id: string) => void;
62
+ }) {
63
+ if (toasts.length === 0) return null;
64
+
65
+ return (
66
+ <div className="opaca-toast-container">
67
+ {toasts.map((toast) => (
68
+ <Toast key={toast.id} {...toast} onClear={onClear} />
69
+ ))}
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import { cn } from "./utils";
3
+ import "../../styles/accordion.scss";
4
+
5
+ interface AccordionProps {
6
+ title: string;
7
+ children: React.ReactNode;
8
+ defaultOpen?: boolean;
9
+ className?: string;
10
+ isCollapsed?: boolean;
11
+ }
12
+
13
+ export const Accordion = ({
14
+ title,
15
+ children,
16
+ defaultOpen = true,
17
+ className,
18
+ isCollapsed = false,
19
+ }: AccordionProps) => {
20
+ const [isOpen, setIsOpen] = React.useState(defaultOpen);
21
+
22
+ return (
23
+ <div className={cn("opaca-sidebar-accordion", className)}>
24
+ <button
25
+ type="button"
26
+ className="opaca-sidebar-accordion-trigger"
27
+ onClick={() => setIsOpen(!isOpen)}
28
+ aria-expanded={isOpen}
29
+ style={{
30
+ opacity: isCollapsed ? 0 : 1,
31
+ pointerEvents: isCollapsed ? "none" : "auto",
32
+ }}
33
+ title={title}
34
+ >
35
+ <span className="opaca-sidebar-accordion-title">{title}</span>
36
+ <svg
37
+ className="opaca-sidebar-accordion-icon"
38
+ viewBox="0 0 24 24"
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ aria-hidden="true"
41
+ >
42
+ <title>Toggle accordion</title>
43
+ <polyline points="6 9 12 15 18 9"></polyline>
44
+ </svg>
45
+ </button>
46
+ <div className="opaca-sidebar-accordion-content" data-state={isOpen ? "open" : "closed"}>
47
+ <div className="opaca-sidebar-accordion-content-inner">{children}</div>
48
+ </div>
49
+ </div>
50
+ );
51
+ };
@@ -0,0 +1,98 @@
1
+ import { Children, cloneElement, isValidElement, type ReactNode, useState } from "react";
2
+ import { Button } from "./button";
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from "./dialog";
11
+
12
+ export interface AlertDialogProps {
13
+ children?: ReactNode;
14
+ trigger?: ReactNode;
15
+ title: string;
16
+ description: string;
17
+ onConfirm: () => void;
18
+ confirmText?: string;
19
+ cancelText?: string;
20
+ variant?: "default" | "destructive";
21
+ }
22
+
23
+ export function AlertDialog({
24
+ children,
25
+ trigger,
26
+ title,
27
+ description,
28
+ onConfirm,
29
+ confirmText = "Continue",
30
+ cancelText = "Cancel",
31
+ variant = "default",
32
+ }: AlertDialogProps) {
33
+ const [open, setOpen] = useState(false);
34
+
35
+ const handleConfirm = () => {
36
+ onConfirm();
37
+ setOpen(false);
38
+ };
39
+
40
+ const triggerElement = trigger || children;
41
+
42
+ const renderTrigger = () => {
43
+ if (Children.count(triggerElement) === 1 && isValidElement(triggerElement)) {
44
+ const element = triggerElement as React.ReactElement<{
45
+ onClick?: (e: React.MouseEvent) => void;
46
+ }>;
47
+ return cloneElement(element, {
48
+ onClick: (e: React.MouseEvent) => {
49
+ const originalOnClick = element.props.onClick;
50
+ if (originalOnClick) originalOnClick(e);
51
+ setOpen(true);
52
+ },
53
+ });
54
+ }
55
+
56
+ return (
57
+ <span
58
+ role="button"
59
+ tabIndex={0}
60
+ onClick={() => setOpen(true)}
61
+ onKeyDown={(e) => {
62
+ if (e.key === "Enter" || e.key === " ") {
63
+ e.preventDefault();
64
+ setOpen(true);
65
+ }
66
+ }}
67
+ style={{
68
+ display: "inline-block",
69
+ cursor: "pointer",
70
+ }}
71
+ >
72
+ {triggerElement}
73
+ </span>
74
+ );
75
+ };
76
+
77
+ return (
78
+ <>
79
+ {renderTrigger()}
80
+ <Dialog open={open} onOpenChange={setOpen}>
81
+ <DialogContent className="opaca-dialog-max-w">
82
+ <DialogHeader>
83
+ <DialogTitle>{title}</DialogTitle>
84
+ <DialogDescription>{description}</DialogDescription>
85
+ </DialogHeader>
86
+ <DialogFooter>
87
+ <Button variant="outline" onClick={() => setOpen(false)}>
88
+ {cancelText}
89
+ </Button>
90
+ <Button variant={variant} onClick={handleConfirm}>
91
+ {confirmText}
92
+ </Button>
93
+ </DialogFooter>
94
+ </DialogContent>
95
+ </Dialog>
96
+ </>
97
+ );
98
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { cn } from "./utils";
3
+
4
+ export interface BlocksProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ label?: string;
6
+ }
7
+
8
+ export const Blocks = React.forwardRef<HTMLDivElement, BlocksProps>(
9
+ ({ className, label, children, ...props }, ref) => {
10
+ return (
11
+ <div ref={ref} className={cn("opaca-ui-blocks", className)} {...props}>
12
+ {label && (
13
+ <div
14
+ style={{
15
+ marginBottom: "1.25rem",
16
+ fontSize: "0.75rem",
17
+ fontWeight: 500,
18
+ color: "var(--opaca-text-muted)",
19
+ textTransform: "uppercase",
20
+ letterSpacing: "0.03em",
21
+ }}
22
+ >
23
+ {label}
24
+ </div>
25
+ )}
26
+ <div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>{children}</div>
27
+ </div>
28
+ );
29
+ },
30
+ );
31
+
32
+ Blocks.displayName = "Blocks";
@@ -0,0 +1,59 @@
1
+ import { ChevronRight, Home } from 'lucide-react';
2
+ import React from 'react';
3
+ import { Link } from '../link';
4
+
5
+ export interface BreadcrumbItem {
6
+ label: string;
7
+ href?: string;
8
+ }
9
+
10
+ export function Breadcrumbs({ items }: { items: BreadcrumbItem[] }) {
11
+ return (
12
+ <nav
13
+ aria-label="Breadcrumb"
14
+ style={{
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ gap: '0.5rem',
18
+ marginBottom: '1.5rem',
19
+ fontSize: '0.8125rem',
20
+ color: 'var(--opaca-text-dim)',
21
+ }}
22
+ >
23
+ <Link
24
+ href="/admin"
25
+ style={{
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ color: 'inherit',
29
+ textDecoration: 'none',
30
+ transition: 'color var(--opaca-transition)',
31
+ }}
32
+ >
33
+ <Home size={14} />
34
+ </Link>
35
+
36
+ {items.map((item, index) => (
37
+ <React.Fragment key={item.label || index}>
38
+ <ChevronRight size={12} style={{ opacity: 0.5 }} />
39
+ {item.href ? (
40
+ <Link
41
+ href={item.href}
42
+ style={{
43
+ color: 'inherit',
44
+ textDecoration: 'none',
45
+ transition: 'color var(--opaca-transition)',
46
+ }}
47
+ >
48
+ {item.label}
49
+ </Link>
50
+ ) : (
51
+ <span style={{ color: 'var(--opaca-text)', fontWeight: '500' }}>
52
+ {item.label}
53
+ </span>
54
+ )}
55
+ </React.Fragment>
56
+ ))}
57
+ </nav>
58
+ );
59
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { cn } from "./utils";
3
+ import "../../styles/button.scss";
4
+
5
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
7
+ size?: "default" | "sm" | "lg" | "icon";
8
+ }
9
+
10
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
11
+ ({ className, variant = "default", size = "default", ...props }, ref) => {
12
+ return (
13
+ <button
14
+ ref={ref}
15
+ className={cn(
16
+ "opaca-ui-btn",
17
+ `opaca-ui-btn-${variant}`,
18
+ `opaca-ui-btn-size-${size}`,
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ },
25
+ );
26
+ Button.displayName = "Button";