@drawnagency/primitives 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 (517) hide show
  1. package/README.md +28 -0
  2. package/dist/auth/cookies.d.ts +8 -0
  3. package/dist/auth/cookies.d.ts.map +1 -0
  4. package/dist/auth/cookies.js +44 -0
  5. package/dist/auth/errors.d.ts +8 -0
  6. package/dist/auth/errors.d.ts.map +1 -0
  7. package/dist/auth/errors.js +10 -0
  8. package/dist/auth/index.d.ts +5 -0
  9. package/dist/auth/index.d.ts.map +1 -0
  10. package/dist/auth/index.js +3 -0
  11. package/dist/auth/security.d.ts +13 -0
  12. package/dist/auth/security.d.ts.map +1 -0
  13. package/dist/auth/security.js +48 -0
  14. package/dist/auth/types.d.ts +64 -0
  15. package/dist/auth/types.d.ts.map +1 -0
  16. package/dist/auth/types.js +1 -0
  17. package/dist/components/brandguide/ColorSwatchSettings.d.ts +8 -0
  18. package/dist/components/brandguide/ColorSwatchSettings.d.ts.map +1 -0
  19. package/dist/components/brandguide/ColorSwatchSettings.js +10 -0
  20. package/dist/components/brandguide/Colors.d.ts +15 -0
  21. package/dist/components/brandguide/Colors.d.ts.map +1 -0
  22. package/dist/components/brandguide/Colors.js +79 -0
  23. package/dist/components/brandguide/DoDontList.d.ts +16 -0
  24. package/dist/components/brandguide/DoDontList.d.ts.map +1 -0
  25. package/dist/components/brandguide/DoDontList.js +22 -0
  26. package/dist/components/brandguide/DoDontMediaGrid.d.ts +16 -0
  27. package/dist/components/brandguide/DoDontMediaGrid.d.ts.map +1 -0
  28. package/dist/components/brandguide/DoDontMediaGrid.js +5 -0
  29. package/dist/components/editor/AudiencePicker.d.ts +9 -0
  30. package/dist/components/editor/AudiencePicker.d.ts.map +1 -0
  31. package/dist/components/editor/AudiencePicker.js +24 -0
  32. package/dist/components/editor/DeleteButton.d.ts +6 -0
  33. package/dist/components/editor/DeleteButton.d.ts.map +1 -0
  34. package/dist/components/editor/DeleteButton.js +6 -0
  35. package/dist/components/editor/DragHandle.d.ts +2 -0
  36. package/dist/components/editor/DragHandle.d.ts.map +1 -0
  37. package/dist/components/editor/DragHandle.js +8 -0
  38. package/dist/components/editor/InsertButton.d.ts +7 -0
  39. package/dist/components/editor/InsertButton.d.ts.map +1 -0
  40. package/dist/components/editor/InsertButton.js +7 -0
  41. package/dist/components/editor/SectionWrapper.d.ts +3 -0
  42. package/dist/components/editor/SectionWrapper.d.ts.map +1 -0
  43. package/dist/components/editor/SectionWrapper.js +135 -0
  44. package/dist/components/editor/SettingsButton.d.ts +6 -0
  45. package/dist/components/editor/SettingsButton.d.ts.map +1 -0
  46. package/dist/components/editor/SettingsButton.js +6 -0
  47. package/dist/components/editor/SettingsForm.d.ts +13 -0
  48. package/dist/components/editor/SettingsForm.d.ts.map +1 -0
  49. package/dist/components/editor/SettingsForm.js +64 -0
  50. package/dist/components/editor/StatusBadge.d.ts +7 -0
  51. package/dist/components/editor/StatusBadge.d.ts.map +1 -0
  52. package/dist/components/editor/StatusBadge.js +10 -0
  53. package/dist/components/editor/StatusPicker.d.ts +9 -0
  54. package/dist/components/editor/StatusPicker.d.ts.map +1 -0
  55. package/dist/components/editor/StatusPicker.js +30 -0
  56. package/dist/components/editor/index.d.ts +8 -0
  57. package/dist/components/editor/index.d.ts.map +1 -0
  58. package/dist/components/editor/index.js +7 -0
  59. package/dist/components/primitives/CustomParagraph.d.ts +2 -0
  60. package/dist/components/primitives/CustomParagraph.d.ts.map +1 -0
  61. package/dist/components/primitives/CustomParagraph.js +24 -0
  62. package/dist/components/primitives/EditableGrid.d.ts +19 -0
  63. package/dist/components/primitives/EditableGrid.d.ts.map +1 -0
  64. package/dist/components/primitives/EditableGrid.js +90 -0
  65. package/dist/components/primitives/EditableList.d.ts +15 -0
  66. package/dist/components/primitives/EditableList.d.ts.map +1 -0
  67. package/dist/components/primitives/EditableList.js +54 -0
  68. package/dist/components/primitives/EditablePlainText.d.ts +12 -0
  69. package/dist/components/primitives/EditablePlainText.d.ts.map +1 -0
  70. package/dist/components/primitives/EditablePlainText.js +52 -0
  71. package/dist/components/primitives/EditableRichText.d.ts +11 -0
  72. package/dist/components/primitives/EditableRichText.d.ts.map +1 -0
  73. package/dist/components/primitives/EditableRichText.js +86 -0
  74. package/dist/components/primitives/HeadingSection.d.ts +10 -0
  75. package/dist/components/primitives/HeadingSection.d.ts.map +1 -0
  76. package/dist/components/primitives/HeadingSection.js +7 -0
  77. package/dist/components/primitives/IconPicker.d.ts +9 -0
  78. package/dist/components/primitives/IconPicker.d.ts.map +1 -0
  79. package/dist/components/primitives/IconPicker.js +21 -0
  80. package/dist/components/primitives/LinkPopover.d.ts +8 -0
  81. package/dist/components/primitives/LinkPopover.d.ts.map +1 -0
  82. package/dist/components/primitives/LinkPopover.js +48 -0
  83. package/dist/components/primitives/MediaSettingsForms.d.ts +19 -0
  84. package/dist/components/primitives/MediaSettingsForms.d.ts.map +1 -0
  85. package/dist/components/primitives/MediaSettingsForms.js +42 -0
  86. package/dist/components/primitives/ResolvedMedia.d.ts +8 -0
  87. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -0
  88. package/dist/components/primitives/ResolvedMedia.js +9 -0
  89. package/dist/components/primitives/RichTextToolbar.d.ts +9 -0
  90. package/dist/components/primitives/RichTextToolbar.d.ts.map +1 -0
  91. package/dist/components/primitives/RichTextToolbar.js +26 -0
  92. package/dist/components/primitives/tiptap-presets.d.ts +4 -0
  93. package/dist/components/primitives/tiptap-presets.d.ts.map +1 -0
  94. package/dist/components/primitives/tiptap-presets.js +44 -0
  95. package/dist/components/primitives/useEditableCollection.d.ts +19 -0
  96. package/dist/components/primitives/useEditableCollection.d.ts.map +1 -0
  97. package/dist/components/primitives/useEditableCollection.js +61 -0
  98. package/dist/components/primitives/useEditablePlainText.d.ts +14 -0
  99. package/dist/components/primitives/useEditablePlainText.d.ts.map +1 -0
  100. package/dist/components/primitives/useEditablePlainText.js +27 -0
  101. package/dist/components/primitives/useEditableRichText.d.ts +16 -0
  102. package/dist/components/primitives/useEditableRichText.d.ts.map +1 -0
  103. package/dist/components/primitives/useEditableRichText.js +52 -0
  104. package/dist/components/sections/Button/CTAButton.d.ts +11 -0
  105. package/dist/components/sections/Button/CTAButton.d.ts.map +1 -0
  106. package/dist/components/sections/Button/CTAButton.js +18 -0
  107. package/dist/components/sections/Button/index.d.ts +11 -0
  108. package/dist/components/sections/Button/index.d.ts.map +1 -0
  109. package/dist/components/sections/Button/index.js +28 -0
  110. package/dist/components/sections/Colors/index.d.ts +22 -0
  111. package/dist/components/sections/Colors/index.d.ts.map +1 -0
  112. package/dist/components/sections/Colors/index.js +34 -0
  113. package/dist/components/sections/DoDontList/index.d.ts +21 -0
  114. package/dist/components/sections/DoDontList/index.d.ts.map +1 -0
  115. package/dist/components/sections/DoDontList/index.js +33 -0
  116. package/dist/components/sections/DoDontMediaGrid/index.d.ts +55 -0
  117. package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +1 -0
  118. package/dist/components/sections/DoDontMediaGrid/index.js +41 -0
  119. package/dist/components/sections/IconList/IconList.d.ts +20 -0
  120. package/dist/components/sections/IconList/IconList.d.ts.map +1 -0
  121. package/dist/components/sections/IconList/IconList.js +131 -0
  122. package/dist/components/sections/IconList/IconListSettings.d.ts +11 -0
  123. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -0
  124. package/dist/components/sections/IconList/IconListSettings.js +22 -0
  125. package/dist/components/sections/IconList/index.d.ts +17 -0
  126. package/dist/components/sections/IconList/index.d.ts.map +1 -0
  127. package/dist/components/sections/IconList/index.js +27 -0
  128. package/dist/components/sections/LinkHeading/index.d.ts +8 -0
  129. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -0
  130. package/dist/components/sections/LinkHeading/index.js +15 -0
  131. package/dist/components/sections/MediaGrid/MediaGrid.d.ts +17 -0
  132. package/dist/components/sections/MediaGrid/MediaGrid.d.ts.map +1 -0
  133. package/dist/components/sections/MediaGrid/MediaGrid.js +62 -0
  134. package/dist/components/sections/MediaGrid/index.d.ts +55 -0
  135. package/dist/components/sections/MediaGrid/index.d.ts.map +1 -0
  136. package/dist/components/sections/MediaGrid/index.js +35 -0
  137. package/dist/components/sections/Prose/Prose.d.ts +8 -0
  138. package/dist/components/sections/Prose/Prose.d.ts.map +1 -0
  139. package/dist/components/sections/Prose/Prose.js +11 -0
  140. package/dist/components/sections/Prose/index.d.ts +8 -0
  141. package/dist/components/sections/Prose/index.d.ts.map +1 -0
  142. package/dist/components/sections/Prose/index.js +15 -0
  143. package/dist/components/sections/SectionLayout.d.ts +11 -0
  144. package/dist/components/sections/SectionLayout.d.ts.map +1 -0
  145. package/dist/components/sections/SectionLayout.js +15 -0
  146. package/dist/components/sections/SplitContent/SplitContent.d.ts +11 -0
  147. package/dist/components/sections/SplitContent/SplitContent.d.ts.map +1 -0
  148. package/dist/components/sections/SplitContent/SplitContent.js +31 -0
  149. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts +9 -0
  150. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts.map +1 -0
  151. package/dist/components/sections/SplitContent/SplitContentSettings.js +17 -0
  152. package/dist/components/sections/SplitContent/index.d.ts +13 -0
  153. package/dist/components/sections/SplitContent/index.d.ts.map +1 -0
  154. package/dist/components/sections/SplitContent/index.js +27 -0
  155. package/dist/components/sections/SubHeading/index.d.ts +9 -0
  156. package/dist/components/sections/SubHeading/index.d.ts.map +1 -0
  157. package/dist/components/sections/SubHeading/index.js +18 -0
  158. package/dist/components/sections/SubSubHeading/index.d.ts +9 -0
  159. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -0
  160. package/dist/components/sections/SubSubHeading/index.js +18 -0
  161. package/dist/components/sections/ViewRenderer.d.ts +8 -0
  162. package/dist/components/sections/ViewRenderer.d.ts.map +1 -0
  163. package/dist/components/sections/ViewRenderer.js +13 -0
  164. package/dist/components/sections/register-schemas.d.ts +2 -0
  165. package/dist/components/sections/register-schemas.d.ts.map +1 -0
  166. package/dist/components/sections/register-schemas.js +15 -0
  167. package/dist/components/sections/register.d.ts +2 -0
  168. package/dist/components/sections/register.d.ts.map +1 -0
  169. package/dist/components/sections/register.js +15 -0
  170. package/dist/components/shared/Button.d.ts +15 -0
  171. package/dist/components/shared/Button.d.ts.map +1 -0
  172. package/dist/components/shared/Button.js +27 -0
  173. package/dist/components/shared/Checkbox.d.ts +14 -0
  174. package/dist/components/shared/Checkbox.d.ts.map +1 -0
  175. package/dist/components/shared/Checkbox.js +10 -0
  176. package/dist/components/shared/ColorPicker.d.ts +9 -0
  177. package/dist/components/shared/ColorPicker.d.ts.map +1 -0
  178. package/dist/components/shared/ColorPicker.js +5 -0
  179. package/dist/components/shared/ErrorBoundary.d.ts +24 -0
  180. package/dist/components/shared/ErrorBoundary.d.ts.map +1 -0
  181. package/dist/components/shared/ErrorBoundary.js +30 -0
  182. package/dist/components/shared/FontPicker.d.ts +8 -0
  183. package/dist/components/shared/FontPicker.d.ts.map +1 -0
  184. package/dist/components/shared/FontPicker.js +190 -0
  185. package/dist/components/shared/FormLabel.d.ts +8 -0
  186. package/dist/components/shared/FormLabel.d.ts.map +1 -0
  187. package/dist/components/shared/FormLabel.js +5 -0
  188. package/dist/components/shared/IconButton.d.ts +12 -0
  189. package/dist/components/shared/IconButton.d.ts.map +1 -0
  190. package/dist/components/shared/IconButton.js +16 -0
  191. package/dist/components/shared/Input.d.ts +8 -0
  192. package/dist/components/shared/Input.d.ts.map +1 -0
  193. package/dist/components/shared/Input.js +8 -0
  194. package/dist/components/shared/Navigation.d.ts +9 -0
  195. package/dist/components/shared/Navigation.d.ts.map +1 -0
  196. package/dist/components/shared/Navigation.js +71 -0
  197. package/dist/components/shared/PasswordInput.d.ts +13 -0
  198. package/dist/components/shared/PasswordInput.d.ts.map +1 -0
  199. package/dist/components/shared/PasswordInput.js +11 -0
  200. package/dist/components/shared/Popover.d.ts +12 -0
  201. package/dist/components/shared/Popover.d.ts.map +1 -0
  202. package/dist/components/shared/Popover.js +33 -0
  203. package/dist/components/shared/PopoverItem.d.ts +7 -0
  204. package/dist/components/shared/PopoverItem.d.ts.map +1 -0
  205. package/dist/components/shared/PopoverItem.js +6 -0
  206. package/dist/components/shared/Select.d.ts +16 -0
  207. package/dist/components/shared/Select.d.ts.map +1 -0
  208. package/dist/components/shared/Select.js +9 -0
  209. package/dist/components/shared/Textarea.d.ts +8 -0
  210. package/dist/components/shared/Textarea.d.ts.map +1 -0
  211. package/dist/components/shared/Textarea.js +8 -0
  212. package/dist/components/shared/Toggle.d.ts +10 -0
  213. package/dist/components/shared/Toggle.d.ts.map +1 -0
  214. package/dist/components/shared/Toggle.js +5 -0
  215. package/dist/components/shared/Tooltip.d.ts +12 -0
  216. package/dist/components/shared/Tooltip.d.ts.map +1 -0
  217. package/dist/components/shared/Tooltip.js +8 -0
  218. package/dist/components/shared/icons.d.ts +17 -0
  219. package/dist/components/shared/icons.d.ts.map +1 -0
  220. package/dist/components/shared/icons.js +23 -0
  221. package/dist/components/shell/AudienceAddForm.d.ts +11 -0
  222. package/dist/components/shell/AudienceAddForm.d.ts.map +1 -0
  223. package/dist/components/shell/AudienceAddForm.js +43 -0
  224. package/dist/components/shell/AudienceRow.d.ts +15 -0
  225. package/dist/components/shell/AudienceRow.d.ts.map +1 -0
  226. package/dist/components/shell/AudienceRow.js +74 -0
  227. package/dist/components/shell/EditorContext.d.ts +14 -0
  228. package/dist/components/shell/EditorContext.d.ts.map +1 -0
  229. package/dist/components/shell/EditorContext.js +24 -0
  230. package/dist/components/shell/EditorLoginForm.d.ts +13 -0
  231. package/dist/components/shell/EditorLoginForm.d.ts.map +1 -0
  232. package/dist/components/shell/EditorLoginForm.js +46 -0
  233. package/dist/components/shell/EditorModal.d.ts +13 -0
  234. package/dist/components/shell/EditorModal.d.ts.map +1 -0
  235. package/dist/components/shell/EditorModal.js +43 -0
  236. package/dist/components/shell/EditorModalContext.d.ts +16 -0
  237. package/dist/components/shell/EditorModalContext.d.ts.map +1 -0
  238. package/dist/components/shell/EditorModalContext.js +20 -0
  239. package/dist/components/shell/EditorShell.d.ts +22 -0
  240. package/dist/components/shell/EditorShell.d.ts.map +1 -0
  241. package/dist/components/shell/EditorShell.js +483 -0
  242. package/dist/components/shell/MediaLibraryContext.d.ts +14 -0
  243. package/dist/components/shell/MediaLibraryContext.d.ts.map +1 -0
  244. package/dist/components/shell/MediaLibraryContext.js +5 -0
  245. package/dist/components/shell/MediaLibraryModal.d.ts +14 -0
  246. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -0
  247. package/dist/components/shell/MediaLibraryModal.js +145 -0
  248. package/dist/components/shell/ProcessingIndicator.d.ts +7 -0
  249. package/dist/components/shell/ProcessingIndicator.d.ts.map +1 -0
  250. package/dist/components/shell/ProcessingIndicator.js +15 -0
  251. package/dist/components/shell/SectionSkeleton.d.ts +9 -0
  252. package/dist/components/shell/SectionSkeleton.d.ts.map +1 -0
  253. package/dist/components/shell/SectionSkeleton.js +22 -0
  254. package/dist/components/shell/SectionTypePicker.d.ts +14 -0
  255. package/dist/components/shell/SectionTypePicker.d.ts.map +1 -0
  256. package/dist/components/shell/SectionTypePicker.js +15 -0
  257. package/dist/components/shell/SiteSettingsDisplay.d.ts +8 -0
  258. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -0
  259. package/dist/components/shell/SiteSettingsDisplay.js +28 -0
  260. package/dist/components/shell/SiteSettingsModal.d.ts +24 -0
  261. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -0
  262. package/dist/components/shell/SiteSettingsModal.js +40 -0
  263. package/dist/components/shell/SiteSettingsUsers.d.ts +9 -0
  264. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -0
  265. package/dist/components/shell/SiteSettingsUsers.js +87 -0
  266. package/dist/components/shell/SiteSettingsViewerAccess.d.ts +9 -0
  267. package/dist/components/shell/SiteSettingsViewerAccess.d.ts.map +1 -0
  268. package/dist/components/shell/SiteSettingsViewerAccess.js +94 -0
  269. package/dist/components/shell/ViewerLoginForm.d.ts +10 -0
  270. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -0
  271. package/dist/components/shell/ViewerLoginForm.js +40 -0
  272. package/dist/data/google-fonts.json +7718 -0
  273. package/dist/hooks/index.d.ts +7 -0
  274. package/dist/hooks/index.d.ts.map +1 -0
  275. package/dist/hooks/index.js +6 -0
  276. package/dist/hooks/useActiveHeadings.d.ts +7 -0
  277. package/dist/hooks/useActiveHeadings.d.ts.map +1 -0
  278. package/dist/hooks/useActiveHeadings.js +99 -0
  279. package/dist/hooks/useEditorPersistence.d.ts +13 -0
  280. package/dist/hooks/useEditorPersistence.d.ts.map +1 -0
  281. package/dist/hooks/useEditorPersistence.js +73 -0
  282. package/dist/hooks/useEditorPublish.d.ts +24 -0
  283. package/dist/hooks/useEditorPublish.d.ts.map +1 -0
  284. package/dist/hooks/useEditorPublish.js +145 -0
  285. package/dist/hooks/useFocusTrap.d.ts +3 -0
  286. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  287. package/dist/hooks/useFocusTrap.js +51 -0
  288. package/dist/hooks/useMediaPipeline.d.ts +65 -0
  289. package/dist/hooks/useMediaPipeline.d.ts.map +1 -0
  290. package/dist/hooks/useMediaPipeline.js +253 -0
  291. package/dist/hooks/useResolvedMedia.d.ts +9 -0
  292. package/dist/hooks/useResolvedMedia.d.ts.map +1 -0
  293. package/dist/hooks/useResolvedMedia.js +39 -0
  294. package/dist/index.d.ts +5 -0
  295. package/dist/index.d.ts.map +1 -0
  296. package/dist/index.js +4 -0
  297. package/dist/lib/cn.d.ts +3 -0
  298. package/dist/lib/cn.d.ts.map +1 -0
  299. package/dist/lib/cn.js +5 -0
  300. package/dist/lib/contrast.d.ts +2 -0
  301. package/dist/lib/contrast.d.ts.map +1 -0
  302. package/dist/lib/contrast.js +11 -0
  303. package/dist/lib/dexie.d.ts +41 -0
  304. package/dist/lib/dexie.d.ts.map +1 -0
  305. package/dist/lib/dexie.js +236 -0
  306. package/dist/lib/events.d.ts +12 -0
  307. package/dist/lib/events.d.ts.map +1 -0
  308. package/dist/lib/events.js +15 -0
  309. package/dist/lib/google-fonts.d.ts +2 -0
  310. package/dist/lib/google-fonts.d.ts.map +1 -0
  311. package/dist/lib/google-fonts.js +11 -0
  312. package/dist/lib/grid.d.ts +2 -0
  313. package/dist/lib/grid.d.ts.map +1 -0
  314. package/dist/lib/grid.js +7 -0
  315. package/dist/lib/icons.d.ts +9 -0
  316. package/dist/lib/icons.d.ts.map +1 -0
  317. package/dist/lib/icons.js +27 -0
  318. package/dist/lib/index.d.ts +13 -0
  319. package/dist/lib/index.d.ts.map +1 -0
  320. package/dist/lib/index.js +12 -0
  321. package/dist/lib/loader.d.ts +28 -0
  322. package/dist/lib/loader.d.ts.map +1 -0
  323. package/dist/lib/loader.js +57 -0
  324. package/dist/lib/nav.d.ts +15 -0
  325. package/dist/lib/nav.d.ts.map +1 -0
  326. package/dist/lib/nav.js +58 -0
  327. package/dist/lib/registry.d.ts +110 -0
  328. package/dist/lib/registry.d.ts.map +1 -0
  329. package/dist/lib/registry.js +64 -0
  330. package/dist/lib/safeRedirect.d.ts +7 -0
  331. package/dist/lib/safeRedirect.d.ts.map +1 -0
  332. package/dist/lib/safeRedirect.js +11 -0
  333. package/dist/lib/sanitize.d.ts +2 -0
  334. package/dist/lib/sanitize.d.ts.map +1 -0
  335. package/dist/lib/sanitize.js +6 -0
  336. package/dist/lib/timestamp.d.ts +2 -0
  337. package/dist/lib/timestamp.d.ts.map +1 -0
  338. package/dist/lib/timestamp.js +28 -0
  339. package/dist/media/github.d.ts +3 -0
  340. package/dist/media/github.d.ts.map +1 -0
  341. package/dist/media/github.js +60 -0
  342. package/dist/media/index.d.ts +8 -0
  343. package/dist/media/index.d.ts.map +1 -0
  344. package/dist/media/index.js +9 -0
  345. package/dist/media/queue.d.ts +74 -0
  346. package/dist/media/queue.d.ts.map +1 -0
  347. package/dist/media/queue.js +116 -0
  348. package/dist/media/resolve.d.ts +14 -0
  349. package/dist/media/resolve.d.ts.map +1 -0
  350. package/dist/media/resolve.js +50 -0
  351. package/dist/media/types.d.ts +42 -0
  352. package/dist/media/types.d.ts.map +1 -0
  353. package/dist/media/types.js +1 -0
  354. package/dist/media/utils.d.ts +7 -0
  355. package/dist/media/utils.d.ts.map +1 -0
  356. package/dist/media/utils.js +41 -0
  357. package/dist/media/videoPoster.d.ts +6 -0
  358. package/dist/media/videoPoster.d.ts.map +1 -0
  359. package/dist/media/videoPoster.js +44 -0
  360. package/dist/media/worker.d.ts +36 -0
  361. package/dist/media/worker.d.ts.map +1 -0
  362. package/dist/media/worker.js +73 -0
  363. package/dist/schemas/audience.d.ts +12 -0
  364. package/dist/schemas/audience.d.ts.map +1 -0
  365. package/dist/schemas/audience.js +19 -0
  366. package/dist/schemas/auth.d.ts +36 -0
  367. package/dist/schemas/auth.d.ts.map +1 -0
  368. package/dist/schemas/auth.js +22 -0
  369. package/dist/schemas/index.d.ts +8 -0
  370. package/dist/schemas/index.d.ts.map +1 -0
  371. package/dist/schemas/index.js +7 -0
  372. package/dist/schemas/media-grid-options.d.ts +8 -0
  373. package/dist/schemas/media-grid-options.d.ts.map +1 -0
  374. package/dist/schemas/media-grid-options.js +7 -0
  375. package/dist/schemas/media.d.ts +63 -0
  376. package/dist/schemas/media.d.ts.map +1 -0
  377. package/dist/schemas/media.js +28 -0
  378. package/dist/schemas/sections.d.ts +12 -0
  379. package/dist/schemas/sections.d.ts.map +1 -0
  380. package/dist/schemas/sections.js +12 -0
  381. package/dist/schemas/shared.d.ts +98 -0
  382. package/dist/schemas/shared.d.ts.map +1 -0
  383. package/dist/schemas/shared.js +71 -0
  384. package/dist/schemas/site-config.d.ts +48 -0
  385. package/dist/schemas/site-config.d.ts.map +1 -0
  386. package/dist/schemas/site-config.js +26 -0
  387. package/package.json +57 -0
  388. package/src/auth/cookies.ts +51 -0
  389. package/src/auth/errors.ts +10 -0
  390. package/src/auth/index.ts +15 -0
  391. package/src/auth/security.ts +44 -0
  392. package/src/auth/types.ts +61 -0
  393. package/src/components/brandguide/ColorSwatchSettings.tsx +34 -0
  394. package/src/components/brandguide/Colors.tsx +277 -0
  395. package/src/components/brandguide/DoDontList.tsx +67 -0
  396. package/src/components/brandguide/DoDontMediaGrid.tsx +19 -0
  397. package/src/components/editor/AudiencePicker.tsx +103 -0
  398. package/src/components/editor/DeleteButton.tsx +18 -0
  399. package/src/components/editor/DragHandle.tsx +18 -0
  400. package/src/components/editor/InsertButton.tsx +22 -0
  401. package/src/components/editor/SectionWrapper.tsx +279 -0
  402. package/src/components/editor/SettingsButton.tsx +17 -0
  403. package/src/components/editor/SettingsForm.tsx +159 -0
  404. package/src/components/editor/StatusBadge.tsx +30 -0
  405. package/src/components/editor/StatusPicker.tsx +86 -0
  406. package/src/components/editor/index.ts +7 -0
  407. package/src/components/primitives/CustomParagraph.ts +25 -0
  408. package/src/components/primitives/EditableGrid.tsx +260 -0
  409. package/src/components/primitives/EditableList.tsx +160 -0
  410. package/src/components/primitives/EditablePlainText.tsx +99 -0
  411. package/src/components/primitives/EditableRichText.tsx +132 -0
  412. package/src/components/primitives/HeadingSection.tsx +25 -0
  413. package/src/components/primitives/IconPicker.tsx +60 -0
  414. package/src/components/primitives/LinkPopover.tsx +109 -0
  415. package/src/components/primitives/MediaSettingsForms.tsx +109 -0
  416. package/src/components/primitives/ResolvedMedia.tsx +36 -0
  417. package/src/components/primitives/RichTextToolbar.tsx +149 -0
  418. package/src/components/primitives/tiptap-presets.ts +50 -0
  419. package/src/components/primitives/useEditableCollection.ts +104 -0
  420. package/src/components/primitives/useEditablePlainText.ts +49 -0
  421. package/src/components/primitives/useEditableRichText.ts +74 -0
  422. package/src/components/sections/Button/CTAButton.tsx +50 -0
  423. package/src/components/sections/Button/index.tsx +37 -0
  424. package/src/components/sections/Colors/index.tsx +45 -0
  425. package/src/components/sections/DoDontList/index.tsx +43 -0
  426. package/src/components/sections/DoDontMediaGrid/index.tsx +53 -0
  427. package/src/components/sections/IconList/IconList.tsx +462 -0
  428. package/src/components/sections/IconList/IconListSettings.tsx +81 -0
  429. package/src/components/sections/IconList/index.tsx +36 -0
  430. package/src/components/sections/LinkHeading/index.tsx +24 -0
  431. package/src/components/sections/MediaGrid/MediaGrid.tsx +219 -0
  432. package/src/components/sections/MediaGrid/index.tsx +47 -0
  433. package/src/components/sections/Prose/Prose.tsx +34 -0
  434. package/src/components/sections/Prose/index.tsx +18 -0
  435. package/src/components/sections/SectionLayout.tsx +31 -0
  436. package/src/components/sections/SplitContent/SplitContent.tsx +77 -0
  437. package/src/components/sections/SplitContent/SplitContentSettings.tsx +42 -0
  438. package/src/components/sections/SplitContent/index.tsx +36 -0
  439. package/src/components/sections/SubHeading/index.tsx +27 -0
  440. package/src/components/sections/SubSubHeading/index.tsx +27 -0
  441. package/src/components/sections/ViewRenderer.tsx +29 -0
  442. package/src/components/sections/register-schemas.ts +16 -0
  443. package/src/components/sections/register.ts +16 -0
  444. package/src/components/shared/Button.tsx +77 -0
  445. package/src/components/shared/Checkbox.tsx +73 -0
  446. package/src/components/shared/ColorPicker.tsx +28 -0
  447. package/src/components/shared/ErrorBoundary.tsx +92 -0
  448. package/src/components/shared/FontPicker.tsx +300 -0
  449. package/src/components/shared/FormLabel.tsx +18 -0
  450. package/src/components/shared/IconButton.tsx +45 -0
  451. package/src/components/shared/Input.tsx +34 -0
  452. package/src/components/shared/Navigation.tsx +196 -0
  453. package/src/components/shared/PasswordInput.tsx +64 -0
  454. package/src/components/shared/Popover.tsx +61 -0
  455. package/src/components/shared/PopoverItem.tsx +24 -0
  456. package/src/components/shared/Select.tsx +60 -0
  457. package/src/components/shared/Textarea.tsx +34 -0
  458. package/src/components/shared/Toggle.tsx +35 -0
  459. package/src/components/shared/Tooltip.tsx +28 -0
  460. package/src/components/shared/icons.tsx +28 -0
  461. package/src/components/shell/AudienceAddForm.tsx +94 -0
  462. package/src/components/shell/AudienceRow.tsx +171 -0
  463. package/src/components/shell/EditorContext.tsx +44 -0
  464. package/src/components/shell/EditorLoginForm.tsx +130 -0
  465. package/src/components/shell/EditorModal.tsx +93 -0
  466. package/src/components/shell/EditorModalContext.tsx +40 -0
  467. package/src/components/shell/EditorShell.tsx +910 -0
  468. package/src/components/shell/MediaLibraryContext.tsx +19 -0
  469. package/src/components/shell/MediaLibraryModal.tsx +371 -0
  470. package/src/components/shell/ProcessingIndicator.tsx +84 -0
  471. package/src/components/shell/SectionSkeleton.tsx +54 -0
  472. package/src/components/shell/SectionTypePicker.tsx +47 -0
  473. package/src/components/shell/SiteSettingsDisplay.tsx +78 -0
  474. package/src/components/shell/SiteSettingsModal.tsx +129 -0
  475. package/src/components/shell/SiteSettingsUsers.tsx +190 -0
  476. package/src/components/shell/SiteSettingsViewerAccess.tsx +171 -0
  477. package/src/components/shell/ViewerLoginForm.tsx +95 -0
  478. package/src/data/google-fonts.json +7718 -0
  479. package/src/env.d.ts +3 -0
  480. package/src/hooks/index.ts +6 -0
  481. package/src/hooks/useActiveHeadings.ts +120 -0
  482. package/src/hooks/useEditorPersistence.ts +103 -0
  483. package/src/hooks/useEditorPublish.ts +187 -0
  484. package/src/hooks/useFocusTrap.ts +57 -0
  485. package/src/hooks/useMediaPipeline.ts +309 -0
  486. package/src/hooks/useResolvedMedia.ts +54 -0
  487. package/src/index.ts +4 -0
  488. package/src/lib/cn.ts +6 -0
  489. package/src/lib/contrast.ts +11 -0
  490. package/src/lib/dexie.ts +347 -0
  491. package/src/lib/events.ts +23 -0
  492. package/src/lib/google-fonts.ts +10 -0
  493. package/src/lib/grid.ts +7 -0
  494. package/src/lib/icons.ts +58 -0
  495. package/src/lib/index.ts +35 -0
  496. package/src/lib/loader.ts +81 -0
  497. package/src/lib/nav.ts +64 -0
  498. package/src/lib/registry.ts +202 -0
  499. package/src/lib/safeRedirect.ts +11 -0
  500. package/src/lib/sanitize.ts +6 -0
  501. package/src/lib/timestamp.ts +31 -0
  502. package/src/media/github.ts +72 -0
  503. package/src/media/index.ts +12 -0
  504. package/src/media/queue.ts +166 -0
  505. package/src/media/resolve.ts +63 -0
  506. package/src/media/types.ts +58 -0
  507. package/src/media/utils.ts +48 -0
  508. package/src/media/videoPoster.ts +53 -0
  509. package/src/media/worker.ts +122 -0
  510. package/src/schemas/audience.ts +28 -0
  511. package/src/schemas/auth.ts +31 -0
  512. package/src/schemas/index.ts +7 -0
  513. package/src/schemas/media-grid-options.ts +8 -0
  514. package/src/schemas/media.ts +40 -0
  515. package/src/schemas/sections.ts +24 -0
  516. package/src/schemas/shared.ts +96 -0
  517. package/src/schemas/site-config.ts +38 -0
@@ -0,0 +1,19 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { MediaManifest, MediaItem } from "../../media/types";
3
+ import type { SiteConfig } from "../../schemas/site-config";
4
+
5
+ export interface MediaLibraryContextValue {
6
+ openSelectModal: (onSelect: (imageId: string) => void) => void;
7
+ uploadFile: (file: File) => void;
8
+ uploadFileWithCallback: (file: File, onComplete: (imageId: string) => void) => string;
9
+ manifest: MediaManifest;
10
+ pendingItems: MediaItem[];
11
+ pendingLocalUrls: Record<string, string>;
12
+ siteConfig: SiteConfig | null;
13
+ }
14
+
15
+ export const MediaLibraryContext = createContext<MediaLibraryContextValue | null>(null);
16
+
17
+ export function useMediaLibrary(): MediaLibraryContextValue | null {
18
+ return useContext(MediaLibraryContext);
19
+ }
@@ -0,0 +1,371 @@
1
+ import { useState, useRef, useCallback } from "react";
2
+ import { Search, Upload, Trash2 } from "lucide-react";
3
+ import { cn } from "../../lib/cn";
4
+ import { Button } from "../shared/Button";
5
+ import { Select } from "../shared/Select";
6
+ import type { MediaItem, MediaKind } from "../../media/types";
7
+ import { displayFilenameExt, mimeToExt } from "../../media/utils";
8
+
9
+ export interface MediaLibraryModalProps {
10
+ mode: "select" | "manage";
11
+ items: MediaItem[];
12
+ onSelect: (id: string) => void;
13
+ onUpload: (files: File[]) => void;
14
+ onDelete: (ids: string[]) => void;
15
+ onAltChange?: (id: string, alt: string) => void;
16
+ referenceCountMap: Record<string, number>;
17
+ localUrlMap?: Record<string, string>;
18
+ maxFileSize?: number;
19
+ }
20
+
21
+ function formatFileSize(bytes: number): string {
22
+ if (bytes >= 1048576) return `${Math.round(bytes / 1048576)}MB`;
23
+ if (bytes >= 1024) return `${Math.round(bytes / 1024)}KB`;
24
+ return `${bytes}B`;
25
+ }
26
+
27
+ type KindFilter = "all" | MediaKind;
28
+
29
+ function thumbnailSrc(item: MediaItem, localUrls: Record<string, string>): string {
30
+ const localUrl = localUrls[item.id];
31
+ if (localUrl) return localUrl;
32
+ if (item.kind === "video") {
33
+ return `/api/media/${item.id}/original.${mimeToExt(item.mimeType)}`;
34
+ }
35
+ if (item.kind === "image" && item.variants.length > 0) {
36
+ const smallest = item.variants.reduce((a, b) => (a.width < b.width ? a : b));
37
+ return `/api/media/${item.id}/${smallest.width}.webp`;
38
+ }
39
+ return `/api/media/${item.id}/poster.webp`;
40
+ }
41
+
42
+ function displayFilename(item: MediaItem): string {
43
+ return `${item.originalName}${displayFilenameExt(item.mimeType)}`;
44
+ }
45
+
46
+ function UploadZone({ onUpload }: { onUpload: (files: File[]) => void }) {
47
+ const [dragging, setDragging] = useState(false);
48
+ const inputRef = useRef<HTMLInputElement>(null);
49
+
50
+ const handleFiles = useCallback(
51
+ (files: FileList | null) => {
52
+ if (!files || files.length === 0) return;
53
+ onUpload(Array.from(files));
54
+ },
55
+ [onUpload],
56
+ );
57
+
58
+ return (
59
+ <button
60
+ type="button"
61
+ onClick={() => inputRef.current?.click()}
62
+ onDragOver={(e) => {
63
+ e.preventDefault();
64
+ setDragging(true);
65
+ }}
66
+ onDragLeave={() => setDragging(false)}
67
+ onDrop={(e) => {
68
+ e.preventDefault();
69
+ setDragging(false);
70
+ handleFiles(e.dataTransfer.files);
71
+ }}
72
+ className={cn(
73
+ "flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors",
74
+ dragging
75
+ ? "border-primary bg-primary/5"
76
+ : "border-base-200 hover:border-base-300",
77
+ )}
78
+ >
79
+ <Upload size={24} className="text-base-contrast-light" />
80
+ <span className="text-sm text-base-contrast-light">
81
+ Drag &amp; drop files or click to browse
82
+ </span>
83
+ <input
84
+ ref={inputRef}
85
+ type="file"
86
+ multiple
87
+ accept="image/*,video/*"
88
+ className="hidden"
89
+ onChange={(e) => {
90
+ handleFiles(e.target.files);
91
+ e.target.value = "";
92
+ }}
93
+ />
94
+ </button>
95
+ );
96
+ }
97
+
98
+ export function MediaLibraryModal({
99
+ mode,
100
+ items,
101
+ onSelect,
102
+ onUpload,
103
+ onDelete,
104
+ onAltChange,
105
+ referenceCountMap,
106
+ localUrlMap = {},
107
+ maxFileSize,
108
+ }: MediaLibraryModalProps) {
109
+ const [search, setSearch] = useState("");
110
+ const [kindFilter, setKindFilter] = useState<KindFilter>("all");
111
+ const [selected, setSelected] = useState<Set<string>>(new Set());
112
+ const [rejectedFiles, setRejectedFiles] = useState<string[]>([]);
113
+
114
+ const handleUpload = useCallback((files: File[]) => {
115
+ if (!maxFileSize) {
116
+ onUpload(files);
117
+ return;
118
+ }
119
+ const valid: File[] = [];
120
+ const rejected: string[] = [];
121
+ for (const file of files) {
122
+ if (file.size > maxFileSize) {
123
+ rejected.push(file.name);
124
+ } else {
125
+ valid.push(file);
126
+ }
127
+ }
128
+ if (rejected.length > 0) setRejectedFiles(rejected);
129
+ if (valid.length > 0) onUpload(valid);
130
+ }, [maxFileSize, onUpload]);
131
+
132
+ const filtered = items.filter((item) => {
133
+ const matchesSearch = item.originalName
134
+ .toLowerCase()
135
+ .includes(search.toLowerCase());
136
+ const matchesKind = kindFilter === "all" || item.kind === kindFilter;
137
+ return matchesSearch && matchesKind;
138
+ });
139
+
140
+ function toggleSelect(id: string) {
141
+ setSelected((prev) => {
142
+ const next = new Set(prev);
143
+ if (next.has(id)) {
144
+ next.delete(id);
145
+ } else {
146
+ next.add(id);
147
+ }
148
+ return next;
149
+ });
150
+ }
151
+
152
+ const [confirmDelete, setConfirmDelete] = useState(false);
153
+
154
+ const selectedIds = Array.from(selected);
155
+ const totalUsage = selectedIds.reduce((sum, id) => sum + (referenceCountMap[id] ?? 0), 0);
156
+
157
+ function handleBatchDelete() {
158
+ if (totalUsage > 0) {
159
+ setConfirmDelete(true);
160
+ return;
161
+ }
162
+ onDelete(selectedIds);
163
+ setSelected(new Set());
164
+ }
165
+
166
+ function handleConfirmDelete() {
167
+ onDelete(selectedIds);
168
+ setSelected(new Set());
169
+ setConfirmDelete(false);
170
+ }
171
+
172
+ return (
173
+ <div className="flex flex-col gap-4">
174
+ <UploadZone onUpload={handleUpload} />
175
+
176
+ {/* Sticky toolbar + confirmation */}
177
+ <div className="sticky top-[-16px] z-10 -mx-6 space-y-3 bg-base px-6 pb-3 pt-4">
178
+ <div className="flex items-center gap-2">
179
+ <div className="relative flex-1">
180
+ <Search
181
+ size={14}
182
+ className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base-contrast-light"
183
+ />
184
+ <input
185
+ type="text"
186
+ placeholder="Search media..."
187
+ value={search}
188
+ onChange={(e) => setSearch(e.target.value)}
189
+ className="w-full rounded border border-base-200 bg-base py-1.5 pl-8 pr-3 text-sm text-base-contrast placeholder:text-base-contrast-light focus:outline-none focus:ring-1 focus:ring-primary"
190
+ />
191
+ </div>
192
+
193
+ <Select
194
+ value={kindFilter}
195
+ onChange={(v) => setKindFilter(v as KindFilter)}
196
+ options={[
197
+ { value: "all", label: "All types" },
198
+ { value: "image", label: "Images" },
199
+ { value: "animated", label: "Animated" },
200
+ { value: "video", label: "Video" },
201
+ ]}
202
+ selectClassName="py-1.5 pr-7 pl-2.5"
203
+ />
204
+
205
+ {mode === "manage" && selected.size > 0 && (
206
+ <Button
207
+ variant="destructive"
208
+ size="sm"
209
+ onClick={handleBatchDelete}
210
+ className="flex items-center gap-1.5"
211
+ >
212
+ <Trash2 size={13} />
213
+ Delete {selected.size}
214
+ </Button>
215
+ )}
216
+ </div>
217
+
218
+ {/* Delete confirmation */}
219
+ {confirmDelete && (
220
+ <div className="flex flex-col items-center rounded-lg border border-red-200 bg-red-50 px-4 py-3 lg:flex-row lg:justify-between dark:border-red-900/50 dark:bg-red-950/30">
221
+ <p className="mb-2 text-center text-sm text-red-800 lg:mb-0 lg:text-left dark:text-red-200">
222
+ Some of these images are being used. Deleting these will remove them from these references.
223
+ </p>
224
+ <div className="flex shrink-0 items-center gap-2">
225
+ <Button variant="secondary" size="sm" onClick={() => setConfirmDelete(false)}>
226
+ Cancel
227
+ </Button>
228
+ <Button variant="destructive" size="sm" onClick={handleConfirmDelete}>
229
+ Confirm Delete
230
+ </Button>
231
+ </div>
232
+ </div>
233
+ )}
234
+
235
+ {/* File size rejection alert */}
236
+ {rejectedFiles.length > 0 && maxFileSize && (
237
+ <div className="flex flex-col items-center rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 lg:flex-row lg:justify-between dark:border-amber-900/50 dark:bg-amber-950/30">
238
+ <p className="mb-2 text-center text-sm text-amber-800 lg:mb-0 lg:text-left dark:text-amber-200">
239
+ {rejectedFiles.length === 1
240
+ ? `"${rejectedFiles[0]}" exceeds`
241
+ : `${rejectedFiles.length} files exceed`} the {formatFileSize(maxFileSize)} file size limit.
242
+ </p>
243
+ <div className="flex shrink-0 items-center">
244
+ <Button variant="secondary" size="sm" onClick={() => setRejectedFiles([])}>
245
+ Dismiss
246
+ </Button>
247
+ </div>
248
+ </div>
249
+ )}
250
+ </div>
251
+
252
+ {/* Grid */}
253
+ {filtered.length === 0 ? (
254
+ <div className="flex flex-col items-center justify-center gap-2 py-12 text-center">
255
+ <p className="text-sm font-medium text-base-contrast">
256
+ {items.length === 0 ? "No media uploaded yet" : "No results found"}
257
+ </p>
258
+ <p className="text-xs text-base-contrast-light">
259
+ {items.length === 0
260
+ ? "Upload files using the drop zone above."
261
+ : "Try a different search or filter."}
262
+ </p>
263
+ </div>
264
+ ) : (
265
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
266
+ {filtered.map((item) => {
267
+ const isSelected = selected.has(item.id);
268
+ const refCount = referenceCountMap[item.id] ?? 0;
269
+ const usageLabel = refCount === 0 ? "Not used" : `Used ${refCount}x`;
270
+
271
+ return (
272
+ <div
273
+ key={item.id}
274
+ className="group flex flex-col gap-1.5"
275
+ onClick={() => {
276
+ if (mode === "select") {
277
+ onSelect(item.id);
278
+ } else {
279
+ toggleSelect(item.id);
280
+ }
281
+ }}
282
+ >
283
+ {/* Thumbnail */}
284
+ <div
285
+ className={cn(
286
+ "relative aspect-square cursor-pointer overflow-hidden rounded-lg border-2 transition-colors",
287
+ mode === "select"
288
+ ? "border-transparent hover:border-primary"
289
+ : isSelected
290
+ ? "border-primary"
291
+ : "border-transparent hover:border-base-300",
292
+ )}
293
+ >
294
+ <div className="h-full w-full bg-base-accent">
295
+ {item.kind === "video" ? (
296
+ <video
297
+ src={thumbnailSrc(item, localUrlMap)}
298
+ muted
299
+ playsInline
300
+ className="h-full w-full object-cover"
301
+ onMouseEnter={(e) => e.currentTarget.play().catch(() => {})}
302
+ onMouseLeave={(e) => { e.currentTarget.pause(); e.currentTarget.currentTime = 0; }}
303
+ />
304
+ ) : (
305
+ <img
306
+ src={thumbnailSrc(item, localUrlMap)}
307
+ alt={item.originalName}
308
+ className="h-full w-full object-cover"
309
+ />
310
+ )}
311
+ </div>
312
+
313
+ {/* Selection radio — top-left */}
314
+ {mode === "manage" && (
315
+ <div className={cn(
316
+ "absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full border-2 transition-colors",
317
+ isSelected
318
+ ? "border-primary bg-primary"
319
+ : "border-white/80 bg-white/40 opacity-0 group-hover:opacity-100",
320
+ )}>
321
+ {isSelected && (
322
+ <div className="h-2 w-2 rounded-full bg-white" />
323
+ )}
324
+ </div>
325
+ )}
326
+
327
+ {/* Usage pill — top-right, visible on hover or when selected */}
328
+ {mode === "manage" && (
329
+ <div className={cn(
330
+ "absolute top-1.5 right-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium transition-opacity",
331
+ isSelected
332
+ ? "opacity-100"
333
+ : "opacity-0 group-hover:opacity-100",
334
+ refCount > 0
335
+ ? "bg-black/50 text-white"
336
+ : "bg-black/50 text-white/70",
337
+ )}>
338
+ {usageLabel}
339
+ </div>
340
+ )}
341
+
342
+ {/* Filename overlay — bottom, visible on hover */}
343
+ <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent px-2 pb-1.5 pt-4 opacity-0 transition-opacity group-hover:opacity-100">
344
+ <p className="truncate text-[11px] text-white">
345
+ {displayFilename(item)}
346
+ </p>
347
+ </div>
348
+ </div>
349
+
350
+ {/* Alt text below image */}
351
+ <div className="px-0.5">
352
+ <label className="text-[10px] font-medium uppercase leading-none tracking-wide text-base-contrast-light/50">
353
+ Alt text
354
+ </label>
355
+ <textarea
356
+ value={item.alt}
357
+ placeholder="Describe this image for accessibility"
358
+ rows={2}
359
+ onClick={(e) => e.stopPropagation()}
360
+ onChange={(e) => onAltChange?.(item.id, e.target.value)}
361
+ className="w-full resize-none border-0 bg-transparent p-0 text-xs leading-snug text-base-contrast placeholder:text-base-contrast-light/40 focus:outline-none focus:ring-0"
362
+ />
363
+ </div>
364
+ </div>
365
+ );
366
+ })}
367
+ </div>
368
+ )}
369
+ </div>
370
+ );
371
+ }
@@ -0,0 +1,84 @@
1
+ import { useRef, useState } from "react";
2
+ import { Loader2 } from "lucide-react";
3
+ import { cn } from "../../lib/cn";
4
+ import type { QueueItem } from "../../media/queue";
5
+
6
+ interface ProcessingIndicatorProps {
7
+ items: QueueItem[];
8
+ }
9
+
10
+ export function ProcessingIndicator({ items }: ProcessingIndicatorProps) {
11
+ const [open, setOpen] = useState(false);
12
+ const buttonRef = useRef<HTMLButtonElement>(null);
13
+
14
+ const activeItems = items.filter((i) => i.state === "active");
15
+ const queuedItems = items.filter((i) => i.state === "queued");
16
+ const errorItems = items.filter((i) => i.state === "error");
17
+ const visibleCount = activeItems.length + queuedItems.length + errorItems.length;
18
+
19
+ if (visibleCount === 0) return null;
20
+
21
+ return (
22
+ <div className="relative inline-flex">
23
+ <button
24
+ ref={buttonRef}
25
+ type="button"
26
+ onClick={() => setOpen((o) => !o)}
27
+ className={cn(
28
+ "relative inline-flex items-center gap-1.5 rounded-md border border-base-200 bg-base px-3 py-1.5 text-sm font-medium text-base-contrast transition-colors hover:bg-base-accent",
29
+ )}
30
+ aria-label="Processing status"
31
+ >
32
+ <Loader2 size={14} className="animate-spin text-primary" />
33
+ <span className="text-xs font-semibold tabular-nums">{visibleCount}</span>
34
+ </button>
35
+
36
+ {open && (
37
+ <div className="absolute bottom-full left-0 z-50 mb-2 w-64 rounded-lg border border-base-200 bg-base shadow-lg">
38
+ <div className="flex flex-col divide-y divide-base-200">
39
+ {/* Active items */}
40
+ {activeItems.map((item) => (
41
+ <div key={item.id} className="flex flex-col gap-1.5 px-3 py-2">
42
+ <div className="flex items-center justify-between">
43
+ <span className="truncate text-xs font-medium text-base-contrast">
44
+ {item.originalName}
45
+ </span>
46
+ <span className="ml-2 shrink-0 text-xs text-base-contrast-light">
47
+ {item.percent}%
48
+ </span>
49
+ </div>
50
+ <div className="h-1 overflow-hidden rounded-full bg-base-accent">
51
+ <div
52
+ className="h-full rounded-full bg-primary transition-all"
53
+ style={{ width: `${item.percent}%` }}
54
+ />
55
+ </div>
56
+ </div>
57
+ ))}
58
+
59
+ {/* Error items */}
60
+ {errorItems.map((item) => (
61
+ <div key={item.id} className="flex flex-col gap-0.5 px-3 py-2">
62
+ <span className="truncate text-xs font-medium text-red-600">
63
+ {item.originalName}
64
+ </span>
65
+ {item.error && (
66
+ <span className="text-xs text-red-500">{item.error}</span>
67
+ )}
68
+ </div>
69
+ ))}
70
+
71
+ {/* Queued footer */}
72
+ {queuedItems.length > 0 && (
73
+ <div className="px-3 py-2">
74
+ <span className="text-xs text-base-contrast-light">
75
+ +{queuedItems.length} queued
76
+ </span>
77
+ </div>
78
+ )}
79
+ </div>
80
+ </div>
81
+ )}
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1,54 @@
1
+ import { useState, useEffect } from "react";
2
+ import { SectionTypePicker, type TypeOption } from "./SectionTypePicker";
3
+ import { SectionLayout } from "../sections/SectionLayout";
4
+ import { cn } from "../../lib/cn";
5
+
6
+ interface SectionSkeletonProps {
7
+ types: TypeOption[];
8
+ onSelect: (type: string) => void;
9
+ onDismiss: () => void;
10
+ }
11
+
12
+ export function SectionSkeleton({ types, onSelect, onDismiss }: SectionSkeletonProps) {
13
+ const [isVisible, setIsVisible] = useState(false);
14
+
15
+ useEffect(() => {
16
+ // Trigger animation on next frame
17
+ requestAnimationFrame(() => setIsVisible(true));
18
+ }, []);
19
+
20
+ useEffect(() => {
21
+ function handleKeyDown(e: KeyboardEvent) {
22
+ if (e.key === "Escape") {
23
+ onDismiss();
24
+ }
25
+ }
26
+ document.addEventListener("keydown", handleKeyDown);
27
+ return () => document.removeEventListener("keydown", handleKeyDown);
28
+ }, [onDismiss]);
29
+
30
+ return (
31
+ <SectionLayout type="skeleton" status="draft">
32
+ <div
33
+ className={cn(
34
+ "relative transition-all duration-200",
35
+ isVisible ? "opacity-100" : "opacity-0 -translate-y-2",
36
+ )}
37
+ >
38
+ {/* Placeholder block */}
39
+ <div className="flex h-20 items-center justify-center rounded-lg bg-base-accent">
40
+ <span className="text-sm text-base-contrast-light/50">New section</span>
41
+ </div>
42
+
43
+ {/* Type picker — centered below skeleton */}
44
+ <div className="relative flex justify-center">
45
+ <SectionTypePicker
46
+ types={types}
47
+ onSelect={onSelect}
48
+ onClose={onDismiss}
49
+ />
50
+ </div>
51
+ </div>
52
+ </SectionLayout>
53
+ );
54
+ }
@@ -0,0 +1,47 @@
1
+ import { useEffect, useRef, type ComponentType } from "react";
2
+
3
+ export interface TypeOption {
4
+ type: string;
5
+ label: string;
6
+ icon: ComponentType;
7
+ }
8
+
9
+ interface SectionTypePickerProps {
10
+ types: TypeOption[];
11
+ onSelect: (type: string) => void;
12
+ onClose: () => void;
13
+ }
14
+
15
+ export function SectionTypePicker({ types, onSelect, onClose }: SectionTypePickerProps) {
16
+ const panelRef = useRef<HTMLDivElement>(null);
17
+
18
+ useEffect(() => {
19
+ function handleMouseDown(e: MouseEvent) {
20
+ if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
21
+ onClose();
22
+ }
23
+ }
24
+ document.addEventListener("mousedown", handleMouseDown);
25
+ return () => document.removeEventListener("mousedown", handleMouseDown);
26
+ }, [onClose]);
27
+
28
+ return (
29
+ <div
30
+ ref={panelRef}
31
+ className="absolute z-50 mt-1 w-64 rounded-lg border border-base-200 bg-base p-2 shadow-lg"
32
+ >
33
+ {types.map(({ type, label, icon: Icon }) => (
34
+ <button
35
+ key={type}
36
+ className="cursor-pointer flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm text-base-contrast-light hover:bg-base-accent hover:text-base-contrast"
37
+ onClick={() => onSelect(type)}
38
+ >
39
+ <span className="flex h-6 w-6 items-center justify-center text-base">
40
+ <Icon />
41
+ </span>
42
+ <span>{label}</span>
43
+ </button>
44
+ ))}
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,78 @@
1
+ import { ColorPicker } from "../shared/ColorPicker";
2
+ import { FontPicker } from "../shared/FontPicker";
3
+ import { Input } from "../shared/Input";
4
+ import { Select } from "../shared/Select";
5
+ import { FormLabel } from "../shared/FormLabel";
6
+ import { deriveContrast } from "../../lib/contrast";
7
+ import { buildGoogleFontsUrl } from "../../lib/google-fonts";
8
+ import type { SiteConfig } from "../../schemas/site-config";
9
+
10
+ interface Props {
11
+ siteConfig: SiteConfig;
12
+ onChange: (config: SiteConfig) => void;
13
+ }
14
+
15
+ const darkModeOptions = [
16
+ { label: "Light", value: "light" },
17
+ { label: "Dark", value: "dark" },
18
+ { label: "Optional (viewer toggle)", value: "optional" },
19
+ ];
20
+
21
+ export function SiteSettingsDisplay({ siteConfig, onChange }: Props) {
22
+ function update(patch: Partial<SiteConfig>) {
23
+ onChange({ ...siteConfig, ...patch });
24
+ }
25
+
26
+ function handleColorChange(color: string) {
27
+ const contrast = deriveContrast(color);
28
+ onChange({ ...siteConfig, primaryColor: color, primaryContrast: contrast });
29
+ }
30
+
31
+ function handleFontChange(field: "headingFont" | "bodyFont", family: string) {
32
+ const next = { ...siteConfig, [field]: family };
33
+ next.googleFontsUrl = buildGoogleFontsUrl(next.headingFont, next.bodyFont);
34
+ onChange(next);
35
+ }
36
+
37
+ return (
38
+ <div className="space-y-5">
39
+ <Input
40
+ label="Site name"
41
+ value={siteConfig.siteName}
42
+ onChange={(value) => update({ siteName: value })}
43
+ placeholder="Brand Portal"
44
+ />
45
+
46
+ <div>
47
+ <FormLabel>Primary color</FormLabel>
48
+ <div className="flex items-center gap-3">
49
+ <ColorPicker
50
+ value={siteConfig.primaryColor}
51
+ onChange={handleColorChange}
52
+ label="Primary color"
53
+ />
54
+ <span className="text-sm text-base-contrast-light">{siteConfig.primaryColor}</span>
55
+ </div>
56
+ </div>
57
+
58
+ <Select
59
+ label="Dark mode"
60
+ value={siteConfig.darkMode}
61
+ onChange={(value) => update({ darkMode: value as SiteConfig["darkMode"] })}
62
+ options={darkModeOptions}
63
+ />
64
+
65
+ <FontPicker
66
+ label="Heading font"
67
+ value={siteConfig.headingFont}
68
+ onChange={(family) => handleFontChange("headingFont", family)}
69
+ />
70
+
71
+ <FontPicker
72
+ label="Body font"
73
+ value={siteConfig.bodyFont}
74
+ onChange={(family) => handleFontChange("bodyFont", family)}
75
+ />
76
+ </div>
77
+ );
78
+ }