@btst/stack 2.8.1 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) 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/client/index.d.cts +58 -1
  74. package/dist/plugins/blog/client/index.d.mts +58 -1
  75. package/dist/plugins/blog/client/index.d.ts +58 -1
  76. package/dist/plugins/cms/client/index.d.cts +73 -3
  77. package/dist/plugins/cms/client/index.d.mts +73 -3
  78. package/dist/plugins/cms/client/index.d.ts +73 -3
  79. package/dist/plugins/kanban/api/index.d.cts +1 -1
  80. package/dist/plugins/kanban/api/index.d.mts +1 -1
  81. package/dist/plugins/kanban/api/index.d.ts +1 -1
  82. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  83. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  84. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  85. package/dist/plugins/kanban/client/index.d.cts +1 -1
  86. package/dist/plugins/kanban/client/index.d.mts +1 -1
  87. package/dist/plugins/kanban/client/index.d.ts +1 -1
  88. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  89. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  90. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  91. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  92. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  93. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  94. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  95. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  96. package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
  97. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  98. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  99. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  100. package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
  101. package/dist/plugins/media/api/index.cjs +26 -0
  102. package/dist/plugins/media/api/index.d.cts +116 -0
  103. package/dist/plugins/media/api/index.d.mts +116 -0
  104. package/dist/plugins/media/api/index.d.ts +116 -0
  105. package/dist/plugins/media/api/index.mjs +6 -0
  106. package/dist/plugins/media/client/components/index.cjs +10 -0
  107. package/dist/plugins/media/client/components/index.d.cts +55 -0
  108. package/dist/plugins/media/client/components/index.d.mts +55 -0
  109. package/dist/plugins/media/client/components/index.d.ts +55 -0
  110. package/dist/plugins/media/client/components/index.mjs +2 -0
  111. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  112. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  113. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  114. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  115. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  116. package/dist/plugins/media/client/index.cjs +9 -0
  117. package/dist/plugins/media/client/index.d.cts +242 -0
  118. package/dist/plugins/media/client/index.d.mts +242 -0
  119. package/dist/plugins/media/client/index.d.ts +242 -0
  120. package/dist/plugins/media/client/index.mjs +2 -0
  121. package/dist/plugins/media/client.css +1 -0
  122. package/dist/plugins/media/query-keys.cjs +72 -0
  123. package/dist/plugins/media/query-keys.d.cts +49 -0
  124. package/dist/plugins/media/query-keys.d.mts +49 -0
  125. package/dist/plugins/media/query-keys.d.ts +49 -0
  126. package/dist/plugins/media/query-keys.mjs +70 -0
  127. package/dist/plugins/media/style.css +1 -0
  128. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  129. package/dist/shared/stack.BttDsJJn.d.cts +109 -0
  130. package/dist/shared/stack.BttDsJJn.d.mts +109 -0
  131. package/dist/shared/stack.BttDsJJn.d.ts +109 -0
  132. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  133. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  134. package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
  135. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  136. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  137. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  138. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  139. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  140. package/dist/shared/stack.DJDjdG64.d.ts +286 -0
  141. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  142. package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
  143. package/package.json +113 -4
  144. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  145. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  146. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  147. package/src/plugins/blog/client/overrides.ts +58 -1
  148. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  149. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  150. package/src/plugins/cms/client/overrides.ts +57 -2
  151. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  152. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  153. package/src/plugins/kanban/client/overrides.ts +25 -0
  154. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  155. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  156. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  157. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  158. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  159. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  160. package/src/plugins/media/api/adapters/local.ts +79 -0
  161. package/src/plugins/media/api/adapters/s3.ts +198 -0
  162. package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
  163. package/src/plugins/media/api/getters.ts +174 -0
  164. package/src/plugins/media/api/index.ts +41 -0
  165. package/src/plugins/media/api/mutations.ts +179 -0
  166. package/src/plugins/media/api/plugin.ts +855 -0
  167. package/src/plugins/media/api/query-key-defs.ts +41 -0
  168. package/src/plugins/media/api/serializers.ts +28 -0
  169. package/src/plugins/media/api/storage-adapter.ts +139 -0
  170. package/src/plugins/media/client/components/index.tsx +6 -0
  171. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  172. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  173. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  174. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  175. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  176. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  177. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  178. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  179. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  180. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  181. package/src/plugins/media/client/hooks/index.tsx +9 -0
  182. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  183. package/src/plugins/media/client/index.ts +4 -0
  184. package/src/plugins/media/client/overrides.ts +127 -0
  185. package/src/plugins/media/client/plugin.tsx +184 -0
  186. package/src/plugins/media/client/upload.ts +171 -0
  187. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  188. package/src/plugins/media/client.css +1 -0
  189. package/src/plugins/media/db.ts +62 -0
  190. package/src/plugins/media/query-keys.ts +96 -0
  191. package/src/plugins/media/schemas.ts +37 -0
  192. package/src/plugins/media/style.css +1 -0
  193. package/src/plugins/media/types.ts +26 -0
  194. package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
  195. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
  196. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
@@ -0,0 +1,171 @@
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 LucideIcons = require('lucide-react');
7
+ const input = require('../../../../../../../ui/src/components/input.cjs');
8
+ const utils = require('../../../../../../../ui/src/lib/utils.cjs');
9
+
10
+ function FolderTree({
11
+ selectedId,
12
+ onSelect
13
+ }) {
14
+ const { data: rootFoldersRaw = [] } = useMedia.useFolders(null);
15
+ const rootFolders = rootFoldersRaw;
16
+ const [newFolderName, setNewFolderName] = React.useState("");
17
+ const [isCreating, setIsCreating] = React.useState(false);
18
+ const { mutateAsync: createFolder } = useMedia.useCreateFolder();
19
+ const { mutateAsync: deleteFolder } = useMedia.useDeleteFolder();
20
+ const handleCreateFolder = async () => {
21
+ const name = newFolderName.trim();
22
+ if (!name) return;
23
+ try {
24
+ await createFolder({ name, parentId: selectedId ?? void 0 });
25
+ setNewFolderName("");
26
+ setIsCreating(false);
27
+ } catch (err) {
28
+ console.error("[btst/media] Failed to create folder", err);
29
+ }
30
+ };
31
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
32
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-2 py-2", children: [
33
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Folders" }),
34
+ /* @__PURE__ */ jsxRuntime.jsx(
35
+ "button",
36
+ {
37
+ type: "button",
38
+ title: "New folder",
39
+ onClick: () => setIsCreating((v) => !v),
40
+ className: "rounded p-0.5 hover:bg-muted",
41
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.FolderPlus, { className: "size-3.5 text-muted-foreground" })
42
+ }
43
+ )
44
+ ] }),
45
+ isCreating && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1 px-2 pb-1", children: [
46
+ /* @__PURE__ */ jsxRuntime.jsx(
47
+ input.Input,
48
+ {
49
+ autoFocus: true,
50
+ value: newFolderName,
51
+ onChange: (e) => setNewFolderName(e.target.value),
52
+ placeholder: "Folder name",
53
+ className: "h-6 text-xs",
54
+ onKeyDown: (e) => {
55
+ if (e.key === "Enter") void handleCreateFolder();
56
+ if (e.key === "Escape") setIsCreating(false);
57
+ }
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsxRuntime.jsx(
61
+ "button",
62
+ {
63
+ type: "button",
64
+ onClick: handleCreateFolder,
65
+ className: "rounded px-1 py-0.5 text-xs hover:bg-muted",
66
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Check, { className: "size-3" })
67
+ }
68
+ )
69
+ ] }),
70
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto overscroll-contain", children: [
71
+ /* @__PURE__ */ jsxRuntime.jsxs(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ onClick: () => onSelect(null),
76
+ className: utils.cn(
77
+ "flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted",
78
+ selectedId === null && "bg-muted font-medium"
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-3" }),
82
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Folder, { className: "size-3.5 shrink-0 text-muted-foreground" }),
83
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: "All files" })
84
+ ]
85
+ }
86
+ ),
87
+ rootFolders.map((folder) => /* @__PURE__ */ jsxRuntime.jsx(
88
+ FolderTreeItem,
89
+ {
90
+ folder,
91
+ selectedId,
92
+ onSelect
93
+ },
94
+ folder.id
95
+ ))
96
+ ] }),
97
+ selectedId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t px-2 py-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ onClick: async () => {
102
+ if (confirm("Delete this folder? Assets inside will be unaffected.")) {
103
+ try {
104
+ await deleteFolder(selectedId);
105
+ onSelect(null);
106
+ } catch (err) {
107
+ console.error("[btst/media] Failed to delete folder", err);
108
+ }
109
+ }
110
+ },
111
+ className: "flex items-center gap-1 text-xs text-destructive hover:underline",
112
+ children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Trash2, { className: "size-3" }),
114
+ "Delete folder"
115
+ ]
116
+ }
117
+ ) })
118
+ ] });
119
+ }
120
+ function FolderTreeItem({
121
+ folder,
122
+ selectedId,
123
+ onSelect,
124
+ depth = 0
125
+ }) {
126
+ const [expanded, setExpanded] = React.useState(false);
127
+ const { data: children = [] } = useMedia.useFolders(folder.id);
128
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
129
+ /* @__PURE__ */ jsxRuntime.jsxs(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ onClick: () => {
134
+ onSelect(folder.id);
135
+ setExpanded((v) => !v);
136
+ },
137
+ className: utils.cn(
138
+ "flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted",
139
+ selectedId === folder.id && "bg-muted font-medium"
140
+ ),
141
+ style: { paddingLeft: `${8 + depth * 12}px` },
142
+ children: [
143
+ children.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
144
+ LucideIcons.ChevronRight,
145
+ {
146
+ className: utils.cn(
147
+ "size-3 shrink-0 transition-transform",
148
+ expanded && "rotate-90"
149
+ )
150
+ }
151
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-3" }),
152
+ expanded ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.FolderOpen, { className: "size-3.5 shrink-0 text-amber-500" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Folder, { className: "size-3.5 shrink-0 text-amber-500" }),
153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: folder.name })
154
+ ]
155
+ }
156
+ ),
157
+ expanded && children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
158
+ FolderTreeItem,
159
+ {
160
+ folder: child,
161
+ selectedId,
162
+ onSelect,
163
+ depth: depth + 1
164
+ },
165
+ child.id
166
+ ))
167
+ ] });
168
+ }
169
+
170
+ exports.FolderTree = FolderTree;
171
+ exports.FolderTreeItem = FolderTreeItem;
@@ -0,0 +1,168 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import { useFolders, useCreateFolder, useDeleteFolder } from '../../hooks/use-media.mjs';
4
+ import { FolderPlus, Check, Folder, Trash2, ChevronRight, FolderOpen } from 'lucide-react';
5
+ import { Input } from '../../../../../../../ui/src/components/input.mjs';
6
+ import { cn } from '../../../../../../../ui/src/lib/utils.mjs';
7
+
8
+ function FolderTree({
9
+ selectedId,
10
+ onSelect
11
+ }) {
12
+ const { data: rootFoldersRaw = [] } = useFolders(null);
13
+ const rootFolders = rootFoldersRaw;
14
+ const [newFolderName, setNewFolderName] = useState("");
15
+ const [isCreating, setIsCreating] = useState(false);
16
+ const { mutateAsync: createFolder } = useCreateFolder();
17
+ const { mutateAsync: deleteFolder } = useDeleteFolder();
18
+ const handleCreateFolder = async () => {
19
+ const name = newFolderName.trim();
20
+ if (!name) return;
21
+ try {
22
+ await createFolder({ name, parentId: selectedId ?? void 0 });
23
+ setNewFolderName("");
24
+ setIsCreating(false);
25
+ } catch (err) {
26
+ console.error("[btst/media] Failed to create folder", err);
27
+ }
28
+ };
29
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 py-2", children: [
31
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Folders" }),
32
+ /* @__PURE__ */ jsx(
33
+ "button",
34
+ {
35
+ type: "button",
36
+ title: "New folder",
37
+ onClick: () => setIsCreating((v) => !v),
38
+ className: "rounded p-0.5 hover:bg-muted",
39
+ children: /* @__PURE__ */ jsx(FolderPlus, { className: "size-3.5 text-muted-foreground" })
40
+ }
41
+ )
42
+ ] }),
43
+ isCreating && /* @__PURE__ */ jsxs("div", { className: "flex gap-1 px-2 pb-1", children: [
44
+ /* @__PURE__ */ jsx(
45
+ Input,
46
+ {
47
+ autoFocus: true,
48
+ value: newFolderName,
49
+ onChange: (e) => setNewFolderName(e.target.value),
50
+ placeholder: "Folder name",
51
+ className: "h-6 text-xs",
52
+ onKeyDown: (e) => {
53
+ if (e.key === "Enter") void handleCreateFolder();
54
+ if (e.key === "Escape") setIsCreating(false);
55
+ }
56
+ }
57
+ ),
58
+ /* @__PURE__ */ jsx(
59
+ "button",
60
+ {
61
+ type: "button",
62
+ onClick: handleCreateFolder,
63
+ className: "rounded px-1 py-0.5 text-xs hover:bg-muted",
64
+ children: /* @__PURE__ */ jsx(Check, { className: "size-3" })
65
+ }
66
+ )
67
+ ] }),
68
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto overscroll-contain", children: [
69
+ /* @__PURE__ */ jsxs(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ onClick: () => onSelect(null),
74
+ className: cn(
75
+ "flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted",
76
+ selectedId === null && "bg-muted font-medium"
77
+ ),
78
+ children: [
79
+ /* @__PURE__ */ jsx("span", { className: "size-3" }),
80
+ /* @__PURE__ */ jsx(Folder, { className: "size-3.5 shrink-0 text-muted-foreground" }),
81
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: "All files" })
82
+ ]
83
+ }
84
+ ),
85
+ rootFolders.map((folder) => /* @__PURE__ */ jsx(
86
+ FolderTreeItem,
87
+ {
88
+ folder,
89
+ selectedId,
90
+ onSelect
91
+ },
92
+ folder.id
93
+ ))
94
+ ] }),
95
+ selectedId && /* @__PURE__ */ jsx("div", { className: "border-t px-2 py-1", children: /* @__PURE__ */ jsxs(
96
+ "button",
97
+ {
98
+ type: "button",
99
+ onClick: async () => {
100
+ if (confirm("Delete this folder? Assets inside will be unaffected.")) {
101
+ try {
102
+ await deleteFolder(selectedId);
103
+ onSelect(null);
104
+ } catch (err) {
105
+ console.error("[btst/media] Failed to delete folder", err);
106
+ }
107
+ }
108
+ },
109
+ className: "flex items-center gap-1 text-xs text-destructive hover:underline",
110
+ children: [
111
+ /* @__PURE__ */ jsx(Trash2, { className: "size-3" }),
112
+ "Delete folder"
113
+ ]
114
+ }
115
+ ) })
116
+ ] });
117
+ }
118
+ function FolderTreeItem({
119
+ folder,
120
+ selectedId,
121
+ onSelect,
122
+ depth = 0
123
+ }) {
124
+ const [expanded, setExpanded] = useState(false);
125
+ const { data: children = [] } = useFolders(folder.id);
126
+ return /* @__PURE__ */ jsxs("div", { children: [
127
+ /* @__PURE__ */ jsxs(
128
+ "button",
129
+ {
130
+ type: "button",
131
+ onClick: () => {
132
+ onSelect(folder.id);
133
+ setExpanded((v) => !v);
134
+ },
135
+ className: cn(
136
+ "flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted",
137
+ selectedId === folder.id && "bg-muted font-medium"
138
+ ),
139
+ style: { paddingLeft: `${8 + depth * 12}px` },
140
+ children: [
141
+ children.length > 0 ? /* @__PURE__ */ jsx(
142
+ ChevronRight,
143
+ {
144
+ className: cn(
145
+ "size-3 shrink-0 transition-transform",
146
+ expanded && "rotate-90"
147
+ )
148
+ }
149
+ ) : /* @__PURE__ */ jsx("span", { className: "size-3" }),
150
+ expanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "size-3.5 shrink-0 text-amber-500" }) : /* @__PURE__ */ jsx(Folder, { className: "size-3.5 shrink-0 text-amber-500" }),
151
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: folder.name })
152
+ ]
153
+ }
154
+ ),
155
+ expanded && children.map((child) => /* @__PURE__ */ jsx(
156
+ FolderTreeItem,
157
+ {
158
+ folder: child,
159
+ selectedId,
160
+ onSelect,
161
+ depth: depth + 1
162
+ },
163
+ child.id
164
+ ))
165
+ ] });
166
+ }
167
+
168
+ export { FolderTree, FolderTreeItem };
@@ -0,0 +1,308 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const popover = require('../../../../../../../ui/src/components/popover.cjs');
7
+ const button = require('../../../../../../../ui/src/components/button.cjs');
8
+ const tabs = require('../../../../../../../ui/src/components/tabs.cjs');
9
+ const LucideIcons = require('lucide-react');
10
+ const folderTree = require('./folder-tree.cjs');
11
+ const browseTab = require('./browse-tab.cjs');
12
+ const uploadTab = require('./upload-tab.cjs');
13
+ const urlTab = require('./url-tab.cjs');
14
+ const context = require('@btst/stack/context');
15
+
16
+ function MediaPicker({
17
+ trigger,
18
+ onSelect,
19
+ multiple = false,
20
+ accept
21
+ }) {
22
+ const [open, setOpen] = React.useState(false);
23
+ const [selectedFolder, setSelectedFolder] = React.useState(null);
24
+ const [selectedAssets, setSelectedAssets] = React.useState([]);
25
+ const [activeTab, setActiveTab] = React.useState(
26
+ "browse"
27
+ );
28
+ const handleClose = () => {
29
+ setOpen(false);
30
+ setSelectedAssets([]);
31
+ };
32
+ const handleConfirm = () => {
33
+ if (selectedAssets.length === 0) return;
34
+ const toSelect = [...selectedAssets];
35
+ handleClose();
36
+ setTimeout(() => onSelect(toSelect), 0);
37
+ };
38
+ const handleToggleAsset = (asset) => {
39
+ if (multiple) {
40
+ setSelectedAssets(
41
+ (prev) => prev.some((a) => a.id === asset.id) ? prev.filter((a) => a.id !== asset.id) : [...prev, asset]
42
+ );
43
+ } else {
44
+ setSelectedAssets([asset]);
45
+ }
46
+ };
47
+ const handleUploaded = (asset) => {
48
+ if (multiple) {
49
+ setSelectedAssets((prev) => [...prev, asset]);
50
+ } else {
51
+ setSelectedAssets([asset]);
52
+ setActiveTab("browse");
53
+ }
54
+ };
55
+ const handleUrlRegistered = (asset) => {
56
+ const toSelect = asset;
57
+ handleClose();
58
+ setTimeout(() => onSelect([toSelect]), 0);
59
+ };
60
+ return /* @__PURE__ */ jsxRuntime.jsxs(
61
+ popover.Popover,
62
+ {
63
+ open,
64
+ onOpenChange: (v) => {
65
+ if (!v) handleClose();
66
+ else setOpen(true);
67
+ },
68
+ children: [
69
+ /* @__PURE__ */ jsxRuntime.jsx(popover.PopoverTrigger, { asChild: true, children: trigger }),
70
+ /* @__PURE__ */ jsxRuntime.jsx(
71
+ popover.PopoverContent,
72
+ {
73
+ className: "w-[calc(100vw-1rem)] max-w-[calc(100vw-1rem)] overflow-hidden p-0 sm:w-[820px]",
74
+ align: "start",
75
+ sideOffset: 8,
76
+ collisionPadding: 8,
77
+ style: {
78
+ maxWidth: "min(820px, calc(100vw - 1rem))",
79
+ height: "min(640px, calc(100dvh - 2rem))"
80
+ },
81
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col overflow-hidden rounded-md", children: [
82
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: "Media Library" }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(
85
+ "button",
86
+ {
87
+ type: "button",
88
+ onClick: handleClose,
89
+ className: "rounded p-0.5 hover:bg-muted",
90
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "size-4" })
91
+ }
92
+ )
93
+ ] }),
94
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col md:flex-row", children: [
95
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
96
+ folderTree.FolderTree,
97
+ {
98
+ selectedId: selectedFolder,
99
+ onSelect: setSelectedFolder
100
+ }
101
+ ) }),
102
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 flex-col p-3 overflow-y-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(
103
+ tabs.Tabs,
104
+ {
105
+ value: activeTab,
106
+ onValueChange: (v) => setActiveTab(v),
107
+ className: "flex flex-1 flex-col min-h-0",
108
+ children: [
109
+ /* @__PURE__ */ jsxRuntime.jsxs(tabs.TabsList, { className: "grid h-auto w-full shrink-0 grid-cols-3 md:flex md:w-fit", children: [
110
+ /* @__PURE__ */ jsxRuntime.jsxs(
111
+ tabs.TabsTrigger,
112
+ {
113
+ value: "browse",
114
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
115
+ children: [
116
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Image, { className: "mr-1 size-3" }),
117
+ "Browse"
118
+ ]
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsxRuntime.jsxs(
122
+ tabs.TabsTrigger,
123
+ {
124
+ value: "upload",
125
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
126
+ children: [
127
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Upload, { className: "mr-1 size-3" }),
128
+ "Upload"
129
+ ]
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsxRuntime.jsxs(
133
+ tabs.TabsTrigger,
134
+ {
135
+ value: "url",
136
+ className: "h-8 px-2 text-xs md:h-6 md:px-3",
137
+ children: [
138
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "mr-1 size-3" }),
139
+ "URL"
140
+ ]
141
+ }
142
+ )
143
+ ] }),
144
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 min-h-0 flex-1", children: [
145
+ /* @__PURE__ */ jsxRuntime.jsx(
146
+ tabs.TabsContent,
147
+ {
148
+ value: "browse",
149
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
150
+ children: /* @__PURE__ */ jsxRuntime.jsx(
151
+ browseTab.BrowseTab,
152
+ {
153
+ folderId: selectedFolder,
154
+ selected: selectedAssets,
155
+ accept,
156
+ onToggle: handleToggleAsset
157
+ }
158
+ )
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsxRuntime.jsx(
162
+ tabs.TabsContent,
163
+ {
164
+ value: "upload",
165
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
166
+ children: /* @__PURE__ */ jsxRuntime.jsx(
167
+ uploadTab.UploadTab,
168
+ {
169
+ folderId: selectedFolder,
170
+ accept,
171
+ onUploaded: handleUploaded
172
+ }
173
+ )
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsxRuntime.jsx(
177
+ tabs.TabsContent,
178
+ {
179
+ value: "url",
180
+ className: "m-0 h-full min-h-0 data-[state=active]:flex data-[state=active]:flex-col",
181
+ children: /* @__PURE__ */ jsxRuntime.jsx(
182
+ urlTab.UrlTab,
183
+ {
184
+ folderId: selectedFolder,
185
+ onRegistered: handleUrlRegistered
186
+ }
187
+ )
188
+ }
189
+ )
190
+ ] })
191
+ ]
192
+ }
193
+ ) })
194
+ ] }),
195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 border-t px-3 py-2 sm:flex-row sm:items-center sm:justify-between", children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: selectedAssets.length > 0 ? `${selectedAssets.length} selected` : "Click a file to select it" }),
197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full gap-2 sm:w-auto", children: [
198
+ /* @__PURE__ */ jsxRuntime.jsx(
199
+ button.Button,
200
+ {
201
+ type: "button",
202
+ variant: "ghost",
203
+ size: "sm",
204
+ onClick: handleClose,
205
+ className: "flex-1 sm:flex-none",
206
+ children: "Cancel"
207
+ }
208
+ ),
209
+ /* @__PURE__ */ jsxRuntime.jsx(
210
+ button.Button,
211
+ {
212
+ type: "button",
213
+ size: "sm",
214
+ "data-testid": "media-select-button",
215
+ onClick: handleConfirm,
216
+ disabled: selectedAssets.length === 0,
217
+ className: "flex-1 sm:flex-none",
218
+ children: multiple ? `Select ${selectedAssets.length > 0 ? `(${selectedAssets.length})` : ""}` : "Select"
219
+ }
220
+ )
221
+ ] })
222
+ ] })
223
+ ] })
224
+ }
225
+ )
226
+ ]
227
+ }
228
+ );
229
+ }
230
+ function ImageInputField({
231
+ value,
232
+ onChange
233
+ }) {
234
+ const { Image: ImageComponent } = context.usePluginOverrides("media", {});
235
+ if (value) {
236
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
237
+ ImageComponent ? /* @__PURE__ */ jsxRuntime.jsx(
238
+ ImageComponent,
239
+ {
240
+ src: value,
241
+ alt: "Featured image preview",
242
+ className: "h-auto w-full max-w-xs rounded-md border object-cover",
243
+ width: 400,
244
+ height: 300,
245
+ "data-testid": "image-preview"
246
+ }
247
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
248
+ "img",
249
+ {
250
+ src: value,
251
+ alt: "Featured image preview",
252
+ className: "h-auto w-full max-w-xs rounded-md border object-cover",
253
+ "data-testid": "image-preview"
254
+ }
255
+ ),
256
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
257
+ /* @__PURE__ */ jsxRuntime.jsx(
258
+ MediaPicker,
259
+ {
260
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
261
+ button.Button,
262
+ {
263
+ variant: "outline",
264
+ size: "sm",
265
+ type: "button",
266
+ "data-testid": "open-media-picker",
267
+ children: "Change Image"
268
+ }
269
+ ),
270
+ accept: ["image/*"],
271
+ onSelect: (assets) => onChange(assets[0]?.url ?? "")
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsxRuntime.jsx(
275
+ button.Button,
276
+ {
277
+ type: "button",
278
+ variant: "destructive",
279
+ size: "sm",
280
+ "data-testid": "remove-image-button",
281
+ onClick: () => onChange(""),
282
+ children: "Remove"
283
+ }
284
+ )
285
+ ] })
286
+ ] });
287
+ }
288
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
289
+ MediaPicker,
290
+ {
291
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
292
+ button.Button,
293
+ {
294
+ variant: "outline",
295
+ size: "sm",
296
+ type: "button",
297
+ "data-testid": "open-media-picker",
298
+ children: "Browse Media"
299
+ }
300
+ ),
301
+ accept: ["image/*"],
302
+ onSelect: (assets) => onChange(assets[0]?.url ?? "")
303
+ }
304
+ ) });
305
+ }
306
+
307
+ exports.ImageInputField = ImageInputField;
308
+ exports.MediaPicker = MediaPicker;