convex-cms 0.0.5-alpha.0 → 0.0.5-alpha.2

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 (323) hide show
  1. package/README.md +95 -144
  2. package/admin/README.md +99 -0
  3. package/admin/src/components/AdminLayout.tsx +22 -0
  4. package/admin/src/components/BreakingChangesWarningDialog.tsx +81 -0
  5. package/admin/src/components/BulkActionBar.tsx +190 -0
  6. package/admin/src/components/BulkOperationModal.tsx +177 -0
  7. package/admin/src/components/ContentEntryEditor.tsx +1104 -0
  8. package/admin/src/components/ContentTypeFormModal.tsx +1012 -0
  9. package/admin/src/components/ErrorBoundary.tsx +83 -0
  10. package/admin/src/components/ErrorState.tsx +147 -0
  11. package/admin/src/components/Header.tsx +294 -0
  12. package/admin/src/components/RouteGuard.tsx +264 -0
  13. package/admin/src/components/Sidebar.tsx +90 -0
  14. package/admin/src/components/TaxonomyEditor.tsx +348 -0
  15. package/admin/src/components/TermTree.tsx +533 -0
  16. package/admin/src/components/UploadDropzone.tsx +383 -0
  17. package/admin/src/components/VersionCompare.tsx +250 -0
  18. package/admin/src/components/VersionHistory.tsx +279 -0
  19. package/admin/src/components/VersionRollbackModal.tsx +79 -0
  20. package/admin/src/components/cmsds/CmsButton.tsx +101 -0
  21. package/admin/src/components/cmsds/CmsDialog.tsx +139 -0
  22. package/admin/src/components/cmsds/CmsDropdown.tsx +62 -0
  23. package/admin/src/components/cmsds/CmsEmptyState.tsx +54 -0
  24. package/admin/src/components/cmsds/CmsField.tsx +47 -0
  25. package/admin/src/components/cmsds/CmsPageHeader.tsx +35 -0
  26. package/admin/src/components/cmsds/CmsStatusBadge.tsx +153 -0
  27. package/admin/src/components/cmsds/CmsSurface.tsx +52 -0
  28. package/admin/src/components/cmsds/CmsTable.tsx +164 -0
  29. package/admin/src/components/cmsds/CmsToolbar.tsx +58 -0
  30. package/admin/src/components/cmsds/index.ts +10 -0
  31. package/admin/src/components/fields/BooleanField.tsx +74 -0
  32. package/admin/src/components/fields/CategoryField.tsx +394 -0
  33. package/admin/src/components/fields/DateField.tsx +173 -0
  34. package/admin/src/components/fields/DefaultFieldRenderer.tsx +74 -0
  35. package/admin/src/components/fields/FieldRenderer.tsx +180 -0
  36. package/admin/src/components/fields/FieldWrapper.tsx +57 -0
  37. package/admin/src/components/fields/JsonField.tsx +172 -0
  38. package/admin/src/components/fields/MediaField.tsx +367 -0
  39. package/admin/src/components/fields/MultiSelectField.tsx +118 -0
  40. package/admin/src/components/fields/NumberField.tsx +77 -0
  41. package/admin/src/components/fields/ReferenceField.tsx +386 -0
  42. package/admin/src/components/fields/RichTextField.tsx +171 -0
  43. package/admin/src/components/fields/SelectField.tsx +62 -0
  44. package/admin/src/components/fields/TagField.tsx +325 -0
  45. package/admin/src/components/fields/TextAreaField.tsx +68 -0
  46. package/admin/src/components/fields/TextField.tsx +56 -0
  47. package/admin/src/components/fields/index.ts +54 -0
  48. package/admin/src/components/fields/registry.ts +64 -0
  49. package/admin/src/components/fields/types.ts +217 -0
  50. package/admin/src/components/filters/TaxonomyFilter.tsx +254 -0
  51. package/admin/src/components/filters/index.ts +1 -0
  52. package/admin/src/components/index.ts +8 -0
  53. package/admin/src/components/media/MediaAssetActions.tsx +115 -0
  54. package/admin/src/components/media/MediaAssetEditDialog.tsx +217 -0
  55. package/admin/src/components/media/MediaBulkActionBar.tsx +51 -0
  56. package/admin/src/components/media/MediaFolderActions.tsx +69 -0
  57. package/admin/src/components/media/MediaFolderEditDialog.tsx +126 -0
  58. package/admin/src/components/media/MediaMoveModal.tsx +179 -0
  59. package/admin/src/components/media/MediaPreviewModal.tsx +370 -0
  60. package/admin/src/components/media/MediaTaxonomyPicker.tsx +304 -0
  61. package/admin/src/components/media/MediaTrashBulkActionBar.tsx +59 -0
  62. package/admin/src/components/ui/accordion.tsx +64 -0
  63. package/admin/src/components/ui/alert-dialog.tsx +155 -0
  64. package/admin/src/components/ui/alert.tsx +66 -0
  65. package/admin/src/components/ui/avatar.tsx +53 -0
  66. package/admin/src/components/ui/badge.tsx +46 -0
  67. package/admin/src/components/ui/breadcrumb.tsx +109 -0
  68. package/admin/src/components/ui/button.tsx +62 -0
  69. package/admin/src/components/ui/calendar.tsx +220 -0
  70. package/admin/src/components/ui/card.tsx +92 -0
  71. package/admin/src/components/ui/checkbox.tsx +30 -0
  72. package/admin/src/components/ui/command.tsx +182 -0
  73. package/admin/src/components/ui/dialog.tsx +143 -0
  74. package/admin/src/components/ui/dropdown-menu.tsx +257 -0
  75. package/admin/src/components/ui/form.tsx +167 -0
  76. package/admin/src/components/ui/input.tsx +21 -0
  77. package/admin/src/components/ui/label.tsx +24 -0
  78. package/admin/src/components/ui/popover.tsx +46 -0
  79. package/admin/src/components/ui/scroll-area.tsx +56 -0
  80. package/admin/src/components/ui/select.tsx +190 -0
  81. package/admin/src/components/ui/separator.tsx +26 -0
  82. package/admin/src/components/ui/sheet.tsx +137 -0
  83. package/admin/src/components/ui/sidebar.tsx +724 -0
  84. package/admin/src/components/ui/skeleton.tsx +13 -0
  85. package/admin/src/components/ui/sonner.tsx +38 -0
  86. package/admin/src/components/ui/switch.tsx +31 -0
  87. package/admin/src/components/ui/table.tsx +114 -0
  88. package/admin/src/components/ui/tabs.tsx +66 -0
  89. package/admin/src/components/ui/textarea.tsx +18 -0
  90. package/admin/src/components/ui/tooltip.tsx +61 -0
  91. package/admin/src/contexts/AdminConfigContext.tsx +30 -0
  92. package/admin/src/contexts/AuthContext.tsx +330 -0
  93. package/admin/src/contexts/BreadcrumbContext.tsx +49 -0
  94. package/admin/src/contexts/SettingsConfigContext.tsx +57 -0
  95. package/admin/src/contexts/ThemeContext.tsx +91 -0
  96. package/admin/src/contexts/index.ts +20 -0
  97. package/admin/src/embed/components/EmbedHeader.tsx +103 -0
  98. package/admin/src/embed/components/EmbedLayout.tsx +29 -0
  99. package/admin/src/embed/components/EmbedSidebar.tsx +119 -0
  100. package/admin/src/embed/components/index.ts +3 -0
  101. package/admin/src/embed/contexts/ApiContext.tsx +32 -0
  102. package/admin/src/embed/index.tsx +184 -0
  103. package/admin/src/embed/navigation.tsx +202 -0
  104. package/admin/src/embed/pages/Content.tsx +19 -0
  105. package/admin/src/embed/pages/ContentTypes.tsx +19 -0
  106. package/admin/src/embed/pages/Dashboard.tsx +19 -0
  107. package/admin/src/embed/pages/Media.tsx +19 -0
  108. package/admin/src/embed/pages/Settings.tsx +22 -0
  109. package/admin/src/embed/pages/Taxonomies.tsx +22 -0
  110. package/admin/src/embed/pages/Trash.tsx +22 -0
  111. package/admin/src/embed/pages/index.ts +7 -0
  112. package/admin/src/embed/types.ts +24 -0
  113. package/admin/src/hooks/index.ts +2 -0
  114. package/admin/src/hooks/use-mobile.ts +19 -0
  115. package/admin/src/hooks/useBreadcrumbLabel.ts +15 -0
  116. package/admin/src/hooks/usePermissions.ts +211 -0
  117. package/admin/src/lib/admin-config.ts +111 -0
  118. package/admin/src/lib/cn.ts +6 -0
  119. package/admin/src/lib/config.server.ts +56 -0
  120. package/admin/src/lib/convex.ts +26 -0
  121. package/admin/src/lib/embed-adapter.ts +80 -0
  122. package/admin/src/lib/icons.tsx +96 -0
  123. package/admin/src/lib/loadAdminConfig.ts +92 -0
  124. package/admin/src/lib/motion.ts +29 -0
  125. package/admin/src/lib/navigation.ts +43 -0
  126. package/admin/src/lib/tanstack-adapter.ts +82 -0
  127. package/admin/src/pages/ContentPage.tsx +337 -0
  128. package/admin/src/pages/ContentTypesPage.tsx +457 -0
  129. package/admin/src/pages/DashboardPage.tsx +163 -0
  130. package/admin/src/pages/MediaPage.tsx +34 -0
  131. package/admin/src/pages/SettingsPage.tsx +486 -0
  132. package/admin/src/pages/TaxonomiesPage.tsx +289 -0
  133. package/admin/src/pages/TrashPage.tsx +421 -0
  134. package/admin/src/pages/index.ts +14 -0
  135. package/admin/src/routeTree.gen.ts +262 -0
  136. package/admin/src/router.tsx +22 -0
  137. package/admin/src/routes/__root.tsx +250 -0
  138. package/admin/src/routes/content-types.tsx +20 -0
  139. package/admin/src/routes/content.tsx +20 -0
  140. package/admin/src/routes/entries/$entryId.tsx +107 -0
  141. package/admin/src/routes/entries/new.$contentTypeId.tsx +69 -0
  142. package/admin/src/routes/entries/type/$contentTypeId.tsx +503 -0
  143. package/admin/src/routes/index.tsx +20 -0
  144. package/admin/src/routes/media.tsx +1095 -0
  145. package/admin/src/routes/settings.tsx +20 -0
  146. package/admin/src/routes/taxonomies.tsx +20 -0
  147. package/admin/src/routes/trash.tsx +20 -0
  148. package/admin/src/styles/globals.css +69 -0
  149. package/admin/src/styles/tailwind-config.css +74 -0
  150. package/admin/src/styles/theme.css +73 -0
  151. package/admin/src/types/index.ts +221 -0
  152. package/admin/src/utils/errorParsing.ts +163 -0
  153. package/admin/src/utils/index.ts +5 -0
  154. package/admin/src/vite-env.d.ts +14 -0
  155. package/admin/tailwind.preset.cjs +102 -0
  156. package/admin-dist/nitro.json +1 -1
  157. package/admin-dist/public/assets/{CmsEmptyState-CiMQwSQV.js → CmsEmptyState-CkqBIab3.js} +1 -1
  158. package/admin-dist/public/assets/{CmsPageHeader-ohOq0luT.js → CmsPageHeader-CUtl5MMG.js} +1 -1
  159. package/admin-dist/public/assets/{CmsStatusBadge-BdNf0V9v.js → CmsStatusBadge-CUYFgEe-.js} +1 -1
  160. package/admin-dist/public/assets/{CmsSurface-CWup6Jh7.js → CmsSurface-CsJfAVa3.js} +1 -1
  161. package/admin-dist/public/assets/{CmsToolbar-cEBlCHa3.js → CmsToolbar-CnfbcxeP.js} +1 -1
  162. package/admin-dist/public/assets/{ContentEntryEditor-BY5ypfUs.js → ContentEntryEditor-BU220CCy.js} +1 -1
  163. package/admin-dist/public/assets/TaxonomyFilter-CWCxC5HZ.js +1 -0
  164. package/admin-dist/public/assets/_contentTypeId-DK8cskRt.js +1 -0
  165. package/admin-dist/public/assets/{_entryId-BpSmrfAm.js → _entryId-CuVMExbb.js} +1 -1
  166. package/admin-dist/public/assets/{alert-Bf2l8kxw.js → alert-CF1BSzGR.js} +1 -1
  167. package/admin-dist/public/assets/{badge-qPrc4AUM.js → badge-CmuOIVKp.js} +1 -1
  168. package/admin-dist/public/assets/{circle-check-big-Dgozy3vV.js → circle-check-big-BKDVG6DU.js} +1 -1
  169. package/admin-dist/public/assets/{command-QOmNhlb0.js → command-XJxnF2Sd.js} +1 -1
  170. package/admin-dist/public/assets/content-QBUxdxbS.js +1 -0
  171. package/admin-dist/public/assets/content-types-CrNEm8Hf.js +2 -0
  172. package/admin-dist/public/assets/globals-B7Wsfh_v.css +1 -0
  173. package/admin-dist/public/assets/index-C7xOwudI.js +1 -0
  174. package/admin-dist/public/assets/{label-DCsUdvFh.js → label-CHCnXeBk.js} +1 -1
  175. package/admin-dist/public/assets/{link-2-Czw1N61H.js → link-2-Bb34judH.js} +1 -1
  176. package/admin-dist/public/assets/{list-DtCsXj8-.js → list-9Pzt48ld.js} +1 -1
  177. package/admin-dist/public/assets/{main-CXgkZMhe.js → main-CjQ2VI9L.js} +3 -3
  178. package/admin-dist/public/assets/media-Dc5PWt2Q.js +1 -0
  179. package/admin-dist/public/assets/{new._contentTypeId-CoTDxKzf.js → new._contentTypeId-C_I4YxIa.js} +1 -1
  180. package/admin-dist/public/assets/{plus-xCFJK0RC.js → plus-Ceef7DHk.js} +1 -1
  181. package/admin-dist/public/assets/{rotate-ccw-DIqK63wY.js → rotate-ccw-7k7-4VUq.js} +1 -1
  182. package/admin-dist/public/assets/{scroll-area-B-yrE66a.js → scroll-area-CC6wujnp.js} +1 -1
  183. package/admin-dist/public/assets/{search-CbCbboeU.js → search-DwoUV2pv.js} +1 -1
  184. package/admin-dist/public/assets/{select-Co3TZFJb.js → select-hOZTp8aC.js} +1 -1
  185. package/admin-dist/public/assets/{settings-BspTTv_o.js → settings-t2PbCZh4.js} +1 -1
  186. package/admin-dist/public/assets/{switch-CfavASmR.js → switch-jX2pDaNU.js} +1 -1
  187. package/admin-dist/public/assets/{tabs-CN5s5u2W.js → tabs-q4EbZk7c.js} +1 -1
  188. package/admin-dist/public/assets/{tanstack-adapter-npeE3RdY.js → tanstack-adapter-B-Glm4kH.js} +1 -1
  189. package/admin-dist/public/assets/taxonomies-kyk5P4ZW.js +1 -0
  190. package/admin-dist/public/assets/{textarea-BJ0XFZpT.js → textarea-B6SfBmr0.js} +1 -1
  191. package/admin-dist/public/assets/trash-BOCnIznD.js +1 -0
  192. package/admin-dist/public/assets/{triangle-alert-BZRcqsUg.js → triangle-alert-CXFIO_Gu.js} +1 -1
  193. package/admin-dist/public/assets/{useBreadcrumbLabel-DwZlwvFF.js → useBreadcrumbLabel-_6qBagc3.js} +1 -1
  194. package/admin-dist/public/assets/{usePermissions-C1JQhfqb.js → usePermissions-M1ijZ7a6.js} +1 -1
  195. package/admin-dist/server/_ssr/{CmsButton-B45JAKR1.mjs → CmsButton-DOiTVKQq.mjs} +1 -1
  196. package/admin-dist/server/_ssr/{CmsEmptyState-D_BQFAVR.mjs → CmsEmptyState-fbnGt3LD.mjs} +2 -2
  197. package/admin-dist/server/_ssr/{CmsPageHeader-CrUZA59A.mjs → CmsPageHeader-DHRrdOZa.mjs} +1 -1
  198. package/admin-dist/server/_ssr/{CmsStatusBadge-B-sj6yaj.mjs → CmsStatusBadge-s7obWbKZ.mjs} +2 -2
  199. package/admin-dist/server/_ssr/{CmsSurface-DKJZhpjk.mjs → CmsSurface-rFoYjb62.mjs} +1 -1
  200. package/admin-dist/server/_ssr/{CmsToolbar-ByaW5iXf.mjs → CmsToolbar-zTE45z2q.mjs} +2 -2
  201. package/admin-dist/server/_ssr/{ContentEntryEditor-D3_Jb1dq.mjs → ContentEntryEditor-BLoEjT_m.mjs} +12 -12
  202. package/admin-dist/server/_ssr/{TaxonomyFilter-BRJkuCtA.mjs → TaxonomyFilter-XAtaJC2z.mjs} +5 -5
  203. package/admin-dist/server/_ssr/{_contentTypeId-B9kA6CaM.mjs → _contentTypeId-Csl4822C.mjs} +13 -13
  204. package/admin-dist/server/_ssr/{_entryId-BddcMkZN.mjs → _entryId-D8alLFBx.mjs} +15 -15
  205. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BffZedId.mjs +4 -0
  206. package/admin-dist/server/_ssr/{command-CGtVr8Gb.mjs → command-C0Di14--.mjs} +1 -1
  207. package/admin-dist/server/_ssr/{content-D1tbeOd0.mjs → content-CT-FPsmV.mjs} +12 -55
  208. package/admin-dist/server/_ssr/{content-types-BZqY_BER.mjs → content-types-C8cBFdzE.mjs} +15 -46
  209. package/admin-dist/server/_ssr/{index-BIdq4xaC.mjs → index-BJtcrEc-.mjs} +5 -5
  210. package/admin-dist/server/_ssr/index.mjs +2 -2
  211. package/admin-dist/server/_ssr/{label-T-QNKAr6.mjs → label-qn2Afwl4.mjs} +1 -1
  212. package/admin-dist/server/_ssr/{media-C-xqjBrl.mjs → media-qv5IAsMZ.mjs} +14 -14
  213. package/admin-dist/server/_ssr/{new._contentTypeId-DWic9cRq.mjs → new._contentTypeId-DdGyrhqs.mjs} +13 -13
  214. package/admin-dist/server/_ssr/{router-D1BMAMJT.mjs → router-nSVkxb6Y.mjs} +11 -11
  215. package/admin-dist/server/_ssr/{scroll-area-C0pic_WA.mjs → scroll-area-BCinP455.mjs} +1 -1
  216. package/admin-dist/server/_ssr/{select-CqmuN2F6.mjs → select-BKQlQScw.mjs} +1 -1
  217. package/admin-dist/server/_ssr/{settings-CAkncGGV.mjs → settings-BCr2KQlk.mjs} +55 -40
  218. package/admin-dist/server/_ssr/{switch-CgmuJkT9.mjs → switch-BaOi42fE.mjs} +1 -1
  219. package/admin-dist/server/_ssr/{tabs-CnMj0aRy.mjs → tabs-DYXEi9kq.mjs} +2 -2
  220. package/admin-dist/server/_ssr/{tanstack-adapter-BXZrMauE.mjs → tanstack-adapter-Bsz8kha-.mjs} +1 -1
  221. package/admin-dist/server/_ssr/{taxonomies-thl3BfVm.mjs → taxonomies-CueMHTbE.mjs} +30 -19
  222. package/admin-dist/server/_ssr/{textarea-4K5OJgeh.mjs → textarea-CI0Jqx2x.mjs} +1 -1
  223. package/admin-dist/server/_ssr/{trash-B40Gx5zP.mjs → trash-DE6W8GoX.mjs} +20 -17
  224. package/admin-dist/server/_ssr/{useBreadcrumbLabel-rn-fL4zV.mjs → useBreadcrumbLabel-B5Yi72lM.mjs} +1 -1
  225. package/admin-dist/server/_ssr/{usePermissions-CKeM6_Vw.mjs → usePermissions-C3nZ-Izm.mjs} +1 -1
  226. package/admin-dist/server/index.mjs +183 -190
  227. package/dist/client/admin/bulk.d.ts +79 -0
  228. package/dist/client/admin/bulk.d.ts.map +1 -0
  229. package/dist/client/admin/bulk.js +72 -0
  230. package/dist/client/admin/bulk.js.map +1 -0
  231. package/dist/client/admin/contentLock.d.ts +118 -0
  232. package/dist/client/admin/contentLock.d.ts.map +1 -0
  233. package/dist/client/admin/contentLock.js +81 -0
  234. package/dist/client/admin/contentLock.js.map +1 -0
  235. package/dist/client/{adminApi.d.ts → admin/contentTypes.d.ts} +39 -1134
  236. package/dist/client/admin/contentTypes.d.ts.map +1 -0
  237. package/dist/client/admin/contentTypes.js +122 -0
  238. package/dist/client/admin/contentTypes.js.map +1 -0
  239. package/dist/client/admin/dashboard.d.ts +16 -0
  240. package/dist/client/admin/dashboard.d.ts.map +1 -0
  241. package/dist/client/admin/dashboard.js +33 -0
  242. package/dist/client/admin/dashboard.js.map +1 -0
  243. package/dist/client/admin/entries.d.ts +358 -0
  244. package/dist/client/admin/entries.d.ts.map +1 -0
  245. package/dist/client/admin/entries.js +220 -0
  246. package/dist/client/admin/entries.js.map +1 -0
  247. package/dist/client/admin/index.d.ts +6568 -0
  248. package/dist/client/admin/index.d.ts.map +1 -0
  249. package/dist/client/admin/index.js +305 -0
  250. package/dist/client/admin/index.js.map +1 -0
  251. package/dist/client/admin/media.d.ts +1038 -0
  252. package/dist/client/admin/media.d.ts.map +1 -0
  253. package/dist/client/admin/media.js +489 -0
  254. package/dist/client/admin/media.js.map +1 -0
  255. package/dist/client/admin/taxonomies.d.ts +339 -0
  256. package/dist/client/admin/taxonomies.d.ts.map +1 -0
  257. package/dist/client/admin/taxonomies.js +364 -0
  258. package/dist/client/admin/taxonomies.js.map +1 -0
  259. package/dist/client/admin/trash.d.ts +91 -0
  260. package/dist/client/admin/trash.d.ts.map +1 -0
  261. package/dist/client/admin/trash.js +71 -0
  262. package/dist/client/admin/trash.js.map +1 -0
  263. package/dist/client/admin/types.d.ts +320 -0
  264. package/dist/client/admin/types.d.ts.map +1 -0
  265. package/dist/client/admin/types.js +7 -0
  266. package/dist/client/admin/types.js.map +1 -0
  267. package/dist/client/admin/validators.d.ts +3886 -0
  268. package/dist/client/admin/validators.d.ts.map +1 -0
  269. package/dist/client/admin/validators.js +322 -0
  270. package/dist/client/admin/validators.js.map +1 -0
  271. package/dist/client/admin/versions.d.ts +106 -0
  272. package/dist/client/admin/versions.d.ts.map +1 -0
  273. package/dist/client/admin/versions.js +57 -0
  274. package/dist/client/admin/versions.js.map +1 -0
  275. package/dist/client/adminApiTypes.d.ts +27 -0
  276. package/dist/client/adminApiTypes.d.ts.map +1 -0
  277. package/dist/client/adminApiTypes.js +12 -0
  278. package/dist/client/adminApiTypes.js.map +1 -0
  279. package/dist/client/{admin-config.d.ts → adminConfig.d.ts} +2 -2
  280. package/dist/client/adminConfig.d.ts.map +1 -0
  281. package/dist/client/{admin-config.js → adminConfig.js} +1 -1
  282. package/dist/client/adminConfig.js.map +1 -0
  283. package/dist/client/agentTools.d.ts +4 -4
  284. package/dist/client/index.d.ts +2 -2
  285. package/dist/client/index.d.ts.map +1 -1
  286. package/dist/client/index.js +15 -2
  287. package/dist/client/index.js.map +1 -1
  288. package/dist/component/contentEntries.d.ts +4 -4
  289. package/dist/component/contentEntryMutations.d.ts +46 -0
  290. package/dist/component/contentEntryMutations.d.ts.map +1 -1
  291. package/dist/component/contentEntryMutations.js +1 -1
  292. package/dist/component/contentEntryMutations.js.map +1 -1
  293. package/dist/component/contentTypeMigration.d.ts +1 -1
  294. package/dist/component/contentTypeMutations.d.ts +22 -0
  295. package/dist/component/contentTypeMutations.d.ts.map +1 -1
  296. package/dist/component/contentTypeMutations.js +1 -1
  297. package/dist/component/contentTypeMutations.js.map +1 -1
  298. package/dist/component/mediaAssetMutations.d.ts +47 -0
  299. package/dist/component/mediaAssetMutations.d.ts.map +1 -1
  300. package/dist/component/mediaAssetMutations.js +1 -1
  301. package/dist/component/mediaAssetMutations.js.map +1 -1
  302. package/dist/component/schema.d.ts +9 -0
  303. package/dist/component/schema.d.ts.map +1 -1
  304. package/dist/component/schema.js +1 -1
  305. package/dist/component/schema.js.map +1 -1
  306. package/package.json +85 -3
  307. package/admin-dist/public/assets/ErrorState-C4nJ-ml4.js +0 -1
  308. package/admin-dist/public/assets/TaxonomyFilter-BgE_SR_O.js +0 -1
  309. package/admin-dist/public/assets/_contentTypeId-DtZectcC.js +0 -1
  310. package/admin-dist/public/assets/content-OEBGlxg1.js +0 -1
  311. package/admin-dist/public/assets/content-types-CjQliqVV.js +0 -2
  312. package/admin-dist/public/assets/globals-hAmgC66w.css +0 -1
  313. package/admin-dist/public/assets/index-BH_ECMhv.js +0 -1
  314. package/admin-dist/public/assets/media-DTJ3-ViE.js +0 -1
  315. package/admin-dist/public/assets/taxonomies-CgG46fIF.js +0 -1
  316. package/admin-dist/public/assets/trash-B3daldm5.js +0 -1
  317. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +0 -89
  318. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dd7AmelK.mjs +0 -4
  319. package/dist/client/admin-config.d.ts.map +0 -1
  320. package/dist/client/admin-config.js.map +0 -1
  321. package/dist/client/adminApi.d.ts.map +0 -1
  322. package/dist/client/adminApi.js +0 -736
  323. package/dist/client/adminApi.js.map +0 -1
@@ -0,0 +1,386 @@
1
+ import { useState, useCallback, useMemo } from 'react'
2
+ import { useQuery } from 'convex/react'
3
+ import { api } from '../../../convex/_generated/api'
4
+ import { FieldWrapper } from './FieldWrapper'
5
+ import type { BaseFieldProps } from './types'
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ DialogFooter,
12
+ } from '~/components/ui/dialog'
13
+ import {
14
+ Command,
15
+ CommandEmpty,
16
+ CommandGroup,
17
+ CommandInput,
18
+ CommandItem,
19
+ CommandList,
20
+ } from '~/components/ui/command'
21
+ import {
22
+ Select,
23
+ SelectContent,
24
+ SelectItem,
25
+ SelectTrigger,
26
+ SelectValue,
27
+ } from '~/components/ui/select'
28
+ import { Badge } from '~/components/ui/badge'
29
+ import { CmsButton } from '~/components/cmsds/CmsButton'
30
+ import { CmsStatusBadge, type ContentStatus } from '~/components/cmsds/CmsStatusBadge'
31
+ import { CmsEmptyState } from '~/components/cmsds/CmsEmptyState'
32
+ import { cn } from '~/lib/cn'
33
+ import { FileText, Link2, X, Check, Plus } from 'lucide-react'
34
+
35
+ export interface ReferenceFieldProps extends BaseFieldProps<string | string[] | null> {
36
+ placeholder?: string
37
+ }
38
+
39
+ function getEntryDisplayTitle(entry: {
40
+ data?: Record<string, unknown>
41
+ slug?: string
42
+ _id: string
43
+ }): string {
44
+ const titleFields = ['title', 'name', 'heading', 'label']
45
+ for (const field of titleFields) {
46
+ const value = entry.data?.[field]
47
+ if (typeof value === 'string' && value.trim()) {
48
+ return value
49
+ }
50
+ }
51
+ return entry.slug || entry._id
52
+ }
53
+
54
+ export function ReferenceField({
55
+ field,
56
+ value,
57
+ onChange,
58
+ error,
59
+ disabled = false,
60
+ readOnly = false,
61
+ className = '',
62
+ id,
63
+ placeholder = 'Select content...',
64
+ }: ReferenceFieldProps) {
65
+ const fieldId = id || `field-${field.name}`
66
+ const [showPicker, setShowPicker] = useState(false)
67
+ const [searchQuery, setSearchQuery] = useState('')
68
+ const [contentTypeFilter, setContentTypeFilter] = useState<string>('')
69
+
70
+ const allowedContentTypes = field.options?.allowedContentTypes ?? []
71
+ const allowMultiple = field.options?.multiple ?? false
72
+
73
+ const selectedIds = useMemo(() => {
74
+ if (!value) return []
75
+ return Array.isArray(value) ? value : [value]
76
+ }, [value])
77
+
78
+ const contentTypes = useQuery(api.contentTypes.list, {
79
+ isActive: true,
80
+ includeEntryCounts: false,
81
+ })
82
+
83
+ const filteredContentTypes = useMemo(() => {
84
+ if (!contentTypes?.page) return []
85
+ if (allowedContentTypes.length === 0) return contentTypes.page
86
+ return contentTypes.page.filter(
87
+ (ct) =>
88
+ allowedContentTypes.includes(ct.name) ||
89
+ allowedContentTypes.includes(ct._id)
90
+ )
91
+ }, [contentTypes?.page, allowedContentTypes])
92
+
93
+ const selectedEntry = useQuery(
94
+ api.entries.get,
95
+ selectedIds.length === 1 ? { id: selectedIds[0] } : 'skip'
96
+ )
97
+
98
+ const selectedEntries = useQuery(
99
+ api.entries.list,
100
+ selectedIds.length > 1
101
+ ? {
102
+ paginationOpts: { numItems: 100, cursor: null },
103
+ }
104
+ : 'skip'
105
+ )
106
+
107
+ const multipleSelectedEntries = useMemo(() => {
108
+ if (!selectedEntries?.page || selectedIds.length <= 1) return []
109
+ return selectedEntries.page.filter((entry) => selectedIds.includes(entry._id))
110
+ }, [selectedEntries?.page, selectedIds])
111
+
112
+ const entriesResult = useQuery(
113
+ api.entries.list,
114
+ showPicker
115
+ ? {
116
+ contentTypeId: contentTypeFilter || undefined,
117
+ search: searchQuery || undefined,
118
+ paginationOpts: { numItems: 50, cursor: null },
119
+ }
120
+ : 'skip'
121
+ )
122
+
123
+ const filteredEntries = useMemo(() => {
124
+ if (!entriesResult?.page) return []
125
+ if (allowedContentTypes.length === 0) return entriesResult.page
126
+
127
+ const allowedIds = filteredContentTypes.map((ct) => ct._id)
128
+ return entriesResult.page.filter((entry) =>
129
+ allowedIds.includes(entry.contentTypeId)
130
+ )
131
+ }, [entriesResult?.page, allowedContentTypes, filteredContentTypes])
132
+
133
+ const getContentTypeName = useCallback(
134
+ (contentTypeId: string) => {
135
+ const ct = contentTypes?.page?.find((c) => c._id === contentTypeId)
136
+ return ct?.displayName || ct?.name || 'Unknown'
137
+ },
138
+ [contentTypes?.page]
139
+ )
140
+
141
+ const handleSelect = useCallback(
142
+ (entryId: string) => {
143
+ if (allowMultiple) {
144
+ if (selectedIds.includes(entryId)) {
145
+ const newIds = selectedIds.filter((id) => id !== entryId)
146
+ onChange(newIds.length > 0 ? newIds : null)
147
+ } else {
148
+ onChange([...selectedIds, entryId])
149
+ }
150
+ } else {
151
+ onChange(entryId)
152
+ setShowPicker(false)
153
+ }
154
+ },
155
+ [allowMultiple, selectedIds, onChange]
156
+ )
157
+
158
+ const handleRemove = useCallback(
159
+ (entryId: string) => {
160
+ if (allowMultiple) {
161
+ const newIds = selectedIds.filter((id) => id !== entryId)
162
+ onChange(newIds.length > 0 ? newIds : null)
163
+ } else {
164
+ onChange(null)
165
+ }
166
+ },
167
+ [allowMultiple, selectedIds, onChange]
168
+ )
169
+
170
+ const handleClear = useCallback(() => {
171
+ onChange(null)
172
+ }, [onChange])
173
+
174
+ const renderSelectedEntry = (
175
+ entry: {
176
+ _id: string
177
+ data?: Record<string, unknown>
178
+ slug?: string
179
+ status: string
180
+ contentTypeId: string
181
+ },
182
+ showRemove = true
183
+ ) => (
184
+ <div
185
+ key={entry._id}
186
+ className="flex items-center gap-3 rounded-lg border border-border bg-card p-3"
187
+ >
188
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted">
189
+ <FileText className="size-4 text-muted-foreground" />
190
+ </div>
191
+ <div className="min-w-0 flex-1">
192
+ <p className="truncate text-sm font-medium text-foreground">
193
+ {getEntryDisplayTitle(entry)}
194
+ </p>
195
+ <div className="flex items-center gap-2">
196
+ <span className="text-xs text-muted-foreground">
197
+ {getContentTypeName(entry.contentTypeId)}
198
+ </span>
199
+ <CmsStatusBadge status={entry.status as ContentStatus} />
200
+ </div>
201
+ </div>
202
+ {showRemove && !disabled && !readOnly && (
203
+ <CmsButton
204
+ variant="ghost"
205
+ size="icon-sm"
206
+ onClick={(e) => {
207
+ e.stopPropagation()
208
+ handleRemove(entry._id)
209
+ }}
210
+ title="Remove reference"
211
+ >
212
+ <X className="size-4" />
213
+ </CmsButton>
214
+ )}
215
+ </div>
216
+ )
217
+
218
+ return (
219
+ <FieldWrapper field={field} error={error} className={className} id={fieldId}>
220
+ <div className="space-y-2">
221
+ {selectedIds.length > 0 ? (
222
+ <div className="space-y-2">
223
+ {selectedIds.length === 1 && selectedEntry && renderSelectedEntry(selectedEntry)}
224
+
225
+ {selectedIds.length > 1 && multipleSelectedEntries.length > 0 && (
226
+ <div className="space-y-2">
227
+ {multipleSelectedEntries.map((entry) => renderSelectedEntry(entry))}
228
+ </div>
229
+ )}
230
+
231
+ {!disabled && !readOnly && (
232
+ <div className="flex items-center gap-2">
233
+ <CmsButton
234
+ variant="outline"
235
+ size="sm"
236
+ onClick={() => setShowPicker(true)}
237
+ >
238
+ <Plus className="size-4" />
239
+ {allowMultiple ? 'Add more' : 'Change'}
240
+ </CmsButton>
241
+ {selectedIds.length > 1 && (
242
+ <CmsButton variant="ghost" size="sm" onClick={handleClear}>
243
+ Clear all
244
+ </CmsButton>
245
+ )}
246
+ </div>
247
+ )}
248
+ </div>
249
+ ) : (
250
+ <button
251
+ type="button"
252
+ className={cn(
253
+ 'flex w-full flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-border bg-muted/30 p-6 text-center transition-colors',
254
+ 'hover:border-primary/50 hover:bg-muted/50',
255
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
256
+ disabled && 'cursor-not-allowed opacity-50'
257
+ )}
258
+ onClick={() => setShowPicker(true)}
259
+ disabled={disabled || readOnly}
260
+ >
261
+ <div className="flex size-10 items-center justify-center rounded-full bg-muted">
262
+ <Link2 className="size-5 text-muted-foreground" />
263
+ </div>
264
+ <span className="text-sm font-medium text-foreground">{placeholder}</span>
265
+ <span className="text-xs text-muted-foreground">
266
+ Click to select {allowMultiple ? 'content entries' : 'a content entry'}
267
+ </span>
268
+ </button>
269
+ )}
270
+ </div>
271
+
272
+ <Dialog open={showPicker} onOpenChange={setShowPicker}>
273
+ <DialogContent className="max-w-lg p-0">
274
+ <DialogHeader className="px-4 pt-4">
275
+ <DialogTitle>Select Content</DialogTitle>
276
+ </DialogHeader>
277
+
278
+ <Command className="border-none">
279
+ <div className="flex items-center gap-2 border-b px-3 pb-2">
280
+ <CommandInput
281
+ placeholder="Search entries..."
282
+ value={searchQuery}
283
+ onValueChange={setSearchQuery}
284
+ className="border-none shadow-none focus:ring-0"
285
+ />
286
+ {filteredContentTypes.length > 1 && (
287
+ <Select
288
+ value={contentTypeFilter || 'all'}
289
+ onValueChange={(v) => setContentTypeFilter(v === 'all' ? '' : v)}
290
+ >
291
+ <SelectTrigger className="h-8 w-[120px] shrink-0">
292
+ <SelectValue placeholder="All Types" />
293
+ </SelectTrigger>
294
+ <SelectContent>
295
+ <SelectItem value="all">All Types</SelectItem>
296
+ {filteredContentTypes.map((ct) => (
297
+ <SelectItem key={ct._id} value={ct._id}>
298
+ {ct.displayName || ct.name}
299
+ </SelectItem>
300
+ ))}
301
+ </SelectContent>
302
+ </Select>
303
+ )}
304
+ </div>
305
+
306
+ <CommandList className="max-h-[300px]">
307
+ {entriesResult === undefined ? (
308
+ <div className="flex flex-col items-center justify-center py-8">
309
+ <div className="size-5 animate-spin rounded-full border-2 border-muted border-t-primary" />
310
+ <p className="mt-2 text-sm text-muted-foreground">Loading...</p>
311
+ </div>
312
+ ) : filteredEntries.length > 0 ? (
313
+ <CommandGroup>
314
+ {filteredEntries.map((entry) => {
315
+ const isSelected = selectedIds.includes(entry._id)
316
+ return (
317
+ <CommandItem
318
+ key={entry._id}
319
+ value={entry._id}
320
+ onSelect={() => handleSelect(entry._id)}
321
+ className="cursor-pointer"
322
+ >
323
+ <div className="flex size-8 shrink-0 items-center justify-center rounded bg-muted">
324
+ <FileText className="size-4 text-muted-foreground" />
325
+ </div>
326
+ <div className="min-w-0 flex-1">
327
+ <p className="truncate text-sm font-medium">
328
+ {getEntryDisplayTitle(entry)}
329
+ </p>
330
+ <div className="flex items-center gap-2">
331
+ <span className="text-xs text-muted-foreground">
332
+ {getContentTypeName(entry.contentTypeId)}
333
+ </span>
334
+ <Badge
335
+ variant="secondary"
336
+ className={cn(
337
+ 'px-1.5 py-0 text-[10px]',
338
+ entry.status === 'published' && 'status-published',
339
+ entry.status === 'draft' && 'status-draft',
340
+ entry.status === 'scheduled' && 'status-scheduled',
341
+ entry.status === 'archived' && 'status-archived'
342
+ )}
343
+ >
344
+ {entry.status}
345
+ </Badge>
346
+ </div>
347
+ </div>
348
+ {isSelected && (
349
+ <Check className="size-4 shrink-0 text-primary" />
350
+ )}
351
+ </CommandItem>
352
+ )
353
+ })}
354
+ </CommandGroup>
355
+ ) : (
356
+ <CommandEmpty>
357
+ <CmsEmptyState
358
+ icon={<Link2 className="size-6" />}
359
+ title="No content found"
360
+ description={
361
+ searchQuery
362
+ ? 'Try adjusting your search'
363
+ : 'Create some content entries first'
364
+ }
365
+ className="py-6"
366
+ />
367
+ </CommandEmpty>
368
+ )}
369
+ </CommandList>
370
+ </Command>
371
+
372
+ <DialogFooter className="border-t px-4 py-3">
373
+ {allowMultiple && selectedIds.length > 0 && (
374
+ <span className="mr-auto text-sm text-muted-foreground">
375
+ {selectedIds.length} selected
376
+ </span>
377
+ )}
378
+ <CmsButton variant="secondary" onClick={() => setShowPicker(false)}>
379
+ {allowMultiple ? 'Done' : 'Cancel'}
380
+ </CmsButton>
381
+ </DialogFooter>
382
+ </DialogContent>
383
+ </Dialog>
384
+ </FieldWrapper>
385
+ )
386
+ }
@@ -0,0 +1,171 @@
1
+ import { useId, type ChangeEvent, useState, useCallback } from 'react'
2
+ import { FieldWrapper } from './FieldWrapper'
3
+ import type { RichTextFieldProps } from './types'
4
+ import { Textarea } from '~/components/ui/textarea'
5
+ import { Button } from '~/components/ui/button'
6
+ import { Separator } from '~/components/ui/separator'
7
+ import { cn } from '~/lib/cn'
8
+ import { Bold, Italic, Heading2, Link, List, Quote, Code } from 'lucide-react'
9
+
10
+ interface ToolbarAction {
11
+ label: string
12
+ icon: React.ReactNode
13
+ prefix: string
14
+ suffix: string
15
+ block?: boolean
16
+ }
17
+
18
+ const toolbarActions: ToolbarAction[] = [
19
+ { label: 'Bold', icon: <Bold className="size-4" />, prefix: '**', suffix: '**' },
20
+ { label: 'Italic', icon: <Italic className="size-4" />, prefix: '_', suffix: '_' },
21
+ { label: 'Heading', icon: <Heading2 className="size-4" />, prefix: '## ', suffix: '', block: true },
22
+ { label: 'Link', icon: <Link className="size-4" />, prefix: '[', suffix: '](url)' },
23
+ { label: 'List', icon: <List className="size-4" />, prefix: '- ', suffix: '', block: true },
24
+ { label: 'Quote', icon: <Quote className="size-4" />, prefix: '> ', suffix: '', block: true },
25
+ { label: 'Code', icon: <Code className="size-4" />, prefix: '`', suffix: '`' },
26
+ ]
27
+
28
+ export function RichTextField({
29
+ field,
30
+ value,
31
+ onChange,
32
+ error,
33
+ disabled = false,
34
+ readOnly = false,
35
+ className = '',
36
+ id: providedId,
37
+ placeholder = 'Write your content here... (Markdown supported)',
38
+ minHeight = 200,
39
+ }: RichTextFieldProps) {
40
+ const generatedId = useId()
41
+ const id = providedId ?? `field-${field.name}-${generatedId}`
42
+ const [textareaRef, setTextareaRef] = useState<HTMLTextAreaElement | null>(null)
43
+
44
+ const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
45
+ onChange(e.target.value)
46
+ }
47
+
48
+ const applyFormatting = useCallback(
49
+ (action: ToolbarAction) => {
50
+ if (!textareaRef || disabled || readOnly) return
51
+
52
+ const start = textareaRef.selectionStart
53
+ const end = textareaRef.selectionEnd
54
+ const selectedText = value.slice(start, end)
55
+ const currentValue = value
56
+
57
+ let newValue: string
58
+ let newCursorPos: number
59
+
60
+ if (action.block) {
61
+ const lineStart = currentValue.lastIndexOf('\n', start - 1) + 1
62
+ const beforeLine = currentValue.slice(0, lineStart)
63
+ const afterLine = currentValue.slice(lineStart)
64
+
65
+ if (afterLine.startsWith(action.prefix)) {
66
+ newValue = beforeLine + afterLine.slice(action.prefix.length)
67
+ newCursorPos = start - action.prefix.length
68
+ } else {
69
+ newValue = beforeLine + action.prefix + afterLine
70
+ newCursorPos = start + action.prefix.length
71
+ }
72
+ } else {
73
+ if (selectedText) {
74
+ newValue =
75
+ currentValue.slice(0, start) +
76
+ action.prefix +
77
+ selectedText +
78
+ action.suffix +
79
+ currentValue.slice(end)
80
+ newCursorPos = end + action.prefix.length + action.suffix.length
81
+ } else {
82
+ const placeholderText = action.label.toLowerCase()
83
+ newValue =
84
+ currentValue.slice(0, start) +
85
+ action.prefix +
86
+ placeholderText +
87
+ action.suffix +
88
+ currentValue.slice(end)
89
+ newCursorPos = start + action.prefix.length
90
+ }
91
+ }
92
+
93
+ onChange(newValue)
94
+
95
+ requestAnimationFrame(() => {
96
+ if (textareaRef) {
97
+ textareaRef.focus()
98
+ textareaRef.setSelectionRange(newCursorPos, newCursorPos)
99
+ }
100
+ })
101
+ },
102
+ [textareaRef, value, onChange, disabled, readOnly]
103
+ )
104
+
105
+ const { maxLength } = field.options ?? {}
106
+
107
+ return (
108
+ <FieldWrapper field={field} error={error} className={className} id={id}>
109
+ <div
110
+ className={cn(
111
+ 'overflow-hidden rounded-md border border-input',
112
+ error && 'border-destructive'
113
+ )}
114
+ >
115
+ {!readOnly && !disabled && (
116
+ <>
117
+ <div
118
+ className="flex flex-wrap gap-1 bg-muted/50 p-1.5"
119
+ role="toolbar"
120
+ aria-label="Formatting options"
121
+ >
122
+ {toolbarActions.map((action) => (
123
+ <Button
124
+ key={action.label}
125
+ type="button"
126
+ variant="ghost"
127
+ size="icon"
128
+ className="size-7"
129
+ onClick={() => applyFormatting(action)}
130
+ title={action.label}
131
+ aria-label={action.label}
132
+ >
133
+ {action.icon}
134
+ </Button>
135
+ ))}
136
+ </div>
137
+ <Separator />
138
+ </>
139
+ )}
140
+
141
+ <Textarea
142
+ ref={setTextareaRef}
143
+ id={id}
144
+ name={field.name}
145
+ value={value ?? ''}
146
+ onChange={handleChange}
147
+ disabled={disabled}
148
+ readOnly={readOnly}
149
+ required={field.required}
150
+ maxLength={maxLength}
151
+ placeholder={placeholder}
152
+ className="resize-none rounded-none border-0 focus-visible:ring-0"
153
+ style={{ minHeight: `${minHeight}px` }}
154
+ aria-invalid={!!error}
155
+ aria-describedby={
156
+ error ? `${id}-error` : field.description ? `${id}-description` : undefined
157
+ }
158
+ />
159
+
160
+ <div className="flex items-center justify-between border-t bg-muted/30 px-3 py-1.5 text-xs text-muted-foreground">
161
+ <span>Markdown supported</span>
162
+ {maxLength && (
163
+ <span>
164
+ {(value ?? '').length} / {maxLength}
165
+ </span>
166
+ )}
167
+ </div>
168
+ </div>
169
+ </FieldWrapper>
170
+ )
171
+ }
@@ -0,0 +1,62 @@
1
+ import { useId } from 'react'
2
+ import { FieldWrapper } from './FieldWrapper'
3
+ import type { BaseFieldProps } from './types'
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from '~/components/ui/select'
11
+ import { cn } from '~/lib/cn'
12
+
13
+ export interface SelectFieldProps extends BaseFieldProps<string> {
14
+ placeholder?: string
15
+ }
16
+
17
+ export function SelectField({
18
+ field,
19
+ value,
20
+ onChange,
21
+ error,
22
+ disabled = false,
23
+ readOnly = false,
24
+ className = '',
25
+ id,
26
+ placeholder = 'Select an option...',
27
+ }: SelectFieldProps) {
28
+ const generatedId = useId()
29
+ const fieldId = id ?? `field-${field.name}-${generatedId}`
30
+ const options = field.options?.options ?? []
31
+
32
+ const handleChange = (newValue: string) => {
33
+ onChange(newValue)
34
+ }
35
+
36
+ return (
37
+ <FieldWrapper field={field} error={error} className={className} id={fieldId}>
38
+ <Select
39
+ value={value ?? ''}
40
+ onValueChange={handleChange}
41
+ disabled={disabled || readOnly}
42
+ required={field.required}
43
+ >
44
+ <SelectTrigger
45
+ id={fieldId}
46
+ className={cn(error && 'border-destructive focus:ring-destructive')}
47
+ aria-invalid={!!error}
48
+ aria-describedby={error ? `${fieldId}-error` : undefined}
49
+ >
50
+ <SelectValue placeholder={placeholder} />
51
+ </SelectTrigger>
52
+ <SelectContent>
53
+ {options.map((option) => (
54
+ <SelectItem key={option.value} value={option.value}>
55
+ {option.label}
56
+ </SelectItem>
57
+ ))}
58
+ </SelectContent>
59
+ </Select>
60
+ </FieldWrapper>
61
+ )
62
+ }