@burdenoff/microfe-files 2026.1014.12 → 2026.1016.10

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 (215) hide show
  1. package/README.md +37 -0
  2. package/dist/config/index.js.map +1 -1
  3. package/dist/files/FilesRoot.js +17 -10
  4. package/dist/files/FilesRoot.js.map +1 -1
  5. package/dist/files/FilesRoutes.d.ts +1 -1
  6. package/dist/files/FilesRoutes.js.map +1 -1
  7. package/dist/files/components/ActionMenu.js.map +1 -1
  8. package/dist/files/components/ConfirmDialog.d.ts +1 -1
  9. package/dist/files/components/ConfirmDialog.js.map +1 -1
  10. package/dist/files/components/ContextMenu.d.ts +1 -1
  11. package/dist/files/components/ContextMenu.js.map +1 -1
  12. package/dist/files/components/CopyDialog.js.map +1 -1
  13. package/dist/files/components/EmptyState.d.ts +1 -1
  14. package/dist/files/components/EmptyState.js.map +1 -1
  15. package/dist/files/components/FileBrowser.js +2 -1
  16. package/dist/files/components/FileBrowser.js.map +1 -1
  17. package/dist/files/components/FileCard.d.ts.map +1 -1
  18. package/dist/files/components/FileCard.js +150 -142
  19. package/dist/files/components/FileCard.js.map +1 -1
  20. package/dist/files/components/FileInfoPanel.d.ts +1 -1
  21. package/dist/files/components/FileInfoPanel.d.ts.map +1 -1
  22. package/dist/files/components/FileInfoPanel.js +3 -7
  23. package/dist/files/components/FileInfoPanel.js.map +1 -1
  24. package/dist/files/components/FileListItem.d.ts.map +1 -1
  25. package/dist/files/components/FileListItem.js +1 -1
  26. package/dist/files/components/FileListItem.js.map +1 -1
  27. package/dist/files/components/FilePreviewModal.d.ts +1 -1
  28. package/dist/files/components/FilePreviewModal.d.ts.map +1 -1
  29. package/dist/files/components/FilePreviewModal.js +5 -0
  30. package/dist/files/components/FilePreviewModal.js.map +1 -1
  31. package/dist/files/components/FileVersionPanel.d.ts.map +1 -1
  32. package/dist/files/components/FileVersionPanel.js +122 -121
  33. package/dist/files/components/FileVersionPanel.js.map +1 -1
  34. package/dist/files/components/FolderCard.d.ts.map +1 -1
  35. package/dist/files/components/FolderCard.js +1 -1
  36. package/dist/files/components/FolderCard.js.map +1 -1
  37. package/dist/files/components/MoveDialog.js +2 -2
  38. package/dist/files/components/MoveDialog.js.map +1 -1
  39. package/dist/files/components/SearchBar.d.ts +1 -1
  40. package/dist/files/components/SearchBar.d.ts.map +1 -1
  41. package/dist/files/components/SearchBar.js +132 -128
  42. package/dist/files/components/SearchBar.js.map +1 -1
  43. package/dist/files/components/ShareDialog.d.ts +1 -1
  44. package/dist/files/components/ShareDialog.js.map +1 -1
  45. package/dist/files/components/StorageBanner.js.map +1 -1
  46. package/dist/files/components/TabBar.d.ts +1 -1
  47. package/dist/files/components/TabBar.js.map +1 -1
  48. package/dist/files/components/Toast.d.ts +1 -1
  49. package/dist/files/components/Toast.js.map +1 -1
  50. package/dist/files/components/UploadQueue.d.ts +1 -1
  51. package/dist/files/components/UploadQueue.js +1 -1
  52. package/dist/files/components/UploadQueue.js.map +1 -1
  53. package/dist/files/components/contextMenuItems.d.ts +4 -4
  54. package/dist/files/components/contextMenuItems.js.map +1 -1
  55. package/dist/files/components/viewers/AudioPlayer.d.ts +1 -1
  56. package/dist/files/components/viewers/AudioPlayer.js.map +1 -1
  57. package/dist/files/components/viewers/CodeViewer.d.ts +1 -1
  58. package/dist/files/components/viewers/CodeViewer.js.map +1 -1
  59. package/dist/files/components/viewers/ImageViewer.d.ts +1 -1
  60. package/dist/files/components/viewers/ImageViewer.js.map +1 -1
  61. package/dist/files/components/viewers/PDFViewer.d.ts +1 -1
  62. package/dist/files/components/viewers/PDFViewer.js.map +1 -1
  63. package/dist/files/components/viewers/TextViewer.d.ts +1 -1
  64. package/dist/files/components/viewers/TextViewer.js.map +1 -1
  65. package/dist/files/components/viewers/VideoPlayer.d.ts +1 -1
  66. package/dist/files/components/viewers/VideoPlayer.js.map +1 -1
  67. package/dist/files/graphql-types.d.ts +2 -17
  68. package/dist/files/graphql-types.d.ts.map +1 -1
  69. package/dist/files/hooks/useBatchOperations.d.ts +6 -1
  70. package/dist/files/hooks/useBatchOperations.d.ts.map +1 -1
  71. package/dist/files/hooks/useBatchOperations.js +8 -7
  72. package/dist/files/hooks/useBatchOperations.js.map +1 -1
  73. package/dist/files/hooks/useBinItems.d.ts.map +1 -1
  74. package/dist/files/hooks/useBinItems.js +14 -12
  75. package/dist/files/hooks/useBinItems.js.map +1 -1
  76. package/dist/files/hooks/useBrowsePage.d.ts +131 -8
  77. package/dist/files/hooks/useBrowsePage.d.ts.map +1 -1
  78. package/dist/files/hooks/useBrowsePage.js +23 -22
  79. package/dist/files/hooks/useBrowsePage.js.map +1 -1
  80. package/dist/files/hooks/useCopyFile.d.ts +22 -1
  81. package/dist/files/hooks/useCopyFile.d.ts.map +1 -1
  82. package/dist/files/hooks/useCopyFile.js +9 -8
  83. package/dist/files/hooks/useCopyFile.js.map +1 -1
  84. package/dist/files/hooks/useCreateFolder.d.ts +23 -1
  85. package/dist/files/hooks/useCreateFolder.d.ts.map +1 -1
  86. package/dist/files/hooks/useCreateFolder.js +16 -9
  87. package/dist/files/hooks/useCreateFolder.js.map +1 -1
  88. package/dist/files/hooks/useCreateFolderShare.d.ts +20 -1
  89. package/dist/files/hooks/useCreateFolderShare.d.ts.map +1 -1
  90. package/dist/files/hooks/useCreateFolderShare.js +22 -7
  91. package/dist/files/hooks/useCreateFolderShare.js.map +1 -1
  92. package/dist/files/hooks/useCreateShare.d.ts +19 -1
  93. package/dist/files/hooks/useCreateShare.d.ts.map +1 -1
  94. package/dist/files/hooks/useCreateShare.js +22 -7
  95. package/dist/files/hooks/useCreateShare.js.map +1 -1
  96. package/dist/files/hooks/useDeleteFile.d.ts +1 -1
  97. package/dist/files/hooks/useDeleteFile.d.ts.map +1 -1
  98. package/dist/files/hooks/useDeleteFile.js +8 -7
  99. package/dist/files/hooks/useDeleteFile.js.map +1 -1
  100. package/dist/files/hooks/useDeleteFolder.d.ts +1 -1
  101. package/dist/files/hooks/useDeleteFolder.d.ts.map +1 -1
  102. package/dist/files/hooks/useDeleteFolder.js +9 -8
  103. package/dist/files/hooks/useDeleteFolder.js.map +1 -1
  104. package/dist/files/hooks/useDragDrop.js.map +1 -1
  105. package/dist/files/hooks/useFileVersions.d.ts +36 -9
  106. package/dist/files/hooks/useFileVersions.d.ts.map +1 -1
  107. package/dist/files/hooks/useFileVersions.js +14 -13
  108. package/dist/files/hooks/useFileVersions.js.map +1 -1
  109. package/dist/files/hooks/useFoldersQuery.d.ts +8 -4
  110. package/dist/files/hooks/useFoldersQuery.d.ts.map +1 -1
  111. package/dist/files/hooks/useFoldersQuery.js +7 -6
  112. package/dist/files/hooks/useFoldersQuery.js.map +1 -1
  113. package/dist/files/hooks/useKeyboardNavigation.js +7 -8
  114. package/dist/files/hooks/useKeyboardNavigation.js.map +1 -1
  115. package/dist/files/hooks/useMoveFile.d.ts +22 -1
  116. package/dist/files/hooks/useMoveFile.d.ts.map +1 -1
  117. package/dist/files/hooks/useMoveFile.js +9 -8
  118. package/dist/files/hooks/useMoveFile.js.map +1 -1
  119. package/dist/files/hooks/useMoveFolder.d.ts +22 -1
  120. package/dist/files/hooks/useMoveFolder.d.ts.map +1 -1
  121. package/dist/files/hooks/useMoveFolder.js +9 -8
  122. package/dist/files/hooks/useMoveFolder.js.map +1 -1
  123. package/dist/files/hooks/usePermanentlyDeleteFile.d.ts +1 -1
  124. package/dist/files/hooks/usePermanentlyDeleteFile.d.ts.map +1 -1
  125. package/dist/files/hooks/usePermanentlyDeleteFile.js +8 -7
  126. package/dist/files/hooks/usePermanentlyDeleteFile.js.map +1 -1
  127. package/dist/files/hooks/usePermanentlyDeleteFolder.d.ts +1 -1
  128. package/dist/files/hooks/usePermanentlyDeleteFolder.d.ts.map +1 -1
  129. package/dist/files/hooks/usePermanentlyDeleteFolder.js +8 -7
  130. package/dist/files/hooks/usePermanentlyDeleteFolder.js.map +1 -1
  131. package/dist/files/hooks/useRecentFiles.d.ts.map +1 -1
  132. package/dist/files/hooks/useRecentFiles.js +13 -11
  133. package/dist/files/hooks/useRecentFiles.js.map +1 -1
  134. package/dist/files/hooks/useRenameFile.d.ts +22 -1
  135. package/dist/files/hooks/useRenameFile.d.ts.map +1 -1
  136. package/dist/files/hooks/useRenameFile.js +9 -8
  137. package/dist/files/hooks/useRenameFile.js.map +1 -1
  138. package/dist/files/hooks/useRenameFolder.d.ts +22 -1
  139. package/dist/files/hooks/useRenameFolder.d.ts.map +1 -1
  140. package/dist/files/hooks/useRenameFolder.js +9 -8
  141. package/dist/files/hooks/useRenameFolder.js.map +1 -1
  142. package/dist/files/hooks/useRestoreFile.d.ts +22 -1
  143. package/dist/files/hooks/useRestoreFile.d.ts.map +1 -1
  144. package/dist/files/hooks/useRestoreFile.js +8 -7
  145. package/dist/files/hooks/useRestoreFile.js.map +1 -1
  146. package/dist/files/hooks/useRestoreFolder.d.ts +22 -1
  147. package/dist/files/hooks/useRestoreFolder.d.ts.map +1 -1
  148. package/dist/files/hooks/useRestoreFolder.js +8 -7
  149. package/dist/files/hooks/useRestoreFolder.js.map +1 -1
  150. package/dist/files/hooks/useSearch.d.ts +2 -3
  151. package/dist/files/hooks/useSearch.d.ts.map +1 -1
  152. package/dist/files/hooks/useSearch.js +14 -12
  153. package/dist/files/hooks/useSearch.js.map +1 -1
  154. package/dist/files/hooks/useStarredItems.d.ts +2 -3
  155. package/dist/files/hooks/useStarredItems.d.ts.map +1 -1
  156. package/dist/files/hooks/useStarredItems.js +12 -10
  157. package/dist/files/hooks/useStarredItems.js.map +1 -1
  158. package/dist/files/hooks/useStorageQuota.d.ts.map +1 -1
  159. package/dist/files/hooks/useStorageQuota.js +17 -16
  160. package/dist/files/hooks/useStorageQuota.js.map +1 -1
  161. package/dist/files/hooks/useToast.js.map +1 -1
  162. package/dist/files/hooks/useToggleStar.d.ts +43 -1
  163. package/dist/files/hooks/useToggleStar.d.ts.map +1 -1
  164. package/dist/files/hooks/useToggleStar.js +9 -8
  165. package/dist/files/hooks/useToggleStar.js.map +1 -1
  166. package/dist/files/hooks/useUploadFile.d.ts +23 -1
  167. package/dist/files/hooks/useUploadFile.d.ts.map +1 -1
  168. package/dist/files/hooks/useUploadFile.js +28 -27
  169. package/dist/files/hooks/useUploadFile.js.map +1 -1
  170. package/dist/files/hooks/useUploadQueueState.d.ts +32 -0
  171. package/dist/files/hooks/useUploadQueueState.d.ts.map +1 -0
  172. package/dist/files/hooks/useUploadQueueState.js +61 -0
  173. package/dist/files/hooks/useUploadQueueState.js.map +1 -0
  174. package/dist/files/index.js +4 -3
  175. package/dist/files/pages/BrowserPage.d.ts +1 -1
  176. package/dist/files/pages/BrowserPage.d.ts.map +1 -1
  177. package/dist/files/pages/BrowserPage.js +383 -387
  178. package/dist/files/pages/BrowserPage.js.map +1 -1
  179. package/dist/files/pages/ShareAccessPage.d.ts +1 -1
  180. package/dist/files/pages/ShareAccessPage.js.map +1 -1
  181. package/dist/files/providers/FilesProvider.js.map +1 -1
  182. package/dist/files/store/filesStore.js.map +1 -1
  183. package/dist/files/types.d.ts.map +1 -1
  184. package/dist/files/utils/events.js.map +1 -1
  185. package/dist/files/utils/fileViewModel.d.ts +7 -0
  186. package/dist/files/utils/fileViewModel.d.ts.map +1 -0
  187. package/dist/files/utils/fileViewModel.js +34 -0
  188. package/dist/files/utils/fileViewModel.js.map +1 -0
  189. package/dist/files/utils/i18n.js.map +1 -1
  190. package/dist/files/utils/index.js +19 -19
  191. package/dist/files/utils/index.js.map +1 -1
  192. package/dist/files/utils/quotaUtils.js +1 -2
  193. package/dist/files/utils/thumbnail.js +19 -23
  194. package/dist/files/utils/thumbnail.js.map +1 -1
  195. package/dist/files/utils/uploadDigest.js +1 -1
  196. package/dist/files/utils/uploadDigest.js.map +1 -1
  197. package/dist/generated/global-operations.d.ts +4 -1
  198. package/dist/generated/global-operations.d.ts.map +1 -1
  199. package/dist/generated/global-types.d.ts +16 -21
  200. package/dist/generated/global-types.d.ts.map +1 -1
  201. package/dist/generated/wspace-operations.d.ts +503 -1784
  202. package/dist/generated/wspace-operations.d.ts.map +1 -1
  203. package/dist/generated/wspace-operations.js +15278 -1110
  204. package/dist/generated/wspace-operations.js.map +1 -1
  205. package/dist/generated/wspace-types.d.ts +90013 -24889
  206. package/dist/generated/wspace-types.d.ts.map +1 -1
  207. package/dist/generated/wspace-types.js +16 -0
  208. package/dist/generated/wspace-types.js.map +1 -0
  209. package/dist/index.d.ts +1 -1
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/shared/hooks/useTr.js.map +1 -1
  212. package/dist/translations/en.js.map +1 -1
  213. package/dist/utils/nativeBridge.js +1 -2
  214. package/package.json +24 -22
  215. package/dist/files.d.ts +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../src/files/components/ShareDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n Link,\n Mail,\n Calendar,\n Download,\n Lock,\n Copy,\n Check,\n Loader2,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n OptionalFields,\n Button,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport interface ShareDialogInput {\n fileId?: string;\n folderId?: string;\n shareType: \"PUBLIC_LINK\" | \"EMAIL_INVITE\";\n accessLevel: \"VIEW\" | \"DOWNLOAD\" | \"EDIT\";\n expiresAt?: string;\n maxDownloads?: number;\n password?: string;\n invitedEmail?: string;\n}\n\ninterface ShareDialogProps {\n item: FileItem | FolderItem;\n itemType: \"file\" | \"folder\";\n onClose: () => void;\n onCreateShare: (\n input: ShareDialogInput,\n ) => Promise<{ publicUrl: string; token: string }>;\n isLoading?: boolean;\n}\n\nexport function ShareDialog({\n item,\n itemType,\n onClose,\n onCreateShare,\n isLoading,\n}: ShareDialogProps) {\n const { t } = useI18n();\n const [shareUrl, setShareUrl] = useState<string | null>(null);\n const [shareType, setShareType] = useState<\n \"PUBLIC_LINK\" | \"EMAIL_INVITE\" | null\n >(null);\n const [copied, setCopied] = useState(false);\n const [expiryDays, setExpiryDays] = useState<number>(7);\n const [maxDownloads, setMaxDownloads] = useState<number>(0);\n const [hasPassword, setHasPassword] = useState(false);\n const [password, setPassword] = useState(\"\");\n const [invitedEmail, setInvitedEmail] = useState(\"\");\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const handleCreateShare = async () => {\n try {\n setErrorMessage(null);\n const expiresAt =\n expiryDays > 0\n ? new Date(\n Date.now() + expiryDays * 24 * 60 * 60 * 1000,\n ).toISOString()\n : undefined;\n\n const type: ShareDialogInput[\"shareType\"] = invitedEmail\n ? \"EMAIL_INVITE\"\n : \"PUBLIC_LINK\";\n\n const shareInput: ShareDialogInput = {\n ...(itemType === \"file\" ? { fileId: item.id } : { folderId: item.id }),\n shareType: type,\n accessLevel: \"DOWNLOAD\",\n expiresAt,\n maxDownloads: maxDownloads > 0 ? maxDownloads : undefined,\n password: hasPassword && password ? password : undefined,\n invitedEmail: invitedEmail || undefined,\n };\n\n const result = await onCreateShare(shareInput);\n\n setShareType(type);\n setShareUrl(result.publicUrl || \"\");\n } catch (error) {\n setErrorMessage(\n error instanceof Error ? error.message : \"Failed to create share\",\n );\n }\n };\n\n const handleCopyLink = async () => {\n if (shareUrl) {\n await navigator.clipboard.writeText(shareUrl);\n setCopied(true);\n }\n };\n\n useEffect(() => {\n if (copied) {\n const timer = setTimeout(() => setCopied(false), 2000);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [copied]);\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(next: boolean) => {\n if (!next) onClose();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tWithFallback(\n t,\n \"files.shareDialog.title\",\n 'Share {{itemType}} \"{{name}}\"',\n {\n itemType: tWithFallback(\n t,\n itemType === \"folder\"\n ? \"files.shareDialog.itemType.folder\"\n : \"files.shareDialog.itemType.file\",\n itemType === \"folder\" ? \"folder\" : \"file\",\n ),\n name: item.name,\n },\n )}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n {shareType === null ? (\n <>\n {errorMessage && (\n <div className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {errorMessage}\n </div>\n )}\n\n <div className=\"space-y-4\">\n {/* Expiry — smart-default field, visible above the fold */}\n <div>\n <label\n htmlFor=\"share-dialog-expiry-select\"\n className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\"\n >\n <Calendar className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.label\",\n \"Link expires in\",\n )}\n </label>\n <select\n id=\"share-dialog-expiry-select\"\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.expiry.label\",\n \"Link expires in\",\n )}\n value={expiryDays}\n onChange={(e) => setExpiryDays(Number(e.target.value))}\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg bg-bg-surface text-text-primary scheme-normal dark:scheme-dark focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200 [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={0}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.never\",\n \"Never\",\n )}\n </option>\n <option value={1}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.oneDay\",\n \"1 day\",\n )}\n </option>\n <option value={7}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.sevenDays\",\n \"7 days\",\n )}\n </option>\n <option value={30}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.thirtyDays\",\n \"30 days\",\n )}\n </option>\n <option value={90}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.ninetyDays\",\n \"90 days\",\n )}\n </option>\n </select>\n </div>\n\n {/* Invited Email — smart-default; \"leave empty for public link\" */}\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Mail className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.invite.label\",\n \"Invite specific person (optional)\",\n )}\n </label>\n <input\n type=\"email\"\n value={invitedEmail}\n onChange={(e) => setInvitedEmail(e.target.value)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.invite.label\",\n \"Invite specific person (optional)\",\n )}\n placeholder=\"user@example.com\"\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.invite.help\",\n \"Leave empty for public link\",\n )}\n </p>\n </div>\n\n {/* Advanced: ACL overrides + retention */}\n <OptionalFields\n title={tWithFallback(\n t,\n \"files.shareDialog.advanced.title\",\n \"Advanced\",\n )}\n description={tWithFallback(\n t,\n \"files.shareDialog.advanced.description\",\n \"Download limits and password protection\",\n )}\n >\n <div className=\"space-y-4\">\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Download className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.downloads.label\",\n \"Maximum downloads\",\n )}\n </label>\n <input\n type=\"number\"\n min=\"0\"\n value={maxDownloads}\n onChange={(e) => setMaxDownloads(Number(e.target.value))}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.downloads.label\",\n \"Maximum downloads\",\n )}\n placeholder={tWithFallback(\n t,\n \"files.shareDialog.downloads.placeholder\",\n \"Unlimited\",\n )}\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Lock className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.password.label\",\n \"Password protection\",\n )}\n </label>\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={hasPassword}\n onChange={(e) => setHasPassword(e.target.checked)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.password.require\",\n \"Require password\",\n )}\n className=\"rounded transition-colors duration-200\"\n />\n <span className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.password.require\",\n \"Require password\",\n )}\n </span>\n </div>\n {hasPassword && (\n <>\n <input\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.password.label\",\n \"Password protection\",\n )}\n placeholder={tWithFallback(\n t,\n \"files.shareDialog.password.placeholder\",\n \"Enter password\",\n )}\n className=\"w-full min-h-11 mt-2 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tWithFallback(\n t,\n \"files.shareDialog.password.help\",\n \"Password must be at least 8 characters and contain uppercase, lowercase, and number\",\n )}\n </p>\n </>\n )}\n </div>\n </div>\n </OptionalFields>\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {tWithFallback(t, \"files.common.actions.cancel\", \"Cancel\")}\n </Button>\n <Button\n type=\"button\"\n onClick={handleCreateShare}\n disabled={isLoading || (hasPassword && !password)}\n >\n {isLoading ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.creating\",\n \"Creating...\",\n )}\n </>\n ) : (\n <>\n <Link className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.createLink\",\n \"Create share link\",\n )}\n </>\n )}\n </Button>\n </ResponsiveDialogFooter>\n </>\n ) : (\n <>\n <div className=\"space-y-4\">\n <div className=\"bg-status-success-bg-subtle border border-status-success-border rounded-lg p-4\">\n {shareType === \"EMAIL_INVITE\" ? (\n <>\n <p className=\"text-sm font-medium text-status-success-text mb-2\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.invitationSent\",\n \"Invitation sent successfully!\",\n )}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.invitationSentTo\",\n \"An invitation has been sent to {{email}}\",\n { email: invitedEmail },\n )}\n </p>\n </>\n ) : (\n <>\n <p className=\"text-sm font-medium text-status-success-text mb-2\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.linkCreated\",\n \"Share link created successfully!\",\n )}\n </p>\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <input\n type=\"text\"\n value={shareUrl || \"\"}\n readOnly\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.success.linkCreated\",\n \"Share link\",\n )}\n className=\"flex-1 min-h-11 px-3 py-2 bg-bg-surface border border-status-success-border rounded-lg text-sm\"\n />\n <Button type=\"button\" onClick={handleCopyLink}>\n {copied ? (\n <>\n <Check className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.copied\",\n \"Copied!\",\n )}\n </>\n ) : (\n <>\n <Copy className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.common.actions.copy\",\n \"Copy\",\n )}\n </>\n )}\n </Button>\n </div>\n </>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" onClick={onClose} className=\"w-full\">\n {tWithFallback(t, \"files.common.actions.done\", \"Done\")}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n </>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ShareDialog.js","names":["_Fragment","ShareDialog","useI18n","shareUrl","setShareUrl","useState","shareType","setShareType","copied","setCopied","expiryDays","setExpiryDays","maxDownloads","setMaxDownloads","hasPassword","setHasPassword","password","setPassword","invitedEmail","setInvitedEmail","errorMessage","setErrorMessage","useEffect","timer","ResponsiveDialog","next","onClose","ResponsiveDialogContent","ResponsiveDialogHeader","ResponsiveDialogTitle","tWithFallback","t","itemType","item","Fragment","Calendar","Mail","OptionalFields","Download","Lock","ResponsiveDialogFooter","Button","expiresAt","type","result","onCreateShare","error","isLoading","Loader2","Link","Check","Copy"],"sources":["../../../src/files/components/ShareDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n Link,\n Mail,\n Calendar,\n Download,\n Lock,\n Copy,\n Check,\n Loader2,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n OptionalFields,\n Button,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport interface ShareDialogInput {\n fileId?: string;\n folderId?: string;\n shareType: \"PUBLIC_LINK\" | \"EMAIL_INVITE\";\n accessLevel: \"VIEW\" | \"DOWNLOAD\" | \"EDIT\";\n expiresAt?: string;\n maxDownloads?: number;\n password?: string;\n invitedEmail?: string;\n}\n\ninterface ShareDialogProps {\n item: FileItem | FolderItem;\n itemType: \"file\" | \"folder\";\n onClose: () => void;\n onCreateShare: (\n input: ShareDialogInput,\n ) => Promise<{ publicUrl: string; token: string }>;\n isLoading?: boolean;\n}\n\nexport function ShareDialog({\n item,\n itemType,\n onClose,\n onCreateShare,\n isLoading,\n}: ShareDialogProps) {\n const { t } = useI18n();\n const [shareUrl, setShareUrl] = useState<string | null>(null);\n const [shareType, setShareType] = useState<\n \"PUBLIC_LINK\" | \"EMAIL_INVITE\" | null\n >(null);\n const [copied, setCopied] = useState(false);\n const [expiryDays, setExpiryDays] = useState<number>(7);\n const [maxDownloads, setMaxDownloads] = useState<number>(0);\n const [hasPassword, setHasPassword] = useState(false);\n const [password, setPassword] = useState(\"\");\n const [invitedEmail, setInvitedEmail] = useState(\"\");\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const handleCreateShare = async () => {\n try {\n setErrorMessage(null);\n const expiresAt =\n expiryDays > 0\n ? new Date(\n Date.now() + expiryDays * 24 * 60 * 60 * 1000,\n ).toISOString()\n : undefined;\n\n const type: ShareDialogInput[\"shareType\"] = invitedEmail\n ? \"EMAIL_INVITE\"\n : \"PUBLIC_LINK\";\n\n const shareInput: ShareDialogInput = {\n ...(itemType === \"file\" ? { fileId: item.id } : { folderId: item.id }),\n shareType: type,\n accessLevel: \"DOWNLOAD\",\n expiresAt,\n maxDownloads: maxDownloads > 0 ? maxDownloads : undefined,\n password: hasPassword && password ? password : undefined,\n invitedEmail: invitedEmail || undefined,\n };\n\n const result = await onCreateShare(shareInput);\n\n setShareType(type);\n setShareUrl(result.publicUrl || \"\");\n } catch (error) {\n setErrorMessage(\n error instanceof Error ? error.message : \"Failed to create share\",\n );\n }\n };\n\n const handleCopyLink = async () => {\n if (shareUrl) {\n await navigator.clipboard.writeText(shareUrl);\n setCopied(true);\n }\n };\n\n useEffect(() => {\n if (copied) {\n const timer = setTimeout(() => setCopied(false), 2000);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [copied]);\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(next: boolean) => {\n if (!next) onClose();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tWithFallback(\n t,\n \"files.shareDialog.title\",\n 'Share {{itemType}} \"{{name}}\"',\n {\n itemType: tWithFallback(\n t,\n itemType === \"folder\"\n ? \"files.shareDialog.itemType.folder\"\n : \"files.shareDialog.itemType.file\",\n itemType === \"folder\" ? \"folder\" : \"file\",\n ),\n name: item.name,\n },\n )}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n {shareType === null ? (\n <>\n {errorMessage && (\n <div className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {errorMessage}\n </div>\n )}\n\n <div className=\"space-y-4\">\n {/* Expiry — smart-default field, visible above the fold */}\n <div>\n <label\n htmlFor=\"share-dialog-expiry-select\"\n className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\"\n >\n <Calendar className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.label\",\n \"Link expires in\",\n )}\n </label>\n <select\n id=\"share-dialog-expiry-select\"\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.expiry.label\",\n \"Link expires in\",\n )}\n value={expiryDays}\n onChange={(e) => setExpiryDays(Number(e.target.value))}\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg bg-bg-surface text-text-primary scheme-normal dark:scheme-dark focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200 [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={0}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.never\",\n \"Never\",\n )}\n </option>\n <option value={1}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.oneDay\",\n \"1 day\",\n )}\n </option>\n <option value={7}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.sevenDays\",\n \"7 days\",\n )}\n </option>\n <option value={30}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.thirtyDays\",\n \"30 days\",\n )}\n </option>\n <option value={90}>\n {tWithFallback(\n t,\n \"files.shareDialog.expiry.options.ninetyDays\",\n \"90 days\",\n )}\n </option>\n </select>\n </div>\n\n {/* Invited Email — smart-default; \"leave empty for public link\" */}\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Mail className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.invite.label\",\n \"Invite specific person (optional)\",\n )}\n </label>\n <input\n type=\"email\"\n value={invitedEmail}\n onChange={(e) => setInvitedEmail(e.target.value)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.invite.label\",\n \"Invite specific person (optional)\",\n )}\n placeholder=\"user@example.com\"\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.invite.help\",\n \"Leave empty for public link\",\n )}\n </p>\n </div>\n\n {/* Advanced: ACL overrides + retention */}\n <OptionalFields\n title={tWithFallback(\n t,\n \"files.shareDialog.advanced.title\",\n \"Advanced\",\n )}\n description={tWithFallback(\n t,\n \"files.shareDialog.advanced.description\",\n \"Download limits and password protection\",\n )}\n >\n <div className=\"space-y-4\">\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Download className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.downloads.label\",\n \"Maximum downloads\",\n )}\n </label>\n <input\n type=\"number\"\n min=\"0\"\n value={maxDownloads}\n onChange={(e) => setMaxDownloads(Number(e.target.value))}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.downloads.label\",\n \"Maximum downloads\",\n )}\n placeholder={tWithFallback(\n t,\n \"files.shareDialog.downloads.placeholder\",\n \"Unlimited\",\n )}\n className=\"w-full min-h-11 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-text-primary mb-2\">\n <Lock className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.password.label\",\n \"Password protection\",\n )}\n </label>\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={hasPassword}\n onChange={(e) => setHasPassword(e.target.checked)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.password.require\",\n \"Require password\",\n )}\n className=\"rounded transition-colors duration-200\"\n />\n <span className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.password.require\",\n \"Require password\",\n )}\n </span>\n </div>\n {hasPassword && (\n <>\n <input\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.password.label\",\n \"Password protection\",\n )}\n placeholder={tWithFallback(\n t,\n \"files.shareDialog.password.placeholder\",\n \"Enter password\",\n )}\n className=\"w-full min-h-11 mt-2 px-3 py-2 border border-border-default rounded-lg focus:outline-none focus:border-action-primary-border text-sm transition-colors duration-200\"\n />\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tWithFallback(\n t,\n \"files.shareDialog.password.help\",\n \"Password must be at least 8 characters and contain uppercase, lowercase, and number\",\n )}\n </p>\n </>\n )}\n </div>\n </div>\n </OptionalFields>\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {tWithFallback(t, \"files.common.actions.cancel\", \"Cancel\")}\n </Button>\n <Button\n type=\"button\"\n onClick={handleCreateShare}\n disabled={isLoading || (hasPassword && !password)}\n >\n {isLoading ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.creating\",\n \"Creating...\",\n )}\n </>\n ) : (\n <>\n <Link className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.createLink\",\n \"Create share link\",\n )}\n </>\n )}\n </Button>\n </ResponsiveDialogFooter>\n </>\n ) : (\n <>\n <div className=\"space-y-4\">\n <div className=\"bg-status-success-bg-subtle border border-status-success-border rounded-lg p-4\">\n {shareType === \"EMAIL_INVITE\" ? (\n <>\n <p className=\"text-sm font-medium text-status-success-text mb-2\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.invitationSent\",\n \"Invitation sent successfully!\",\n )}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.invitationSentTo\",\n \"An invitation has been sent to {{email}}\",\n { email: invitedEmail },\n )}\n </p>\n </>\n ) : (\n <>\n <p className=\"text-sm font-medium text-status-success-text mb-2\">\n {tWithFallback(\n t,\n \"files.shareDialog.success.linkCreated\",\n \"Share link created successfully!\",\n )}\n </p>\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <input\n type=\"text\"\n value={shareUrl || \"\"}\n readOnly\n aria-label={tWithFallback(\n t,\n \"files.shareDialog.success.linkCreated\",\n \"Share link\",\n )}\n className=\"flex-1 min-h-11 px-3 py-2 bg-bg-surface border border-status-success-border rounded-lg text-sm\"\n />\n <Button type=\"button\" onClick={handleCopyLink}>\n {copied ? (\n <>\n <Check className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.shareDialog.actions.copied\",\n \"Copied!\",\n )}\n </>\n ) : (\n <>\n <Copy className=\"size-4\" />\n {tWithFallback(\n t,\n \"files.common.actions.copy\",\n \"Copy\",\n )}\n </>\n )}\n </Button>\n </div>\n </>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" onClick={onClose} className=\"w-full\">\n {tWithFallback(t, \"files.common.actions.done\", \"Done\")}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n </>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;AA6CA,SAAgBC,EAAY,EAC1B,SACA,aACA,YACA,kBACA,gBACmB;CACnB,IAAM,EAAE,SAAMC,EAAQ,GAChB,CAACC,GAAUC,KAAeC,EAAwB,IAAI,GACtD,CAACC,GAAWC,KAAgBF,EAEhC,IAAI,GACA,CAACG,GAAQC,KAAaJ,EAAS,EAAK,GACpC,CAACK,GAAYC,KAAiBN,EAAiB,CAAC,GAChD,CAACO,GAAcC,KAAmBR,EAAiB,CAAC,GACpD,CAACS,GAAaC,KAAkBV,EAAS,EAAK,GAC9C,CAACW,GAAUC,KAAeZ,EAAS,EAAE,GACrC,CAACa,GAAcC,KAAmBd,EAAS,EAAE,GAC7C,CAACe,GAAcC,KAAmBhB,EAAwB,IAAI;CAoDpE,OARAiB,QAAgB;EACd,IAAId,GAAQ;GACV,IAAMe,IAAQ,iBAAiBd,EAAU,EAAK,GAAG,GAAI;GACrD,aAAa,aAAac,CAAK;EACjC;CAEF,GAAG,CAACf,CAAM,CAAC,GAGT,kBAACgB,GAAD;EACE,MAAA;EACA,eAAe,MAAkB;GAC/B,AAAKC,KAAMC,EAAQ;EACrB;EAEA,UAAA,kBAACC,GAAD,EAAA,UAAA,CACE,kBAACC,GAAD,EAAA,UACE,kBAACC,GAAD,EAAA,UACGC,EACCC,GACA,2BACA,mCACA;GACE,UAAUD,EACRC,GACAC,MAAa,WACT,sCACA,mCACJA,MAAa,WAAW,WAAW,MACrC;GACA,MAAMC,EAAK;EACb,CACF,EACqB,CAAA,EACD,CAAA,GAEvB3B,MAAc,OACb,kBAAAN,GAAA,EAAA,UAAA;GACGoB,KACC,kBAAC,OAAD;IAAK,WAAU;IACZ,UAAAA;GACE,CAAA;GAGP,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KAEE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;MAFZ,UAAA,CAIE,kBAACe,GAAD,EAAU,WAAU,SAAU,CAAA,GAC7BL,EACCC,GACA,kCACA,iBACF,CACK;KACP,CAAA,GAAA,kBAAC,UAAD;MACE,IAAG;MACH,cAAYD,EACVC,GACA,kCACA,iBACF;MACA,OAAOrB;MACP,WAAW,MAAMC,EAAc,OAAO,EAAE,OAAO,KAAK,CAAC;MACrD,WAAU;MATZ,UAAA;OAWE,kBAAC,UAAD;QAAQ,OAAO;QACZ,UAAAmB,EACCC,GACA,0CACA,OACF;OACM,CAAA;OACR,kBAAC,UAAD;QAAQ,OAAO;QACZ,UAAAD,EACCC,GACA,2CACA,OACF;OACM,CAAA;OACR,kBAAC,UAAD;QAAQ,OAAO;QACZ,UAAAD,EACCC,GACA,8CACA,QACF;OACM,CAAA;OACR,kBAAC,UAAD;QAAQ,OAAO;QACZ,UAAAD,EACCC,GACA,+CACA,SACF;OACM,CAAA;OACR,kBAAC,UAAD;QAAQ,OAAO;QACZ,UAAAD,EACCC,GACA,+CACA,SACF;OACM,CAAA;MACF;KACL,CAAA,CAAA,EAAA,CAAA;KAGL,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,SAAD;OAAO,WAAU;OAAjB,UAAA,CACE,kBAACK,GAAD,EAAM,WAAU,SAAU,CAAA,GACzBN,EACCC,GACA,kCACA,mCACF,CACK;;MACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAOb;OACP,WAAW,MAAMC,EAAgB,EAAE,OAAO,KAAK;OAC/C,cAAYW,EACVC,GACA,kCACA,mCACF;OACA,aAAY;OACZ,WAAU;MACX,CAAA;MACD,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAD,EACCC,GACA,iCACA,6BACF;MACC,CAAA;KACA,EAAA,CAAA;KAGL,kBAACM,GAAD;MACE,OAAOP,EACLC,GACA,oCACA,UACF;MACA,aAAaD,EACXC,GACA,0CACA,yCACF;MAEA,UAAA,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;QAAjB,UAAA,CACE,kBAACO,GAAD,EAAU,WAAU,SAAU,CAAA,GAC7BR,EACCC,GACA,qCACA,mBACF,CACK;OACP,CAAA,GAAA,kBAAC,SAAD;QACE,MAAK;QACL,KAAI;QACJ,OAAOnB;QACP,WAAW,MAAMC,EAAgB,OAAO,EAAE,OAAO,KAAK,CAAC;QACvD,cAAYiB,EACVC,GACA,qCACA,mBACF;QACA,aAAaD,EACXC,GACA,2CACA,WACF;QACA,WAAU;OACX,CAAA,CACE,EAAA,CAAA,GAEL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,WAAU;SAAjB,UAAA,CACE,kBAACQ,GAAD,EAAM,WAAU,SAAU,CAAA,GACzBT,EACCC,GACA,oCACA,qBACF,CACK;;QACP,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,SAAD;UACE,MAAK;UACL,SAASjB;UACT,WAAW,MAAMC,EAAe,EAAE,OAAO,OAAO;UAChD,cAAYe,EACVC,GACA,sCACA,kBACF;UACA,WAAU;SACX,CAAA,GACD,kBAAC,QAAD;UAAM,WAAU;UACb,UAAAD,EACCC,GACA,sCACA,kBACF;SACI,CAAA,CACH;;QACJjB,KACC,kBAAAd,GAAA,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,MAAK;SACL,OAAOgB;SACP,WAAW,MAAMC,EAAY,EAAE,OAAO,KAAK;SAC3C,cAAYa,EACVC,GACA,oCACA,qBACF;SACA,aAAaD,EACXC,GACA,0CACA,gBACF;SACA,WAAU;QACX,CAAA,GACD,kBAAC,KAAD;SAAG,WAAU;SACV,UAAAD,EACCC,GACA,mCACA,qFACF;QACC,CAAA,CACH,EAAA,CAAA;OAED,EAAA,CAAA,CACF;;KACS,CAAA;IACb;;GAEL,kBAACS,GAAD,EAAA,UAAA,CACE,kBAACC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,SAASf;IAC9C,UAAAI,EAAcC,GAAG,+BAA+B,QAAQ;GACnD,CAAA,GACR,kBAACU,GAAD;IACE,MAAK;IACL,SAAS,YAjSe;KACpC,IAAI;MACF,EAAgB,IAAI;MACpB,IAAMC,IACJhC,IAAa,IACT,IAAI,KACF,KAAK,IAAI,IAAIA,IAAa,KAAK,KAAK,KAAK,GAC3C,CAAC,CAAC,YAAY,IACd,KAAA,GAEAiC,IAAsCzB,IACxC,iBACA,eAYE0B,IAAS,MAAMC,EAAc;OATjC,GAAIb,MAAa,SAAS,EAAE,QAAQC,EAAK,GAAG,IAAI,EAAE,UAAUA,EAAK,GAAG;OACpE,WAAWU;OACX,aAAa;OACb;OACA,cAAc/B,IAAe,IAAIA,IAAe,KAAA;OAChD,UAAUE,KAAeE,IAAWA,IAAW,KAAA;OAC/C,cAAcE,KAAgB,KAAA;MAGG,CAAU;MAG7C,AADAX,EAAaoC,CAAI,GACjBvC,EAAYwC,EAAO,aAAa,EAAE;KACpC,SAASE,GAAO;MACd,EACEA,aAAiB,QAAQA,EAAM,UAAU,wBAC3C;KACF;IACF;IAiQc,UAAUC,KAAcjC,KAAe,CAACE;IAEvC,UAAA+B,IACC,kBAAA/C,GAAA,EAAA,UAAA,CACE,kBAACgD,GAAD,EAAS,WAAU,sBAAuB,CAAA,GACzClB,EACCC,GACA,sCACA,aACF,CACA,EAAA,CAAA,IAEF,kBAAA/B,GAAA,EAAA,UAAA,CACE,kBAACiD,GAAD,EAAM,WAAU,SAAU,CAAA,GACzBnB,EACCC,GACA,wCACA,mBACF,CACA,EAAA,CAAA;GAEE,CAAA,CACc,EAAA,CAAA;EACxB,EAAA,CAAA,IAEF,kBAAA/B,GAAA,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IACZ,UAAAM,MAAc,iBACb,kBAAAN,GAAA,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KACV,UAAA8B,EACCC,GACA,4CACA,+BACF;IACC,CAAA,GACH,kBAAC,KAAD;KAAG,WAAU;KACV,UAAAD,EACCC,GACA,8CACA,4CACA,EAAE,OAAOb,EAAa,CACxB;IACC,CAAA,CACH,EAAA,CAAA,IAEF,kBAAAlB,GAAA,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KACV,UAAA8B,EACCC,GACA,yCACA,kCACF;IACC,CAAA,GACH,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,SAAD;MACE,MAAK;MACL,OAAO5B,KAAY;MACnB,UAAA;MACA,cAAY2B,EACVC,GACA,yCACA,YACF;MACA,WAAU;KACX,CAAA,GACD,kBAACU,GAAD;MAAQ,MAAK;MAAS,SAAS,YAlUhB;OACjC,AAAItC,MACF,MAAM,UAAU,UAAU,UAAUA,CAAQ,GAC5CM,EAAU,EAAI;MAElB;MA8TuB,UAAAD,IACC,kBAAAR,GAAA,EAAA,UAAA,CACE,kBAACkD,GAAD,EAAO,WAAU,SAAU,CAAA,GAC1BpB,EACCC,GACA,oCACA,SACF,CACA,EAAA,CAAA,IAEF,kBAAA/B,GAAA,EAAA,UAAA,CACE,kBAACmD,GAAD,EAAM,WAAU,SAAU,CAAA,GACzBrB,EACCC,GACA,6BACA,MACF,CACA,EAAA,CAAA;KAEE,CAAA,CACL;IACL,CAAA,CAAA,EAAA,CAAA;GAED,CAAA,GAEL,kBAACS,GAAD,EAAA,UACE,kBAACC,GAAD;IAAQ,MAAK;IAAS,SAASf;IAAS,WAAU;IAC/C,UAAAI,EAAcC,GAAG,6BAA6B,MAAM;GAC/C,CAAA,EACc,CAAA,CACrB;EACL,CAAA,EAAA,CAAA,CAEmB,EAAA,CAAA;CACT,CAAA;AAEtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"StorageBanner.js","names":[],"sources":["../../../src/files/components/StorageBanner.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AlertCircle, AlertTriangle, HardDrive } from \"lucide-react\";\nimport { QuotaMeter } from \"@burdenoff/fe-libs/shared/components\";\nimport type { StorageQuotaState } from \"../hooks/useStorageQuota\";\nimport { useTr } from \"../../shared/hooks/useTr\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`;\n if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n}\n\ninterface StorageBannerProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageBanner: FC<StorageBannerProps> = ({ quota }) => {\n const tr = useTr();\n\n if (\n quota.loading ||\n quota.noLimit ||\n (!quota.isExhausted && !quota.isNearLimit)\n ) {\n return null;\n }\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = quota.limitBytes ? formatBytes(quota.limitBytes) : null;\n const quotaMeterUsed = Math.round(quota.usedBytes / 1048576);\n const quotaMeterLimit = quota.limitBytes\n ? Math.round(quota.limitBytes / 1048576)\n : quotaMeterUsed;\n\n if (quota.isExhausted) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-bg/30 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle className=\"size-4 shrink-0 text-status-error-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">\n {tr(\"files.storage.full\", \"Storage full.\")}\n </span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? tr(\n \"files.storage.fullWithLimit\",\n \"{{used}} of {{limit}} used — no space left.\",\n { used: usedLabel, limit: limitLabel },\n )\n : tr(\n \"files.storage.fullNoLimit\",\n \"{{used}} used — no space left.\",\n { used: usedLabel },\n )}{\" \"}\n {tr(\n \"files.storage.freeUpOrUpgrade\",\n \"Delete files to free up space or upgrade your plan.\",\n )}\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label={tr(\"files.storage.label\", \"Storage\")}\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit={tr(\"files.storage.unit\", \"MB\")}\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div className=\"h-full w-full rounded-full bg-status-error-bg\" />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-warning-bg/30 bg-status-warning-bg/10 px-4 py-3 text-sm\">\n <AlertTriangle className=\"size-4 shrink-0 text-status-warning-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">\n {tr(\"files.storage.almostFull\", \"Storage almost full.\")}\n </span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? tr(\n \"files.storage.almostFullWithLimit\",\n \"{{used}} of {{limit}} used ({{percent}}%).\",\n {\n used: usedLabel,\n limit: limitLabel,\n percent: Math.round(quota.usedPercent),\n },\n )\n : tr(\"files.storage.almostFullNoLimit\", \"{{used}} used.\", {\n used: usedLabel,\n })}{\" \"}\n {tr(\n \"files.storage.deleteOrUpgrade\",\n \"Consider deleting files or upgrading your plan.\",\n )}\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label={tr(\"files.storage.label\", \"Storage\")}\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit={tr(\"files.storage.unit\", \"MB\")}\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg transition-all\"\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n\ninterface StorageIndicatorProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageIndicator: FC<StorageIndicatorProps> = ({ quota }) => {\n if (quota.loading || quota.noLimit || !quota.limitBytes) return null;\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = formatBytes(quota.limitBytes);\n const color = quota.isExhausted\n ? \"bg-status-error-bg\"\n : quota.isNearLimit\n ? \"bg-status-warning-bg\"\n : \"bg-status-info-bg\";\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-text-muted\">\n <HardDrive className=\"size-3.5 shrink-0\" />\n <span className=\"tabular-nums\">\n {usedLabel} / {limitLabel}\n </span>\n <div className=\"h-1 w-16 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className={`h-full rounded-full transition-all ${color}`}\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;AAMA,SAAS,EAAY,GAAuB;AAI1C,QAHI,KAAS,aAAmB,IAAI,IAAQ,YAAY,QAAQ,EAAE,CAAC,OAC/D,KAAS,UAAgB,IAAI,IAAQ,SAAS,QAAQ,EAAE,CAAC,OACzD,KAAS,OAAa,IAAI,IAAQ,MAAM,QAAQ,EAAE,CAAC,OAChD,GAAG,EAAM;;AAOlB,IAAa,KAAyC,EAAE,eAAY;CAClE,IAAM,IAAK,GAAO;AAElB,KACE,EAAM,WACN,EAAM,WACL,CAAC,EAAM,eAAe,CAAC,EAAM,YAE9B,QAAO;CAGT,IAAM,IAAY,EAAY,EAAM,UAAU,EACxC,IAAa,EAAM,aAAa,EAAY,EAAM,WAAW,GAAG,MAChE,IAAiB,KAAK,MAAM,EAAM,YAAY,QAAQ,EACtD,IAAkB,EAAM,aAC1B,KAAK,MAAM,EAAM,aAAa,QAAQ,GACtC;AA2CJ,QAzCI,EAAM,cAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAa,WAAU,wCAAyC,CAAA;GAChE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAG,sBAAsB,gBAAgB;KACrC,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,EACE,+BACA,+CACA;OAAE,MAAM;OAAW,OAAO;OAAY,CACvC,GACD,EACE,6BACA,kCACA,EAAE,MAAM,GAAW,CACpB;MAAE;MACN,EACC,iCACA,sDACD;MACI;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,OAAO,EAAG,uBAAuB,UAAU;KAC3C,MAAM;KACN,OAAO;KACP,MAAM,EAAG,sBAAsB,KAAK;KACpC,CAAA;IACE,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAK,WAAU,iDAAkD,CAAA;IAC7D,CAAA;GACF;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAe,WAAU,0CAA2C,CAAA;GACpE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAG,4BAA4B,uBAAuB;KAClD,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,EACE,qCACA,8CACA;OACE,MAAM;OACN,OAAO;OACP,SAAS,KAAK,MAAM,EAAM,YAAY;OACvC,CACF,GACD,EAAG,mCAAmC,kBAAkB,EACtD,MAAM,GACP,CAAC;MAAE;MACP,EACC,iCACA,kDACD;MACI;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,OAAO,EAAG,uBAAuB,UAAU;KAC3C,MAAM;KACN,OAAO;KACP,MAAM,EAAG,sBAAsB,KAAK;KACpC,CAAA;IACE,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,EAAM,YAAY,IAAI;KACzC,CAAA;IACE,CAAA;GACF"}
1
+ {"version":3,"file":"StorageBanner.js","names":["formatBytes","bytes","StorageBanner","tr","useTr","quota","usedLabel","limitLabel","quotaMeterUsed","quotaMeterLimit","AlertCircle","QuotaMeter","AlertTriangle"],"sources":["../../../src/files/components/StorageBanner.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AlertCircle, AlertTriangle, HardDrive } from \"lucide-react\";\nimport { QuotaMeter } from \"@burdenoff/fe-libs/shared/components\";\nimport type { StorageQuotaState } from \"../hooks/useStorageQuota\";\nimport { useTr } from \"../../shared/hooks/useTr\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`;\n if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n}\n\ninterface StorageBannerProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageBanner: FC<StorageBannerProps> = ({ quota }) => {\n const tr = useTr();\n\n if (\n quota.loading ||\n quota.noLimit ||\n (!quota.isExhausted && !quota.isNearLimit)\n ) {\n return null;\n }\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = quota.limitBytes ? formatBytes(quota.limitBytes) : null;\n const quotaMeterUsed = Math.round(quota.usedBytes / 1048576);\n const quotaMeterLimit = quota.limitBytes\n ? Math.round(quota.limitBytes / 1048576)\n : quotaMeterUsed;\n\n if (quota.isExhausted) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-bg/30 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle className=\"size-4 shrink-0 text-status-error-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">\n {tr(\"files.storage.full\", \"Storage full.\")}\n </span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? tr(\n \"files.storage.fullWithLimit\",\n \"{{used}} of {{limit}} used — no space left.\",\n { used: usedLabel, limit: limitLabel },\n )\n : tr(\n \"files.storage.fullNoLimit\",\n \"{{used}} used — no space left.\",\n { used: usedLabel },\n )}{\" \"}\n {tr(\n \"files.storage.freeUpOrUpgrade\",\n \"Delete files to free up space or upgrade your plan.\",\n )}\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label={tr(\"files.storage.label\", \"Storage\")}\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit={tr(\"files.storage.unit\", \"MB\")}\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div className=\"h-full w-full rounded-full bg-status-error-bg\" />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-warning-bg/30 bg-status-warning-bg/10 px-4 py-3 text-sm\">\n <AlertTriangle className=\"size-4 shrink-0 text-status-warning-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">\n {tr(\"files.storage.almostFull\", \"Storage almost full.\")}\n </span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? tr(\n \"files.storage.almostFullWithLimit\",\n \"{{used}} of {{limit}} used ({{percent}}%).\",\n {\n used: usedLabel,\n limit: limitLabel,\n percent: Math.round(quota.usedPercent),\n },\n )\n : tr(\"files.storage.almostFullNoLimit\", \"{{used}} used.\", {\n used: usedLabel,\n })}{\" \"}\n {tr(\n \"files.storage.deleteOrUpgrade\",\n \"Consider deleting files or upgrading your plan.\",\n )}\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label={tr(\"files.storage.label\", \"Storage\")}\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit={tr(\"files.storage.unit\", \"MB\")}\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg transition-all\"\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n\ninterface StorageIndicatorProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageIndicator: FC<StorageIndicatorProps> = ({ quota }) => {\n if (quota.loading || quota.noLimit || !quota.limitBytes) return null;\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = formatBytes(quota.limitBytes);\n const color = quota.isExhausted\n ? \"bg-status-error-bg\"\n : quota.isNearLimit\n ? \"bg-status-warning-bg\"\n : \"bg-status-info-bg\";\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-text-muted\">\n <HardDrive className=\"size-3.5 shrink-0\" />\n <span className=\"tabular-nums\">\n {usedLabel} / {limitLabel}\n </span>\n <div className=\"h-1 w-16 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className={`h-full rounded-full transition-all ${color}`}\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;AAMA,SAASA,EAAY,GAAuB;CAI1C,OAHIC,KAAS,aAAmB,IAAIA,IAAQ,WAAA,CAAY,QAAQ,CAAC,EAAE,OAC/DA,KAAS,UAAgB,IAAIA,IAAQ,QAAA,CAAS,QAAQ,CAAC,EAAE,OACzDA,KAAS,OAAa,IAAIA,IAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE,OAChD,GAAGA,EAAM;AAClB;AAMA,IAAaC,KAAyC,EAAE,eAAY;CAClE,IAAMC,IAAKC,EAAM;CAEjB,IACEC,EAAM,WACNA,EAAM,WACL,CAACA,EAAM,eAAe,CAACA,EAAM,aAE9B,OAAO;CAGT,IAAMC,IAAYN,EAAYK,EAAM,SAAS,GACvCE,IAAaF,EAAM,aAAaL,EAAYK,EAAM,UAAU,IAAI,MAChEG,IAAiB,KAAK,MAAMH,EAAM,YAAY,OAAO,GACrDI,IAAkBJ,EAAM,aAC1B,KAAK,MAAMA,EAAM,aAAa,OAAO,IACrCG;CA2CJ,OAzCIH,EAAM,cAEN,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAACK,GAAD,EAAa,WAAU,uCAAwC,CAAA;GAC/D,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KACb,UAAAP,EAAG,sBAAsB,eAAe;IACrC,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA;MACGI,IACGJ,EACE,+BACA,+CACA;OAAE,MAAMG;OAAW,OAAOC;MAAW,CACvC,IACAJ,EACE,6BACA,kCACA,EAAE,MAAMG,EAAU,CACpB;MAAG;MACNH,EACC,iCACA,qDACF;KACI;IACH,CAAA,CAAA;;GACL,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAACQ,GAAD;KACE,OAAOR,EAAG,uBAAuB,SAAS;KAC1C,MAAMK;KACN,OAAOC;KACP,MAAMN,EAAG,sBAAsB,IAAI;IACpC,CAAA;GACE,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAAC,OAAD,EAAK,WAAU,gDAAiD,CAAA;GAC7D,CAAA;EACF;C,KAKP,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAACS,GAAD,EAAe,WAAU,yCAA0C,CAAA;GACnE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KACb,UAAAT,EAAG,4BAA4B,sBAAsB;IAClD,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA;MACGI,IACGJ,EACE,qCACA,8CACA;OACE,MAAMG;OACN,OAAOC;OACP,SAAS,KAAK,MAAMF,EAAM,WAAW;MACvC,CACF,IACAF,EAAG,mCAAmC,kBAAkB,EACtD,MAAMG,EACR,CAAC;MAAG;MACPH,EACC,iCACA,iDACF;KACI;IACH,CAAA,CAAA;;GACL,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAACQ,GAAD;KACE,OAAOR,EAAG,uBAAuB,SAAS;KAC1C,MAAMK;KACN,OAAOC;KACP,MAAMN,EAAG,sBAAsB,IAAI;IACpC,CAAA;GACE,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAGE,EAAM,YAAY,GAAG;IACzC,CAAA;GACE,CAAA;EACF;;AAET"}
@@ -11,6 +11,6 @@ interface TabBarProps {
11
11
  */
12
12
  tabs?: TabView[];
13
13
  }
14
- export declare function TabBar({ activeTab, onTabChange, tabs, }: TabBarProps): import("react").JSX.Element;
14
+ export declare function TabBar({ activeTab, onTabChange, tabs, }: TabBarProps): import('react').JSX.Element;
15
15
  export {};
16
16
  //# sourceMappingURL=TabBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TabBar.js","names":[],"sources":["../../../src/files/components/TabBar.tsx"],"sourcesContent":["import { Clock, Star, Trash2, Folder } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport type TabView = \"all\" | \"recent\" | \"starred\" | \"bin\";\n\ninterface TabBarProps {\n activeTab: TabView;\n onTabChange: (tab: TabView) => void;\n /**\n * Which tabs to render. Order is preserved. No tab here is feature-flag\n * -gated — Bin, Starred and Recent are always available; the backend serves\n * each view's query directly (subject to RBAC), so there is nothing to gate\n * on the client.\n * @default [\"all\", \"recent\", \"starred\", \"bin\"]\n */\n tabs?: TabView[];\n}\n\nconst ALL_TABS: { id: TabView; label: string; icon: React.ReactNode }[] = [\n { id: \"all\", label: \"All Files\", icon: <Folder className=\"size-5\" /> },\n { id: \"recent\", label: \"Recent\", icon: <Clock className=\"size-5\" /> },\n { id: \"starred\", label: \"Starred\", icon: <Star className=\"size-5\" /> },\n { id: \"bin\", label: \"Bin\", icon: <Trash2 className=\"size-5\" /> },\n];\n\nexport function TabBar({\n activeTab,\n onTabChange,\n tabs = [\"all\", \"recent\", \"starred\", \"bin\"],\n}: TabBarProps) {\n const { t } = useI18n();\n const visibleTabs = ALL_TABS.filter((tab) => tabs.includes(tab.id));\n const labels: Record<TabView, string> = {\n all: tWithFallback(t, \"files.browser.tabs.all\", \"All Files\"),\n recent: tWithFallback(t, \"files.browser.tabs.recent\", \"Recent\"),\n starred: tWithFallback(t, \"files.browser.tabs.starred\", \"Starred\"),\n bin: tWithFallback(t, \"files.browser.tabs.bin\", \"Bin\"),\n };\n\n return (\n <div className=\"bg-bg-surface border-b border-border-default\">\n <div className=\"overflow-x-auto px-4 sm:px-6 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\">\n <nav\n className=\"-mb-px flex min-w-max gap-1\"\n role=\"tablist\"\n aria-label={tWithFallback(\n t,\n \"files.browser.tabs.ariaLabel\",\n \"Files sections\",\n )}\n >\n {visibleTabs.map((tab) => (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n className={`\n flex items-center gap-2 whitespace-nowrap px-4 py-3 text-sm font-medium border-b-2 transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-border focus-visible:ring-offset-2\n ${\n activeTab === tab.id\n ? \"border-action-primary-bg text-text-primary bg-action-ghost-bg-hover\"\n : \"border-transparent text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover\"\n }\n `}\n >\n {tab.icon}\n {labels[tab.id] ?? tab.label}\n </button>\n ))}\n </nav>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAmBA,IAAM,IAAoE;CACxE;EAAE,IAAI;EAAO,OAAO;EAAa,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;EAAE;CACtE;EAAE,IAAI;EAAU,OAAO;EAAU,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;EAAE;CACrE;EAAE,IAAI;EAAW,OAAO;EAAW,MAAM,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;EAAE;CACtE;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;EAAE;CACjE;AAED,SAAgB,EAAO,EACrB,cACA,gBACA,UAAO;CAAC;CAAO;CAAU;CAAW;CAAM,IAC5B;CACd,IAAM,EAAE,SAAM,GAAS,EACjB,IAAc,EAAS,QAAQ,MAAQ,EAAK,SAAS,EAAI,GAAG,CAAC,EAC7D,IAAkC;EACtC,KAAK,EAAc,GAAG,0BAA0B,YAAY;EAC5D,QAAQ,EAAc,GAAG,6BAA6B,SAAS;EAC/D,SAAS,EAAc,GAAG,8BAA8B,UAAU;EAClE,KAAK,EAAc,GAAG,0BAA0B,MAAM;EACvD;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,cAAY,EACV,GACA,gCACA,iBACD;cAEA,EAAY,KAAK,MAChB,kBAAC,UAAD;KACE,MAAK;KAEL,eAAe,EAAY,EAAI,GAAG;KAClC,MAAK;KACL,iBAAe,MAAc,EAAI;KACjC,WAAW;;kBAGP,MAAc,EAAI,KACd,wEACA,gGACL;;eAZL,CAeG,EAAI,MACJ,EAAO,EAAI,OAAO,EAAI,MAChB;OAfF,EAAI,GAeF,CACT;IACE,CAAA;GACF,CAAA;EACF,CAAA"}
1
+ {"version":3,"file":"TabBar.js","names":["ALL_TABS","Folder","Clock","Star","Trash2","TabBar","useI18n","visibleTabs","tabs","tab","labels","tWithFallback","t","onTabChange","activeTab"],"sources":["../../../src/files/components/TabBar.tsx"],"sourcesContent":["import { Clock, Star, Trash2, Folder } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport type TabView = \"all\" | \"recent\" | \"starred\" | \"bin\";\n\ninterface TabBarProps {\n activeTab: TabView;\n onTabChange: (tab: TabView) => void;\n /**\n * Which tabs to render. Order is preserved. No tab here is feature-flag\n * -gated — Bin, Starred and Recent are always available; the backend serves\n * each view's query directly (subject to RBAC), so there is nothing to gate\n * on the client.\n * @default [\"all\", \"recent\", \"starred\", \"bin\"]\n */\n tabs?: TabView[];\n}\n\nconst ALL_TABS: { id: TabView; label: string; icon: React.ReactNode }[] = [\n { id: \"all\", label: \"All Files\", icon: <Folder className=\"size-5\" /> },\n { id: \"recent\", label: \"Recent\", icon: <Clock className=\"size-5\" /> },\n { id: \"starred\", label: \"Starred\", icon: <Star className=\"size-5\" /> },\n { id: \"bin\", label: \"Bin\", icon: <Trash2 className=\"size-5\" /> },\n];\n\nexport function TabBar({\n activeTab,\n onTabChange,\n tabs = [\"all\", \"recent\", \"starred\", \"bin\"],\n}: TabBarProps) {\n const { t } = useI18n();\n const visibleTabs = ALL_TABS.filter((tab) => tabs.includes(tab.id));\n const labels: Record<TabView, string> = {\n all: tWithFallback(t, \"files.browser.tabs.all\", \"All Files\"),\n recent: tWithFallback(t, \"files.browser.tabs.recent\", \"Recent\"),\n starred: tWithFallback(t, \"files.browser.tabs.starred\", \"Starred\"),\n bin: tWithFallback(t, \"files.browser.tabs.bin\", \"Bin\"),\n };\n\n return (\n <div className=\"bg-bg-surface border-b border-border-default\">\n <div className=\"overflow-x-auto px-4 sm:px-6 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\">\n <nav\n className=\"-mb-px flex min-w-max gap-1\"\n role=\"tablist\"\n aria-label={tWithFallback(\n t,\n \"files.browser.tabs.ariaLabel\",\n \"Files sections\",\n )}\n >\n {visibleTabs.map((tab) => (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n className={`\n flex items-center gap-2 whitespace-nowrap px-4 py-3 text-sm font-medium border-b-2 transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-border focus-visible:ring-offset-2\n ${\n activeTab === tab.id\n ? \"border-action-primary-bg text-text-primary bg-action-ghost-bg-hover\"\n : \"border-transparent text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover\"\n }\n `}\n >\n {tab.icon}\n {labels[tab.id] ?? tab.label}\n </button>\n ))}\n </nav>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAmBA,IAAMA,IAAoE;CACxE;EAAE,IAAI;EAAO,OAAO;EAAa,MAAM,kBAACC,GAAD,EAAQ,WAAU,SAAU,CAAA;CAAE;CACrE;EAAE,IAAI;EAAU,OAAO;EAAU,MAAM,kBAACC,GAAD,EAAO,WAAU,SAAU,CAAA;CAAE;CACpE;EAAE,IAAI;EAAW,OAAO;EAAW,MAAM,kBAACC,GAAD,EAAM,WAAU,SAAU,CAAA;CAAE;CACrE;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM,kBAACC,GAAD,EAAQ,WAAU,SAAU,CAAA;CAAE;AACjE;AAEA,SAAgBC,EAAO,EACrB,cACA,gBACA,UAAO;CAAC;CAAO;CAAU;CAAW;AAAK,KAC3B;CACd,IAAM,EAAE,SAAMC,EAAQ,GAChBC,IAAcP,EAAS,QAAQ,MAAQQ,EAAK,SAASC,EAAI,EAAE,CAAC,GAC5DC,IAAkC;EACtC,KAAKC,EAAcC,GAAG,0BAA0B,WAAW;EAC3D,QAAQD,EAAcC,GAAG,6BAA6B,QAAQ;EAC9D,SAASD,EAAcC,GAAG,8BAA8B,SAAS;EACjE,KAAKD,EAAcC,GAAG,0BAA0B,KAAK;CACvD;CAEA,OACE,kBAAC,OAAD;EAAK,WAAU;EACb,UAAA,kBAAC,OAAD;GAAK,WAAU;GACb,UAAA,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,cAAYD,EACVC,GACA,gCACA,gBACF;IAEC,UAAAL,EAAY,KAAK,MAChB,kBAAC,UAAD;KACE,MAAK;KAEL,eAAeM,EAAYJ,EAAI,EAAE;KACjC,MAAK;KACL,iBAAeK,MAAcL,EAAI;KACjC,WAAW;;kBAGPK,MAAcL,EAAI,KACd,wEACA,gGACL;;KAZL,UAAA,CAeGA,EAAI,MACJC,EAAOD,EAAI,OAAOA,EAAI,KACjB;IAfD,GAAAA,EAAI,EAeH,CACT;GACE,CAAA;EACF,CAAA;CACF,CAAA;AAET"}
@@ -9,6 +9,6 @@ interface ToastContainerProps {
9
9
  toasts: Toast[];
10
10
  onClose: (id: string) => void;
11
11
  }
12
- export declare function ToastContainer({ toasts, onClose }: ToastContainerProps): import("react").JSX.Element;
12
+ export declare function ToastContainer({ toasts, onClose }: ToastContainerProps): import('react').JSX.Element;
13
13
  export {};
14
14
  //# sourceMappingURL=Toast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.js","names":[],"sources":["../../../src/files/components/Toast.tsx"],"sourcesContent":["import { useEffect, useEffectEvent } from \"react\";\nimport { X, CheckCircle, XCircle, AlertCircle, Info } from \"lucide-react\";\n\nexport type ToastType = \"success\" | \"error\" | \"warning\" | \"info\";\n\nexport interface Toast {\n id: string;\n type: ToastType;\n message: string;\n duration?: number;\n}\n\ninterface ToastItemProps {\n toast: Toast;\n onClose: (id: string) => void;\n}\n\nfunction ToastItem({ toast, onClose }: ToastItemProps) {\n const closeToast = useEffectEvent(() => {\n onClose(toast.id);\n });\n\n useEffect(() => {\n const duration = toast.duration || 5000;\n const timer = setTimeout(() => {\n closeToast();\n }, duration);\n\n return () => clearTimeout(timer);\n }, [toast.duration]);\n\n const icons = {\n success: <CheckCircle className=\"size-5 text-status-success-bg\" />,\n error: <XCircle className=\"size-5 text-status-error-bg\" />,\n warning: <AlertCircle className=\"size-5 text-status-warning-bg\" />,\n info: <Info className=\"size-5 text-status-info-bg\" />,\n };\n\n const colors = {\n success: \"bg-status-success-bg-subtle border-status-success-border\",\n error: \"bg-status-error-bg-subtle border-status-error-border\",\n warning: \"bg-status-warning-bg-subtle border-status-warning-border\",\n info: \"bg-status-info-bg-subtle border-status-info-border\",\n };\n\n return (\n <div\n className={`flex items-center gap-3 p-4 rounded-lg shadow-lg border ${colors[toast.type]} w-80 max-w-[90vw] md:max-w-md animate-in slide-in-from-right duration-200`}\n >\n {icons[toast.type]}\n <p className=\"flex-1 text-sm font-medium text-text-primary\">\n {toast.message}\n </p>\n <button\n type=\"button\"\n onClick={() => onClose(toast.id)}\n className=\"text-text-muted hover:text-text-secondary transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n );\n}\n\ninterface ToastContainerProps {\n toasts: Toast[];\n onClose: (id: string) => void;\n}\n\nexport function ToastContainer({ toasts, onClose }: ToastContainerProps) {\n return (\n <div className=\"fixed bottom-4 right-4 z-50 flex flex-col gap-2\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onClose={onClose} />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAiBA,SAAS,EAAU,EAAE,UAAO,cAA2B;CACrD,IAAM,IAAa,QAAqB;AACtC,IAAQ,EAAM,GAAG;GACjB;AAEF,SAAgB;EACd,IAAM,IAAW,EAAM,YAAY,KAC7B,IAAQ,iBAAiB;AAC7B,MAAY;KACX,EAAS;AAEZ,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,SAAS,CAAC;CAEpB,IAAM,IAAQ;EACZ,SAAS,kBAAC,GAAD,EAAa,WAAU,iCAAkC,CAAA;EAClE,OAAO,kBAAC,GAAD,EAAS,WAAU,+BAAgC,CAAA;EAC1D,SAAS,kBAAC,GAAD,EAAa,WAAU,iCAAkC,CAAA;EAClE,MAAM,kBAAC,GAAD,EAAM,WAAU,8BAA+B,CAAA;EACtD;AASD,QACE,kBAAC,OAAD;EACE,WAAW,2DATA;GACb,SAAS;GACT,OAAO;GACP,SAAS;GACT,MAAM;GACP,CAIgF,EAAM,MAAM;YAD3F;GAGG,EAAM,EAAM;GACb,kBAAC,KAAD;IAAG,WAAU;cACV,EAAM;IACL,CAAA;GACJ,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAQ,EAAM,GAAG;IAChC,WAAU;cAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA;GACL;;;AASV,SAAgB,EAAe,EAAE,WAAQ,cAAgC;AACvE,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAO,KAAK,MACX,kBAAC,GAAD;GAAiC;GAAgB;GAAW,EAA5C,EAAM,GAAsC,CAC5D;EACE,CAAA"}
1
+ {"version":3,"file":"Toast.js","names":["ToastItem","closeToast","useEffectEvent","toast","duration","timer","icons","CheckCircle","XCircle","AlertCircle","Info","onClose","X","ToastContainer","toasts"],"sources":["../../../src/files/components/Toast.tsx"],"sourcesContent":["import { useEffect, useEffectEvent } from \"react\";\nimport { X, CheckCircle, XCircle, AlertCircle, Info } from \"lucide-react\";\n\nexport type ToastType = \"success\" | \"error\" | \"warning\" | \"info\";\n\nexport interface Toast {\n id: string;\n type: ToastType;\n message: string;\n duration?: number;\n}\n\ninterface ToastItemProps {\n toast: Toast;\n onClose: (id: string) => void;\n}\n\nfunction ToastItem({ toast, onClose }: ToastItemProps) {\n const closeToast = useEffectEvent(() => {\n onClose(toast.id);\n });\n\n useEffect(() => {\n const duration = toast.duration || 5000;\n const timer = setTimeout(() => {\n closeToast();\n }, duration);\n\n return () => clearTimeout(timer);\n }, [toast.duration]);\n\n const icons = {\n success: <CheckCircle className=\"size-5 text-status-success-bg\" />,\n error: <XCircle className=\"size-5 text-status-error-bg\" />,\n warning: <AlertCircle className=\"size-5 text-status-warning-bg\" />,\n info: <Info className=\"size-5 text-status-info-bg\" />,\n };\n\n const colors = {\n success: \"bg-status-success-bg-subtle border-status-success-border\",\n error: \"bg-status-error-bg-subtle border-status-error-border\",\n warning: \"bg-status-warning-bg-subtle border-status-warning-border\",\n info: \"bg-status-info-bg-subtle border-status-info-border\",\n };\n\n return (\n <div\n className={`flex items-center gap-3 p-4 rounded-lg shadow-lg border ${colors[toast.type]} w-80 max-w-[90vw] md:max-w-md animate-in slide-in-from-right duration-200`}\n >\n {icons[toast.type]}\n <p className=\"flex-1 text-sm font-medium text-text-primary\">\n {toast.message}\n </p>\n <button\n type=\"button\"\n onClick={() => onClose(toast.id)}\n className=\"text-text-muted hover:text-text-secondary transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n );\n}\n\ninterface ToastContainerProps {\n toasts: Toast[];\n onClose: (id: string) => void;\n}\n\nexport function ToastContainer({ toasts, onClose }: ToastContainerProps) {\n return (\n <div className=\"fixed bottom-4 right-4 z-50 flex flex-col gap-2\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onClose={onClose} />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAiBA,SAASA,EAAU,EAAE,UAAO,cAA2B;CACrD,IAAMC,IAAaC,QAAqB;EACtC,EAAQC,EAAM,EAAE;CAClB,CAAC;CAED,QAAgB;EACd,IAAMC,IAAWD,EAAM,YAAY,KAC7BE,IAAQ,iBAAiB;GAC7B,EAAW;EACb,GAAGD,CAAQ;EAEX,aAAa,aAAaC,CAAK;CACjC,GAAG,CAACF,EAAM,QAAQ,CAAC;CAEnB,IAAMG,IAAQ;EACZ,SAAS,kBAACC,GAAD,EAAa,WAAU,gCAAiC,CAAA;EACjE,OAAO,kBAACC,GAAD,EAAS,WAAU,8BAA+B,CAAA;EACzD,SAAS,kBAACC,GAAD,EAAa,WAAU,gCAAiC,CAAA;EACjE,MAAM,kBAACC,GAAD,EAAM,WAAU,6BAA8B,CAAA;CACtD;CASA,OACE,kBAAC,OAAD;EACE,WAAW,2DAA2D;GARxE,SAAS;GACT,OAAO;GACP,SAAS;GACT,MAAM;EAKkE,EAAOP,EAAM,MAAM;EAD3F,UAAA;GAGGG,EAAMH,EAAM;GACb,kBAAC,KAAD;IAAG,WAAU;IACV,UAAAA,EAAM;GACN,CAAA;GACH,kBAAC,UAAD;IACE,MAAK;IACL,eAAeQ,EAAQR,EAAM,EAAE;IAC/B,WAAU;IAEV,UAAA,kBAACS,GAAD,EAAG,WAAU,SAAU,CAAA;GACjB,CAAA;EACL;;AAET;AAOA,SAAgBC,EAAe,EAAE,WAAQ,cAAgC;CACvE,OACE,kBAAC,OAAD;EAAK,WAAU;EACZ,UAAAC,EAAO,KAAK,MACX,kBAACd,GAAD;GAAiC;GAAgB;EAAU,GAA3CG,EAAM,EAAqC,CAC5D;CACE,CAAA;AAET"}
@@ -8,6 +8,6 @@ interface UploadQueueProps {
8
8
  onRetry: (id: string) => void;
9
9
  onCancel: (id: string) => void;
10
10
  }
11
- export declare function UploadQueue({ items, isOpen, onClose, onPause, onResume, onRetry, onCancel, }: UploadQueueProps): import("react").JSX.Element | null;
11
+ export declare function UploadQueue({ items, isOpen, onClose, onPause, onResume, onRetry, onCancel, }: UploadQueueProps): import('react').JSX.Element | null;
12
12
  export {};
13
13
  //# sourceMappingURL=UploadQueue.d.ts.map
@@ -42,7 +42,7 @@ function p({ item: f, onPause: p, onResume: m, onRetry: h, onCancel: g }) {
42
42
  "MB",
43
43
  "GB"
44
44
  ], r = Math.floor(Math.log(e) / Math.log(t));
45
- return `${parseFloat((e / t ** +r).toFixed(1))} ${n[r]}`;
45
+ return `${parseFloat((e / t ** r).toFixed(1))} ${n[r]}`;
46
46
  })(f.file.size)
47
47
  })]
48
48
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"UploadQueue.js","names":[],"sources":["../../../src/files/components/UploadQueue.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport {\n Pause,\n Play,\n RotateCcw,\n X,\n CheckCircle,\n AlertCircle,\n Loader2,\n Image as ImageIcon,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport type { UploadItem } from \"../types\";\nimport { isImageType } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface UploadProgressBarProps {\n item: UploadItem;\n onPause: (id: string) => void;\n onResume: (id: string) => void;\n onRetry: (id: string) => void;\n onCancel: (id: string) => void;\n}\n\nfunction UploadProgressBar({\n item,\n onPause,\n onResume,\n onRetry,\n onCancel,\n}: UploadProgressBarProps) {\n const { t } = useI18n();\n const progressRef = useRef<HTMLDivElement>(null);\n const isImage = isImageType(item.file.type);\n\n useEffect(() => {\n if (progressRef.current) {\n progressRef.current.style.width = `${item.progress}%`;\n }\n }, [item.progress]);\n\n const getStatusIcon = () => {\n switch (item.status) {\n case \"uploading\":\n return (\n <Loader2 className=\"size-4 animate-spin text-action-primary-bg\" />\n );\n case \"paused\":\n return <Pause className=\"size-4 text-status-warning-text\" />;\n case \"completed\":\n return <CheckCircle className=\"size-4 text-status-success-text\" />;\n case \"failed\":\n return <AlertCircle className=\"size-4 text-status-error-text\" />;\n default:\n return <Loader2 className=\"size-4 animate-spin text-text-tertiary\" />;\n }\n };\n\n const getStatusColor = () => {\n switch (item.status) {\n case \"uploading\":\n return \"bg-action-primary-bg\";\n case \"paused\":\n return \"bg-status-warning-bg\";\n case \"completed\":\n return \"bg-status-success-bg\";\n case \"failed\":\n return \"bg-status-error-bg\";\n default:\n return \"bg-text-tertiary\";\n }\n };\n\n const formatFileSize = (bytes: number) => {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;\n };\n\n return (\n <div className=\"bg-bg-elevated border border-border-default rounded-lg p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex-shrink-0 size-10 bg-bg-sunken rounded flex items-center justify-center overflow-hidden\">\n {item.thumbnailUrl ? (\n <img\n src={item.thumbnailUrl}\n alt={item.file.name}\n className=\"w-full h-full object-cover\"\n />\n ) : isImage ? (\n <ImageIcon className=\"size-5 text-text-tertiary\" />\n ) : (\n <div className=\"w-full h-full bg-bg-accent\" />\n )}\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center justify-between gap-2 mb-1.5\">\n <span className=\"text-sm font-medium text-text-primary truncate\">\n {item.file.name}\n </span>\n <span className=\"text-xs text-text-secondary whitespace-nowrap\">\n {formatFileSize(item.file.size)}\n </span>\n </div>\n\n <div className=\"relative h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n ref={progressRef}\n className={`absolute inset-y-0 left-0 ${getStatusColor()} transition-all duration-300 ease-out rounded-full`}\n style={{ width: `${item.progress}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between mt-1.5\">\n <span className=\"text-xs text-text-secondary\">\n {item.status === \"paused\" &&\n tWithFallback(t, \"files.upload.paused\", \"Paused\")}\n {item.status === \"uploading\" && `${item.progress}%`}\n {item.status === \"completed\" &&\n tWithFallback(t, \"files.upload.completedStatus\", \"Completed\")}\n {item.status === \"failed\" &&\n (item.error ||\n tWithFallback(t, \"files.upload.failed\", \"Failed\"))}\n {item.status === \"pending\" &&\n tWithFallback(t, \"files.upload.pendingStatus\", \"Pending...\")}\n </span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-1 flex-shrink-0\">\n {item.status === \"uploading\" && (\n <button\n type=\"button\"\n onClick={() => onPause(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.pause\", \"Pause\")}\n >\n <Pause className=\"size-4\" />\n </button>\n )}\n\n {item.status === \"paused\" && (\n <button\n type=\"button\"\n onClick={() => onResume(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.resume\", \"Resume\")}\n >\n <Play className=\"size-4\" />\n </button>\n )}\n\n {item.status === \"failed\" && (\n <button\n type=\"button\"\n onClick={() => onRetry(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.retry\", \"Retry\")}\n >\n <RotateCcw className=\"size-4\" />\n </button>\n )}\n\n {(item.status === \"pending\" ||\n item.status === \"paused\" ||\n item.status === \"failed\") && (\n <button\n type=\"button\"\n onClick={() => onCancel(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.cancel\", \"Cancel\")}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n\ninterface UploadQueueProps {\n items: UploadItem[];\n isOpen: boolean;\n onClose: () => void;\n onPause: (id: string) => void;\n onResume: (id: string) => void;\n onRetry: (id: string) => void;\n onCancel: (id: string) => void;\n}\n\nexport function UploadQueue({\n items,\n isOpen,\n onClose,\n onPause,\n onResume,\n onRetry,\n onCancel,\n}: UploadQueueProps) {\n const { t } = useI18n();\n if (!isOpen) return null;\n\n const completedCount = items.filter((i) => i.status === \"completed\").length;\n const failedCount = items.filter((i) => i.status === \"failed\").length;\n const activeCount = items.filter(\n (i) => i.status === \"uploading\" || i.status === \"pending\",\n ).length;\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"fixed bottom-4 right-4 z-50 w-80 max-w-[90vw] md:w-96\"\n >\n <div className=\"flex items-center justify-between p-3 border-b border-border-seam\">\n <div>\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n {activeCount > 0 && (\n <span\n className=\"size-2 shrink-0 rounded-full bg-action-primary-bg motion-safe:animate-vc-glow-pulse\"\n aria-hidden=\"true\"\n />\n )}\n {tWithFallback(t, \"files.upload.uploads\", \"Uploads\")} (\n {tWithFallback(t, \"files.upload.active\", \"{{count}} uploading\", {\n count: activeCount,\n })}\n )\n </h3>\n {completedCount > 0 && (\n <p className=\"text-xs text-status-success-text\">\n {tWithFallback(\n t,\n \"files.upload.completed\",\n \"{{count}} completed\",\n {\n count: completedCount,\n },\n )}\n </p>\n )}\n {failedCount > 0 && (\n <p className=\"text-xs text-status-error-text\">\n {tWithFallback(\n t,\n \"files.upload.failedCount\",\n \"{{count}} failed\",\n {\n count: failedCount,\n },\n )}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={tWithFallback(t, \"files.upload.close\", \"Close uploads\")}\n className=\"p-1 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"max-h-64 overflow-y-auto p-2 space-y-2\">\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-4\">\n {tWithFallback(t, \"files.upload.noUploads\", \"No uploads\")}\n </p>\n ) : (\n items.map((item) => (\n <UploadProgressBar\n key={item.id}\n item={item}\n onPause={onPause}\n onResume={onResume}\n onRetry={onRetry}\n onCancel={onCancel}\n />\n ))\n )}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UploadQueue.js","names":["ImageIcon","UploadProgressBar","useI18n","progressRef","useRef","isImage","isImageType","item","useEffect","Image","bytes","k","sizes","i","tWithFallback","t","onPause","Pause","onResume","Play","onRetry","RotateCcw","onCancel","X","UploadQueue","isOpen","completedCount","items","failedCount","activeCount","GlassCard","onClose"],"sources":["../../../src/files/components/UploadQueue.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport {\n Pause,\n Play,\n RotateCcw,\n X,\n CheckCircle,\n AlertCircle,\n Loader2,\n Image as ImageIcon,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport type { UploadItem } from \"../types\";\nimport { isImageType } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface UploadProgressBarProps {\n item: UploadItem;\n onPause: (id: string) => void;\n onResume: (id: string) => void;\n onRetry: (id: string) => void;\n onCancel: (id: string) => void;\n}\n\nfunction UploadProgressBar({\n item,\n onPause,\n onResume,\n onRetry,\n onCancel,\n}: UploadProgressBarProps) {\n const { t } = useI18n();\n const progressRef = useRef<HTMLDivElement>(null);\n const isImage = isImageType(item.file.type);\n\n useEffect(() => {\n if (progressRef.current) {\n progressRef.current.style.width = `${item.progress}%`;\n }\n }, [item.progress]);\n\n const getStatusIcon = () => {\n switch (item.status) {\n case \"uploading\":\n return (\n <Loader2 className=\"size-4 animate-spin text-action-primary-bg\" />\n );\n case \"paused\":\n return <Pause className=\"size-4 text-status-warning-text\" />;\n case \"completed\":\n return <CheckCircle className=\"size-4 text-status-success-text\" />;\n case \"failed\":\n return <AlertCircle className=\"size-4 text-status-error-text\" />;\n default:\n return <Loader2 className=\"size-4 animate-spin text-text-tertiary\" />;\n }\n };\n\n const getStatusColor = () => {\n switch (item.status) {\n case \"uploading\":\n return \"bg-action-primary-bg\";\n case \"paused\":\n return \"bg-status-warning-bg\";\n case \"completed\":\n return \"bg-status-success-bg\";\n case \"failed\":\n return \"bg-status-error-bg\";\n default:\n return \"bg-text-tertiary\";\n }\n };\n\n const formatFileSize = (bytes: number) => {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;\n };\n\n return (\n <div className=\"bg-bg-elevated border border-border-default rounded-lg p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex-shrink-0 size-10 bg-bg-sunken rounded flex items-center justify-center overflow-hidden\">\n {item.thumbnailUrl ? (\n <img\n src={item.thumbnailUrl}\n alt={item.file.name}\n className=\"w-full h-full object-cover\"\n />\n ) : isImage ? (\n <ImageIcon className=\"size-5 text-text-tertiary\" />\n ) : (\n <div className=\"w-full h-full bg-bg-accent\" />\n )}\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center justify-between gap-2 mb-1.5\">\n <span className=\"text-sm font-medium text-text-primary truncate\">\n {item.file.name}\n </span>\n <span className=\"text-xs text-text-secondary whitespace-nowrap\">\n {formatFileSize(item.file.size)}\n </span>\n </div>\n\n <div className=\"relative h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n ref={progressRef}\n className={`absolute inset-y-0 left-0 ${getStatusColor()} transition-all duration-300 ease-out rounded-full`}\n style={{ width: `${item.progress}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between mt-1.5\">\n <span className=\"text-xs text-text-secondary\">\n {item.status === \"paused\" &&\n tWithFallback(t, \"files.upload.paused\", \"Paused\")}\n {item.status === \"uploading\" && `${item.progress}%`}\n {item.status === \"completed\" &&\n tWithFallback(t, \"files.upload.completedStatus\", \"Completed\")}\n {item.status === \"failed\" &&\n (item.error ||\n tWithFallback(t, \"files.upload.failed\", \"Failed\"))}\n {item.status === \"pending\" &&\n tWithFallback(t, \"files.upload.pendingStatus\", \"Pending...\")}\n </span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-1 flex-shrink-0\">\n {item.status === \"uploading\" && (\n <button\n type=\"button\"\n onClick={() => onPause(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.pause\", \"Pause\")}\n >\n <Pause className=\"size-4\" />\n </button>\n )}\n\n {item.status === \"paused\" && (\n <button\n type=\"button\"\n onClick={() => onResume(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.resume\", \"Resume\")}\n >\n <Play className=\"size-4\" />\n </button>\n )}\n\n {item.status === \"failed\" && (\n <button\n type=\"button\"\n onClick={() => onRetry(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.retry\", \"Retry\")}\n >\n <RotateCcw className=\"size-4\" />\n </button>\n )}\n\n {(item.status === \"pending\" ||\n item.status === \"paused\" ||\n item.status === \"failed\") && (\n <button\n type=\"button\"\n onClick={() => onCancel(item.id)}\n className=\"p-1.5 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n title={tWithFallback(t, \"files.upload.cancel\", \"Cancel\")}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n\ninterface UploadQueueProps {\n items: UploadItem[];\n isOpen: boolean;\n onClose: () => void;\n onPause: (id: string) => void;\n onResume: (id: string) => void;\n onRetry: (id: string) => void;\n onCancel: (id: string) => void;\n}\n\nexport function UploadQueue({\n items,\n isOpen,\n onClose,\n onPause,\n onResume,\n onRetry,\n onCancel,\n}: UploadQueueProps) {\n const { t } = useI18n();\n if (!isOpen) return null;\n\n const completedCount = items.filter((i) => i.status === \"completed\").length;\n const failedCount = items.filter((i) => i.status === \"failed\").length;\n const activeCount = items.filter(\n (i) => i.status === \"uploading\" || i.status === \"pending\",\n ).length;\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"fixed bottom-4 right-4 z-50 w-80 max-w-[90vw] md:w-96\"\n >\n <div className=\"flex items-center justify-between p-3 border-b border-border-seam\">\n <div>\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n {activeCount > 0 && (\n <span\n className=\"size-2 shrink-0 rounded-full bg-action-primary-bg motion-safe:animate-vc-glow-pulse\"\n aria-hidden=\"true\"\n />\n )}\n {tWithFallback(t, \"files.upload.uploads\", \"Uploads\")} (\n {tWithFallback(t, \"files.upload.active\", \"{{count}} uploading\", {\n count: activeCount,\n })}\n )\n </h3>\n {completedCount > 0 && (\n <p className=\"text-xs text-status-success-text\">\n {tWithFallback(\n t,\n \"files.upload.completed\",\n \"{{count}} completed\",\n {\n count: completedCount,\n },\n )}\n </p>\n )}\n {failedCount > 0 && (\n <p className=\"text-xs text-status-error-text\">\n {tWithFallback(\n t,\n \"files.upload.failedCount\",\n \"{{count}} failed\",\n {\n count: failedCount,\n },\n )}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={tWithFallback(t, \"files.upload.close\", \"Close uploads\")}\n className=\"p-1 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"max-h-64 overflow-y-auto p-2 space-y-2\">\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-4\">\n {tWithFallback(t, \"files.upload.noUploads\", \"No uploads\")}\n </p>\n ) : (\n items.map((item) => (\n <UploadProgressBar\n key={item.id}\n item={item}\n onPause={onPause}\n onResume={onResume}\n onRetry={onRetry}\n onCancel={onCancel}\n />\n ))\n )}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -13,12 +13,12 @@ export declare function createFileMenuItems(options: {
13
13
  isStarred?: boolean;
14
14
  }, t: I18nContextValue["t"]): ({
15
15
  label: string;
16
- icon: import("react").JSX.Element;
16
+ icon: import('react').JSX.Element;
17
17
  onClick: () => void;
18
18
  variant?: undefined;
19
19
  } | {
20
20
  label: string;
21
- icon: import("react").JSX.Element;
21
+ icon: import('react').JSX.Element;
22
22
  onClick: () => void;
23
23
  variant: "danger";
24
24
  })[];
@@ -33,12 +33,12 @@ export declare function createFolderMenuItems(options: {
33
33
  isStarred?: boolean;
34
34
  }, t: I18nContextValue["t"]): ({
35
35
  label: string;
36
- icon: import("react").JSX.Element;
36
+ icon: import('react').JSX.Element;
37
37
  onClick: () => void;
38
38
  variant?: undefined;
39
39
  } | {
40
40
  label: string;
41
- icon: import("react").JSX.Element;
41
+ icon: import('react').JSX.Element;
42
42
  onClick: () => void;
43
43
  variant: "danger";
44
44
  })[];
@@ -1 +1 @@
1
- {"version":3,"file":"contextMenuItems.js","names":[],"sources":["../../../src/files/components/contextMenuItems.tsx"],"sourcesContent":["import {\n Share2,\n Download,\n Copy,\n Trash2,\n Edit3,\n Star,\n FolderInput,\n Info,\n Clock,\n RotateCcw,\n} from \"lucide-react\";\nimport type { I18nContextValue } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport function createFileMenuItems(\n options: {\n onShare?: () => void;\n onDownload?: () => void;\n onCopy?: () => void;\n onRename?: () => void;\n onStar?: () => void;\n onMove?: () => void;\n onInfo?: () => void;\n onActivity?: () => void;\n onRestore?: () => void;\n onDelete?: () => void;\n isStarred?: boolean;\n },\n t: I18nContextValue[\"t\"],\n) {\n return [\n ...(options.onShare\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: options.onShare,\n },\n ]\n : []),\n ...(options.onDownload\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.download\", \"Download\"),\n icon: <Download className=\"size-4\" />,\n onClick: options.onDownload,\n },\n ]\n : []),\n ...(options.onCopy\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.makeCopy\",\n \"Make a copy\",\n ),\n icon: <Copy className=\"size-4\" />,\n onClick: options.onCopy,\n },\n ]\n : []),\n ...(options.onRename\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.rename\", \"Rename\"),\n icon: <Edit3 className=\"size-4\" />,\n onClick: options.onRename,\n },\n ]\n : []),\n ...(options.onStar\n ? [\n {\n label: options.isStarred\n ? tWithFallback(t, \"files.contextMenu.removeStar\", \"Remove star\")\n : tWithFallback(\n t,\n \"files.contextMenu.addToStarred\",\n \"Add to starred\",\n ),\n icon: <Star className=\"size-4\" />,\n onClick: options.onStar,\n },\n ]\n : []),\n ...(options.onMove\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: options.onMove,\n },\n ]\n : []),\n ...(options.onInfo\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.fileInfo\",\n \"File information\",\n ),\n icon: <Info className=\"size-4\" />,\n onClick: options.onInfo,\n },\n ]\n : []),\n ...(options.onActivity\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.activity\", \"Activity\"),\n icon: <Clock className=\"size-4\" />,\n onClick: options.onActivity,\n },\n ]\n : []),\n ...(options.onRestore\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: options.onRestore,\n },\n ]\n : []),\n ...(options.onDelete\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: options.onDelete,\n variant: \"danger\" as const,\n },\n ]\n : []),\n ];\n}\n\nexport function createFolderMenuItems(\n options: {\n onShare?: () => void;\n onRename?: () => void;\n onStar?: () => void;\n onMove?: () => void;\n onInfo?: () => void;\n onRestore?: () => void;\n onDelete?: () => void;\n isStarred?: boolean;\n },\n t: I18nContextValue[\"t\"],\n) {\n return [\n ...(options.onShare\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: options.onShare,\n },\n ]\n : []),\n ...(options.onRename\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.rename\", \"Rename\"),\n icon: <Edit3 className=\"size-4\" />,\n onClick: options.onRename,\n },\n ]\n : []),\n ...(options.onStar\n ? [\n {\n label: options.isStarred\n ? tWithFallback(t, \"files.contextMenu.removeStar\", \"Remove star\")\n : tWithFallback(\n t,\n \"files.contextMenu.addToStarred\",\n \"Add to starred\",\n ),\n icon: <Star className=\"size-4\" />,\n onClick: options.onStar,\n },\n ]\n : []),\n ...(options.onMove\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: options.onMove,\n },\n ]\n : []),\n ...(options.onInfo\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.folderInfo\",\n \"Folder information\",\n ),\n icon: <Info className=\"size-4\" />,\n onClick: options.onInfo,\n },\n ]\n : []),\n ...(options.onRestore\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: options.onRestore,\n },\n ]\n : []),\n ...(options.onDelete\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: options.onDelete,\n variant: \"danger\" as const,\n },\n ]\n : []),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"contextMenuItems.js","names":["createFileMenuItems","options","tWithFallback","t","Share2","Download","Copy","Edit3","Star","FolderInput","Info","Clock","RotateCcw","Trash2","createFolderMenuItems"],"sources":["../../../src/files/components/contextMenuItems.tsx"],"sourcesContent":["import {\n Share2,\n Download,\n Copy,\n Trash2,\n Edit3,\n Star,\n FolderInput,\n Info,\n Clock,\n RotateCcw,\n} from \"lucide-react\";\nimport type { I18nContextValue } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport function createFileMenuItems(\n options: {\n onShare?: () => void;\n onDownload?: () => void;\n onCopy?: () => void;\n onRename?: () => void;\n onStar?: () => void;\n onMove?: () => void;\n onInfo?: () => void;\n onActivity?: () => void;\n onRestore?: () => void;\n onDelete?: () => void;\n isStarred?: boolean;\n },\n t: I18nContextValue[\"t\"],\n) {\n return [\n ...(options.onShare\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: options.onShare,\n },\n ]\n : []),\n ...(options.onDownload\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.download\", \"Download\"),\n icon: <Download className=\"size-4\" />,\n onClick: options.onDownload,\n },\n ]\n : []),\n ...(options.onCopy\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.makeCopy\",\n \"Make a copy\",\n ),\n icon: <Copy className=\"size-4\" />,\n onClick: options.onCopy,\n },\n ]\n : []),\n ...(options.onRename\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.rename\", \"Rename\"),\n icon: <Edit3 className=\"size-4\" />,\n onClick: options.onRename,\n },\n ]\n : []),\n ...(options.onStar\n ? [\n {\n label: options.isStarred\n ? tWithFallback(t, \"files.contextMenu.removeStar\", \"Remove star\")\n : tWithFallback(\n t,\n \"files.contextMenu.addToStarred\",\n \"Add to starred\",\n ),\n icon: <Star className=\"size-4\" />,\n onClick: options.onStar,\n },\n ]\n : []),\n ...(options.onMove\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: options.onMove,\n },\n ]\n : []),\n ...(options.onInfo\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.fileInfo\",\n \"File information\",\n ),\n icon: <Info className=\"size-4\" />,\n onClick: options.onInfo,\n },\n ]\n : []),\n ...(options.onActivity\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.activity\", \"Activity\"),\n icon: <Clock className=\"size-4\" />,\n onClick: options.onActivity,\n },\n ]\n : []),\n ...(options.onRestore\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: options.onRestore,\n },\n ]\n : []),\n ...(options.onDelete\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: options.onDelete,\n variant: \"danger\" as const,\n },\n ]\n : []),\n ];\n}\n\nexport function createFolderMenuItems(\n options: {\n onShare?: () => void;\n onRename?: () => void;\n onStar?: () => void;\n onMove?: () => void;\n onInfo?: () => void;\n onRestore?: () => void;\n onDelete?: () => void;\n isStarred?: boolean;\n },\n t: I18nContextValue[\"t\"],\n) {\n return [\n ...(options.onShare\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: options.onShare,\n },\n ]\n : []),\n ...(options.onRename\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.rename\", \"Rename\"),\n icon: <Edit3 className=\"size-4\" />,\n onClick: options.onRename,\n },\n ]\n : []),\n ...(options.onStar\n ? [\n {\n label: options.isStarred\n ? tWithFallback(t, \"files.contextMenu.removeStar\", \"Remove star\")\n : tWithFallback(\n t,\n \"files.contextMenu.addToStarred\",\n \"Add to starred\",\n ),\n icon: <Star className=\"size-4\" />,\n onClick: options.onStar,\n },\n ]\n : []),\n ...(options.onMove\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: options.onMove,\n },\n ]\n : []),\n ...(options.onInfo\n ? [\n {\n label: tWithFallback(\n t,\n \"files.contextMenu.folderInfo\",\n \"Folder information\",\n ),\n icon: <Info className=\"size-4\" />,\n onClick: options.onInfo,\n },\n ]\n : []),\n ...(options.onRestore\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: options.onRestore,\n },\n ]\n : []),\n ...(options.onDelete\n ? [\n {\n label: tWithFallback(t, \"files.contextMenu.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: options.onDelete,\n variant: \"danger\" as const,\n },\n ]\n : []),\n ];\n}\n"],"mappings":"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"}
@@ -3,6 +3,6 @@ interface AudioPlayerProps {
3
3
  file: FileItem;
4
4
  url: string;
5
5
  }
6
- export declare function AudioPlayer({ file, url }: AudioPlayerProps): import("react").JSX.Element;
6
+ export declare function AudioPlayer({ file, url }: AudioPlayerProps): import('react').JSX.Element;
7
7
  export {};
8
8
  //# sourceMappingURL=AudioPlayer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AudioPlayer.js","names":[],"sources":["../../../../src/files/components/viewers/AudioPlayer.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { Play, Pause, Volume2, VolumeX, Music } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\n\ninterface AudioPlayerProps {\n file: FileItem;\n url: string;\n}\n\nexport function AudioPlayer({ file, url }: AudioPlayerProps) {\n const tr = useTr();\n const audioRef = useRef<HTMLAudioElement>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [volume, setVolume] = useState(1);\n const [isMuted, setIsMuted] = useState(false);\n\n useEffect(() => {\n const audio = audioRef.current;\n if (!audio) return;\n\n const handleTimeUpdate = () => setCurrentTime(audio.currentTime);\n const handleLoadedMetadata = () => setDuration(audio.duration);\n const handleEnded = () => setIsPlaying(false);\n\n audio.addEventListener(\"timeupdate\", handleTimeUpdate);\n audio.addEventListener(\"loadedmetadata\", handleLoadedMetadata);\n audio.addEventListener(\"ended\", handleEnded);\n\n return () => {\n audio.removeEventListener(\"timeupdate\", handleTimeUpdate);\n audio.removeEventListener(\"loadedmetadata\", handleLoadedMetadata);\n audio.removeEventListener(\"ended\", handleEnded);\n };\n }, []);\n\n const togglePlay = () => {\n if (audioRef.current) {\n if (isPlaying) {\n audioRef.current.pause();\n } else {\n audioRef.current.play();\n }\n setIsPlaying(!isPlaying);\n }\n };\n\n const handleSeek = (time: number) => {\n if (audioRef.current) {\n audioRef.current.currentTime = time;\n setCurrentTime(time);\n }\n };\n\n const handleVolumeChange = (newVolume: number) => {\n setVolume(newVolume);\n setIsMuted(newVolume === 0);\n if (audioRef.current) {\n audioRef.current.volume = newVolume;\n }\n };\n\n const toggleMute = () => {\n if (audioRef.current) {\n const newMuted = !isMuted;\n setIsMuted(newMuted);\n audioRef.current.muted = newMuted;\n }\n };\n\n const formatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, \"0\")}`;\n };\n\n return (\n <div className=\"flex flex-col h-full bg-bg-canvas\">\n <audio ref={audioRef} src={url} aria-label={file.name}>\n <track kind=\"captions\" />\n </audio>\n\n {/* Album Art Placeholder */}\n <div className=\"flex-1 flex flex-col items-center justify-center p-8\">\n <div className=\"size-64 rounded-lg bg-bg-sunken flex items-center justify-center mb-8 shadow-lg\">\n <Music className=\"size-32 text-text-tertiary\" />\n </div>\n\n {/* Track Info */}\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {file.name}\n </h2>\n <p className=\"text-sm text-text-secondary mb-1\">\n {(file.size / 1024 / 1024).toFixed(2)} MB\n </p>\n <p className=\"text-sm text-text-secondary\">{formatTime(duration)}</p>\n </div>\n\n {/* Controls */}\n <div className=\"bg-bg-surface border-t border-border-default p-6\">\n {/* Progress Bar */}\n <div className=\"mb-4\">\n <input\n type=\"range\"\n min={0}\n max={duration || 0}\n value={currentTime}\n onChange={(e) => handleSeek(Number(e.target.value))}\n aria-label={tr(\"files.preview.audio.seek\", \"Seek\")}\n className=\"w-full h-2 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n <div className=\"flex justify-between mt-2 text-sm text-text-secondary\">\n <span>{formatTime(currentTime)}</span>\n <span>{formatTime(duration)}</span>\n </div>\n </div>\n\n {/* Control Buttons */}\n <div className=\"flex items-center justify-center gap-4\">\n {/* Play/Pause */}\n <button\n type=\"button\"\n onClick={togglePlay}\n className=\"p-4 text-text-inverse bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-full transition-colors duration-200\"\n title={\n isPlaying\n ? tr(\"files.preview.audio.pause\", \"Pause\")\n : tr(\"files.preview.audio.play\", \"Play\")\n }\n >\n {isPlaying ? (\n <Pause className=\"size-6\" />\n ) : (\n <Play className=\"size-6\" />\n )}\n </button>\n\n {/* Volume Controls */}\n <div className=\"flex items-center gap-2 ml-4\">\n <button\n type=\"button\"\n onClick={toggleMute}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n title={\n isMuted\n ? tr(\"files.preview.audio.unmute\", \"Unmute\")\n : tr(\"files.preview.audio.mute\", \"Mute\")\n }\n >\n {isMuted ? (\n <VolumeX className=\"size-5\" />\n ) : (\n <Volume2 className=\"size-5\" />\n )}\n </button>\n <input\n type=\"range\"\n min={0}\n max={1}\n step={0.1}\n value={volume}\n onChange={(e) => handleVolumeChange(Number(e.target.value))}\n aria-label={tr(\"files.preview.audio.volume\", \"Volume\")}\n className=\"w-24 h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AudioPlayer.js","names":["AudioPlayer","tr","useTr","audioRef","useRef","isPlaying","setIsPlaying","useState","currentTime","setCurrentTime","duration","setDuration","volume","setVolume","isMuted","setIsMuted","audio","handleTimeUpdate","handleLoadedMetadata","handleEnded","togglePlay","handleSeek","time","handleVolumeChange","newVolume","toggleMute","newMuted","formatTime","seconds","url","file","Music","jsx","Pause","Play","VolumeX","Volume2"],"sources":["../../../../src/files/components/viewers/AudioPlayer.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { Play, Pause, Volume2, VolumeX, Music } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\n\ninterface AudioPlayerProps {\n file: FileItem;\n url: string;\n}\n\nexport function AudioPlayer({ file, url }: AudioPlayerProps) {\n const tr = useTr();\n const audioRef = useRef<HTMLAudioElement>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [volume, setVolume] = useState(1);\n const [isMuted, setIsMuted] = useState(false);\n\n useEffect(() => {\n const audio = audioRef.current;\n if (!audio) return;\n\n const handleTimeUpdate = () => setCurrentTime(audio.currentTime);\n const handleLoadedMetadata = () => setDuration(audio.duration);\n const handleEnded = () => setIsPlaying(false);\n\n audio.addEventListener(\"timeupdate\", handleTimeUpdate);\n audio.addEventListener(\"loadedmetadata\", handleLoadedMetadata);\n audio.addEventListener(\"ended\", handleEnded);\n\n return () => {\n audio.removeEventListener(\"timeupdate\", handleTimeUpdate);\n audio.removeEventListener(\"loadedmetadata\", handleLoadedMetadata);\n audio.removeEventListener(\"ended\", handleEnded);\n };\n }, []);\n\n const togglePlay = () => {\n if (audioRef.current) {\n if (isPlaying) {\n audioRef.current.pause();\n } else {\n audioRef.current.play();\n }\n setIsPlaying(!isPlaying);\n }\n };\n\n const handleSeek = (time: number) => {\n if (audioRef.current) {\n audioRef.current.currentTime = time;\n setCurrentTime(time);\n }\n };\n\n const handleVolumeChange = (newVolume: number) => {\n setVolume(newVolume);\n setIsMuted(newVolume === 0);\n if (audioRef.current) {\n audioRef.current.volume = newVolume;\n }\n };\n\n const toggleMute = () => {\n if (audioRef.current) {\n const newMuted = !isMuted;\n setIsMuted(newMuted);\n audioRef.current.muted = newMuted;\n }\n };\n\n const formatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, \"0\")}`;\n };\n\n return (\n <div className=\"flex flex-col h-full bg-bg-canvas\">\n <audio ref={audioRef} src={url} aria-label={file.name}>\n <track kind=\"captions\" />\n </audio>\n\n {/* Album Art Placeholder */}\n <div className=\"flex-1 flex flex-col items-center justify-center p-8\">\n <div className=\"size-64 rounded-lg bg-bg-sunken flex items-center justify-center mb-8 shadow-lg\">\n <Music className=\"size-32 text-text-tertiary\" />\n </div>\n\n {/* Track Info */}\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {file.name}\n </h2>\n <p className=\"text-sm text-text-secondary mb-1\">\n {(file.size / 1024 / 1024).toFixed(2)} MB\n </p>\n <p className=\"text-sm text-text-secondary\">{formatTime(duration)}</p>\n </div>\n\n {/* Controls */}\n <div className=\"bg-bg-surface border-t border-border-default p-6\">\n {/* Progress Bar */}\n <div className=\"mb-4\">\n <input\n type=\"range\"\n min={0}\n max={duration || 0}\n value={currentTime}\n onChange={(e) => handleSeek(Number(e.target.value))}\n aria-label={tr(\"files.preview.audio.seek\", \"Seek\")}\n className=\"w-full h-2 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n <div className=\"flex justify-between mt-2 text-sm text-text-secondary\">\n <span>{formatTime(currentTime)}</span>\n <span>{formatTime(duration)}</span>\n </div>\n </div>\n\n {/* Control Buttons */}\n <div className=\"flex items-center justify-center gap-4\">\n {/* Play/Pause */}\n <button\n type=\"button\"\n onClick={togglePlay}\n className=\"p-4 text-text-inverse bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-full transition-colors duration-200\"\n title={\n isPlaying\n ? tr(\"files.preview.audio.pause\", \"Pause\")\n : tr(\"files.preview.audio.play\", \"Play\")\n }\n >\n {isPlaying ? (\n <Pause className=\"size-6\" />\n ) : (\n <Play className=\"size-6\" />\n )}\n </button>\n\n {/* Volume Controls */}\n <div className=\"flex items-center gap-2 ml-4\">\n <button\n type=\"button\"\n onClick={toggleMute}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n title={\n isMuted\n ? tr(\"files.preview.audio.unmute\", \"Unmute\")\n : tr(\"files.preview.audio.mute\", \"Mute\")\n }\n >\n {isMuted ? (\n <VolumeX className=\"size-5\" />\n ) : (\n <Volume2 className=\"size-5\" />\n )}\n </button>\n <input\n type=\"range\"\n min={0}\n max={1}\n step={0.1}\n value={volume}\n onChange={(e) => handleVolumeChange(Number(e.target.value))}\n aria-label={tr(\"files.preview.audio.volume\", \"Volume\")}\n className=\"w-24 h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -11,6 +11,6 @@ interface CodeViewerProps {
11
11
  * For full syntax highlighting, install prism-react-renderer:
12
12
  * bun add prism-react-renderer
13
13
  */
14
- export declare function CodeViewer({ file, url, language }: CodeViewerProps): import("react").JSX.Element;
14
+ export declare function CodeViewer({ file, url, language }: CodeViewerProps): import('react').JSX.Element;
15
15
  export {};
16
16
  //# sourceMappingURL=CodeViewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodeViewer.js","names":[],"sources":["../../../../src/files/components/viewers/CodeViewer.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { Copy, Check, WrapText } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\n\ninterface CodeViewerProps {\n file: FileItem;\n url: string;\n language?: string;\n}\n\n/**\n * CodeViewer - Displays code files with syntax highlighting\n *\n * Note: This is a simplified implementation with basic highlighting.\n * For full syntax highlighting, install prism-react-renderer:\n * bun add prism-react-renderer\n */\nexport function CodeViewer({ file, url, language }: CodeViewerProps) {\n const tr = useTr();\n const [code, setCode] = useState(\"\");\n const [loading, setLoading] = useState(true);\n const [wordWrap, setWordWrap] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const detectedLanguage = language || detectLanguage(file.name);\n\n useEffect(() => {\n fetch(url)\n .then((res) => res.text())\n .then((text) => {\n setCode(text);\n setLoading(false);\n })\n .catch(() => {\n setLoading(false);\n });\n }, [url]);\n\n const handleCopy = () => {\n navigator.clipboard.writeText(code);\n setCopied(true);\n };\n\n // Cleanup timeout on unmount to prevent React state update warning\n useEffect(() => {\n if (copied) {\n const timer = setTimeout(() => setCopied(false), 2000);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [copied]);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full bg-bg-canvas\">\n <span className=\"text-text-secondary\">\n {tr(\"files.preview.code.loading\", \"Loading code...\")}\n </span>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full bg-bg-canvas\">\n {/* Toolbar */}\n <div className=\"flex items-center justify-between p-3 bg-bg-surface border-b border-border-default\">\n <div className=\"flex items-center gap-2\">\n <span className=\"px-2 py-1 text-xs font-medium text-text-link bg-bg-accent rounded\">\n {detectedLanguage || tr(\"files.preview.code.text\", \"Text\")}\n </span>\n <span className=\"text-sm text-text-secondary\">{file.name}</span>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setWordWrap(!wordWrap)}\n className={`p-2 rounded transition-colors duration-200 ${\n wordWrap\n ? \"text-text-link bg-bg-accent\"\n : \"text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover\"\n }`}\n title={\n wordWrap\n ? tr(\"files.preview.code.disableWordWrap\", \"Disable word wrap\")\n : tr(\"files.preview.code.enableWordWrap\", \"Enable word wrap\")\n }\n aria-label={\n wordWrap\n ? tr(\"files.preview.code.disableWordWrap\", \"Disable word wrap\")\n : tr(\"files.preview.code.enableWordWrap\", \"Enable word wrap\")\n }\n >\n <WrapText className=\"size-4\" />\n </button>\n\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium text-text-inverse bg-action-primary-bg transition-colors duration-200 hover:bg-action-primary-bg-hover\"\n title={tr(\"files.preview.code.copyCode\", \"Copy code\")}\n aria-label={tr(\n \"files.preview.code.copyCodeContent\",\n \"Copy code content\",\n )}\n >\n {copied ? (\n <>\n <Check className=\"size-4\" />\n {tr(\"files.preview.code.copied\", \"Copied!\")}\n </>\n ) : (\n <>\n <Copy className=\"size-4\" />\n {tr(\"files.preview.code.copy\", \"Copy\")}\n </>\n )}\n </button>\n </div>\n </div>\n\n {/* Code Content */}\n <div className=\"flex-1 overflow-auto\">\n <pre\n className={`p-4 text-sm font-mono leading-relaxed ${\n wordWrap ? \"whitespace-pre-wrap\" : \"whitespace-pre\"\n }`}\n style={{ tabSize: 2 }}\n >\n {code.split(\"\\n\").map((line, i) => (\n <div key={i} className=\"flex\">\n <span className=\"inline-block w-12 text-right pr-4 text-text-tertiary select-none\">\n {i + 1}\n </span>\n <span className=\"text-text-primary\">{line || \" \"}</span>\n </div>\n ))}\n </pre>\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-between p-3 bg-bg-surface border-t border-border-default text-xs text-text-secondary\">\n <span>\n {code.split(\"\\n\").length} {tr(\"files.preview.code.lines\", \"lines\")}\n </span>\n <span>{(file.size / 1024).toFixed(2)} KB</span>\n </div>\n </div>\n );\n}\n\nfunction detectLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const languageMap: Record<string, string> = {\n js: \"JavaScript\",\n jsx: \"React JSX\",\n ts: \"TypeScript\",\n tsx: \"React TSX\",\n py: \"Python\",\n java: \"Java\",\n cpp: \"C++\",\n c: \"C\",\n cs: \"C#\",\n rb: \"Ruby\",\n go: \"Go\",\n rs: \"Rust\",\n php: \"PHP\",\n html: \"HTML\",\n css: \"CSS\",\n scss: \"SCSS\",\n json: \"JSON\",\n md: \"Markdown\",\n yaml: \"YAML\",\n yml: \"YAML\",\n xml: \"XML\",\n sql: \"SQL\",\n sh: \"Shell\",\n bash: \"Bash\",\n vue: \"Vue\",\n swift: \"Swift\",\n kt: \"Kotlin\",\n r: \"R\",\n dart: \"Dart\",\n lua: \"Lua\",\n };\n return languageMap[ext || \"\"] || \"Text\";\n}\n"],"mappings":";;;;;AAkBA,SAAgB,EAAW,EAAE,SAAM,QAAK,eAA6B;CACnE,IAAM,IAAK,GAAO,EACZ,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAS,KAAc,EAAS,GAAK,EACtC,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,IAAmB,KAAY,EAAe,EAAK,KAAK;AAsC9D,QApCA,QAAgB;AACd,QAAM,EAAI,CACP,MAAM,MAAQ,EAAI,MAAM,CAAC,CACzB,MAAM,MAAS;AAEd,GADA,EAAQ,EAAK,EACb,EAAW,GAAM;IACjB,CACD,YAAY;AACX,KAAW,GAAM;IACjB;IACH,CAAC,EAAI,CAAC,EAQT,QAAgB;AACd,MAAI,GAAQ;GACV,IAAM,IAAQ,iBAAiB,EAAU,GAAM,EAAE,IAAK;AACtD,gBAAa,aAAa,EAAM;;IAGjC,CAAC,EAAO,CAAC,EAER,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,QAAD;GAAM,WAAU;aACb,EAAG,8BAA8B,kBAAkB;GAC/C,CAAA;EACH,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACb,KAAoB,EAAG,2BAA2B,OAAO;MACrD,CAAA,EACP,kBAAC,QAAD;MAAM,WAAU;gBAA+B,EAAK;MAAY,CAAA,CAC5D;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAY,CAAC,EAAS;MACrC,WAAW,8CACT,IACI,gCACA;MAEN,OACE,IACI,EAAG,sCAAsC,oBAAoB,GAC7D,EAAG,qCAAqC,mBAAmB;MAEjE,cACE,IACI,EAAG,sCAAsC,oBAAoB,GAC7D,EAAG,qCAAqC,mBAAmB;gBAGjE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;MACxB,CAAA,EAET,kBAAC,UAAD;MACE,MAAK;MACL,eA5De;AAEvB,OADA,UAAU,UAAU,UAAU,EAAK,EACnC,EAAU,GAAK;;MA2DP,WAAU;MACV,OAAO,EAAG,+BAA+B,YAAY;MACrD,cAAY,EACV,sCACA,oBACD;gBAEA,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAC3B,EAAG,6BAA6B,UAAU,CAC1C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,2BAA2B,OAAO,CACrC,EAAA,CAAA;MAEE,CAAA,CACL;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAW,yCACT,IAAW,wBAAwB;KAErC,OAAO,EAAE,SAAS,GAAG;eAEpB,EAAK,MAAM,KAAK,CAAC,KAAK,GAAM,MAC3B,kBAAC,OAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,IAAI;OACA,CAAA,EACP,kBAAC,QAAD;OAAM,WAAU;iBAAqB,KAAQ;OAAW,CAAA,CACpD;QALI,EAKJ,CACN;KACE,CAAA;IACF,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD,EAAA,UAAA;KACG,EAAK,MAAM,KAAK,CAAC;KAAO;KAAE,EAAG,4BAA4B,QAAQ;KAC7D,EAAA,CAAA,EACP,kBAAC,QAAD,EAAA,UAAA,EAAQ,EAAK,OAAO,MAAM,QAAQ,EAAE,EAAC,MAAU,EAAA,CAAA,CAC3C;;GACF;;;AAIV,SAAS,EAAe,GAA0B;AAkChD,QAhC4C;EAC1C,IAAI;EACJ,KAAK;EACL,IAAI;EACJ,KAAK;EACL,IAAI;EACJ,MAAM;EACN,KAAK;EACL,GAAG;EACH,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,KAAK;EACL,MAAM;EACN,KAAK;EACL,MAAM;EACN,MAAM;EACN,IAAI;EACJ,MAAM;EACN,KAAK;EACL,KAAK;EACL,KAAK;EACL,IAAI;EACJ,MAAM;EACN,KAAK;EACL,OAAO;EACP,IAAI;EACJ,GAAG;EACH,MAAM;EACN,KAAK;EACN,CAhCW,EAAS,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa,IAiC1B,OAAO"}
1
+ {"version":3,"file":"CodeViewer.js","names":["_Fragment","CodeViewer","tr","useTr","code","setCode","useState","loading","setLoading","wordWrap","setWordWrap","copied","setCopied","detectedLanguage","language","detectLanguage","file","useEffect","url","res","text","timer","WrapText","Fragment","Check","Copy","i","line","filename"],"sources":["../../../../src/files/components/viewers/CodeViewer.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { Copy, Check, WrapText } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\n\ninterface CodeViewerProps {\n file: FileItem;\n url: string;\n language?: string;\n}\n\n/**\n * CodeViewer - Displays code files with syntax highlighting\n *\n * Note: This is a simplified implementation with basic highlighting.\n * For full syntax highlighting, install prism-react-renderer:\n * bun add prism-react-renderer\n */\nexport function CodeViewer({ file, url, language }: CodeViewerProps) {\n const tr = useTr();\n const [code, setCode] = useState(\"\");\n const [loading, setLoading] = useState(true);\n const [wordWrap, setWordWrap] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const detectedLanguage = language || detectLanguage(file.name);\n\n useEffect(() => {\n fetch(url)\n .then((res) => res.text())\n .then((text) => {\n setCode(text);\n setLoading(false);\n })\n .catch(() => {\n setLoading(false);\n });\n }, [url]);\n\n const handleCopy = () => {\n navigator.clipboard.writeText(code);\n setCopied(true);\n };\n\n // Cleanup timeout on unmount to prevent React state update warning\n useEffect(() => {\n if (copied) {\n const timer = setTimeout(() => setCopied(false), 2000);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [copied]);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full bg-bg-canvas\">\n <span className=\"text-text-secondary\">\n {tr(\"files.preview.code.loading\", \"Loading code...\")}\n </span>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full bg-bg-canvas\">\n {/* Toolbar */}\n <div className=\"flex items-center justify-between p-3 bg-bg-surface border-b border-border-default\">\n <div className=\"flex items-center gap-2\">\n <span className=\"px-2 py-1 text-xs font-medium text-text-link bg-bg-accent rounded\">\n {detectedLanguage || tr(\"files.preview.code.text\", \"Text\")}\n </span>\n <span className=\"text-sm text-text-secondary\">{file.name}</span>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setWordWrap(!wordWrap)}\n className={`p-2 rounded transition-colors duration-200 ${\n wordWrap\n ? \"text-text-link bg-bg-accent\"\n : \"text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover\"\n }`}\n title={\n wordWrap\n ? tr(\"files.preview.code.disableWordWrap\", \"Disable word wrap\")\n : tr(\"files.preview.code.enableWordWrap\", \"Enable word wrap\")\n }\n aria-label={\n wordWrap\n ? tr(\"files.preview.code.disableWordWrap\", \"Disable word wrap\")\n : tr(\"files.preview.code.enableWordWrap\", \"Enable word wrap\")\n }\n >\n <WrapText className=\"size-4\" />\n </button>\n\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium text-text-inverse bg-action-primary-bg transition-colors duration-200 hover:bg-action-primary-bg-hover\"\n title={tr(\"files.preview.code.copyCode\", \"Copy code\")}\n aria-label={tr(\n \"files.preview.code.copyCodeContent\",\n \"Copy code content\",\n )}\n >\n {copied ? (\n <>\n <Check className=\"size-4\" />\n {tr(\"files.preview.code.copied\", \"Copied!\")}\n </>\n ) : (\n <>\n <Copy className=\"size-4\" />\n {tr(\"files.preview.code.copy\", \"Copy\")}\n </>\n )}\n </button>\n </div>\n </div>\n\n {/* Code Content */}\n <div className=\"flex-1 overflow-auto\">\n <pre\n className={`p-4 text-sm font-mono leading-relaxed ${\n wordWrap ? \"whitespace-pre-wrap\" : \"whitespace-pre\"\n }`}\n style={{ tabSize: 2 }}\n >\n {code.split(\"\\n\").map((line, i) => (\n <div key={i} className=\"flex\">\n <span className=\"inline-block w-12 text-right pr-4 text-text-tertiary select-none\">\n {i + 1}\n </span>\n <span className=\"text-text-primary\">{line || \" \"}</span>\n </div>\n ))}\n </pre>\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-between p-3 bg-bg-surface border-t border-border-default text-xs text-text-secondary\">\n <span>\n {code.split(\"\\n\").length} {tr(\"files.preview.code.lines\", \"lines\")}\n </span>\n <span>{(file.size / 1024).toFixed(2)} KB</span>\n </div>\n </div>\n );\n}\n\nfunction detectLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const languageMap: Record<string, string> = {\n js: \"JavaScript\",\n jsx: \"React JSX\",\n ts: \"TypeScript\",\n tsx: \"React TSX\",\n py: \"Python\",\n java: \"Java\",\n cpp: \"C++\",\n c: \"C\",\n cs: \"C#\",\n rb: \"Ruby\",\n go: \"Go\",\n rs: \"Rust\",\n php: \"PHP\",\n html: \"HTML\",\n css: \"CSS\",\n scss: \"SCSS\",\n json: \"JSON\",\n md: \"Markdown\",\n yaml: \"YAML\",\n yml: \"YAML\",\n xml: \"XML\",\n sql: \"SQL\",\n sh: \"Shell\",\n bash: \"Bash\",\n vue: \"Vue\",\n swift: \"Swift\",\n kt: \"Kotlin\",\n r: \"R\",\n dart: \"Dart\",\n lua: \"Lua\",\n };\n return languageMap[ext || \"\"] || \"Text\";\n}\n"],"mappings":"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"}
@@ -3,6 +3,6 @@ interface ImageViewerProps {
3
3
  file: FileItem;
4
4
  url: string;
5
5
  }
6
- export declare function ImageViewer({ file, url }: ImageViewerProps): import("react").JSX.Element;
6
+ export declare function ImageViewer({ file, url }: ImageViewerProps): import('react').JSX.Element;
7
7
  export {};
8
8
  //# sourceMappingURL=ImageViewer.d.ts.map