DragDropUpload.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import React, { useCallback, useState } from 'react';
  2. import { Upload as UploadIcon, FileText, Image as ImageIcon, Folder } from 'lucide-react';
  3. import { useLanguage } from '../contexts/LanguageContext';
  4. interface DragDropUploadProps {
  5. onFilesSelected: (files: FileList) => void;
  6. isAdmin: boolean;
  7. globalMode?: boolean; // グローバルモードかどうかを制御するための追加属性
  8. }
  9. export const DragDropUpload: React.FC<DragDropUploadProps> = ({ onFilesSelected, isAdmin, globalMode = false }) => {
  10. const { t } = useLanguage();
  11. const [isDragging, setIsDragging] = useState(false);
  12. const handleDragEnter = useCallback((e: React.DragEvent) => {
  13. e.preventDefault();
  14. e.stopPropagation();
  15. if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
  16. setIsDragging(true);
  17. }
  18. }, []);
  19. const handleDragLeave = useCallback((e: React.DragEvent) => {
  20. e.preventDefault();
  21. e.stopPropagation();
  22. // マウスが実際にドラッグ領域を離れた場合のみfalseに設定
  23. setTimeout(() => setIsDragging(false), 100);
  24. }, []);
  25. const handleDragOver = useCallback((e: React.DragEvent) => {
  26. e.preventDefault();
  27. e.stopPropagation();
  28. e.dataTransfer.dropEffect = 'copy';
  29. }, []);
  30. const handleDrop = useCallback((e: React.DragEvent) => {
  31. e.preventDefault();
  32. e.stopPropagation();
  33. setIsDragging(false);
  34. if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
  35. onFilesSelected(e.dataTransfer.files);
  36. e.dataTransfer.clearData();
  37. }
  38. }, [onFilesSelected]);
  39. const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
  40. if (e.target.files && e.target.files.length > 0) {
  41. onFilesSelected(e.target.files);
  42. // Reset the input so the same file can be selected again
  43. e.target.value = '';
  44. }
  45. };
  46. if (!isAdmin) {
  47. return null;
  48. }
  49. // モードに応じてCSSクラスを決定
  50. const containerClass = globalMode
  51. ? `fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 transition-opacity duration-300 ${isDragging ? 'opacity-100' : 'opacity-0 pointer-events-none'}`
  52. : `border-2 border-dashed rounded-xl p-8 text-center transition-all duration-200 cursor-pointer ${isDragging
  53. ? 'border-blue-500 bg-blue-50 scale-[1.02] shadow-lg'
  54. : 'border-slate-300 bg-slate-50 hover:border-blue-400 hover:bg-blue-25'
  55. }`;
  56. const contentClass = globalMode
  57. ? "w-3/4 max-w-2xl"
  58. : "";
  59. return (
  60. <div className={containerClass}>
  61. <div className={contentClass}>
  62. <div
  63. className={`border-2 border-dashed rounded-xl p-8 text-center transition-all duration-200 cursor-pointer ${isDragging
  64. ? 'border-blue-500 bg-blue-50 scale-[1.02] shadow-lg'
  65. : 'border-slate-300 bg-slate-50 hover:border-blue-400 hover:bg-blue-25'
  66. }`}
  67. onDragEnter={handleDragEnter}
  68. onDragOver={handleDragOver}
  69. onDragLeave={handleDragLeave}
  70. onDrop={handleDrop}
  71. onClick={() => document.getElementById('file-upload-input')?.click()}
  72. >
  73. <div className="flex flex-col items-center justify-center gap-6">
  74. <div className="p-4 bg-blue-100 rounded-full">
  75. <UploadIcon className="w-10 h-10 text-blue-600" />
  76. </div>
  77. <div className="space-y-2">
  78. <h3 className="text-lg font-semibold text-slate-700">
  79. {t('dragDropUploadTitle')}
  80. </h3>
  81. <p className="text-sm text-slate-500">
  82. {t('dragDropUploadDesc')}
  83. </p>
  84. </div>
  85. <div className="flex items-center gap-6 mt-2">
  86. <div className="flex items-center gap-2 text-xs text-slate-500 bg-white px-3 py-1.5 rounded-full border border-slate-200">
  87. <FileText className="w-4 h-4" />
  88. <span>{t('supportedFormats')}</span>
  89. </div>
  90. <div className="flex items-center gap-2 text-xs text-slate-500 bg-white px-3 py-1.5 rounded-full border border-slate-200">
  91. <ImageIcon className="w-4 h-4" />
  92. <span>PDF, DOC, XLS, PPT, TXT, Images...</span>
  93. </div>
  94. </div>
  95. <div className="pt-2">
  96. <button
  97. type="button"
  98. className="flex items-center gap-2 px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium shadow-sm"
  99. >
  100. <Folder className="w-4 h-4" />
  101. {t('browseFiles')}
  102. </button>
  103. <input
  104. type="file"
  105. multiple
  106. onChange={handleFileInput}
  107. className="hidden"
  108. id="file-upload-input"
  109. accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md,.html,.csv,.rtf,.odt,.ods,.odp,.json,.js,.jsx,.ts,.tsx,.css,.xml,image/*"
  110. />
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. </div>
  116. );
  117. };