EDMS/pages/dms/design-system-demo.vue
2025-06-05 14:57:08 +08:00

800 lines
29 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue';
import { useDesignSystem } from '~/composables/useDesignSystem';
import { useNotifications } from '~/composables/useNotifications';
import { useTouchInteractions } from '~/composables/useTouchInteractions';
// Page meta
definePageMeta({
title: "Design System Demo",
layout: "default"
});
// Components
import ResponsiveContainer from '~/components/base/ResponsiveContainer.vue';
import BaseModal from '~/components/base/BaseModal.vue';
import LoadingStates from '~/components/base/LoadingStates.vue';
import AdvancedDataTable from '~/components/base/AdvancedDataTable.vue';
import WindowsExplorerTree from '~/components/dms/explorer/WindowsExplorerTree.vue';
import NotificationDisplay from '~/components/base/NotificationDisplay.vue';
import RsButton from '~/components/RsButton.vue';
// Design system and utilities - wrapped in try-catch to handle SSR
let designSystem, notifications;
try {
designSystem = useDesignSystem();
notifications = useNotifications();
} catch (error) {
console.warn('Design system initialization error:', error);
}
const { tokens, utils, dmsPatterns, statusColors } = designSystem || {};
const { success, error, warning, info, confirm, loading, dms } = notifications || {};
// Demo state
const showModal = ref(false);
const currentDemo = ref('overview');
const loadingDemo = ref(false);
const tableLoading = ref(false);
const treeLoading = ref(false);
const isClient = ref(false);
// Sample data for demos
const sampleTreeData = ref([
{
id: '1',
name: 'Documents',
type: 'folder',
parentId: null,
size: null,
modifiedAt: '2024-01-15T10:30:00Z'
},
{
id: '2',
name: 'Projects',
type: 'folder',
parentId: '1',
size: null,
modifiedAt: '2024-01-14T15:45:00Z'
},
{
id: '3',
name: 'Project_Plan.pdf',
type: 'file',
parentId: '2',
size: 2048576,
modifiedAt: '2024-01-13T09:15:00Z'
},
{
id: '4',
name: 'Budget.xlsx',
type: 'file',
parentId: '2',
size: 1024000,
modifiedAt: '2024-01-12T14:20:00Z'
},
{
id: '5',
name: 'Archive',
type: 'folder',
parentId: null,
size: null,
modifiedAt: '2024-01-10T11:00:00Z'
}
]);
const sampleTableData = ref([
{
id: 1,
name: 'Annual_Report_2024.pdf',
type: 'PDF Document',
size: 2048576,
owner: 'John Doe',
modified: '2024-01-15T10:30:00Z',
status: 'active',
version: '2.1'
},
{
id: 2,
name: 'Q4_Budget_Analysis.xlsx',
type: 'Spreadsheet',
size: 1536000,
owner: 'Jane Smith',
modified: '2024-01-14T15:45:00Z',
status: 'active',
version: '1.3'
},
{
id: 3,
name: 'Meeting_Notes_Draft.docx',
type: 'Word Document',
size: 512000,
owner: 'Mike Johnson',
modified: '2024-01-13T09:15:00Z',
status: 'inactive',
version: '1.0'
},
{
id: 4,
name: 'Project_Presentation.pptx',
type: 'Presentation',
size: 3072000,
owner: 'Sarah Wilson',
modified: '2024-01-12T14:20:00Z',
status: 'active',
version: '1.7'
}
]);
const tableColumns = ref([
{
key: 'name',
title: 'Document Name',
sortable: true,
filterable: true
},
{
key: 'type',
title: 'Type',
sortable: true,
filterable: true
},
{
key: 'size',
title: 'Size',
type: 'filesize',
sortable: true,
align: 'right'
},
{
key: 'owner',
title: 'Owner',
sortable: true,
filterable: true
},
{
key: 'modified',
title: 'Modified',
type: 'date',
sortable: true
},
{
key: 'status',
title: 'Status',
type: 'status',
sortable: true,
filterable: true
},
{
key: 'version',
title: 'Version',
sortable: true,
align: 'center'
}
]);
const tableActions = ref([
{
key: 'view',
icon: 'mdi:eye',
title: 'View Document',
variant: 'secondary-outline'
},
{
key: 'edit',
icon: 'mdi:pencil',
title: 'Edit Document',
variant: 'primary'
},
{
key: 'delete',
icon: 'mdi:delete',
title: 'Delete Document',
variant: 'danger'
}
]);
// Demo sections
const demoSections = ref([
{ id: 'overview', title: 'Design System Overview', icon: 'mdi:palette' },
{ id: 'components', title: 'Base Components', icon: 'mdi:view-grid' },
{ id: 'loading', title: 'Loading States', icon: 'mdi:loading' },
{ id: 'notifications', title: 'Notifications', icon: 'mdi:bell' },
{ id: 'table', title: 'Data Tables', icon: 'mdi:table' },
{ id: 'tree', title: 'File Explorer', icon: 'mdi:file-tree' },
{ id: 'responsive', title: 'Responsive Design', icon: 'mdi:responsive' },
{ id: 'touch', title: 'Touch Interactions', icon: 'mdi:gesture-tap' }
]);
// Demo functions
const showNotificationDemo = (type) => {
switch (type) {
case 'success':
success('Document uploaded successfully!');
break;
case 'error':
error('Failed to save document. Please try again.');
break;
case 'warning':
warning('Document will be auto-saved in 30 seconds.');
break;
case 'info':
info('New version of the document is available.');
break;
case 'loading':
loading(
new Promise(resolve => setTimeout(resolve, 3000)),
{
title: 'Processing Document',
message: 'Please wait while we process your document...'
}
);
break;
case 'confirm':
confirm({
title: 'Delete Document',
message: 'Are you sure you want to permanently delete this document?',
dangerous: true
});
break;
}
};
const showDMSNotificationDemo = (type) => {
switch (type) {
case 'upload':
dms.documentUploaded('Annual_Report_2024.pdf');
break;
case 'share':
dms.documentShared('Budget_Analysis.xlsx', ['john.doe', 'jane.smith']);
break;
case 'sync':
dms.syncStarted();
setTimeout(() => dms.syncCompleted(42), 2000);
break;
case 'version':
dms.newVersionCreated('Project_Plan.pdf', '2.1');
break;
case 'access':
dms.accessGranted('Financial Reports folder');
break;
}
};
const toggleTableLoading = () => {
tableLoading.value = true;
setTimeout(() => {
tableLoading.value = false;
}, 2000);
};
const toggleTreeLoading = () => {
treeLoading.value = true;
setTimeout(() => {
treeLoading.value = false;
}, 1500);
};
// Touch interaction demo
const setupTouchDemo = () => {
const { setupTouchInteractions, onSwipeLeft, onSwipeRight, onTap, onLongPress } = useTouchInteractions();
const touchArea = document.getElementById('touch-demo-area');
if (touchArea) {
setupTouchInteractions(touchArea);
onSwipeLeft(() => {
info('Swiped left! In DMS, this could delete a file.');
});
onSwipeRight(() => {
info('Swiped right! In DMS, this could open file details.');
});
onTap(() => {
info('Tapped! This would select a file.');
});
onLongPress(() => {
info('Long pressed! This would show the context menu.');
});
}
};
// File size formatter
const formatFileSize = (bytes) => {
if (!bytes) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
};
// Date formatter
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString();
};
// Lifecycle
onMounted(() => {
setupTouchDemo();
isClient.value = true;
});
</script>
<template>
<div>
<!-- Loading state for SSR -->
<div v-if="!isClient" class="flex items-center justify-center min-h-screen">
<div class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p class="text-gray-600">Loading Design System Demo...</p>
</div>
</div>
<!-- Client-only content -->
<ClientOnly>
<ResponsiveContainer v-if="isClient" layout="sidebar" class="design-system-demo">
<!-- Sidebar Navigation -->
<template #sidebar>
<div class="h-full flex flex-col">
<div class="p-6 border-b border-gray-200 dark:border-gray-600">
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">
DMS Design System
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Interactive component demonstrations
</p>
</div>
<nav class="flex-1 p-4 space-y-2">
<button
v-for="section in demoSections"
:key="section.id"
@click="currentDemo = section.id"
:class="[
'w-full flex items-center space-x-3 px-3 py-2 rounded-md text-left transition-colors',
currentDemo === section.id
? 'bg-blue-600 text-white'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
]"
>
<Icon :name="section.icon" class="w-5 h-5" />
<span>{{ section.title }}</span>
</button>
</nav>
</div>
</template>
<!-- Main Content -->
<div class="space-y-8">
<!-- Design System Overview -->
<section v-if="currentDemo === 'overview'" class="space-y-6">
<div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-4">
Design System Overview
</h1>
<p class="text-lg text-gray-600 dark:text-gray-400">
A comprehensive design system for the Document Management System featuring consistent components, responsive design, and enhanced user experience.
</p>
</div>
<!-- Color Palette -->
<div v-if="statusColors" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Status Colors
</h3>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
<div v-for="(color, status) in statusColors" :key="status" class="text-center">
<div
:class="[color.bg, color.border, 'w-16 h-16 rounded-lg border-2 mx-auto mb-2']"
></div>
<p class="text-sm font-medium capitalize">{{ status }}</p>
<p :class="[color.text, 'text-xs']">{{ status }} state</p>
</div>
</div>
</div>
<!-- Typography Scale -->
<div v-if="tokens?.typography" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Typography Scale
</h3>
<div class="space-y-3">
<div v-for="(size, key) in tokens.typography.sizes" :key="key" class="flex items-center space-x-4">
<span class="w-12 text-xs text-gray-500 dark:text-gray-400">{{ key }}</span>
<span :style="{ fontSize: size }" class="text-gray-900 dark:text-gray-100">
The quick brown fox jumps over the lazy dog
</span>
</div>
</div>
</div>
<!-- Spacing System -->
<div v-if="tokens?.spacing" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Spacing System
</h3>
<div class="space-y-2">
<div v-for="(space, key) in tokens.spacing" :key="key" class="flex items-center space-x-4">
<span class="w-12 text-xs text-gray-500 dark:text-gray-400">{{ key }}</span>
<div
class="bg-blue-200 dark:bg-blue-300 rounded"
:style="{ width: space, height: '20px' }"
></div>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ space }}</span>
</div>
</div>
</div>
</section>
<!-- Base Components -->
<section v-if="currentDemo === 'components'" class="space-y-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Base Components
</h1>
<!-- Buttons -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Button Variants
</h3>
<div class="flex flex-wrap gap-4">
<RsButton variant="primary">Primary</RsButton>
<RsButton variant="secondary">Secondary</RsButton>
<RsButton variant="secondary-outline">Secondary Outline</RsButton>
<RsButton variant="danger">Danger</RsButton>
<RsButton variant="success">Success</RsButton>
</div>
</div>
<!-- Modal Demo -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Enhanced Modal
</h3>
<RsButton @click="showModal = true">Show Modal</RsButton>
<BaseModal
v-model:visible="showModal"
title="Enhanced Modal Dialog"
size="lg"
@confirm="success && success('Modal confirmed!')"
@cancel="info && info('Modal cancelled')"
>
<div class="space-y-4">
<p class="text-gray-600 dark:text-gray-400">
This is an enhanced modal with consistent styling, animations, and responsive behavior.
</p>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Features:</h4>
<ul class="list-disc list-inside space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li>Responsive sizing and positioning</li>
<li>Keyboard navigation and focus management</li>
<li>Smooth entrance and exit animations</li>
<li>Dark mode support</li>
<li>Accessibility features</li>
</ul>
</div>
</div>
</BaseModal>
</div>
</section>
<!-- Loading States -->
<section v-if="currentDemo === 'loading'" class="space-y-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Loading States
</h1>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Spinners -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Spinner Variants
</h3>
<div class="space-y-4">
<LoadingStates type="spinner" size="sm" message="Small spinner" />
<LoadingStates type="spinner" size="md" message="Medium spinner" />
<LoadingStates type="spinner" size="lg" message="Large spinner" />
<LoadingStates type="pulse" message="Pulse animation" />
</div>
</div>
<!-- Skeleton Loaders -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Skeleton Loaders
</h3>
<LoadingStates type="skeleton-list" :count="3" />
</div>
<!-- Table Skeleton -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6 lg:col-span-2">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Table Skeleton
</h3>
<LoadingStates type="skeleton-table" :count="4" />
</div>
<!-- Card Skeleton -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6 lg:col-span-2">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Card Skeletons
</h3>
<LoadingStates type="skeleton-card" :count="3" />
</div>
</div>
</section>
<!-- Notifications -->
<section v-if="currentDemo === 'notifications'" class="space-y-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Notification System
</h1>
<!-- Basic Notifications -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Basic Notifications
</h3>
<div class="flex flex-wrap gap-4">
<RsButton @click="showNotificationDemo('success')" variant="success">
Success Toast
</RsButton>
<RsButton @click="showNotificationDemo('error')" variant="danger">
Error Toast
</RsButton>
<RsButton @click="showNotificationDemo('warning')" variant="secondary">
Warning Toast
</RsButton>
<RsButton @click="showNotificationDemo('info')" variant="primary">
Info Toast
</RsButton>
<RsButton @click="showNotificationDemo('loading')" variant="secondary-outline">
Loading Toast
</RsButton>
<RsButton @click="showNotificationDemo('confirm')" variant="secondary-outline">
Confirmation Dialog
</RsButton>
</div>
</div>
<!-- DMS-Specific Notifications -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
DMS-Specific Notifications
</h3>
<div class="flex flex-wrap gap-4">
<RsButton @click="showDMSNotificationDemo('upload')" variant="success">
Document Uploaded
</RsButton>
<RsButton @click="showDMSNotificationDemo('share')" variant="primary">
Document Shared
</RsButton>
<RsButton @click="showDMSNotificationDemo('sync')" variant="secondary">
Sync Operation
</RsButton>
<RsButton @click="showDMSNotificationDemo('version')" variant="secondary-outline">
New Version
</RsButton>
<RsButton @click="showDMSNotificationDemo('access')" variant="success">
Access Granted
</RsButton>
</div>
</div>
</section>
<!-- Data Table -->
<section v-if="currentDemo === 'table'" class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Advanced Data Table
</h1>
<RsButton @click="toggleTableLoading" :disabled="tableLoading">
{{ tableLoading ? 'Loading...' : 'Demo Loading' }}
</RsButton>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Advanced Data Table
</h3>
<div class="h-96 overflow-hidden">
<AdvancedDataTable
:data="sampleTableData"
:columns="tableColumns"
:actions="tableActions"
:loading="tableLoading"
:pagination="{ page: 1, pageSize: 10, total: 4, showSizeChanger: true }"
selectable
@action-click="(action, row) => info && info(`Action '${action}' clicked for ${row.name}`)"
@row-click="(row) => info && info(`Clicked on ${row.name}`)"
@selection-change="(selected) => info && info(`Selected ${selected.length} items`)"
/>
</div>
</div>
</section>
<!-- File Explorer Tree -->
<section v-if="currentDemo === 'tree'" class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Windows Explorer Tree
</h1>
<RsButton @click="toggleTreeLoading" :disabled="treeLoading">
{{ treeLoading ? 'Loading...' : 'Demo Loading' }}
</RsButton>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 overflow-hidden">
<div class="h-96">
<WindowsExplorerTree
:data="sampleTreeData"
:loading="treeLoading"
@node-click="(node) => info && info(`Clicked on ${node.name}`)"
@node-rename="(data) => success && success(`Renamed to ${data.newName}`)"
@folder-create="(data) => success && success(`Created folder: ${data.name}`)"
@file-upload="() => dms && dms.documentUploaded('NewDocument.pdf')"
/>
</div>
</div>
</section>
<!-- Responsive Design -->
<section v-if="currentDemo === 'responsive'" class="space-y-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Responsive Design
</h1>
<div v-if="utils?.responsive" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Breakpoint System
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="(width, breakpoint) in utils.responsive" :key="breakpoint" class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<h4 class="font-medium text-gray-900 dark:text-gray-100">{{ breakpoint }}</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">{{ width }}</p>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Layout Patterns
</h3>
<div class="space-y-4">
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Stack Layout</h4>
<div class="flex flex-col space-y-2">
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Item 1</div>
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Item 2</div>
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Item 3</div>
</div>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Grid Layout</h4>
<div class="grid grid-cols-3 gap-2">
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Grid 1</div>
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Grid 2</div>
<div class="bg-blue-200 dark:bg-blue-300 p-2 rounded">Grid 3</div>
</div>
</div>
</div>
</div>
</section>
<!-- Touch Interactions -->
<section v-if="currentDemo === 'touch'" class="space-y-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
Touch Interactions
</h1>
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Interactive Touch Area
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Try different touch gestures on the area below (works on mobile devices):
</p>
<div
id="touch-demo-area"
class="bg-gradient-to-br from-blue-100 to-blue-200 dark:from-blue-200 dark:to-blue-300
border-2 border-dashed border-blue-500 rounded-lg p-8 text-center"
>
<Icon name="mdi:gesture-tap" class="w-16 h-16 mx-auto mb-4 text-blue-600" />
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
Touch Gesture Area
</h4>
<p class="text-gray-600 dark:text-gray-400 text-sm">
Tap, long press, swipe left/right to test interactions
</p>
</div>
<div class="mt-6 grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<Icon name="mdi:gesture-tap" class="w-6 h-6 mx-auto mb-1 text-blue-500" />
<p class="font-medium">Tap</p>
<p class="text-gray-500 dark:text-gray-400">Select file</p>
</div>
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<Icon name="mdi:gesture-tap-hold" class="w-6 h-6 mx-auto mb-1 text-green-500" />
<p class="font-medium">Long Press</p>
<p class="text-gray-500 dark:text-gray-400">Context menu</p>
</div>
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<Icon name="mdi:gesture-swipe-left" class="w-6 h-6 mx-auto mb-1 text-red-500" />
<p class="font-medium">Swipe Left</p>
<p class="text-gray-500 dark:text-gray-400">Delete action</p>
</div>
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<Icon name="mdi:gesture-swipe-right" class="w-6 h-6 mx-auto mb-1 text-purple-500" />
<p class="font-medium">Swipe Right</p>
<p class="text-gray-500 dark:text-gray-400">File details</p>
</div>
</div>
</div>
</section>
</div>
<!-- Notification Display Component -->
<NotificationDisplay />
</ResponsiveContainer>
<template #fallback>
<div class="flex items-center justify-center min-h-screen">
<div class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p class="text-gray-600">Loading components...</p>
</div>
</div>
</template>
</ClientOnly>
</div>
</template>
<style scoped>
.design-system-demo {
min-height: 100vh;
}
/* Touch demo area enhancements */
#touch-demo-area {
user-select: none;
touch-action: manipulation;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* Demo section transitions */
.demo-section {
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.grid.grid-cols-2.md\\:grid-cols-4 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid.grid-cols-1.lg\\:grid-cols-2 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
</style>