779 lines
18 KiB
Vue
779 lines
18 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="components-library-container">
|
|||
|
|
<!-- Toggle Button -->
|
|||
|
|
<VBtn
|
|||
|
|
v-if="!isLibraryOpen"
|
|||
|
|
@click="toggleLibrary"
|
|||
|
|
class="library-toggle-btn"
|
|||
|
|
color="primary"
|
|||
|
|
variant="elevated"
|
|||
|
|
size="large"
|
|||
|
|
>
|
|||
|
|
<VIcon icon="tabler-layout-dashboard" size="20" class="me-2" />
|
|||
|
|
Components Library
|
|||
|
|
<VIcon icon="tabler-chevron-up" size="16" class="ms-2" />
|
|||
|
|
</VBtn>
|
|||
|
|
|
|||
|
|
<!-- Components Library Panel -->
|
|||
|
|
<VCard
|
|||
|
|
v-if="isLibraryOpen"
|
|||
|
|
class="components-library-panel"
|
|||
|
|
:class="{ 'panel-open': isLibraryOpen }"
|
|||
|
|
elevation="8"
|
|||
|
|
>
|
|||
|
|
<VCardTitle class="library-header">
|
|||
|
|
<div class="header-content">
|
|||
|
|
<div class="header-left">
|
|||
|
|
<VIcon icon="tabler-layout-dashboard" size="24" class="me-2" />
|
|||
|
|
<span>Dashboard Components</span>
|
|||
|
|
</div>
|
|||
|
|
<VBtn
|
|||
|
|
@click="toggleLibrary"
|
|||
|
|
variant="text"
|
|||
|
|
size="small"
|
|||
|
|
icon
|
|||
|
|
class="close-btn"
|
|||
|
|
>
|
|||
|
|
<VIcon icon="tabler-x" size="20" />
|
|||
|
|
</VBtn>
|
|||
|
|
</div>
|
|||
|
|
</VCardTitle>
|
|||
|
|
|
|||
|
|
<VCardText class="library-content">
|
|||
|
|
<div class="search-section">
|
|||
|
|
<VTextField
|
|||
|
|
v-model="searchQuery"
|
|||
|
|
placeholder="Search components..."
|
|||
|
|
variant="outlined"
|
|||
|
|
density="compact"
|
|||
|
|
prepend-inner-icon="tabler-search"
|
|||
|
|
clearable
|
|||
|
|
class="search-input"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="categories-tabs">
|
|||
|
|
<VChipGroup
|
|||
|
|
v-model="selectedCategory"
|
|||
|
|
selected-class="text-primary"
|
|||
|
|
class="category-chips"
|
|||
|
|
>
|
|||
|
|
<VChip
|
|||
|
|
v-for="category in categories"
|
|||
|
|
:key="category"
|
|||
|
|
:value="category"
|
|||
|
|
variant="outlined"
|
|||
|
|
size="small"
|
|||
|
|
>
|
|||
|
|
{{ category }}
|
|||
|
|
</VChip>
|
|||
|
|
</VChipGroup>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="components-grid">
|
|||
|
|
<div
|
|||
|
|
v-for="component in filteredComponents"
|
|||
|
|
:key="component.id"
|
|||
|
|
class="component-item"
|
|||
|
|
:draggable="isEditMode"
|
|||
|
|
@dragstart="handleDragStart($event, component)"
|
|||
|
|
@dragend="handleDragEnd"
|
|||
|
|
:class="{ 'draggable': isEditMode, 'disabled': !isEditMode }"
|
|||
|
|
>
|
|||
|
|
<div class="component-preview">
|
|||
|
|
<VIcon :icon="component.icon" size="32" :color="component.color" />
|
|||
|
|
<div v-if="isEditMode" class="component-overlay">
|
|||
|
|
<VIcon icon="tabler-plus" size="20" color="white" />
|
|||
|
|
</div>
|
|||
|
|
<div v-if="!isEditMode" class="disabled-overlay">
|
|||
|
|
<VIcon icon="tabler-lock" size="16" color="grey" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="component-info">
|
|||
|
|
<h4 class="component-title">{{ component.title }}</h4>
|
|||
|
|
<p class="component-description">{{ component.description }}</p>
|
|||
|
|
<div class="component-meta">
|
|||
|
|
<VChip
|
|||
|
|
:color="component.color"
|
|||
|
|
size="x-small"
|
|||
|
|
variant="outlined"
|
|||
|
|
class="component-type-chip"
|
|||
|
|
>
|
|||
|
|
{{ component.category }}
|
|||
|
|
</VChip>
|
|||
|
|
<VChip
|
|||
|
|
size="x-small"
|
|||
|
|
variant="outlined"
|
|||
|
|
color="grey"
|
|||
|
|
class="size-chip"
|
|||
|
|
>
|
|||
|
|
{{ component.defaultSize.cols }}×{{ Math.round(component.defaultSize.height) }}
|
|||
|
|
</VChip>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-if="filteredComponents.length === 0" class="no-results">
|
|||
|
|
<VIcon icon="tabler-layout-dashboard" size="48" color="grey-lighten-1" />
|
|||
|
|
<h3>No components found</h3>
|
|||
|
|
<p>Try adjusting your search or category filter</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-if="!isEditMode" class="edit-mode-notice">
|
|||
|
|
<VAlert
|
|||
|
|
type="info"
|
|||
|
|
variant="tonal"
|
|||
|
|
class="ma-2"
|
|||
|
|
>
|
|||
|
|
<template #prepend>
|
|||
|
|
<VIcon icon="tabler-info-circle" />
|
|||
|
|
</template>
|
|||
|
|
<div>
|
|||
|
|
<strong>Edit Mode Required</strong>
|
|||
|
|
<br>
|
|||
|
|
Enable edit mode to add components to your dashboard
|
|||
|
|
</div>
|
|||
|
|
</VAlert>
|
|||
|
|
</div>
|
|||
|
|
</VCardText>
|
|||
|
|
</VCard>
|
|||
|
|
|
|||
|
|
<!-- Drop Zone Overlay -->
|
|||
|
|
<div
|
|||
|
|
v-if="isDragging"
|
|||
|
|
class="drop-zone-overlay"
|
|||
|
|
@dragover="handleDragOver"
|
|||
|
|
@drop="handleDrop"
|
|||
|
|
@dragleave="handleDragLeave"
|
|||
|
|
>
|
|||
|
|
<div class="drop-zone-content">
|
|||
|
|
<VIcon icon="tabler-download" size="64" color="primary" />
|
|||
|
|
<h2>Drop component here to add to dashboard</h2>
|
|||
|
|
<p>Release to add the component to your dashboard</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
isEditMode: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const emit = defineEmits(['add-component'])
|
|||
|
|
|
|||
|
|
const isLibraryOpen = ref(false)
|
|||
|
|
const isDragging = ref(false)
|
|||
|
|
const searchQuery = ref('')
|
|||
|
|
const selectedCategory = ref('All')
|
|||
|
|
|
|||
|
|
const categories = ['All', 'CRM', 'Ecommerce', 'Analytics', 'Reports', 'Activity']
|
|||
|
|
|
|||
|
|
const availableComponents = ref([
|
|||
|
|
{
|
|||
|
|
id: 'crm-active-project',
|
|||
|
|
title: 'Active Project',
|
|||
|
|
description: 'Shows current active project details',
|
|||
|
|
icon: 'tabler-folder-open',
|
|||
|
|
color: 'primary',
|
|||
|
|
category: 'CRM',
|
|||
|
|
component: 'CrmActiveProject',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-activity-timeline',
|
|||
|
|
title: 'Activity Timeline',
|
|||
|
|
description: 'Recent activities and updates timeline',
|
|||
|
|
icon: 'tabler-timeline',
|
|||
|
|
color: 'info',
|
|||
|
|
category: 'Activity',
|
|||
|
|
component: 'CrmActivityTimeline',
|
|||
|
|
defaultSize: { cols: 6, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-analytics-sales',
|
|||
|
|
title: 'Sales Analytics',
|
|||
|
|
description: 'Sales performance and analytics',
|
|||
|
|
icon: 'tabler-chart-line',
|
|||
|
|
color: 'success',
|
|||
|
|
category: 'Analytics',
|
|||
|
|
component: 'CrmAnalyticsSales',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-earning-reports',
|
|||
|
|
title: 'Earning Reports',
|
|||
|
|
description: 'Yearly earning overview and reports',
|
|||
|
|
icon: 'tabler-report-money',
|
|||
|
|
color: 'warning',
|
|||
|
|
category: 'Reports',
|
|||
|
|
component: 'CrmEarningReportsYearlyOverview',
|
|||
|
|
defaultSize: { cols: 8, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-project-status',
|
|||
|
|
title: 'Project Status',
|
|||
|
|
description: 'Current status of all projects',
|
|||
|
|
icon: 'tabler-checkup-list',
|
|||
|
|
color: 'deep-purple',
|
|||
|
|
category: 'CRM',
|
|||
|
|
component: 'CrmProjectStatus',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-recent-transactions',
|
|||
|
|
title: 'Recent Transactions',
|
|||
|
|
description: 'Latest financial transactions',
|
|||
|
|
icon: 'tabler-credit-card',
|
|||
|
|
color: 'teal',
|
|||
|
|
category: 'CRM',
|
|||
|
|
component: 'CrmRecentTransactions',
|
|||
|
|
defaultSize: { cols: 6, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'crm-sales-countries',
|
|||
|
|
title: 'Sales by Countries',
|
|||
|
|
description: 'Geographic sales distribution',
|
|||
|
|
icon: 'tabler-world',
|
|||
|
|
color: 'indigo',
|
|||
|
|
category: 'Analytics',
|
|||
|
|
component: 'CrmSalesByCountries',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'project-activity-bar',
|
|||
|
|
title: 'Project Activity Chart',
|
|||
|
|
description: 'Bar chart showing project activities',
|
|||
|
|
icon: 'tabler-chart-bar',
|
|||
|
|
color: 'cyan',
|
|||
|
|
category: 'Analytics',
|
|||
|
|
component: 'ProjectActivityBarChart',
|
|||
|
|
defaultSize: { cols: 8, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'analysis-card-1',
|
|||
|
|
title: 'Analysis Card (Projects)',
|
|||
|
|
description: 'Active projects progress analysis',
|
|||
|
|
icon: 'tabler-analytics',
|
|||
|
|
color: 'pink',
|
|||
|
|
category: 'Analytics',
|
|||
|
|
component: 'AnalysisCard',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: { chartName: 'Active Projects Progress' }
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'analysis-card-2',
|
|||
|
|
title: 'Analysis Card (Cost)',
|
|||
|
|
description: 'Cost overview analysis',
|
|||
|
|
icon: 'tabler-analytics',
|
|||
|
|
color: 'pink',
|
|||
|
|
category: 'Analytics',
|
|||
|
|
component: 'AnalysisCard',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: { chartName: 'Cost overview' }
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'cost-overview',
|
|||
|
|
title: 'Cost Overview',
|
|||
|
|
description: 'Detailed cost breakdown and overview',
|
|||
|
|
icon: 'tabler-coin',
|
|||
|
|
color: 'orange',
|
|||
|
|
category: 'Reports',
|
|||
|
|
component: 'CostOverview',
|
|||
|
|
defaultSize: { cols: 8, height: 33.33 },
|
|||
|
|
defaultProps: {}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'generated-leads-1',
|
|||
|
|
title: 'Generated Leads (Primary)',
|
|||
|
|
description: 'Lead generation progress - Primary theme',
|
|||
|
|
icon: 'tabler-users',
|
|||
|
|
color: 'primary',
|
|||
|
|
category: 'Ecommerce',
|
|||
|
|
component: 'GeneratedLeadsCard',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: { progress: 33 }
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'generated-leads-2',
|
|||
|
|
title: 'Generated Leads (Success)',
|
|||
|
|
description: 'Lead generation progress - Success theme',
|
|||
|
|
icon: 'tabler-users',
|
|||
|
|
color: 'success',
|
|||
|
|
category: 'Ecommerce',
|
|||
|
|
component: 'GeneratedLeadsCard',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: { donutColors: ["primary"], progress: 71 }
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'generated-leads-3',
|
|||
|
|
title: 'Generated Leads (Warning)',
|
|||
|
|
description: 'Lead generation progress - Warning theme',
|
|||
|
|
icon: 'tabler-users',
|
|||
|
|
color: 'warning',
|
|||
|
|
category: 'Ecommerce',
|
|||
|
|
component: 'GeneratedLeadsCard',
|
|||
|
|
defaultSize: { cols: 4, height: 33.33 },
|
|||
|
|
defaultProps: { donutColors: ["warning"], progress: 56 }
|
|||
|
|
}
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
const filteredComponents = computed(() => {
|
|||
|
|
let components = availableComponents.value
|
|||
|
|
|
|||
|
|
// Filter by category
|
|||
|
|
if (selectedCategory.value !== 'All') {
|
|||
|
|
components = components.filter(comp => comp.category === selectedCategory.value)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Filter by search query
|
|||
|
|
if (searchQuery.value) {
|
|||
|
|
const query = searchQuery.value.toLowerCase()
|
|||
|
|
components = components.filter(comp =>
|
|||
|
|
comp.title.toLowerCase().includes(query) ||
|
|||
|
|
comp.description.toLowerCase().includes(query) ||
|
|||
|
|
comp.category.toLowerCase().includes(query)
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return components
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const toggleLibrary = () => {
|
|||
|
|
isLibraryOpen.value = !isLibraryOpen.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleDragStart = (event, component) => {
|
|||
|
|
if (!props.isEditMode) {
|
|||
|
|
event.preventDefault()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isDragging.value = true
|
|||
|
|
event.dataTransfer.setData('application/json', JSON.stringify(component))
|
|||
|
|
event.dataTransfer.effectAllowed = 'copy'
|
|||
|
|
|
|||
|
|
// Create drag image
|
|||
|
|
const dragImage = event.target.cloneNode(true)
|
|||
|
|
dragImage.style.transform = 'rotate(5deg) scale(0.8)'
|
|||
|
|
dragImage.style.opacity = '0.8'
|
|||
|
|
dragImage.style.zIndex = '9999'
|
|||
|
|
document.body.appendChild(dragImage)
|
|||
|
|
event.dataTransfer.setDragImage(dragImage, 50, 50)
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (document.body.contains(dragImage)) {
|
|||
|
|
document.body.removeChild(dragImage)
|
|||
|
|
}
|
|||
|
|
}, 0)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleDragEnd = () => {
|
|||
|
|
isDragging.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleDragOver = (event) => {
|
|||
|
|
event.preventDefault()
|
|||
|
|
event.dataTransfer.dropEffect = 'copy'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleDrop = (event) => {
|
|||
|
|
event.preventDefault()
|
|||
|
|
isDragging.value = false
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const componentData = JSON.parse(event.dataTransfer.getData('application/json'))
|
|||
|
|
emit('add-component', componentData)
|
|||
|
|
|
|||
|
|
// Show success feedback
|
|||
|
|
console.log('Component added successfully:', componentData.title)
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error adding component:', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleDragLeave = (event) => {
|
|||
|
|
// Only hide if we're leaving the drop zone entirely
|
|||
|
|
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|||
|
|
isDragging.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Close library when clicking outside
|
|||
|
|
const handleClickOutside = (event) => {
|
|||
|
|
if (isLibraryOpen.value && !event.target.closest('.components-library-panel, .library-toggle-btn')) {
|
|||
|
|
isLibraryOpen.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
document.addEventListener('click', handleClickOutside)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
onUnmounted(() => {
|
|||
|
|
document.removeEventListener('click', handleClickOutside)
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.components-library-container {
|
|||
|
|
position: fixed;
|
|||
|
|
bottom: 2rem;
|
|||
|
|
right: 2rem;
|
|||
|
|
z-index: 1000;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.library-toggle-btn {
|
|||
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 8px 32px rgba(var(--v-theme-primary), 0.2) !important;
|
|||
|
|
border-radius: 12px !important;
|
|||
|
|
padding: 12px 20px !important;
|
|||
|
|
font-weight: 600 !important;
|
|||
|
|
text-transform: none !important;
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
animation: bounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.library-toggle-btn:hover {
|
|||
|
|
transform: translateY(-2px) scale(1.02);
|
|||
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 12px 40px rgba(var(--v-theme-primary), 0.3) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.components-library-panel {
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 80px;
|
|||
|
|
right: 0;
|
|||
|
|
width: 450px;
|
|||
|
|
max-height: 600px;
|
|||
|
|
border-radius: 16px !important;
|
|||
|
|
backdrop-filter: blur(20px);
|
|||
|
|
background: rgba(255, 255, 255, 0.95) !important;
|
|||
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|||
|
|
animation: slideUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.library-header {
|
|||
|
|
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.1), rgba(var(--v-theme-secondary), 0.05));
|
|||
|
|
border-radius: 16px 16px 0 0;
|
|||
|
|
padding: 1rem 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-content {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-left {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
font-weight: 600;
|
|||
|
|
font-size: 1.1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.close-btn {
|
|||
|
|
opacity: 0.7;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.close-btn:hover {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: scale(1.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.library-content {
|
|||
|
|
max-height: 500px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
padding: 1rem 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-section {
|
|||
|
|
margin-bottom: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-input {
|
|||
|
|
border-radius: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.categories-tabs {
|
|||
|
|
margin-bottom: 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.category-chips {
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.components-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
gap: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
padding: 1rem;
|
|||
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: rgba(255, 255, 255, 0.7);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item.draggable {
|
|||
|
|
cursor: grab;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item.draggable:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|||
|
|
border-color: rgba(var(--v-theme-primary), 0.3);
|
|||
|
|
background: rgba(var(--v-theme-primary), 0.02);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item.disabled {
|
|||
|
|
opacity: 0.6;
|
|||
|
|
cursor: not-allowed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item:active.draggable {
|
|||
|
|
cursor: grabbing;
|
|||
|
|
transform: scale(0.98);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-preview {
|
|||
|
|
position: relative;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
width: 60px;
|
|||
|
|
height: 60px;
|
|||
|
|
background: rgba(var(--v-theme-surface), 0.8);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-overlay {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
bottom: 0;
|
|||
|
|
background: rgba(var(--v-theme-primary), 0.9);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
opacity: 0;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.disabled-overlay {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
bottom: 0;
|
|||
|
|
background: rgba(128, 128, 128, 0.3);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item.draggable:hover .component-overlay {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-info {
|
|||
|
|
flex: 1;
|
|||
|
|
min-width: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-title {
|
|||
|
|
font-size: 0.95rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin: 0 0 0.25rem 0;
|
|||
|
|
color: rgba(0, 0, 0, 0.87);
|
|||
|
|
line-height: 1.2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-description {
|
|||
|
|
font-size: 0.8rem;
|
|||
|
|
color: rgba(0, 0, 0, 0.6);
|
|||
|
|
margin: 0 0 0.5rem 0;
|
|||
|
|
line-height: 1.3;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-meta {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-type-chip,
|
|||
|
|
.size-chip {
|
|||
|
|
font-size: 0.7rem !important;
|
|||
|
|
height: 20px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.no-results {
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 2rem 1rem;
|
|||
|
|
color: rgba(0, 0, 0, 0.6);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.no-results h3 {
|
|||
|
|
margin: 1rem 0 0.5rem 0;
|
|||
|
|
font-size: 1.1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.no-results p {
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: 0.9rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.edit-mode-notice {
|
|||
|
|
margin-top: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.drop-zone-overlay {
|
|||
|
|
position: fixed;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
bottom: 0;
|
|||
|
|
background: rgba(var(--v-theme-primary), 0.1);
|
|||
|
|
backdrop-filter: blur(4px);
|
|||
|
|
z-index: 999;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
animation: fadeIn 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.drop-zone-content {
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 3rem;
|
|||
|
|
background: rgba(255, 255, 255, 0.95);
|
|||
|
|
border-radius: 20px;
|
|||
|
|
border: 3px dashed rgba(var(--v-theme-primary), 0.5);
|
|||
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|||
|
|
animation: dropZonePulse 1.5s ease-in-out infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.drop-zone-content h2 {
|
|||
|
|
margin: 1rem 0 0.5rem 0;
|
|||
|
|
color: rgba(var(--v-theme-primary), 0.9);
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.drop-zone-content p {
|
|||
|
|
margin: 0;
|
|||
|
|
color: rgba(0, 0, 0, 0.7);
|
|||
|
|
font-size: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes bounceIn {
|
|||
|
|
0% {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: scale(0.3) translateY(100px);
|
|||
|
|
}
|
|||
|
|
50% {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: scale(1.05);
|
|||
|
|
}
|
|||
|
|
70% {
|
|||
|
|
transform: scale(0.9);
|
|||
|
|
}
|
|||
|
|
100% {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: scale(1) translateY(0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideUp {
|
|||
|
|
from {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: translateY(20px) scale(0.95);
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: translateY(0) scale(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes fadeIn {
|
|||
|
|
from {
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes dropZonePulse {
|
|||
|
|
0%, 100% {
|
|||
|
|
transform: scale(1);
|
|||
|
|
border-color: rgba(var(--v-theme-primary), 0.5);
|
|||
|
|
}
|
|||
|
|
50% {
|
|||
|
|
transform: scale(1.02);
|
|||
|
|
border-color: rgba(var(--v-theme-primary), 0.8);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.components-library-container {
|
|||
|
|
bottom: 1rem;
|
|||
|
|
right: 1rem;
|
|||
|
|
left: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.components-library-panel {
|
|||
|
|
width: 100%;
|
|||
|
|
max-width: none;
|
|||
|
|
right: 0;
|
|||
|
|
left: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.library-toggle-btn {
|
|||
|
|
width: 100%;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-item {
|
|||
|
|
padding: 0.75rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-preview {
|
|||
|
|
width: 50px;
|
|||
|
|
height: 50px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-title {
|
|||
|
|
font-size: 0.9rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.component-description {
|
|||
|
|
font-size: 0.75rem;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|