@btst/stack 2.8.1 → 2.9.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 (214) hide show
  1. package/README.md +3 -2
  2. package/dist/components/markdown/index.d.cts +15 -2
  3. package/dist/components/markdown/index.d.mts +15 -2
  4. package/dist/components/markdown/index.d.ts +15 -2
  5. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
  6. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
  7. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
  8. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
  9. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
  10. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
  11. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
  12. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
  13. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
  14. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
  15. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
  16. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
  17. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
  18. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
  19. package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
  20. package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
  21. package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
  22. package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
  23. package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
  24. package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
  25. package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
  26. package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
  27. package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
  28. package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
  29. package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
  30. package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
  31. package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
  32. package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
  33. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
  34. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
  35. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
  36. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
  37. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
  38. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
  39. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
  40. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
  41. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
  42. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
  43. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
  44. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
  45. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
  46. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
  47. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
  48. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
  49. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
  50. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
  51. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
  52. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
  53. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
  54. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
  55. package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
  56. package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
  57. package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
  58. package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
  59. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
  60. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
  61. package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
  62. package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
  63. package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
  64. package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
  65. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
  66. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
  67. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
  68. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
  69. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
  70. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
  71. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
  72. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
  73. package/dist/plugins/blog/api/index.d.cts +2 -2
  74. package/dist/plugins/blog/api/index.d.mts +2 -2
  75. package/dist/plugins/blog/api/index.d.ts +2 -2
  76. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  77. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  78. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  79. package/dist/plugins/blog/client/index.d.cts +60 -3
  80. package/dist/plugins/blog/client/index.d.mts +60 -3
  81. package/dist/plugins/blog/client/index.d.ts +60 -3
  82. package/dist/plugins/blog/query-keys.d.cts +2 -2
  83. package/dist/plugins/blog/query-keys.d.mts +2 -2
  84. package/dist/plugins/blog/query-keys.d.ts +2 -2
  85. package/dist/plugins/cms/client/index.d.cts +73 -3
  86. package/dist/plugins/cms/client/index.d.mts +73 -3
  87. package/dist/plugins/cms/client/index.d.ts +73 -3
  88. package/dist/plugins/kanban/api/index.d.cts +1 -1
  89. package/dist/plugins/kanban/api/index.d.mts +1 -1
  90. package/dist/plugins/kanban/api/index.d.ts +1 -1
  91. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  92. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  93. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  94. package/dist/plugins/kanban/client/index.d.cts +1 -1
  95. package/dist/plugins/kanban/client/index.d.mts +1 -1
  96. package/dist/plugins/kanban/client/index.d.ts +1 -1
  97. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  98. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  99. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  100. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  101. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  102. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  103. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  104. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  105. package/dist/plugins/media/api/adapters/vercel-blob.cjs +54 -0
  106. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  107. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  108. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  109. package/dist/plugins/media/api/adapters/vercel-blob.mjs +52 -0
  110. package/dist/plugins/media/api/index.cjs +26 -0
  111. package/dist/plugins/media/api/index.d.cts +116 -0
  112. package/dist/plugins/media/api/index.d.mts +116 -0
  113. package/dist/plugins/media/api/index.d.ts +116 -0
  114. package/dist/plugins/media/api/index.mjs +6 -0
  115. package/dist/plugins/media/client/components/index.cjs +10 -0
  116. package/dist/plugins/media/client/components/index.d.cts +55 -0
  117. package/dist/plugins/media/client/components/index.d.mts +55 -0
  118. package/dist/plugins/media/client/components/index.d.ts +55 -0
  119. package/dist/plugins/media/client/components/index.mjs +2 -0
  120. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  121. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  122. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  123. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  124. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  125. package/dist/plugins/media/client/index.cjs +9 -0
  126. package/dist/plugins/media/client/index.d.cts +242 -0
  127. package/dist/plugins/media/client/index.d.mts +242 -0
  128. package/dist/plugins/media/client/index.d.ts +242 -0
  129. package/dist/plugins/media/client/index.mjs +2 -0
  130. package/dist/plugins/media/client.css +1 -0
  131. package/dist/plugins/media/query-keys.cjs +72 -0
  132. package/dist/plugins/media/query-keys.d.cts +49 -0
  133. package/dist/plugins/media/query-keys.d.mts +49 -0
  134. package/dist/plugins/media/query-keys.d.ts +49 -0
  135. package/dist/plugins/media/query-keys.mjs +70 -0
  136. package/dist/plugins/media/style.css +1 -0
  137. package/dist/shared/{stack.DOZ1EXjM.d.mts → stack.6mEHS2WH.d.mts} +3 -3
  138. package/dist/shared/{stack.DX-tQ93o.d.cts → stack.AJTXI7kw.d.cts} +3 -3
  139. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  140. package/dist/shared/stack.BUTXWiG-.d.ts +286 -0
  141. package/dist/shared/stack.C7Y9sBDg.d.mts +286 -0
  142. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  143. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  144. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  145. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  146. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  147. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  148. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  149. package/dist/shared/stack.CoBj86jf.d.cts +109 -0
  150. package/dist/shared/stack.CoBj86jf.d.mts +109 -0
  151. package/dist/shared/stack.CoBj86jf.d.ts +109 -0
  152. package/dist/shared/{stack.BXxrFL9R.d.ts → stack.D7HSzZdG.d.ts} +5 -5
  153. package/dist/shared/{stack.DzOhpIYM.d.mts → stack.DjgpFWq3.d.cts} +5 -5
  154. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  155. package/dist/shared/{stack.BSqJrCTM.d.cts → stack.IUeyQKrm.d.mts} +5 -5
  156. package/dist/shared/{stack.VF6FhyZw.d.ts → stack.QYn-Px94.d.ts} +3 -3
  157. package/dist/shared/stack.vxskCkim.d.cts +286 -0
  158. package/package.json +113 -4
  159. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  160. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  161. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  162. package/src/plugins/blog/client/overrides.ts +58 -1
  163. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  164. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  165. package/src/plugins/cms/client/overrides.ts +57 -2
  166. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  167. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  168. package/src/plugins/kanban/client/overrides.ts +25 -0
  169. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  170. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  171. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  172. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  173. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  174. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  175. package/src/plugins/media/api/adapters/local.ts +79 -0
  176. package/src/plugins/media/api/adapters/s3.ts +198 -0
  177. package/src/plugins/media/api/adapters/vercel-blob.ts +132 -0
  178. package/src/plugins/media/api/getters.ts +174 -0
  179. package/src/plugins/media/api/index.ts +41 -0
  180. package/src/plugins/media/api/mutations.ts +179 -0
  181. package/src/plugins/media/api/plugin.ts +855 -0
  182. package/src/plugins/media/api/query-key-defs.ts +41 -0
  183. package/src/plugins/media/api/serializers.ts +28 -0
  184. package/src/plugins/media/api/storage-adapter.ts +139 -0
  185. package/src/plugins/media/client/components/index.tsx +6 -0
  186. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  187. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  188. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  189. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  190. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  191. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  192. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  193. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  194. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  195. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  196. package/src/plugins/media/client/hooks/index.tsx +9 -0
  197. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  198. package/src/plugins/media/client/index.ts +4 -0
  199. package/src/plugins/media/client/overrides.ts +127 -0
  200. package/src/plugins/media/client/plugin.tsx +184 -0
  201. package/src/plugins/media/client/upload.ts +171 -0
  202. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  203. package/src/plugins/media/client.css +1 -0
  204. package/src/plugins/media/db.ts +62 -0
  205. package/src/plugins/media/query-keys.ts +96 -0
  206. package/src/plugins/media/schemas.ts +37 -0
  207. package/src/plugins/media/style.css +1 -0
  208. package/src/plugins/media/types.ts +26 -0
  209. package/dist/shared/{stack.BWp0hcm9.d.ts → stack.BQmuNl5p.d.cts} +3 -3
  210. package/dist/shared/{stack.BWp0hcm9.d.cts → stack.BQmuNl5p.d.mts} +3 -3
  211. package/dist/shared/{stack.BWp0hcm9.d.mts → stack.BQmuNl5p.d.ts} +3 -3
  212. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.D4Cea8II.d.ts} +3 -3
  213. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.HE_IvqV5.d.mts} +3 -3
  214. package/dist/shared/{stack.BOokfhZD.d.cts → stack.Rtcvl8sS.d.cts} +3 -3
@@ -0,0 +1,305 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { Popover, PopoverTrigger, PopoverContent } from '../../../../../../../ui/src/components/popover.mjs';
5
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
6
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../../../../ui/src/components/tabs.mjs';
7
+ import { X, Image, Upload, Link } from 'lucide-react';
8
+ import { FolderTree } from './folder-tree.mjs';
9
+ import { BrowseTab } from './browse-tab.mjs';
10
+ import { UploadTab } from './upload-tab.mjs';
11
+ import { UrlTab } from './url-tab.mjs';
12
+ import { usePluginOverrides } from '@btst/stack/context';
13
+
14
+ function MediaPicker({
15
+ trigger,
16
+ onSelect,
17
+ multiple = false,
18
+ accept
19
+ }) {
20
+ const [open, setOpen] = useState(false);
21
+ const [selectedFolder, setSelectedFolder] = useState(null);
22
+ const [selectedAssets, setSelectedAssets] = useState([]);
23
+ const [activeTab, setActiveTab] = useState(
24
+ "browse"
25
+ );
26
+ const handleClose = () => {
27
+ setOpen(false);
28
+ setSelectedAssets([]);
29
+ };
30
+ const handleConfirm = () => {
31
+ if (selectedAssets.length === 0) return;
32
+ const toSelect = [...selectedAssets];
33
+ handleClose();
34
+ setTimeout(() => onSelect(toSelect), 0);
35
+ };
36
+ const handleToggleAsset = (asset) => {
37
+ if (multiple) {
38
+ setSelectedAssets(
39
+ (prev) => prev.some((a) => a.id === asset.id) ? prev.filter((a) => a.id !== asset.id) : [...prev, asset]
40
+ );
41
+ } else {
42
+ setSelectedAssets([asset]);
43
+ }
44
+ };
45
+ const handleUploaded = (asset) => {
46
+ if (multiple) {
47
+ setSelectedAssets((prev) => [...prev, asset]);
48
+ } else {
49
+ setSelectedAssets([asset]);
50
+ setActiveTab("browse");
51
+ }
52
+ };
53
+ const handleUrlRegistered = (asset) => {
54
+ const toSelect = asset;
55
+ handleClose();
56
+ setTimeout(() => onSelect([toSelect]), 0);
57
+ };
58
+ return /* @__PURE__ */ jsxs(
59
+ Popover,
60
+ {
61
+ open,
62
+ onOpenChange: (v) => {
63
+ if (!v) handleClose();
64
+ else setOpen(true);
65
+ },
66
+ children: [
67
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: trigger }),
68
+ /* @__PURE__ */ jsx(
69
+ PopoverContent,
70
+ {
71
+ className: "w-[calc(100vw-1rem)] max-w-[calc(100vw-1rem)] overflow-hidden p-0 sm:w-[820px]",
72
+ align: "start",
73
+ sideOffset: 8,
74
+ collisionPadding: 8,
75
+ style: {
76
+ maxWidth: "min(820px, calc(100vw - 1rem))",
77
+ height: "min(640px, calc(100dvh - 2rem))"
78
+ },
79
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col overflow-hidden rounded-md", children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
81
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Media Library" }),
82
+ /* @__PURE__ */ jsx(
83
+ "button",
84
+ {
85
+ type: "button",
86
+ onClick: handleClose,
87
+ className: "rounded p-0.5 hover:bg-muted",
88
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
89
+ }
90
+ )
91
+ ] }),
92
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col md:flex-row", children: [
93
+ /* @__PURE__ */ jsx("div", { className: "max-h-40 w-full shrink-0 overflow-hidden border-b bg-muted/20 md:max-h-none md:w-44 md:border-b-0 md:border-r", children: /* @__PURE__ */ jsx(
94
+ FolderTree,
95
+ {
96
+ selectedId: selectedFolder,
97
+ onSelect: setSelectedFolder
98
+ }
99
+ ) }),
100
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col p-3 overflow-y-hidden", children: /* @__PURE__ */ jsxs(
101
+ Tabs,
102
+ {
103
+ value: activeTab,
104
+ onValueChange: (v) => setActiveTab(v),
105
+ className: "flex flex-1 flex-col min-h-0",
106
+ children: [
107
+ /* @__PURE__ */ jsxs(TabsList, { className: "grid h-auto w-full shrink-0 grid-cols-3 md:flex md:w-fit", children: [
108
+ /* @__PURE__ */ jsxs(
109
+ TabsTrigger,
110
+ {
111
+ value: "browse",
112
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
113
+ children: [
114
+ /* @__PURE__ */ jsx(Image, { className: "mr-1 size-3" }),
115
+ "Browse"
116
+ ]
117
+ }
118
+ ),
119
+ /* @__PURE__ */ jsxs(
120
+ TabsTrigger,
121
+ {
122
+ value: "upload",
123
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
124
+ children: [
125
+ /* @__PURE__ */ jsx(Upload, { className: "mr-1 size-3" }),
126
+ "Upload"
127
+ ]
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsxs(
131
+ TabsTrigger,
132
+ {
133
+ value: "url",
134
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
135
+ children: [
136
+ /* @__PURE__ */ jsx(Link, { className: "mr-1 size-3" }),
137
+ "URL"
138
+ ]
139
+ }
140
+ )
141
+ ] }),
142
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 min-h-0 flex-1", children: [
143
+ /* @__PURE__ */ jsx(
144
+ TabsContent,
145
+ {
146
+ value: "browse",
147
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
148
+ children: /* @__PURE__ */ jsx(
149
+ BrowseTab,
150
+ {
151
+ folderId: selectedFolder,
152
+ selected: selectedAssets,
153
+ accept,
154
+ onToggle: handleToggleAsset
155
+ }
156
+ )
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsx(
160
+ TabsContent,
161
+ {
162
+ value: "upload",
163
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
164
+ children: /* @__PURE__ */ jsx(
165
+ UploadTab,
166
+ {
167
+ folderId: selectedFolder,
168
+ accept,
169
+ onUploaded: handleUploaded
170
+ }
171
+ )
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx(
175
+ TabsContent,
176
+ {
177
+ value: "url",
178
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
179
+ children: /* @__PURE__ */ jsx(
180
+ UrlTab,
181
+ {
182
+ folderId: selectedFolder,
183
+ onRegistered: handleUrlRegistered
184
+ }
185
+ )
186
+ }
187
+ )
188
+ ] })
189
+ ]
190
+ }
191
+ ) })
192
+ ] }),
193
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border-t px-3 py-2 sm:flex-row sm:items-center sm:justify-between", children: [
194
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: selectedAssets.length > 0 ? `${selectedAssets.length} selected` : "Click a file to select it" }),
195
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full gap-2 sm:w-auto", children: [
196
+ /* @__PURE__ */ jsx(
197
+ Button,
198
+ {
199
+ type: "button",
200
+ variant: "ghost",
201
+ size: "sm",
202
+ onClick: handleClose,
203
+ className: "flex-1 sm:flex-none",
204
+ children: "Cancel"
205
+ }
206
+ ),
207
+ /* @__PURE__ */ jsx(
208
+ Button,
209
+ {
210
+ type: "button",
211
+ size: "sm",
212
+ "data-testid": "media-select-button",
213
+ onClick: handleConfirm,
214
+ disabled: selectedAssets.length === 0,
215
+ className: "flex-1 sm:flex-none",
216
+ children: multiple ? `Select ${selectedAssets.length > 0 ? `(${selectedAssets.length})` : ""}` : "Select"
217
+ }
218
+ )
219
+ ] })
220
+ ] })
221
+ ] })
222
+ }
223
+ )
224
+ ]
225
+ }
226
+ );
227
+ }
228
+ function ImageInputField({
229
+ value,
230
+ onChange
231
+ }) {
232
+ const { Image: ImageComponent } = usePluginOverrides("media", {});
233
+ if (value) {
234
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
235
+ ImageComponent ? /* @__PURE__ */ jsx(
236
+ ImageComponent,
237
+ {
238
+ src: value,
239
+ alt: "Featured image preview",
240
+ className: "h-auto w-full max-w-xs rounded-md border object-cover",
241
+ width: 400,
242
+ height: 300,
243
+ "data-testid": "image-preview"
244
+ }
245
+ ) : /* @__PURE__ */ jsx(
246
+ "img",
247
+ {
248
+ src: value,
249
+ alt: "Featured image preview",
250
+ className: "h-auto w-full max-w-xs rounded-md border object-cover",
251
+ "data-testid": "image-preview"
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
255
+ /* @__PURE__ */ jsx(
256
+ MediaPicker,
257
+ {
258
+ trigger: /* @__PURE__ */ jsx(
259
+ Button,
260
+ {
261
+ variant: "outline",
262
+ size: "sm",
263
+ type: "button",
264
+ "data-testid": "open-media-picker",
265
+ children: "Change Image"
266
+ }
267
+ ),
268
+ accept: ["image/*"],
269
+ onSelect: (assets) => onChange(assets[0]?.url ?? "")
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(
273
+ Button,
274
+ {
275
+ type: "button",
276
+ variant: "destructive",
277
+ size: "sm",
278
+ "data-testid": "remove-image-button",
279
+ onClick: () => onChange(""),
280
+ children: "Remove"
281
+ }
282
+ )
283
+ ] })
284
+ ] });
285
+ }
286
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 items-center", children: /* @__PURE__ */ jsx(
287
+ MediaPicker,
288
+ {
289
+ trigger: /* @__PURE__ */ jsx(
290
+ Button,
291
+ {
292
+ variant: "outline",
293
+ size: "sm",
294
+ type: "button",
295
+ "data-testid": "open-media-picker",
296
+ children: "Browse Media"
297
+ }
298
+ ),
299
+ accept: ["image/*"],
300
+ onSelect: (assets) => onChange(assets[0]?.url ?? "")
301
+ }
302
+ ) });
303
+ }
304
+
305
+ export { ImageInputField, MediaPicker };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const React = require('react');
5
+ const useMedia = require('../../hooks/use-media.cjs');
6
+ const button = require('../../../../../../../ui/src/components/button.cjs');
7
+ const LucideIcons = require('lucide-react');
8
+ const utils$1 = require('../../../../../../../ui/src/lib/utils.cjs');
9
+ const utils = require('./utils.cjs');
10
+
11
+ function UploadTab({
12
+ folderId,
13
+ accept,
14
+ onUploaded
15
+ }) {
16
+ const [dragging, setDragging] = React.useState(false);
17
+ const [uploading, setUploading] = React.useState(false);
18
+ const [error, setError] = React.useState(null);
19
+ const fileInputRef = React.useRef(null);
20
+ const { mutateAsync: uploadAsset } = useMedia.useUploadAsset();
21
+ const acceptAttr = accept?.join(",") ?? void 0;
22
+ const handleFiles = React.useCallback(
23
+ async (files) => {
24
+ const fileArr = Array.from(files);
25
+ if (fileArr.length === 0) return;
26
+ setError(null);
27
+ setUploading(true);
28
+ try {
29
+ for (const file of fileArr) {
30
+ if (accept && !utils.matchesAccept(file.type, accept)) {
31
+ setError(`File type ${file.type} is not accepted.`);
32
+ continue;
33
+ }
34
+ const asset = await uploadAsset({
35
+ file,
36
+ folderId: folderId ?? void 0
37
+ });
38
+ onUploaded(asset);
39
+ }
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err.message : "Upload failed");
42
+ } finally {
43
+ setUploading(false);
44
+ }
45
+ },
46
+ [accept, folderId, uploadAsset, onUploaded]
47
+ );
48
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col gap-3", children: [
49
+ /* @__PURE__ */ jsxRuntime.jsx(
50
+ "div",
51
+ {
52
+ onDragOver: (e) => {
53
+ e.preventDefault();
54
+ setDragging(true);
55
+ },
56
+ onDragLeave: () => setDragging(false),
57
+ onDrop: (e) => {
58
+ e.preventDefault();
59
+ setDragging(false);
60
+ void handleFiles(e.dataTransfer.files);
61
+ },
62
+ className: utils$1.cn(
63
+ "flex flex-1 flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-6 text-center transition-colors sm:px-6",
64
+ dragging ? "border-ring bg-ring/5" : "border-muted-foreground/30"
65
+ ),
66
+ children: uploading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
67
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "size-8 animate-spin text-muted-foreground" }),
68
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Uploading\u2026" })
69
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
70
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Upload, { className: "size-8 text-muted-foreground" }),
71
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Drop files here" }),
73
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "or click to browse" })
74
+ ] }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(
76
+ button.Button,
77
+ {
78
+ type: "button",
79
+ variant: "outline",
80
+ size: "sm",
81
+ onClick: () => fileInputRef.current?.click(),
82
+ children: "Choose files"
83
+ }
84
+ )
85
+ ] })
86
+ }
87
+ ),
88
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: error }),
89
+ /* @__PURE__ */ jsxRuntime.jsx(
90
+ "input",
91
+ {
92
+ ref: fileInputRef,
93
+ type: "file",
94
+ accept: acceptAttr,
95
+ multiple: true,
96
+ className: "hidden",
97
+ "data-testid": "media-upload-input",
98
+ onChange: (e) => e.target.files && handleFiles(e.target.files)
99
+ }
100
+ )
101
+ ] });
102
+ }
103
+
104
+ exports.UploadTab = UploadTab;
@@ -0,0 +1,102 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useState, useRef, useCallback } from 'react';
3
+ import { useUploadAsset } from '../../hooks/use-media.mjs';
4
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
5
+ import { Loader2, Upload } from 'lucide-react';
6
+ import { cn } from '../../../../../../../ui/src/lib/utils.mjs';
7
+ import { matchesAccept } from './utils.mjs';
8
+
9
+ function UploadTab({
10
+ folderId,
11
+ accept,
12
+ onUploaded
13
+ }) {
14
+ const [dragging, setDragging] = useState(false);
15
+ const [uploading, setUploading] = useState(false);
16
+ const [error, setError] = useState(null);
17
+ const fileInputRef = useRef(null);
18
+ const { mutateAsync: uploadAsset } = useUploadAsset();
19
+ const acceptAttr = accept?.join(",") ?? void 0;
20
+ const handleFiles = useCallback(
21
+ async (files) => {
22
+ const fileArr = Array.from(files);
23
+ if (fileArr.length === 0) return;
24
+ setError(null);
25
+ setUploading(true);
26
+ try {
27
+ for (const file of fileArr) {
28
+ if (accept && !matchesAccept(file.type, accept)) {
29
+ setError(`File type ${file.type} is not accepted.`);
30
+ continue;
31
+ }
32
+ const asset = await uploadAsset({
33
+ file,
34
+ folderId: folderId ?? void 0
35
+ });
36
+ onUploaded(asset);
37
+ }
38
+ } catch (err) {
39
+ setError(err instanceof Error ? err.message : "Upload failed");
40
+ } finally {
41
+ setUploading(false);
42
+ }
43
+ },
44
+ [accept, folderId, uploadAsset, onUploaded]
45
+ );
46
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-3", children: [
47
+ /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ onDragOver: (e) => {
51
+ e.preventDefault();
52
+ setDragging(true);
53
+ },
54
+ onDragLeave: () => setDragging(false),
55
+ onDrop: (e) => {
56
+ e.preventDefault();
57
+ setDragging(false);
58
+ void handleFiles(e.dataTransfer.files);
59
+ },
60
+ className: cn(
61
+ "flex flex-1 flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-6 text-center transition-colors sm:px-6",
62
+ dragging ? "border-ring bg-ring/5" : "border-muted-foreground/30"
63
+ ),
64
+ children: uploading ? /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin text-muted-foreground" }),
66
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Uploading\u2026" })
67
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
68
+ /* @__PURE__ */ jsx(Upload, { className: "size-8 text-muted-foreground" }),
69
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
70
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Drop files here" }),
71
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "or click to browse" })
72
+ ] }),
73
+ /* @__PURE__ */ jsx(
74
+ Button,
75
+ {
76
+ type: "button",
77
+ variant: "outline",
78
+ size: "sm",
79
+ onClick: () => fileInputRef.current?.click(),
80
+ children: "Choose files"
81
+ }
82
+ )
83
+ ] })
84
+ }
85
+ ),
86
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
87
+ /* @__PURE__ */ jsx(
88
+ "input",
89
+ {
90
+ ref: fileInputRef,
91
+ type: "file",
92
+ accept: acceptAttr,
93
+ multiple: true,
94
+ className: "hidden",
95
+ "data-testid": "media-upload-input",
96
+ onChange: (e) => e.target.files && handleFiles(e.target.files)
97
+ }
98
+ )
99
+ ] });
100
+ }
101
+
102
+ export { UploadTab };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const React = require('react');
5
+ const useMedia = require('../../hooks/use-media.cjs');
6
+ const input = require('../../../../../../../ui/src/components/input.cjs');
7
+ const button = require('../../../../../../../ui/src/components/button.cjs');
8
+ const LucideIcons = require('lucide-react');
9
+
10
+ function UrlTab({
11
+ folderId,
12
+ onRegistered
13
+ }) {
14
+ const [url, setUrl] = React.useState("");
15
+ const [error, setError] = React.useState(null);
16
+ const { mutateAsync: registerAsset, isPending } = useMedia.useRegisterAsset();
17
+ const handleSubmit = async (e) => {
18
+ e.preventDefault();
19
+ setError(null);
20
+ const trimmed = url.trim();
21
+ if (!trimmed) return;
22
+ try {
23
+ const filename = trimmed.split("/").pop() ?? "asset";
24
+ const asset = await registerAsset({
25
+ url: trimmed,
26
+ filename,
27
+ folderId: folderId ?? void 0
28
+ });
29
+ setUrl("");
30
+ onRegistered(asset);
31
+ } catch (err) {
32
+ setError(err instanceof Error ? err.message : "Failed to register URL");
33
+ }
34
+ };
35
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col gap-3 pt-2", children: [
36
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Paste a public URL to register it as an asset without uploading a file." }),
37
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-2", children: [
38
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
39
+ /* @__PURE__ */ jsxRuntime.jsx(
40
+ input.Input,
41
+ {
42
+ type: "url",
43
+ value: url,
44
+ onChange: (e) => setUrl(e.target.value),
45
+ placeholder: "https://example.com/image.png",
46
+ className: "flex-1",
47
+ "data-testid": "media-url-input",
48
+ autoFocus: true
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsxRuntime.jsxs(
52
+ button.Button,
53
+ {
54
+ type: "submit",
55
+ size: "sm",
56
+ disabled: isPending || !url.trim(),
57
+ className: "w-full sm:w-auto",
58
+ children: [
59
+ isPending ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "mr-1 size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Check, { className: "mr-1 size-4" }),
60
+ "Use URL"
61
+ ]
62
+ }
63
+ )
64
+ ] }),
65
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: error })
66
+ ] })
67
+ ] });
68
+ }
69
+
70
+ exports.UrlTab = UrlTab;
@@ -0,0 +1,68 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import { useRegisterAsset } from '../../hooks/use-media.mjs';
4
+ import { Input } from '../../../../../../../ui/src/components/input.mjs';
5
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
6
+ import { Loader2, Check } from 'lucide-react';
7
+
8
+ function UrlTab({
9
+ folderId,
10
+ onRegistered
11
+ }) {
12
+ const [url, setUrl] = useState("");
13
+ const [error, setError] = useState(null);
14
+ const { mutateAsync: registerAsset, isPending } = useRegisterAsset();
15
+ const handleSubmit = async (e) => {
16
+ e.preventDefault();
17
+ setError(null);
18
+ const trimmed = url.trim();
19
+ if (!trimmed) return;
20
+ try {
21
+ const filename = trimmed.split("/").pop() ?? "asset";
22
+ const asset = await registerAsset({
23
+ url: trimmed,
24
+ filename,
25
+ folderId: folderId ?? void 0
26
+ });
27
+ setUrl("");
28
+ onRegistered(asset);
29
+ } catch (err) {
30
+ setError(err instanceof Error ? err.message : "Failed to register URL");
31
+ }
32
+ };
33
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-3 pt-2", children: [
34
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Paste a public URL to register it as an asset without uploading a file." }),
35
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-2", children: [
36
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
37
+ /* @__PURE__ */ jsx(
38
+ Input,
39
+ {
40
+ type: "url",
41
+ value: url,
42
+ onChange: (e) => setUrl(e.target.value),
43
+ placeholder: "https://example.com/image.png",
44
+ className: "flex-1",
45
+ "data-testid": "media-url-input",
46
+ autoFocus: true
47
+ }
48
+ ),
49
+ /* @__PURE__ */ jsxs(
50
+ Button,
51
+ {
52
+ type: "submit",
53
+ size: "sm",
54
+ disabled: isPending || !url.trim(),
55
+ className: "w-full sm:w-auto",
56
+ children: [
57
+ isPending ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1 size-4 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "mr-1 size-4" }),
58
+ "Use URL"
59
+ ]
60
+ }
61
+ )
62
+ ] }),
63
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error })
64
+ ] })
65
+ ] });
66
+ }
67
+
68
+ export { UrlTab };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ function matchesAccept(mimeType, accept) {
4
+ if (!accept || accept.length === 0) return true;
5
+ return accept.some((a) => {
6
+ if (a.endsWith("/*")) return mimeType.startsWith(a.slice(0, -1));
7
+ return mimeType === a;
8
+ });
9
+ }
10
+ function isImage(mimeType) {
11
+ return mimeType.startsWith("image/");
12
+ }
13
+ function formatBytes(bytes) {
14
+ if (bytes < 1024) return `${bytes} B`;
15
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
16
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
17
+ }
18
+
19
+ exports.formatBytes = formatBytes;
20
+ exports.isImage = isImage;
21
+ exports.matchesAccept = matchesAccept;