feat: add CSS file import with drag & drop support
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 16s

Add CssFileImport component to StylesheetViewer allowing users to
import CSS files to replace custom CSS content.

Features:
- Click to select file via file dialog
- Drag & drop support with visual feedback
- File validation (.css only, max 1MB)
- Error messages for invalid files
- Direct replacement of customCss content

New component:
- src/components/ui/CssFileImport.vue

Integration:
- Added at top of StylesheetViewer
- Emits 'import' event with file content
- Content replaces customCss in store

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-01-09 14:41:56 +01:00
parent b692047ff2
commit e88c217b1e
2 changed files with 168 additions and 0 deletions

View file

@ -1,5 +1,8 @@
<template> <template>
<div id="stylesheet-viewer"> <div id="stylesheet-viewer">
<!-- CSS File Import -->
<CssFileImport @import="handleCssImport" />
<!-- Base CSS Section (Collapsable, closed by default) --> <!-- Base CSS Section (Collapsable, closed by default) -->
<div class="css-section"> <div class="css-section">
<div class="section-header" @click="isBaseCssExpanded = !isBaseCssExpanded"> <div class="section-header" @click="isBaseCssExpanded = !isBaseCssExpanded">
@ -53,6 +56,7 @@
<script setup> <script setup>
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet'; import { useStylesheetStore } from '../stores/stylesheet';
import CssFileImport from './ui/CssFileImport.vue';
import hljs from 'highlight.js/lib/core'; import hljs from 'highlight.js/lib/core';
import css from 'highlight.js/lib/languages/css'; import css from 'highlight.js/lib/languages/css';
import 'highlight.js/styles/atom-one-dark.css'; import 'highlight.js/styles/atom-one-dark.css';
@ -90,6 +94,11 @@ const handleFocus = () => {
stylesheetStore.isEditing = true; stylesheetStore.isEditing = true;
}; };
const handleCssImport = (cssContent) => {
// Replace custom CSS with imported content
stylesheetStore.customCss = cssContent;
};
// Watch editing mode and format when exiting // Watch editing mode and format when exiting
watch(isCustomCssEditable, async (newValue, oldValue) => { watch(isCustomCssEditable, async (newValue, oldValue) => {
stylesheetStore.isEditing = newValue; stylesheetStore.isEditing = newValue;

View file

@ -0,0 +1,159 @@
<template>
<div
class="css-import"
:class="{ 'is-dragging': isDragging }"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
>
<input
ref="fileInput"
type="file"
accept=".css"
@change="handleFileSelect"
style="display: none"
/>
<button class="import-button" @click="openFileDialog" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/>
</svg>
<span>Importer un fichier CSS</span>
</button>
<span class="import-hint">ou glisser-déposer ici</span>
<div v-if="error" class="import-error">{{ error }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const emit = defineEmits(['import']);
const fileInput = ref(null);
const isDragging = ref(false);
const error = ref('');
const openFileDialog = () => {
fileInput.value?.click();
};
const handleDragOver = (e) => {
isDragging.value = true;
};
const handleDragLeave = (e) => {
isDragging.value = false;
};
const handleDrop = (e) => {
isDragging.value = false;
const files = e.dataTransfer.files;
if (files.length > 0) {
processFile(files[0]);
}
};
const handleFileSelect = (e) => {
const files = e.target.files;
if (files.length > 0) {
processFile(files[0]);
}
};
const processFile = (file) => {
error.value = '';
// Validate file type
if (!file.name.endsWith('.css')) {
error.value = 'Seuls les fichiers .css sont acceptés';
return;
}
// Validate file size (max 1MB)
if (file.size > 1024 * 1024) {
error.value = 'Le fichier est trop volumineux (max 1MB)';
return;
}
// Read file content
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
emit('import', content);
// Reset input
if (fileInput.value) {
fileInput.value.value = '';
}
};
reader.onerror = () => {
error.value = 'Erreur lors de la lecture du fichier';
};
reader.readAsText(file);
};
</script>
<style scoped>
.css-import {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: #21252b;
border: 2px dashed transparent;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.css-import.is-dragging {
border-color: #61afef;
background: #2c313c;
}
.import-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: #2c313c;
color: #abb2bf;
border: 1px solid #3e4451;
border-radius: 0.25rem;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s ease;
}
.import-button:hover {
background: #3e4451;
border-color: #61afef;
color: #61afef;
}
.import-button svg {
width: 1rem;
height: 1rem;
}
.import-hint {
font-size: 0.75rem;
color: #5c6370;
font-style: italic;
}
.import-error {
font-size: 0.75rem;
color: #e06c75;
background: rgba(224, 108, 117, 0.1);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
</style>