EBOLITSATAN / components /floating-controls.js
doeqoth's picture
Manual changes saved
9bff52c verified
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>
PDF
</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);