@burdenoff/microfe-files 2026.624.1 → 2026.624.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"FileListItem.js","names":[],"sources":["../../../src/files/components/FileListItem.tsx"],"sourcesContent":["import { type FC, useMemo, useState } from \"react\";\nimport {\n Download,\n Share2,\n Trash2,\n Star,\n RotateCcw,\n Edit,\n Copy,\n FolderInput,\n Info,\n Activity,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate, getFileIcon } from \"../utils\";\nimport { useFilesStore } from \"../store/filesStore\";\nimport { ActionMenu } from \"./ActionMenu\";\nimport { getDragOperation } from \"../hooks/useDragDrop\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface FileListItemProps {\n file: FileItem;\n isSelected?: boolean;\n isDragging?: boolean;\n onSelect?: (fileId: string) => void;\n onPreview?: (file: FileItem) => void;\n onShare?: (file: FileItem) => void;\n onDelete?: (file: FileItem) => void;\n onRestore?: (file: FileItem) => void;\n onFavorite?: (file: FileItem) => void;\n onRename?: (file: FileItem) => void;\n onDownload?: (file: FileItem) => void;\n onCopy?: (file: FileItem) => void;\n onMove?: (file: FileItem) => void;\n onInfo?: (file: FileItem) => void;\n onActivity?: (file: FileItem) => void;\n onDragStart?: (file: FileItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onContextMenu?: (file: FileItem, x: number, y: number) => void;\n}\n\ninterface DraggableFileListItemProps extends Omit<\n FileListItemProps,\n \"onDragStart\" | \"onDragEnd\"\n> {\n onDragStart?: (file: FileItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n}\n\nexport const FileListItem: FC<DraggableFileListItemProps> = ({\n file,\n isSelected = false,\n isDragging = false,\n onSelect,\n onPreview,\n onShare,\n onDelete,\n onRestore,\n onFavorite,\n onRename,\n onDownload,\n onCopy,\n onMove,\n onInfo,\n onActivity,\n onDragStart,\n onDragEnd,\n onContextMenu,\n}) => {\n const { t } = useI18n();\n const [menuOpen, setMenuOpen] = useState(false);\n const { toggleFileSelection } = useFilesStore();\n\n const openPreview = () => {\n if (onPreview) {\n onPreview(file);\n }\n };\n\n const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n e.stopPropagation();\n if (onSelect) {\n onSelect(file.id);\n } else {\n toggleFileSelection(file.id);\n }\n };\n\n const handleActionClick = (e: React.MouseEvent, action: () => void) => {\n e.stopPropagation();\n action();\n };\n\n const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {\n e.dataTransfer.effectAllowed = \"copyMove\";\n e.dataTransfer.setData(\"application/x-file-id\", file.id);\n e.dataTransfer.setData(\"application/x-file-type\", \"file\");\n const operation = getDragOperation(e);\n e.dataTransfer.setData(\"application/x-drag-operation\", operation);\n onDragStart?.(file, operation);\n };\n\n const handleDragEnd = () => {\n onDragEnd?.();\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(file, e.clientX, e.clientY);\n };\n\n const FileIconComponent = getFileIcon(file.mimeType);\n\n // Memoize menu items to prevent re-renders\n const menuItems = useMemo(\n () => [\n ...(onShare\n ? [\n {\n label: tWithFallback(t, \"files.card.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => onShare(file),\n },\n ]\n : []),\n ...(onDownload\n ? [\n {\n label: tWithFallback(t, \"files.card.download\", \"Download\"),\n icon: <Download className=\"size-4\" />,\n onClick: () => onDownload(file),\n },\n ]\n : []),\n ...(onCopy\n ? [\n {\n label: tWithFallback(t, \"files.card.makeCopy\", \"Make a copy\"),\n icon: <Copy className=\"size-4\" />,\n onClick: () => onCopy(file),\n },\n ]\n : []),\n ...(onMove\n ? [\n {\n label: tWithFallback(t, \"files.card.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: () => onMove(file),\n },\n ]\n : []),\n ...(onRename\n ? [\n {\n label: tWithFallback(t, \"files.card.rename\", \"Rename\"),\n icon: <Edit className=\"size-4\" />,\n onClick: () => onRename(file),\n divider: true,\n },\n ]\n : []),\n ...(onFavorite\n ? [\n {\n label: file.starred\n ? tWithFallback(\n t,\n \"files.card.removeFromStarred\",\n \"Remove from starred\",\n )\n : tWithFallback(t, \"files.card.addToStarred\", \"Add to starred\"),\n icon: <Star className=\"size-4\" />,\n onClick: () => onFavorite(file),\n },\n ]\n : []),\n ...(onInfo\n ? [\n {\n label: tWithFallback(t, \"files.card.details\", \"Details\"),\n icon: <Info className=\"size-4\" />,\n onClick: () => onInfo(file),\n },\n ]\n : []),\n ...(onActivity\n ? [\n {\n label: tWithFallback(t, \"files.card.activity\", \"Activity\"),\n icon: <Activity className=\"size-4\" />,\n onClick: () => onActivity(file),\n },\n ]\n : []),\n ...(onDelete\n ? [\n {\n label: tWithFallback(t, \"files.card.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: () => onDelete(file),\n variant: \"danger\" as const,\n divider: true,\n },\n ]\n : []),\n ...(onRestore\n ? [\n {\n label: tWithFallback(t, \"files.card.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: () => onRestore(file),\n },\n ]\n : []),\n ],\n [\n file,\n onShare,\n onDownload,\n onCopy,\n onMove,\n onRename,\n onFavorite,\n onInfo,\n onActivity,\n onDelete,\n onRestore,\n t,\n ],\n );\n\n return (\n <div\n className={`@container group flex items-center gap-3 border-b border-border-subtle bg-bg-surface p-3 transition-colors hover:bg-bg-sunken focus-within:bg-bg-sunken ${isDragging ? \"opacity-50\" : \"\"}`}\n onClick={openPreview}\n onContextMenu={handleContextMenu}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n openPreview();\n }\n }}\n draggable={Boolean(onDragStart)}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n role=\"button\"\n tabIndex={0}\n aria-label={`Preview ${file.name}`}\n >\n {/* Selection checkbox */}\n <div className=\"flex-shrink-0\">\n <input\n type=\"checkbox\"\n checked={isSelected}\n onChange={handleCheckboxChange}\n className=\"size-4 rounded border-border-default bg-bg-surface text-action-primary-bg focus:ring-2 focus:ring-action-primary-bg focus:ring-offset-2\"\n aria-label={`Select ${file.name}`}\n />\n </div>\n\n {/* File icon */}\n <div className=\"flex-shrink-0\">\n {file.thumbnailUrl ? (\n <img\n src={file.thumbnailUrl}\n alt={file.name}\n className=\"size-8 object-cover rounded\"\n />\n ) : (\n <FileIconComponent className=\"size-6 text-text-muted\" />\n )}\n </div>\n\n {/* File name - takes remaining space */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-text-md font-weight-medium text-text-primary truncate\">\n {file.name}\n </span>\n {file.starred && (\n <Star className=\"size-3 flex-shrink-0 fill-yellow-400 text-yellow-400\" />\n )}\n {file.isShared && (\n <Share2 className=\"size-3 flex-shrink-0 text-text-muted\" />\n )}\n </div>\n\n {/* Tags - visible on medium+ containers */}\n {file.tags && file.tags.length > 0 && (\n <div className=\"hidden @md:flex flex-wrap gap-1 mt-1\">\n {file.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"px-1.5 py-0.5 bg-bg-accent text-text-primary font-text-xs rounded-sm\"\n >\n {tag}\n </span>\n ))}\n {file.tags.length > 3 && (\n <span className=\"text-text-muted font-text-xs\">\n +{file.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Owner - visible on large+ containers */}\n {file.createdBy && (\n <div className=\"hidden @lg:flex items-center gap-2 flex-shrink-0 w-40\">\n {file.createdBy.avatar && (\n <img\n src={file.createdBy.avatar}\n alt={file.createdBy.name}\n className=\"size-6 rounded-full\"\n />\n )}\n <span className=\"font-text-sm text-text-secondary truncate\">\n {file.createdBy.name}\n </span>\n </div>\n )}\n\n {/* Size - visible on medium+ containers */}\n <div className=\"hidden @md:block flex-shrink-0 w-24 text-right\">\n <span className=\"font-text-sm text-text-secondary\">\n {formatFileSize(file.size)}\n </span>\n </div>\n\n {/* Modified date - visible on large+ containers */}\n <div className=\"hidden @lg:block flex-shrink-0 w-32 text-right\">\n <span className=\"font-text-sm text-text-secondary\">\n {formatFileDate(file.updatedAt)}\n </span>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-1\">\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\">\n {file.canRead && onDownload && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onDownload(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.download\", \"Download\")}\n title={tWithFallback(t, \"files.card.download\", \"Download\")}\n >\n <Download className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onShare && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onShare(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.share\", \"Share\")}\n title={tWithFallback(t, \"files.card.share\", \"Share\")}\n >\n <Share2 className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onFavorite && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onFavorite(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={\n file.starred\n ? tWithFallback(t, \"files.card.unfavorite\", \"Unfavorite\")\n : tWithFallback(t, \"files.card.favorite\", \"Favorite\")\n }\n title={\n file.starred\n ? tWithFallback(t, \"files.card.unfavorite\", \"Unfavorite\")\n : tWithFallback(t, \"files.card.favorite\", \"Favorite\")\n }\n >\n <Star\n className={`size-4 ${\n file.starred\n ? \"fill-status-warning-bg text-status-warning-bg\"\n : \"text-text-primary\"\n }`}\n />\n </button>\n )}\n\n {onRestore && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRestore(file))}\n className=\"p-1.5 hover:bg-status-success-bg-subtle rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n title={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n >\n <RotateCcw className=\"size-4 text-status-success-text\" />\n </button>\n )}\n\n {file.canWrite && onRename && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRename(file))}\n className=\"hidden @md:block p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n title={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n >\n <Edit className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {file.canDelete && onDelete && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onDelete(file))}\n className=\"p-1.5 hover:bg-action-danger-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.delete\", \"Delete\")}\n title={tWithFallback(t, \"files.card.delete\", \"Delete\")}\n >\n <Trash2 className=\"size-4 text-status-error-text\" />\n </button>\n )}\n </div>\n\n {/* More actions dropdown - always visible */}\n <ActionMenu\n items={menuItems}\n align=\"right\"\n isOpen={menuOpen}\n onToggle={setMenuOpen}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"FileListItem.js","names":[],"sources":["../../../src/files/components/FileListItem.tsx"],"sourcesContent":["import { type FC, useMemo, useState } from \"react\";\nimport {\n Download,\n Share2,\n Trash2,\n Star,\n RotateCcw,\n Edit,\n Copy,\n FolderInput,\n Info,\n Activity,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate, getFileIcon } from \"../utils\";\nimport { useFilesStore } from \"../store/filesStore\";\nimport { ActionMenu } from \"./ActionMenu\";\nimport { getDragOperation } from \"../hooks/useDragDrop\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface FileListItemProps {\n file: FileItem;\n isSelected?: boolean;\n isDragging?: boolean;\n onSelect?: (fileId: string) => void;\n onPreview?: (file: FileItem) => void;\n onShare?: (file: FileItem) => void;\n onDelete?: (file: FileItem) => void;\n onRestore?: (file: FileItem) => void;\n onFavorite?: (file: FileItem) => void;\n onRename?: (file: FileItem) => void;\n onDownload?: (file: FileItem) => void;\n onCopy?: (file: FileItem) => void;\n onMove?: (file: FileItem) => void;\n onInfo?: (file: FileItem) => void;\n onActivity?: (file: FileItem) => void;\n onDragStart?: (file: FileItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onContextMenu?: (file: FileItem, x: number, y: number) => void;\n}\n\ninterface DraggableFileListItemProps extends Omit<\n FileListItemProps,\n \"onDragStart\" | \"onDragEnd\"\n> {\n onDragStart?: (file: FileItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n}\n\nexport const FileListItem: FC<DraggableFileListItemProps> = ({\n file,\n isSelected = false,\n isDragging = false,\n onSelect,\n onPreview,\n onShare,\n onDelete,\n onRestore,\n onFavorite,\n onRename,\n onDownload,\n onCopy,\n onMove,\n onInfo,\n onActivity,\n onDragStart,\n onDragEnd,\n onContextMenu,\n}) => {\n const { t } = useI18n();\n const [menuOpen, setMenuOpen] = useState(false);\n const { toggleFileSelection } = useFilesStore();\n\n const openPreview = () => {\n if (onPreview) {\n onPreview(file);\n }\n };\n\n const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n e.stopPropagation();\n if (onSelect) {\n onSelect(file.id);\n } else {\n toggleFileSelection(file.id);\n }\n };\n\n const handleActionClick = (e: React.MouseEvent, action: () => void) => {\n e.stopPropagation();\n action();\n };\n\n const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {\n e.dataTransfer.effectAllowed = \"copyMove\";\n e.dataTransfer.setData(\"application/x-file-id\", file.id);\n e.dataTransfer.setData(\"application/x-file-type\", \"file\");\n const operation = getDragOperation(e);\n e.dataTransfer.setData(\"application/x-drag-operation\", operation);\n onDragStart?.(file, operation);\n };\n\n const handleDragEnd = () => {\n onDragEnd?.();\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(file, e.clientX, e.clientY);\n };\n\n const FileIconComponent = getFileIcon(file.mimeType);\n\n // Memoize menu items to prevent re-renders\n const menuItems = useMemo(\n () => [\n ...(onShare\n ? [\n {\n label: tWithFallback(t, \"files.card.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => onShare(file),\n },\n ]\n : []),\n ...(onDownload\n ? [\n {\n label: tWithFallback(t, \"files.card.download\", \"Download\"),\n icon: <Download className=\"size-4\" />,\n onClick: () => onDownload(file),\n },\n ]\n : []),\n ...(onCopy\n ? [\n {\n label: tWithFallback(t, \"files.card.makeCopy\", \"Make a copy\"),\n icon: <Copy className=\"size-4\" />,\n onClick: () => onCopy(file),\n },\n ]\n : []),\n ...(onMove\n ? [\n {\n label: tWithFallback(t, \"files.card.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: () => onMove(file),\n },\n ]\n : []),\n ...(onRename\n ? [\n {\n label: tWithFallback(t, \"files.card.rename\", \"Rename\"),\n icon: <Edit className=\"size-4\" />,\n onClick: () => onRename(file),\n divider: true,\n },\n ]\n : []),\n ...(onFavorite\n ? [\n {\n label: file.starred\n ? tWithFallback(\n t,\n \"files.card.removeFromStarred\",\n \"Remove from starred\",\n )\n : tWithFallback(t, \"files.card.addToStarred\", \"Add to starred\"),\n icon: <Star className=\"size-4\" />,\n onClick: () => onFavorite(file),\n },\n ]\n : []),\n ...(onInfo\n ? [\n {\n label: tWithFallback(t, \"files.card.details\", \"Details\"),\n icon: <Info className=\"size-4\" />,\n onClick: () => onInfo(file),\n },\n ]\n : []),\n ...(onActivity\n ? [\n {\n label: tWithFallback(t, \"files.card.activity\", \"Activity\"),\n icon: <Activity className=\"size-4\" />,\n onClick: () => onActivity(file),\n },\n ]\n : []),\n ...(onDelete\n ? [\n {\n label: tWithFallback(t, \"files.card.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: () => onDelete(file),\n variant: \"danger\" as const,\n divider: true,\n },\n ]\n : []),\n ...(onRestore\n ? [\n {\n label: tWithFallback(t, \"files.card.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: () => onRestore(file),\n },\n ]\n : []),\n ],\n [\n file,\n onShare,\n onDownload,\n onCopy,\n onMove,\n onRename,\n onFavorite,\n onInfo,\n onActivity,\n onDelete,\n onRestore,\n t,\n ],\n );\n\n return (\n <div\n className={`@container group flex items-center gap-3 border-b border-border-subtle bg-bg-surface p-3 transition-colors hover:bg-bg-sunken focus-within:bg-bg-sunken ${isDragging ? \"opacity-50\" : \"\"}`}\n onClick={openPreview}\n onContextMenu={handleContextMenu}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n openPreview();\n }\n }}\n draggable={Boolean(onDragStart)}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n role=\"button\"\n tabIndex={0}\n aria-label={`Preview ${file.name}`}\n >\n {/* Selection checkbox */}\n <div className=\"flex-shrink-0\">\n <input\n type=\"checkbox\"\n checked={isSelected}\n onChange={handleCheckboxChange}\n className=\"size-4 rounded border-border-default bg-bg-surface text-action-primary-bg focus:ring-2 focus:ring-action-primary-bg focus:ring-offset-2\"\n aria-label={`Select ${file.name}`}\n />\n </div>\n\n {/* File icon */}\n <div className=\"flex-shrink-0\">\n {file.thumbnailUrl ? (\n <img\n src={file.thumbnailUrl}\n alt={file.name}\n className=\"size-8 object-cover rounded\"\n />\n ) : (\n <FileIconComponent className=\"size-6 text-text-muted\" />\n )}\n </div>\n\n {/* File name - takes remaining space */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-text-md font-weight-medium text-text-primary truncate\">\n {file.name}\n </span>\n {file.starred && (\n <Star className=\"size-3 flex-shrink-0 fill-yellow-400 text-yellow-400\" />\n )}\n {file.isShared && (\n <Share2 className=\"size-3 flex-shrink-0 text-text-muted\" />\n )}\n </div>\n\n {/* Tags - visible on medium+ containers */}\n {file.tags && file.tags.length > 0 && (\n <div className=\"hidden @md:flex flex-wrap gap-1 mt-1\">\n {file.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"px-1.5 py-0.5 bg-bg-accent text-text-primary font-text-xs rounded-sm\"\n >\n {tag}\n </span>\n ))}\n {file.tags.length > 3 && (\n <span className=\"text-text-muted font-text-xs\">\n +{file.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Owner - visible on large+ containers */}\n {file.createdBy && (\n <div className=\"hidden @lg:flex items-center gap-2 flex-shrink-0 w-40\">\n {file.createdBy.avatar && (\n <img\n src={file.createdBy.avatar}\n alt={file.createdBy.name}\n className=\"size-6 rounded-full\"\n />\n )}\n <span className=\"font-text-sm text-text-secondary truncate\">\n {file.createdBy.name}\n </span>\n </div>\n )}\n\n {/* Size - visible on medium+ containers */}\n <div className=\"hidden @md:block flex-shrink-0 w-24 text-right\">\n <span className=\"font-text-sm text-text-secondary tabular-nums\">\n {formatFileSize(file.size)}\n </span>\n </div>\n\n {/* Modified date - visible on large+ containers */}\n <div className=\"hidden @lg:block flex-shrink-0 w-32 text-right\">\n <span className=\"font-text-sm text-text-secondary\">\n {formatFileDate(file.updatedAt)}\n </span>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-1\">\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\">\n {file.canRead && onDownload && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onDownload(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.download\", \"Download\")}\n title={tWithFallback(t, \"files.card.download\", \"Download\")}\n >\n <Download className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onShare && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onShare(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.share\", \"Share\")}\n title={tWithFallback(t, \"files.card.share\", \"Share\")}\n >\n <Share2 className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onFavorite && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onFavorite(file))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={\n file.starred\n ? tWithFallback(t, \"files.card.unfavorite\", \"Unfavorite\")\n : tWithFallback(t, \"files.card.favorite\", \"Favorite\")\n }\n title={\n file.starred\n ? tWithFallback(t, \"files.card.unfavorite\", \"Unfavorite\")\n : tWithFallback(t, \"files.card.favorite\", \"Favorite\")\n }\n >\n <Star\n className={`size-4 ${\n file.starred\n ? \"fill-status-warning-bg text-status-warning-bg\"\n : \"text-text-primary\"\n }`}\n />\n </button>\n )}\n\n {onRestore && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRestore(file))}\n className=\"p-1.5 hover:bg-status-success-bg-subtle rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n title={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n >\n <RotateCcw className=\"size-4 text-status-success-text\" />\n </button>\n )}\n\n {file.canWrite && onRename && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRename(file))}\n className=\"hidden @md:block p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n title={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n >\n <Edit className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {file.canDelete && onDelete && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onDelete(file))}\n className=\"p-1.5 hover:bg-action-danger-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.delete\", \"Delete\")}\n title={tWithFallback(t, \"files.card.delete\", \"Delete\")}\n >\n <Trash2 className=\"size-4 text-status-error-text\" />\n </button>\n )}\n </div>\n\n {/* More actions dropdown - always visible */}\n <ActionMenu\n items={menuItems}\n align=\"right\"\n isOpen={menuOpen}\n onToggle={setMenuOpen}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -152,7 +152,10 @@ var C = ({ file: C, onClose: w }) => {
152
152
  /* @__PURE__ */ x("div", {
153
153
  className: "flex items-center gap-4 text-xs text-text-secondary",
154
154
  children: [
155
- /* @__PURE__ */ b("span", { children: n(e.size) }),
155
+ /* @__PURE__ */ b("span", {
156
+ className: "tabular-nums",
157
+ children: n(e.size)
158
+ }),
156
159
  /* @__PURE__ */ b("span", { children: "•" }),
157
160
  /* @__PURE__ */ b("span", { children: t(e.createdAt) })
158
161
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"FileVersionPanel.js","names":[],"sources":["../../../src/files/components/FileVersionPanel.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport {\n Clock,\n Download,\n RotateCcw,\n Trash2,\n X,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Tag,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport {\n useFileVersions,\n useRestoreFileVersion,\n useDeleteFileVersion,\n useVersionDownloadUrl,\n} from \"../hooks/useFileVersions\";\nimport { useToast } from \"../hooks/useToast\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\n\ninterface FileVersionPanelProps {\n file: FileItem;\n onClose: () => void;\n}\n\nexport const FileVersionPanel: FC<FileVersionPanelProps> = ({\n file,\n onClose,\n}) => {\n const { t } = useI18n();\n const [selectedVersionId, setSelectedVersionId] = useState<string | null>(\n null,\n );\n const [confirmAction, setConfirmAction] = useState<{\n title: string;\n description: string;\n confirmLabel: string;\n variant?: \"danger\" | \"default\";\n onConfirm: () => Promise<void>;\n loading?: boolean;\n } | null>(null);\n const toast = useToast();\n\n // Fetch versions\n const { data: versionsData, isLoading, error } = useFileVersions(file.id);\n const versions = versionsData?.versions || [];\n\n // Mutations\n const restoreMutation = useRestoreFileVersion();\n const deleteMutation = useDeleteFileVersion();\n const versionDownload = useVersionDownloadUrl();\n\n const handleRestore = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.restoreVersion\", \"Restore version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.restoreDescription\",\n \"This will create a new version with the restored content.\",\n ),\n confirmLabel: tWithFallback(t, \"files.version.restore\", \"Restore\"),\n onConfirm: async () => {\n try {\n await restoreMutation.mutateAsync({\n versionId,\n createNewVersion: true,\n changeMessage: `${tWithFallback(t, \"files.version.restoredFrom\", \"Restored from version\")} ${versionNumber}`,\n });\n toast.success(\n `${tWithFallback(t, \"files.version.versionRestored\", \"Version {{number}} restored successfully\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.restoreFailed\",\n \"Failed to restore version\",\n ),\n );\n }\n },\n });\n };\n\n const handleDelete = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.deleteVersion\", \"Delete version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.deleteDescription\",\n \"This action cannot be undone.\",\n ),\n confirmLabel: tWithFallback(\n t,\n \"files.version.deleteVersionBtn\",\n \"Delete version\",\n ),\n variant: \"danger\",\n onConfirm: async () => {\n try {\n await deleteMutation.mutateAsync(versionId);\n toast.success(\n `${tWithFallback(t, \"files.version.versionDeleted\", \"Version {{number}} deleted\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.deleteFailed\",\n \"Failed to delete version\",\n ),\n );\n }\n },\n });\n };\n\n const runConfirmAction = async () => {\n if (!confirmAction || confirmAction.loading) return;\n\n const action = confirmAction.onConfirm;\n setConfirmAction({ ...confirmAction, loading: true });\n await action();\n setConfirmAction(null);\n };\n\n const handleDownload = async (versionId: string) => {\n try {\n const downloadUrl = await versionDownload.getDownloadUrl(versionId);\n\n if (!downloadUrl) {\n throw new Error(\n tWithFallback(\n t,\n \"files.version.downloadUnavailable\",\n \"Version download URL is unavailable\",\n ),\n );\n }\n\n window.open(downloadUrl, \"_blank\", \"noopener,noreferrer\");\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tWithFallback(\n t,\n \"files.version.downloadFailed\",\n \"Failed to download version\",\n ),\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 z-[110] bg-bg-overlay flex items-center justify-center p-4 animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.version.historyFor\",\n \"Version history for {{name}}\",\n { name: file.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default\">\n <div className=\"flex items-center gap-3\">\n <Clock className=\"size-6 text-text-link\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(\n t,\n \"files.version.versionHistory\",\n \"Version History\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">{file.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-2 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.version.closeHistory\",\n \"Close version history\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto p-6\">\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 text-text-link animate-spin\" />\n </div>\n )}\n\n {error && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.loadFailed\",\n \"Failed to load version history\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <Clock className=\"size-12 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.noHistory\",\n \"No version history available\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length > 0 && (\n <div className=\"space-y-3\">\n {versions.map((version: (typeof versions)[number]) => (\n <div\n key={version.id}\n className={`p-4 border rounded-lg transition-all duration-200 ${\n version.isCurrentVersion\n ? \"border-action-primary-border bg-bg-accent\"\n : selectedVersionId === version.id\n ? \"border-border-default bg-bg-sunken\"\n : \"border-border-default hover:border-border-default hover:bg-action-ghost-bg-hover\"\n }`}\n onClick={() => setSelectedVersionId(version.id)}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Version info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {tWithFallback(t, \"files.version.version\", \"Version\")}{\" \"}\n {version.versionNumber}\n </span>\n\n {version.isCurrentVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-action-primary-bg text-text-inverse text-xs font-medium rounded\">\n <CheckCircle2 className=\"size-3\" />\n {tWithFallback(\n t,\n \"files.version.current\",\n \"Current\",\n )}\n </span>\n )}\n\n {version.isMajorVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-status-info-bg-subtle text-status-info-text text-xs font-medium rounded\">\n <Tag className=\"size-3\" />\n {tWithFallback(t, \"files.version.major\", \"Major\")}\n </span>\n )}\n\n {version.versionLabel && (\n <span className=\"inline-flex items-center px-2 py-0.5 bg-status-success-bg-subtle text-status-success-text text-xs font-medium rounded\">\n {version.versionLabel}\n </span>\n )}\n </div>\n\n {version.changeMessage && (\n <p className=\"text-sm text-text-primary mb-2\">\n {version.changeMessage}\n </p>\n )}\n\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span>{formatFileSize(version.size)}</span>\n <span>•</span>\n <span>{formatFileDate(version.createdAt)}</span>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDownload(version.id);\n }}\n className=\"p-2 text-text-secondary hover:text-text-link hover:bg-bg-accent rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.version.downloadThisVersion\",\n \"Download this version\",\n )}\n >\n <Download className=\"size-4\" />\n </button>\n\n {!version.isCurrentVersion && (\n <>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleRestore(version.id, version.versionNumber);\n }}\n disabled={restoreMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.restoreThisVersion\",\n \"Restore this version\",\n )}\n >\n {restoreMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-4\" />\n )}\n </button>\n\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(version.id, version.versionNumber);\n }}\n disabled={deleteMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.deleteThisVersion\",\n \"Delete this version\",\n )}\n >\n {deleteMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"p-6 border-t border-border-default bg-bg-sunken\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {versionsData?.total || 0}{\" \"}\n {versionsData?.total !== 1\n ? tWithFallback(t, \"files.version.versions\", \"versions\")\n : tWithFallback(\n t,\n \"files.version.versionSingular\",\n \"version\",\n )}{\" \"}\n {tWithFallback(t, \"files.version.total\", \"total\")}\n </span>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n </button>\n </div>\n </div>\n </div>\n\n {confirmAction && (\n <ConfirmDialog\n title={confirmAction.title}\n description={confirmAction.description}\n confirmLabel={confirmAction.confirmLabel}\n cancelLabel={tWithFallback(\n t,\n \"files.common.actions.cancel\",\n \"Cancel\",\n )}\n variant={confirmAction.variant}\n loading={confirmAction.loading}\n onConfirm={runConfirmAction}\n onCancel={() => {\n if (!confirmAction.loading) {\n setConfirmAction(null);\n }\n }}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"FileVersionPanel.js","names":[],"sources":["../../../src/files/components/FileVersionPanel.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport {\n Clock,\n Download,\n RotateCcw,\n Trash2,\n X,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Tag,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport {\n useFileVersions,\n useRestoreFileVersion,\n useDeleteFileVersion,\n useVersionDownloadUrl,\n} from \"../hooks/useFileVersions\";\nimport { useToast } from \"../hooks/useToast\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\n\ninterface FileVersionPanelProps {\n file: FileItem;\n onClose: () => void;\n}\n\nexport const FileVersionPanel: FC<FileVersionPanelProps> = ({\n file,\n onClose,\n}) => {\n const { t } = useI18n();\n const [selectedVersionId, setSelectedVersionId] = useState<string | null>(\n null,\n );\n const [confirmAction, setConfirmAction] = useState<{\n title: string;\n description: string;\n confirmLabel: string;\n variant?: \"danger\" | \"default\";\n onConfirm: () => Promise<void>;\n loading?: boolean;\n } | null>(null);\n const toast = useToast();\n\n // Fetch versions\n const { data: versionsData, isLoading, error } = useFileVersions(file.id);\n const versions = versionsData?.versions || [];\n\n // Mutations\n const restoreMutation = useRestoreFileVersion();\n const deleteMutation = useDeleteFileVersion();\n const versionDownload = useVersionDownloadUrl();\n\n const handleRestore = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.restoreVersion\", \"Restore version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.restoreDescription\",\n \"This will create a new version with the restored content.\",\n ),\n confirmLabel: tWithFallback(t, \"files.version.restore\", \"Restore\"),\n onConfirm: async () => {\n try {\n await restoreMutation.mutateAsync({\n versionId,\n createNewVersion: true,\n changeMessage: `${tWithFallback(t, \"files.version.restoredFrom\", \"Restored from version\")} ${versionNumber}`,\n });\n toast.success(\n `${tWithFallback(t, \"files.version.versionRestored\", \"Version {{number}} restored successfully\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.restoreFailed\",\n \"Failed to restore version\",\n ),\n );\n }\n },\n });\n };\n\n const handleDelete = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.deleteVersion\", \"Delete version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.deleteDescription\",\n \"This action cannot be undone.\",\n ),\n confirmLabel: tWithFallback(\n t,\n \"files.version.deleteVersionBtn\",\n \"Delete version\",\n ),\n variant: \"danger\",\n onConfirm: async () => {\n try {\n await deleteMutation.mutateAsync(versionId);\n toast.success(\n `${tWithFallback(t, \"files.version.versionDeleted\", \"Version {{number}} deleted\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.deleteFailed\",\n \"Failed to delete version\",\n ),\n );\n }\n },\n });\n };\n\n const runConfirmAction = async () => {\n if (!confirmAction || confirmAction.loading) return;\n\n const action = confirmAction.onConfirm;\n setConfirmAction({ ...confirmAction, loading: true });\n await action();\n setConfirmAction(null);\n };\n\n const handleDownload = async (versionId: string) => {\n try {\n const downloadUrl = await versionDownload.getDownloadUrl(versionId);\n\n if (!downloadUrl) {\n throw new Error(\n tWithFallback(\n t,\n \"files.version.downloadUnavailable\",\n \"Version download URL is unavailable\",\n ),\n );\n }\n\n window.open(downloadUrl, \"_blank\", \"noopener,noreferrer\");\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tWithFallback(\n t,\n \"files.version.downloadFailed\",\n \"Failed to download version\",\n ),\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 z-[110] bg-bg-overlay flex items-center justify-center p-4 animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.version.historyFor\",\n \"Version history for {{name}}\",\n { name: file.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default\">\n <div className=\"flex items-center gap-3\">\n <Clock className=\"size-6 text-text-link\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(\n t,\n \"files.version.versionHistory\",\n \"Version History\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">{file.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-2 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.version.closeHistory\",\n \"Close version history\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto p-6\">\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 text-text-link animate-spin\" />\n </div>\n )}\n\n {error && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.loadFailed\",\n \"Failed to load version history\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <Clock className=\"size-12 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.noHistory\",\n \"No version history available\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length > 0 && (\n <div className=\"space-y-3\">\n {versions.map((version: (typeof versions)[number]) => (\n <div\n key={version.id}\n className={`p-4 border rounded-lg transition-all duration-200 ${\n version.isCurrentVersion\n ? \"border-action-primary-border bg-bg-accent\"\n : selectedVersionId === version.id\n ? \"border-border-default bg-bg-sunken\"\n : \"border-border-default hover:border-border-default hover:bg-action-ghost-bg-hover\"\n }`}\n onClick={() => setSelectedVersionId(version.id)}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Version info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {tWithFallback(t, \"files.version.version\", \"Version\")}{\" \"}\n {version.versionNumber}\n </span>\n\n {version.isCurrentVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-action-primary-bg text-text-inverse text-xs font-medium rounded\">\n <CheckCircle2 className=\"size-3\" />\n {tWithFallback(\n t,\n \"files.version.current\",\n \"Current\",\n )}\n </span>\n )}\n\n {version.isMajorVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-status-info-bg-subtle text-status-info-text text-xs font-medium rounded\">\n <Tag className=\"size-3\" />\n {tWithFallback(t, \"files.version.major\", \"Major\")}\n </span>\n )}\n\n {version.versionLabel && (\n <span className=\"inline-flex items-center px-2 py-0.5 bg-status-success-bg-subtle text-status-success-text text-xs font-medium rounded\">\n {version.versionLabel}\n </span>\n )}\n </div>\n\n {version.changeMessage && (\n <p className=\"text-sm text-text-primary mb-2\">\n {version.changeMessage}\n </p>\n )}\n\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">\n {formatFileSize(version.size)}\n </span>\n <span>•</span>\n <span>{formatFileDate(version.createdAt)}</span>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDownload(version.id);\n }}\n className=\"p-2 text-text-secondary hover:text-text-link hover:bg-bg-accent rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.version.downloadThisVersion\",\n \"Download this version\",\n )}\n >\n <Download className=\"size-4\" />\n </button>\n\n {!version.isCurrentVersion && (\n <>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleRestore(version.id, version.versionNumber);\n }}\n disabled={restoreMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.restoreThisVersion\",\n \"Restore this version\",\n )}\n >\n {restoreMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-4\" />\n )}\n </button>\n\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(version.id, version.versionNumber);\n }}\n disabled={deleteMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.deleteThisVersion\",\n \"Delete this version\",\n )}\n >\n {deleteMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"p-6 border-t border-border-default bg-bg-sunken\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {versionsData?.total || 0}{\" \"}\n {versionsData?.total !== 1\n ? tWithFallback(t, \"files.version.versions\", \"versions\")\n : tWithFallback(\n t,\n \"files.version.versionSingular\",\n \"version\",\n )}{\" \"}\n {tWithFallback(t, \"files.version.total\", \"total\")}\n </span>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n </button>\n </div>\n </div>\n </div>\n\n {confirmAction && (\n <ConfirmDialog\n title={confirmAction.title}\n description={confirmAction.description}\n confirmLabel={confirmAction.confirmLabel}\n cancelLabel={tWithFallback(\n t,\n \"files.common.actions.cancel\",\n \"Cancel\",\n )}\n variant={confirmAction.variant}\n loading={confirmAction.loading}\n onConfirm={runConfirmAction}\n onCancel={() => {\n if (!confirmAction.loading) {\n setConfirmAction(null);\n }\n }}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,121 +1,116 @@
1
1
  import { tWithFallback as e } from "../utils/i18n.js";
2
2
  import { useFoldersQuery as t } from "../hooks/useFoldersQuery.js";
3
3
  import { useState as n } from "react";
4
- import { ChevronRight as r, Folder as i, Home as a, Loader2 as o, X as s } from "lucide-react";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
- import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ import { ChevronRight as r, Folder as i, Home as a, Loader2 as o } from "lucide-react";
5
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { Button as u, ResponsiveDialog as d, ResponsiveDialogContent as f, ResponsiveDialogFooter as p, ResponsiveDialogHeader as m, ResponsiveDialogTitle as h } from "@burdenoff/fe-libs/ui";
7
8
  //#region src/files/components/MoveDialog.tsx
8
- var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
9
- let { t: g } = u(), [_, v] = n(null), [y, b] = n([]), [x, S] = n(void 0), { data: C, loading: w } = t(_ || void 0), T = C?.getFolders?.folders || [], E = (e) => {
10
- v(e.id), b([...y, {
9
+ var g = ({ item: g, type: _, onClose: v, onMove: y, isLoading: b = !1 }) => {
10
+ let { t: x } = l(), [S, C] = n(null), [w, T] = n([]), [E, D] = n(void 0), { data: O, loading: k } = t(S || void 0), A = O?.getFolders?.folders || [], j = (e) => {
11
+ C(e.id), T([...w, {
11
12
  id: e.id,
12
13
  name: e.name
13
- }]), S(e.id);
14
- }, D = (e) => {
15
- if (e === -1) v(null), b([]), S(null);
14
+ }]), D(e.id);
15
+ }, M = (e) => {
16
+ if (e === -1) C(null), T([]), D(null);
16
17
  else {
17
- let t = y[e];
18
- v(t.id), b(y.slice(0, e + 1)), S(t.id);
18
+ let t = w[e];
19
+ C(t.id), T(w.slice(0, e + 1)), D(t.id);
19
20
  }
20
- }, O = async () => {
21
- x === void 0 || x === d.id || (await m(x), p());
22
- }, k = T.filter((e) => !(f === "folder" && e.id === d.id));
23
- return /* @__PURE__ */ c("div", {
24
- className: "fixed inset-0 backdrop-blur-sm flex items-center justify-center z-[100] animate-in fade-in duration-200",
25
- role: "dialog",
26
- "aria-modal": "true",
27
- "aria-label": e(g, "files.moveDialog.ariaLabel", "Move {{type}} {{name}}", {
28
- type: f,
29
- name: d.name
30
- }),
31
- children: /* @__PURE__ */ l("div", {
32
- className: "bg-bg-surface rounded-lg shadow-xl w-full max-w-md mx-4 max-h-[80vh] flex flex-col animate-in zoom-in-95 duration-200",
21
+ }, N = async () => {
22
+ E === void 0 || E === g.id || (await y(E), v());
23
+ }, P = A.filter((e) => !(_ === "folder" && e.id === g.id));
24
+ return /* @__PURE__ */ s(d, {
25
+ open: !0,
26
+ onOpenChange: (e) => {
27
+ !e && !b && v();
28
+ },
29
+ children: /* @__PURE__ */ c(f, {
30
+ className: "flex max-h-[80vh] w-full max-w-md flex-col gap-0 p-0",
31
+ "aria-label": e(x, "files.moveDialog.ariaLabel", "Move {{type}} {{name}}", {
32
+ type: _,
33
+ name: g.name
34
+ }),
33
35
  children: [
34
- /* @__PURE__ */ l("div", {
35
- className: "flex items-center justify-between px-6 py-4 border-b border-border-default",
36
- children: [/* @__PURE__ */ c("h2", {
36
+ /* @__PURE__ */ s(m, {
37
+ className: "px-6 py-4 border-b border-border-default text-left",
38
+ children: /* @__PURE__ */ s(h, {
37
39
  className: "text-lg font-semibold text-text-primary",
38
- children: e(g, "files.moveDialog.title", "Move \"{{name}}\"", { name: d.name })
39
- }), /* @__PURE__ */ c("button", {
40
- type: "button",
41
- onClick: p,
42
- className: "p-1 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200",
43
- "aria-label": e(g, "files.moveDialog.actions.close", "Close move dialog"),
44
- children: /* @__PURE__ */ c(s, { className: "size-5" })
45
- })]
40
+ children: e(x, "files.moveDialog.title", "Move \"{{name}}\"", { name: g.name })
41
+ })
46
42
  }),
47
- /* @__PURE__ */ c("div", {
43
+ /* @__PURE__ */ s("div", {
48
44
  className: "px-6 py-3 border-b border-border-default bg-bg-sunken",
49
- children: /* @__PURE__ */ l("div", {
45
+ children: /* @__PURE__ */ c("div", {
50
46
  className: "flex items-center gap-2 text-sm overflow-x-auto",
51
- children: [/* @__PURE__ */ l("button", {
47
+ children: [/* @__PURE__ */ c("button", {
52
48
  type: "button",
53
- onClick: () => D(-1),
49
+ onClick: () => M(-1),
54
50
  className: "flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap",
55
- children: [/* @__PURE__ */ c(a, { className: "size-4" }), /* @__PURE__ */ c("span", { children: e(g, "files.browser.rootLabel", "My Drive") })]
56
- }), y.map((e, t) => /* @__PURE__ */ l("div", {
51
+ children: [/* @__PURE__ */ s(a, { className: "size-4" }), /* @__PURE__ */ s("span", { children: e(x, "files.browser.rootLabel", "My Drive") })]
52
+ }), w.map((e, t) => /* @__PURE__ */ c("div", {
57
53
  className: "flex items-center gap-2 whitespace-nowrap",
58
- children: [/* @__PURE__ */ c(r, { className: "size-4 text-text-tertiary" }), /* @__PURE__ */ c("button", {
54
+ children: [/* @__PURE__ */ s(r, { className: "size-4 text-text-tertiary" }), /* @__PURE__ */ s("button", {
59
55
  type: "button",
60
- onClick: () => D(t),
61
- className: `hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${t === y.length - 1 ? "text-text-primary font-medium" : "text-text-secondary"}`,
56
+ onClick: () => M(t),
57
+ className: `hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${t === w.length - 1 ? "text-text-primary font-medium" : "text-text-secondary"}`,
62
58
  children: e.name
63
59
  })]
64
60
  }, e.id))]
65
61
  })
66
62
  }),
67
- /* @__PURE__ */ c("div", {
63
+ /* @__PURE__ */ s("div", {
68
64
  className: "flex-1 overflow-y-auto px-6 py-4",
69
- children: w ? /* @__PURE__ */ c("div", {
65
+ children: k ? /* @__PURE__ */ s("div", {
70
66
  className: "flex items-center justify-center py-8",
71
- children: /* @__PURE__ */ c(o, { className: "size-6 text-text-tertiary animate-spin" })
72
- }) : /* @__PURE__ */ l("div", {
67
+ children: /* @__PURE__ */ s(o, { className: "size-6 text-text-tertiary animate-spin" })
68
+ }) : /* @__PURE__ */ c("div", {
73
69
  className: "space-y-1",
74
- children: [/* @__PURE__ */ l("button", {
70
+ children: [/* @__PURE__ */ c("button", {
75
71
  type: "button",
76
- onClick: () => S(_),
77
- className: `w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${x === _ ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
78
- children: [/* @__PURE__ */ c(i, { className: "size-5 text-text-link" }), /* @__PURE__ */ c("span", {
72
+ onClick: () => D(S),
73
+ className: `w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${E === S ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
74
+ children: [/* @__PURE__ */ s(i, { className: "size-5 text-text-link" }), /* @__PURE__ */ s("span", {
79
75
  className: "font-medium text-text-primary",
80
- children: e(g, "files.moveDialog.actions.moveHere", "Move here")
76
+ children: e(x, "files.moveDialog.actions.moveHere", "Move here")
81
77
  })]
82
- }), k.length === 0 ? /* @__PURE__ */ c("p", {
78
+ }), P.length === 0 ? /* @__PURE__ */ s("p", {
83
79
  className: "text-sm text-text-secondary text-center py-4",
84
- children: e(g, "files.moveDialog.empty.noSubfolders", "No subfolders")
85
- }) : k.map((t) => /* @__PURE__ */ l("div", {
86
- className: `flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${x === t.id ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
87
- children: [/* @__PURE__ */ l("button", {
80
+ children: e(x, "files.moveDialog.empty.noSubfolders", "No subfolders")
81
+ }) : P.map((t) => /* @__PURE__ */ c("div", {
82
+ className: `flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${E === t.id ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
83
+ children: [/* @__PURE__ */ c("button", {
88
84
  type: "button",
89
- onClick: () => S(t.id),
85
+ onClick: () => D(t.id),
90
86
  className: "flex items-center gap-3 flex-1 min-w-0",
91
- children: [/* @__PURE__ */ c(i, { className: "size-5 text-text-link flex-shrink-0" }), /* @__PURE__ */ c("span", {
87
+ children: [/* @__PURE__ */ s(i, { className: "size-5 text-text-link flex-shrink-0" }), /* @__PURE__ */ s("span", {
92
88
  className: "font-medium text-text-primary truncate",
93
89
  children: t.name
94
90
  })]
95
- }), /* @__PURE__ */ c("button", {
91
+ }), /* @__PURE__ */ s("button", {
96
92
  type: "button",
97
- onClick: () => E(t),
93
+ onClick: () => j(t),
98
94
  className: "p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0",
99
- title: e(g, "files.moveDialog.actions.openFolder", "Open folder"),
100
- "aria-label": e(g, "files.moveDialog.actions.openFolderNamed", "Open {{name}}", { name: t.name }),
101
- children: /* @__PURE__ */ c(r, { className: "size-4" })
95
+ title: e(x, "files.moveDialog.actions.openFolder", "Open folder"),
96
+ "aria-label": e(x, "files.moveDialog.actions.openFolderNamed", "Open {{name}}", { name: t.name }),
97
+ children: /* @__PURE__ */ s(r, { className: "size-4" })
102
98
  })]
103
99
  }, t.id))]
104
100
  })
105
101
  }),
106
- /* @__PURE__ */ l("div", {
107
- className: "flex items-center justify-end gap-2 px-6 py-4 border-t border-border-default",
108
- children: [/* @__PURE__ */ c("button", {
102
+ /* @__PURE__ */ c(p, {
103
+ className: "px-6 py-4 border-t border-border-default",
104
+ children: [/* @__PURE__ */ s(u, {
109
105
  type: "button",
110
- onClick: p,
111
- className: "px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
112
- children: e(g, "files.common.actions.cancel", "Cancel")
113
- }), /* @__PURE__ */ l("button", {
106
+ variant: "outline",
107
+ onClick: v,
108
+ children: e(x, "files.common.actions.cancel", "Cancel")
109
+ }), /* @__PURE__ */ c(u, {
114
110
  type: "button",
115
- onClick: O,
116
- disabled: h || x === void 0,
117
- className: "px-4 py-2 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-sunken disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2",
118
- children: [h && /* @__PURE__ */ c(o, { className: "size-4 animate-spin" }), h ? e(g, "files.moveDialog.actions.moving", "Moving...") : e(g, "files.common.actions.move", "Move")]
111
+ onClick: N,
112
+ disabled: b || E === void 0,
113
+ children: [b && /* @__PURE__ */ s(o, { className: "size-4 animate-spin" }), b ? e(x, "files.moveDialog.actions.moving", "Moving...") : e(x, "files.common.actions.move", "Move")]
119
114
  })]
120
115
  })
121
116
  ]
@@ -123,6 +118,6 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
123
118
  });
124
119
  };
125
120
  //#endregion
126
- export { d as MoveDialog };
121
+ export { g as MoveDialog };
127
122
 
128
123
  //# sourceMappingURL=MoveDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MoveDialog.js","names":[],"sources":["../../../src/files/components/MoveDialog.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport { Folder, Home, ChevronRight, Loader2, X } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { useFoldersQuery } from \"../hooks/useFoldersQuery\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface MoveDialogProps {\n item: FileItem | FolderItem;\n type: \"file\" | \"folder\";\n onClose: () => void;\n onMove: (destinationFolderId: string | null) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const MoveDialog: FC<MoveDialogProps> = ({\n item,\n type,\n onClose,\n onMove,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const [currentFolderId, setCurrentFolderId] = useState<string | null>(null);\n const [breadcrumbs, setBreadcrumbs] = useState<\n Array<{ id: string; name: string }>\n >([]);\n // undefined = nothing selected yet; null = root selected; string = folder id selected\n const [selectedDestination, setSelectedDestination] = useState<\n string | null | undefined\n >(undefined);\n\n // Fetch folders in current location\n const { data, loading: loadingFolders } = useFoldersQuery(\n currentFolderId || undefined,\n );\n const folders = (data?.getFolders?.folders || []) as FolderItem[];\n\n const handleFolderNavigate = (folder: FolderItem) => {\n setCurrentFolderId(folder.id);\n setBreadcrumbs([...breadcrumbs, { id: folder.id, name: folder.name }]);\n setSelectedDestination(folder.id);\n };\n\n const handleBreadcrumbClick = (index: number) => {\n if (index === -1) {\n setCurrentFolderId(null);\n setBreadcrumbs([]);\n setSelectedDestination(null);\n } else {\n const crumb = breadcrumbs[index];\n setCurrentFolderId(crumb.id);\n setBreadcrumbs(breadcrumbs.slice(0, index + 1));\n setSelectedDestination(crumb.id);\n }\n };\n\n const handleMove = async () => {\n if (selectedDestination === undefined || selectedDestination === item.id) {\n return;\n }\n await onMove(selectedDestination);\n onClose();\n };\n\n const filteredFolders = folders.filter((folder) => {\n // Don't show the item itself if it's a folder\n if (type === \"folder\" && folder.id === item.id) {\n return false;\n }\n return true;\n });\n\n return (\n <div\n className=\"fixed inset-0 backdrop-blur-sm flex items-center justify-center z-[100] animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.ariaLabel\",\n \"Move {{type}} {{name}}\",\n { type, name: item.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl w-full max-w-md mx-4 max-h-[80vh] flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(t, \"files.moveDialog.title\", 'Move \"{{name}}\"', {\n name: item.name,\n })}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.close\",\n \"Close move dialog\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Breadcrumb navigation */}\n <div className=\"px-6 py-3 border-b border-border-default bg-bg-sunken\">\n <div className=\"flex items-center gap-2 text-sm overflow-x-auto\">\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(-1)}\n className=\"flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap\"\n >\n <Home className=\"size-4\" />\n <span>\n {tWithFallback(t, \"files.browser.rootLabel\", \"My Drive\")}\n </span>\n </button>\n {breadcrumbs.map((crumb, index) => (\n <div\n key={crumb.id}\n className=\"flex items-center gap-2 whitespace-nowrap\"\n >\n <ChevronRight className=\"size-4 text-text-tertiary\" />\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(index)}\n className={`hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${\n index === breadcrumbs.length - 1\n ? \"text-text-primary font-medium\"\n : \"text-text-secondary\"\n }`}\n >\n {crumb.name}\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Folder list */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">\n {loadingFolders ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 text-text-tertiary animate-spin\" />\n </div>\n ) : (\n <div className=\"space-y-1\">\n {/* Option to move to current folder */}\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(currentFolderId)}\n className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${\n selectedDestination === currentFolderId\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <Folder className=\"size-5 text-text-link\" />\n <span className=\"font-medium text-text-primary\">\n {tWithFallback(\n t,\n \"files.moveDialog.actions.moveHere\",\n \"Move here\",\n )}\n </span>\n </button>\n\n {filteredFolders.length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-4\">\n {tWithFallback(\n t,\n \"files.moveDialog.empty.noSubfolders\",\n \"No subfolders\",\n )}\n </p>\n ) : (\n /* List of subfolders — clicking name selects, ChevronRight navigates in */\n filteredFolders.map((folder) => (\n <div\n key={folder.id}\n className={`flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${\n selectedDestination === folder.id\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(folder.id)}\n className=\"flex items-center gap-3 flex-1 min-w-0\"\n >\n <Folder className=\"size-5 text-text-link flex-shrink-0\" />\n <span className=\"font-medium text-text-primary truncate\">\n {folder.name}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={() => handleFolderNavigate(folder)}\n className=\"p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0\"\n title={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolder\",\n \"Open folder\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolderNamed\",\n \"Open {{name}}\",\n { name: folder.name },\n )}\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n ))\n )}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-2 px-6 py-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleMove}\n disabled={isLoading || selectedDestination === undefined}\n className=\"px-4 py-2 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-sunken disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2\"\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {isLoading\n ? tWithFallback(t, \"files.moveDialog.actions.moving\", \"Moving...\")\n : tWithFallback(t, \"files.common.actions.move\", \"Move\")}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"MoveDialog.js","names":[],"sources":["../../../src/files/components/MoveDialog.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport { Folder, Home, ChevronRight, Loader2 } 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 Button,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { useFoldersQuery } from \"../hooks/useFoldersQuery\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface MoveDialogProps {\n item: FileItem | FolderItem;\n type: \"file\" | \"folder\";\n onClose: () => void;\n onMove: (destinationFolderId: string | null) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const MoveDialog: FC<MoveDialogProps> = ({\n item,\n type,\n onClose,\n onMove,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const [currentFolderId, setCurrentFolderId] = useState<string | null>(null);\n const [breadcrumbs, setBreadcrumbs] = useState<\n Array<{ id: string; name: string }>\n >([]);\n // undefined = nothing selected yet; null = root selected; string = folder id selected\n const [selectedDestination, setSelectedDestination] = useState<\n string | null | undefined\n >(undefined);\n\n // Fetch folders in current location\n const { data, loading: loadingFolders } = useFoldersQuery(\n currentFolderId || undefined,\n );\n const folders = (data?.getFolders?.folders || []) as FolderItem[];\n\n const handleFolderNavigate = (folder: FolderItem) => {\n setCurrentFolderId(folder.id);\n setBreadcrumbs([...breadcrumbs, { id: folder.id, name: folder.name }]);\n setSelectedDestination(folder.id);\n };\n\n const handleBreadcrumbClick = (index: number) => {\n if (index === -1) {\n setCurrentFolderId(null);\n setBreadcrumbs([]);\n setSelectedDestination(null);\n } else {\n const crumb = breadcrumbs[index];\n setCurrentFolderId(crumb.id);\n setBreadcrumbs(breadcrumbs.slice(0, index + 1));\n setSelectedDestination(crumb.id);\n }\n };\n\n const handleMove = async () => {\n if (selectedDestination === undefined || selectedDestination === item.id) {\n return;\n }\n await onMove(selectedDestination);\n onClose();\n };\n\n const filteredFolders = folders.filter((folder) => {\n // Don't show the item itself if it's a folder\n if (type === \"folder\" && folder.id === item.id) {\n return false;\n }\n return true;\n });\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(next: boolean) => {\n if (!next && !isLoading) {\n onClose();\n }\n }}\n >\n <ResponsiveDialogContent\n className=\"flex max-h-[80vh] w-full max-w-md flex-col gap-0 p-0\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.ariaLabel\",\n \"Move {{type}} {{name}}\",\n { type, name: item.name },\n )}\n >\n {/* Header */}\n <ResponsiveDialogHeader className=\"px-6 py-4 border-b border-border-default text-left\">\n <ResponsiveDialogTitle className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(t, \"files.moveDialog.title\", 'Move \"{{name}}\"', {\n name: item.name,\n })}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n {/* Breadcrumb navigation */}\n <div className=\"px-6 py-3 border-b border-border-default bg-bg-sunken\">\n <div className=\"flex items-center gap-2 text-sm overflow-x-auto\">\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(-1)}\n className=\"flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap\"\n >\n <Home className=\"size-4\" />\n <span>\n {tWithFallback(t, \"files.browser.rootLabel\", \"My Drive\")}\n </span>\n </button>\n {breadcrumbs.map((crumb, index) => (\n <div\n key={crumb.id}\n className=\"flex items-center gap-2 whitespace-nowrap\"\n >\n <ChevronRight className=\"size-4 text-text-tertiary\" />\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(index)}\n className={`hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${\n index === breadcrumbs.length - 1\n ? \"text-text-primary font-medium\"\n : \"text-text-secondary\"\n }`}\n >\n {crumb.name}\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Folder list */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">\n {loadingFolders ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 text-text-tertiary animate-spin\" />\n </div>\n ) : (\n <div className=\"space-y-1\">\n {/* Option to move to current folder */}\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(currentFolderId)}\n className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${\n selectedDestination === currentFolderId\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <Folder className=\"size-5 text-text-link\" />\n <span className=\"font-medium text-text-primary\">\n {tWithFallback(\n t,\n \"files.moveDialog.actions.moveHere\",\n \"Move here\",\n )}\n </span>\n </button>\n\n {filteredFolders.length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-4\">\n {tWithFallback(\n t,\n \"files.moveDialog.empty.noSubfolders\",\n \"No subfolders\",\n )}\n </p>\n ) : (\n /* List of subfolders — clicking name selects, ChevronRight navigates in */\n filteredFolders.map((folder) => (\n <div\n key={folder.id}\n className={`flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${\n selectedDestination === folder.id\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(folder.id)}\n className=\"flex items-center gap-3 flex-1 min-w-0\"\n >\n <Folder className=\"size-5 text-text-link flex-shrink-0\" />\n <span className=\"font-medium text-text-primary truncate\">\n {folder.name}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={() => handleFolderNavigate(folder)}\n className=\"p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0\"\n title={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolder\",\n \"Open folder\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolderNamed\",\n \"Open {{name}}\",\n { name: folder.name },\n )}\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n ))\n )}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <ResponsiveDialogFooter className=\"px-6 py-4 border-t border-border-default\">\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={handleMove}\n disabled={isLoading || selectedDestination === undefined}\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {isLoading\n ? tWithFallback(t, \"files.moveDialog.actions.moving\", \"Moving...\")\n : tWithFallback(t, \"files.common.actions.move\", \"Move\")}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n};\n"],"mappings":"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"}
@@ -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 type { StorageQuotaState } from \"../hooks/useStorageQuota\";\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 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\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\">Storage full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used — no space left.`\n : `${usedLabel} used — no space left.`}{\" \"}\n Delete files to free up space or upgrade your plan.\n </span>\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\">Storage almost full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used (${Math.round(quota.usedPercent)}%).`\n : `${usedLabel} used.`}{\" \"}\n Consider deleting files or upgrading your plan.\n </span>\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>\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":";;;AAIA,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;AAClE,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;AAsBtE,QApBI,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;eAAc;KAAoB,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,0BAC9B,GAAG,EAAU;MAAyB;MAAI;MAEzC;OACH;;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;eAAc;KAA2B,CAAA,EACzD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,SAAS,KAAK,MAAM,EAAM,YAAY,CAAC,OACrE,GAAG,EAAU;MAAS;MAAI;MAEzB;OACH;;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":[],"sources":["../../../src/files/components/StorageBanner.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AlertCircle, AlertTriangle, HardDrive } from \"lucide-react\";\nimport type { StorageQuotaState } from \"../hooks/useStorageQuota\";\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 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\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\">Storage full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used — no space left.`\n : `${usedLabel} used — no space left.`}{\" \"}\n Delete files to free up space or upgrade your plan.\n </span>\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\">Storage almost full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used (${Math.round(quota.usedPercent)}%).`\n : `${usedLabel} used.`}{\" \"}\n Consider deleting files or upgrading your plan.\n </span>\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":";;;AAIA,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;AAClE,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;AAsBtE,QApBI,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;eAAc;KAAoB,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,0BAC9B,GAAG,EAAU;MAAyB;MAAI;MAEzC;OACH;;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;eAAc;KAA2B,CAAA,EACzD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,SAAS,KAAK,MAAM,EAAM,YAAY,CAAC,OACrE,GAAG,EAAU;MAAS;MAAI;MAEzB;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,EAAM,YAAY,IAAI;KACzC,CAAA;IACE,CAAA;GACF"}
@@ -24,7 +24,7 @@ function l({ toast: l, onClose: u }) {
24
24
  error: "bg-status-error-bg-subtle border-status-error-border",
25
25
  warning: "bg-status-warning-bg-subtle border-status-warning-border",
26
26
  info: "bg-status-info-bg-subtle border-status-info-border"
27
- }[l.type]} w-80 max-w-[90vw] md:max-w-md animate-in slide-in-from-right duration-300`,
27
+ }[l.type]} w-80 max-w-[90vw] md:max-w-md animate-in slide-in-from-right duration-200`,
28
28
  children: [
29
29
  f[l.type],
30
30
  /* @__PURE__ */ s("p", {
@@ -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-300`}\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":[],"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"}
@@ -69,7 +69,7 @@ function d({ file: d, url: f }) {
69
69
  onChange: (e) => b(e.target.value),
70
70
  placeholder: "Search...",
71
71
  "aria-label": "Search text",
72
- className: "w-40 bg-transparent text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none"
72
+ className: "w-40 bg-transparent text-sm text-text-primary placeholder:text-text-tertiary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
73
73
  }),
74
74
  D > 0 && /* @__PURE__ */ l("span", {
75
75
  className: "text-xs text-text-secondary",