Skip to content

Commit 2e082fe

Browse files
committed
Close #45
1 parent c762c48 commit 2e082fe

File tree

5 files changed

+345
-0
lines changed

5 files changed

+345
-0
lines changed

css/components/file-upload.css

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/* Base Styles */
2+
.file-upload {
3+
display: flex;
4+
flex-direction: column;
5+
gap: var(--space-4);
6+
width: 100%;
7+
max-width: clamp(320px, 80vw, 600px);
8+
font-family: var(--font-sans);
9+
color: var(--neutral-800);
10+
/*background-color: var(--neutral-50);
11+
border-radius: var(--radius-lg);
12+
box-shadow: var(--shadow-xl);*/
13+
padding: var(--space-6);
14+
transition: var(--transition-all);
15+
}
16+
17+
.file-upload[data-variant="primary"] {
18+
--file-upload-bg: var(--primary-light);
19+
--file-upload-text: var(--primary-text);
20+
--file-upload-border: var(--primary);
21+
--file-upload-hover-bg: var(--primary-hover);
22+
}
23+
24+
.file-upload[data-variant="secondary"] {
25+
--file-upload-bg: var(--secondary-light);
26+
--file-upload-text: var(--secondary-text);
27+
--file-upload-border: var(--secondary);
28+
--file-upload-hover-bg: var(--secondary-hover);
29+
}
30+
31+
.file-upload[data-variant="accent"] {
32+
--file-upload-bg: var(--accent-light);
33+
--file-upload-text: var(--accent-text);
34+
--file-upload-border: var(--accent);
35+
--file-upload-hover-bg: var(--accent-hover);
36+
}
37+
38+
.file-upload[data-variant="success"] {
39+
--file-upload-bg: var(--success-light);
40+
--file-upload-text: var(--success-text);
41+
--file-upload-border: var(--success);
42+
--file-upload-hover-bg: var(--success-hover);
43+
}
44+
45+
.file-upload[data-variant="info"] {
46+
--file-upload-bg: var(--info-light);
47+
--file-upload-text: var(--info-text);
48+
--file-upload-border: var(--info);
49+
--file-upload-hover-bg: var(--info-hover);
50+
}
51+
52+
.file-upload[data-variant="warning"] {
53+
--file-upload-bg: var(--warning-light);
54+
--file-upload-text: var(--warning-text);
55+
--file-upload-border: var(--warning);
56+
--file-upload-hover-bg: var(--warning-hover);
57+
}
58+
59+
.file-upload[data-variant="danger"] {
60+
--file-upload-bg: var(--danger-light);
61+
--file-upload-text: var(--danger-text);
62+
--file-upload-border: var(--danger);
63+
--file-upload-hover-bg: var(--danger-hover);
64+
}
65+
66+
/* Dropzone Styles */
67+
.file-upload-dropzone {
68+
display: flex;
69+
align-items: center;
70+
justify-content: center;
71+
height: clamp(200px, 35vw, 320px);
72+
background: var(--file-upload-bg) 60%;
73+
border: 5px dashed var(--file-upload-border);
74+
border-radius: var(--radius-lg);
75+
text-align: center;
76+
cursor: pointer;
77+
position: relative;
78+
overflow: hidden;
79+
padding: var(--space-4);
80+
transition: background-color 0.3s ease, border-color 0.3s ease, transform 0.2s ease;
81+
box-shadow: var(--shadow-lg);
82+
}
83+
84+
.file-upload-dropzone::after {
85+
position: absolute;
86+
top: 50%;
87+
left: 50%;
88+
transform: translate(-50%, -50%) scale(0.95);
89+
font-size: clamp(1rem, 1.5vw, 1.25rem);
90+
color: var(--neutral-50); /* Brighter text for better contrast */
91+
background-color: rgba(0, 0, 0, 0.2); /* Subtle overlay */
92+
text-align: center;
93+
padding: var(--space-3) var(--space-4);
94+
border-radius: var(--radius-lg);
95+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
96+
opacity: 0;
97+
content: "Drop File to Upload";
98+
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
99+
z-index: 2;
100+
}
101+
102+
.file-upload-dropzone:hover::after,
103+
.file-upload-dropzone.dragging::after {
104+
opacity: 1;
105+
transform: translate(-50%, -50%) scale(1.05);
106+
background-color: var(--file-upload-hover-bg);
107+
color: var(--file-upload-text);
108+
}
109+
110+
.file-upload-dropzone:hover,
111+
.file-upload-dropzone:focus,
112+
.file-upload-dropzone.dragging {
113+
background-color: var(--file-upload-hover-bg);
114+
border-color: var(--file-upload-border);
115+
transform: scale(1.03);
116+
cursor: copy;
117+
position: relative;
118+
overflow: hidden;
119+
}
120+
121+
.file-upload-instructions {
122+
font-size: clamp(1rem, 1.5vw, 1.25rem);
123+
color: var(--neutral-900);
124+
font-weight: 500;
125+
text-align: center;
126+
transition: opacity 0.3s ease, visibility 0.3s ease;
127+
}
128+
129+
.file-upload-dropzone:hover ~ .file-upload-instructions,
130+
.file-upload-dropzone.dragging ~ .file-upload-instructions {
131+
opacity: 0;
132+
visibility: hidden;
133+
}
134+
135+
.file-upload-browse {
136+
font-weight: bold;
137+
color: var(--file-upload-text);
138+
cursor: pointer;
139+
text-decoration: underline;
140+
transition: color 0.3s ease;
141+
}
142+
143+
.file-upload-browse:hover {
144+
color: var(--file-upload-border);
145+
}
146+
147+
/* Hidden File Input */
148+
.file-upload-input {
149+
display: none;
150+
}
151+
152+
/* File Details */
153+
.file-upload-details {
154+
display: flex;
155+
flex-direction: column;
156+
gap: var(--space-3);
157+
animation: file-fadeIn 0.3s ease;
158+
}
159+
160+
.file-upload-filename {
161+
font-size: clamp(1rem, 1.2vw, 1.125rem);
162+
color: var(--neutral-800);
163+
word-break: break-word;
164+
}
165+
166+
.file-upload-progress {
167+
font-size: clamp(0.875rem, 1vw, 1rem);
168+
color: var(--neutral-600);
169+
display: inline-block;
170+
margin-top: var(--space-1);
171+
}
172+
173+
/* Multiple File Upload List */
174+
.file-upload-list {
175+
display: flex;
176+
flex-direction: column;
177+
gap: var(--space-2);
178+
}
179+
180+
.file-upload-list-item {
181+
display: flex;
182+
justify-content: space-between;
183+
align-items: center;
184+
background-color: var(--neutral-100);
185+
padding: var(--space-3);
186+
border-radius: var(--radius-md);
187+
box-shadow: var(--shadow-sm);
188+
font-size: clamp(1rem, 1.2vw, 1.125rem);
189+
transition: background-color 0.2s ease, transform 0.2s ease;
190+
}
191+
192+
.file-upload-list-item:hover {
193+
background-color: var(--neutral-200);
194+
transform: translateY(-2px);
195+
}
196+
197+
.file-upload-list-item button {
198+
background: none;
199+
border: none;
200+
color: var(--danger);
201+
font-size: 1.2rem;
202+
cursor: pointer;
203+
transition: color 0.2s ease;
204+
}
205+
206+
.file-upload-list-item button:hover {
207+
color: var(--danger-hover);
208+
}
209+
210+
/* Animations */
211+
@keyframes file-fadeIn {
212+
from {
213+
opacity: 0;
214+
transform: translateY(10px);
215+
}
216+
to {
217+
opacity: 1;
218+
transform: translateY(0);
219+
}
220+
}
221+
222+
/* Placeholder Animation for Dropzone */
223+
.file-upload-dropzone-placeholder {
224+
position: absolute;
225+
font-size: clamp(1rem, 1.5vw, 1.25rem);
226+
color: var(--neutral-600);
227+
animation: file-fadeIn 0.3s ease-in-out;
228+
}
229+
230+
/* Responsive Adjustments */
231+
@media (max-width: 768px) {
232+
.file-upload-dropzone {
233+
height: clamp(150px, 30vw, 250px);
234+
}
235+
236+
.file-upload-instructions {
237+
font-size: clamp(0.875rem, 1.2vw, 1rem);
238+
}
239+
240+
.file-upload {
241+
padding: var(--space-3);
242+
}
243+
}

css/main.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@
4343
@import './components/stepper.css';
4444
@import './components/calendar.css';
4545
@import './components/navbar.css';
46+
@import './components/file-upload.css';
4647

4748

demo.html

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,39 @@ <h2 class="font-lg text-info my-4">Calendar with Events (November 2024)</h2>
13431343
</div>
13441344
</div>
13451345
</section>
1346+
1347+
<section class="p-6 bg-neutral-50 shadow-md rounded my-6">
1348+
<h2 class="text-lg text-primary mb-6">File Upload Component Showcase</h2>
1349+
1350+
<!-- Single File Upload -->
1351+
<div class="flex justify-center">
1352+
<div class="file-upload" data-variant="primary">
1353+
<div class="file-upload-dropzone" tabindex="0" aria-label="Drop a file or browse to upload">
1354+
<div class="file-upload-instructions">
1355+
<span class="file-upload-title">Upload a File</span><br />
1356+
Drag & Drop a File or <span class="file-upload-browse">Browse</span>
1357+
</div>
1358+
<input type="file" class="file-upload-input" />
1359+
</div>
1360+
<div class="file-upload-details">
1361+
<div class="file-upload-filename" aria-live="polite"></div>
1362+
<div class="file-upload-progress" aria-live="polite"></div>
1363+
</div>
1364+
</div>
1365+
</div>
1366+
1367+
<!-- Multiple File Upload -->
1368+
<div class="file-upload" data-variant="success" data-multiple="true">
1369+
<div class="file-upload-dropzone" tabindex="0" aria-label="Drop files or browse to upload multiple files">
1370+
<div class="file-upload-instructions">
1371+
<span class="file-upload-title">Upload Multiple Files</span><br />
1372+
Drag & Drop Files or <span class="file-upload-browse">Browse</span>
1373+
</div>
1374+
<input type="file" class="file-upload-input" multiple />
1375+
</div>
1376+
<div class="file-upload-list" aria-live="polite"></div>
1377+
</div>
1378+
</section>
13461379

13471380
</body>
13481381
<script type="module" src="js/main.js"></script>

js/components/file-upload.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export function initializeFileUploadComponents() {
2+
document.querySelectorAll('.file-upload').forEach((fileUpload) => {
3+
const dropzone = fileUpload.querySelector('.file-upload-dropzone');
4+
const input = fileUpload.querySelector('.file-upload-input');
5+
const isMultiple = fileUpload.dataset.multiple === 'true';
6+
const fileList = fileUpload.querySelector('.file-upload-list');
7+
const fileNameDisplay = fileUpload.querySelector('.file-upload-filename');
8+
const fileProgressDisplay = fileUpload.querySelector('.file-upload-progress');
9+
10+
dropzone.addEventListener('click', () => input.click());
11+
12+
input.addEventListener('change', (event) => {
13+
const files = Array.from(event.target.files);
14+
if (files.length === 0) return;
15+
16+
if (isMultiple && fileList) {
17+
fileList.innerHTML = '';
18+
files.forEach((file) => appendFileToList(file, fileList));
19+
} else if (fileNameDisplay && fileProgressDisplay) {
20+
const file = files[0];
21+
fileNameDisplay.textContent = file.name;
22+
fileProgressDisplay.textContent = 'Ready to upload';
23+
}
24+
});
25+
26+
dropzone.addEventListener('dragover', (event) => {
27+
event.preventDefault();
28+
dropzone.classList.add('dragging');
29+
});
30+
31+
dropzone.addEventListener('dragleave', () => {
32+
dropzone.classList.remove('dragging');
33+
});
34+
35+
dropzone.addEventListener('drop', (event) => {
36+
event.preventDefault();
37+
dropzone.classList.remove('dragging');
38+
const files = Array.from(event.dataTransfer.files);
39+
input.files = event.dataTransfer.files;
40+
41+
if (isMultiple && fileList) {
42+
fileList.innerHTML = '';
43+
files.forEach((file) => appendFileToList(file, fileList));
44+
} else if (fileNameDisplay && fileProgressDisplay) {
45+
const file = files[0];
46+
fileNameDisplay.textContent = file.name;
47+
fileProgressDisplay.textContent = 'Ready to upload';
48+
}
49+
});
50+
51+
function appendFileToList(file, listContainer) {
52+
const listItem = document.createElement('div');
53+
listItem.className = 'file-upload-list-item';
54+
listItem.innerHTML = `
55+
<span>${file.name}</span>
56+
<button aria-label="Remove file">&times;</button>
57+
`;
58+
listItem.querySelector('button').addEventListener('click', () => {
59+
listItem.remove();
60+
});
61+
listContainer.appendChild(listItem);
62+
}
63+
});
64+
}

js/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Slideshow } from './components/slideshow.js';
1010
import { initializeRatingComponents } from './components/rating.js';
1111
import { initializeCalendarComponents } from './components/calendar.js';
1212
import { Navbar } from './components/navbar.js';
13+
import { initializeFileUploadComponents } from './components/file-upload.js';
1314

1415
document.addEventListener("DOMContentLoaded", () => {
1516
// Initialize rating components
@@ -18,6 +19,9 @@ document.addEventListener("DOMContentLoaded", () => {
1819
// Initialize calendar components
1920
initializeCalendarComponents();
2021

22+
// Initialize file upload components
23+
initializeFileUploadComponents();
24+
2125
// Initialize other components, if applicable
2226
document.querySelectorAll('.alert .alert-close').forEach(button => {
2327
button.addEventListener('click', event => {

0 commit comments

Comments
 (0)