Spaces:
Running
Running
| class UploadZone extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.dragCounter = 0; | |
| } | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| width: 100%; | |
| } | |
| .upload-container { | |
| border: 2px dashed #cbd5e1; | |
| border-radius: 20px; | |
| padding: 48px 32px; | |
| text-align: center; | |
| background: white; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| cursor: pointer; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .upload-container:hover { | |
| border-color: #3b82f6; | |
| background: #f8fafc; | |
| transform: translateY(-2px); | |
| box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); | |
| } | |
| .upload-container.drag-over { | |
| border-color: #2563eb; | |
| background: #eff6ff; | |
| transform: scale(1.02); | |
| box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); | |
| } | |
| .upload-icon { | |
| width: 64px; | |
| height: 64px; | |
| background: #dbeafe; | |
| border-radius: 16px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 0 auto 20px; | |
| color: #2563eb; | |
| transition: transform 0.3s; | |
| } | |
| .upload-container:hover .upload-icon { | |
| transform: scale(1.1) rotate(-5deg); | |
| } | |
| .upload-title { | |
| font-size: 1.125rem; | |
| font-weight: 600; | |
| color: #1e293b; | |
| margin-bottom: 8px; | |
| font-family: 'Sarabun', sans-serif; | |
| } | |
| .upload-text { | |
| font-size: 0.875rem; | |
| color: #64748b; | |
| margin-bottom: 24px; | |
| font-family: 'Sarabun', sans-serif; | |
| } | |
| .upload-formats { | |
| display: flex; | |
| justify-content: center; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .format-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 12px; | |
| background: #f1f5f9; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| color: #475569; | |
| font-weight: 500; | |
| border: 1px solid #e2e8f0; | |
| } | |
| .format-badge.pdf { | |
| background: #fee2e2; | |
| color: #991b1b; | |
| border-color: #fecaca; | |
| } | |
| .format-badge.image { | |
| background: #dbeafe; | |
| color: #1e40af; | |
| border-color: #bfdbfe; | |
| } | |
| input[type="file"] { | |
| display: none; | |
| } | |
| .pulse-ring { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 100%; | |
| height: 100%; | |
| border-radius: 20px; | |
| border: 2px solid #3b82f6; | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| .upload-container.drag-over .pulse-ring { | |
| animation: pulse-ring 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| @keyframes pulse-ring { | |
| 0% { | |
| transform: translate(-50%, -50%) scale(0.95); | |
| opacity: 0.5; | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.05); | |
| opacity: 0; | |
| } | |
| 100% { | |
| transform: translate(-50%, -50%) scale(0.95); | |
| opacity: 0; | |
| } | |
| } | |
| </style> | |
| <div class="upload-container" id="upload-zone"> | |
| <div class="pulse-ring"></div> | |
| <div class="upload-icon"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> | |
| <polyline points="17 8 12 3 7 8"></polyline> | |
| <line x1="12" y1="3" x2="12" y2="15"></line> | |
| </svg> | |
| </div> | |
| <h3 class="upload-title">ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือกไฟล์</h3> | |
| <p class="upload-text">รองรับไฟล์ PDF และรูปภาพ (JPG, PNG)</p> | |
| <div class="upload-formats"> | |
| <span class="format-badge pdf"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="class"> | |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> | |
| <polyline points="14 2 14 8 20 8"></polyline> | |
| </svg> | |
| </span> | |
| <span class="format-badge image"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect> | |
| <circle cx="8.5" cy="8.5" r="1.5"></circle> | |
| <polyline points="21 15 16 10 5 21"></polyline> | |
| </svg> | |
| JPG / PNG | |
| </span> | |
| </div> | |
| <input type="file" id="file-input" accept=".pdf,.jpg,.jpeg,.png,application/pdf,image/jpeg,image/png"> | |
| </div> | |
| `; | |
| this.setupEventListeners(); | |
| } | |
| setupEventListeners() { | |
| const uploadZone = this.shadowRoot.getElementById('upload-zone'); | |
| const fileInput = this.shadowRoot.getElementById('file-input'); | |
| // Click to select file | |
| uploadZone.addEventListener('click', (e) => { | |
| if (e.target !== fileInput) { | |
| fileInput.click(); | |
| } | |
| }); | |
| // File input change | |
| fileInput.addEventListener('change', (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| this.dispatchFileSelected(file); | |
| } | |
| }); | |
| // Drag and drop events | |
| uploadZone.addEventListener('dragenter', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| this.dragCounter++; | |
| uploadZone.classList.add('drag-over'); | |
| }); | |
| uploadZone.addEventListener('dragleave', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| this.dragCounter--; | |
| if (this.dragCounter === 0) { | |
| uploadZone.classList.remove('drag-over'); | |
| } | |
| }); | |
| uploadZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }); | |
| uploadZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| this.dragCounter = 0; | |
| uploadZone.classList.remove('drag-over'); | |
| const files = e.dataTransfer.files; | |
| if (files.length > 0) { | |
| const file = files[0]; | |
| const validTypes = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png']; | |
| if (validTypes.includes(file.type)) { | |
| this.dispatchFileSelected(file); | |
| } else { | |
| this.dispatchEvent(new CustomEvent('invalid-file', { | |
| detail: { message: 'ไฟล์ไม่รองรับ กรุณาอัปโหลด PDF, JPG หรือ PNG' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| } | |
| }); | |
| } | |
| dispatchFileSelected(file) { | |
| this.dispatchEvent(new CustomEvent('file-selected', { | |
| detail: { file: file }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| } | |
| customElements.define('upload-zone', UploadZone); | |