"添加前端模板和运行代码模块"
This commit is contained in:
119
qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx
Normal file
119
qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Routes, Route, Link, useLocation } from 'react-router-dom';
|
||||
import { useAppStore } from './store/appStore';
|
||||
|
||||
// 页面组件
|
||||
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
|
||||
const ComponentShowcase = React.lazy(() => import('./pages/ComponentShowcase'));
|
||||
const InteractiveDemo = React.lazy(() => import('./pages/InteractiveDemo'));
|
||||
const ConfigurationGuide = React.lazy(() => import('./pages/ConfigurationGuide'));
|
||||
|
||||
// 通用组件
|
||||
const LoadingSpinner: React.FC = () => (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '2rem'
|
||||
}}>
|
||||
<div style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
border: '3px solid #f3f3f3',
|
||||
borderTop: '3px solid #3498db',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite'
|
||||
}} />
|
||||
<style>{`
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Navigation: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const { theme, toggleTheme } = useAppStore();
|
||||
|
||||
const navItems = [
|
||||
{ path: '/', label: 'Dashboard', icon: '🏠' },
|
||||
{ path: '/showcase', label: 'Component Showcase', icon: '🎨' },
|
||||
{ path: '/interactive', label: 'Interactive Demo', icon: '🎯' },
|
||||
{ path: '/config', label: 'Configuration', icon: '⚙️' }
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className={`nav ${theme}`} data-component="navigation">
|
||||
<div className="nav-brand" data-element="brand">
|
||||
<h1>Advanced Design Mode</h1>
|
||||
<span className="version">v1.0.0</span>
|
||||
</div>
|
||||
|
||||
<div className="nav-links" data-element="nav-links">
|
||||
{navItems.map(item => (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={`nav-link ${location.pathname === item.path ? 'active' : ''}`}
|
||||
data-nav-item={item.path.replace('/', '') || 'dashboard'}
|
||||
>
|
||||
<span className="icon">{item.icon}</span>
|
||||
<span className="label">{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="nav-actions" data-element="actions">
|
||||
<button
|
||||
className="theme-toggle"
|
||||
onClick={toggleTheme}
|
||||
data-action="toggle-theme"
|
||||
title="Toggle theme"
|
||||
>
|
||||
{theme === 'light' ? '🌙' : '☀️'}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { isLoading, error } = useAppStore();
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="app-error">
|
||||
<h1>Application Error</h1>
|
||||
<p>{error}</p>
|
||||
<button onClick={() => window.location.reload()}>
|
||||
Reload Application
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`app ${useAppStore.getState().theme}`} data-app="advanced-demo">
|
||||
<Navigation />
|
||||
|
||||
<main className="main-content" data-component="main">
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/showcase" element={<ComponentShowcase />} />
|
||||
<Route path="/interactive" element={<InteractiveDemo />} />
|
||||
<Route path="/config" element={<ConfigurationGuide />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
<footer className="app-footer" data-component="footer">
|
||||
<p>Built with Vite + React + TypeScript + AppDev Design Mode Plugin</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
export interface DemoElementProps {
|
||||
element: {
|
||||
id: string;
|
||||
tag: string;
|
||||
content: string;
|
||||
className: string;
|
||||
};
|
||||
isDesignMode: boolean;
|
||||
onSelect: (element: HTMLElement) => void;
|
||||
}
|
||||
|
||||
export const DemoElement: React.FC<DemoElementProps> = ({ element, isDesignMode, onSelect }) => {
|
||||
const Tag = element.tag as any;
|
||||
|
||||
return (
|
||||
<Tag
|
||||
id={element.id}
|
||||
className={cn(
|
||||
element.className,
|
||||
"transition-all duration-200 cursor-default",
|
||||
isDesignMode && "hover:outline hover:outline-2 hover:outline-blue-400 hover:outline-offset-2 cursor-pointer",
|
||||
"data-[selected=true]:outline data-[selected=true]:outline-2 data-[selected=true]:outline-blue-600 data-[selected=true]:outline-offset-2"
|
||||
)}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
if (isDesignMode) {
|
||||
e.preventDefault();
|
||||
onSelect(e.currentTarget as HTMLElement);
|
||||
}
|
||||
}}
|
||||
data-element={element.id}
|
||||
>
|
||||
{element.content}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
957
qiming-vite-plugin-design-mode/examples/advanced/src/index.css
Normal file
957
qiming-vite-plugin-design-mode/examples/advanced/src/index.css
Normal file
@@ -0,0 +1,957 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Advanced AppDev Design Mode Example Styles */
|
||||
|
||||
/* CSS Variables for theming */
|
||||
:root {
|
||||
/* Light theme colors */
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8fafc;
|
||||
--bg-tertiary: #f1f5f9;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--text-muted: #94a3b8;
|
||||
--border-color: #e2e8f0;
|
||||
--border-hover: #cbd5e1;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #8b5cf6;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
--spacing-2xl: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-mono: 'Fira Code', 'Monaco', 'Cascadia Code', monospace;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
.dark {
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--border-color: #334155;
|
||||
--border-hover: #475569;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* App layout */
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.nav-brand h1 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-brand .version {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-md);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--accent-primary);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.nav-link .icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: var(--spacing-xl);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Dashboard specific styles */
|
||||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Welcome section */
|
||||
.welcome-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xl);
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
border-radius: var(--radius-xl);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.welcome-content p {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.status-indicators {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--success);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section-title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Metrics section */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: var(--bg-secondary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.metric-header h3 {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.metric-change.up {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.metric-change.down {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.metric-change.neutral {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Feature demonstrations */
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.feature-demo {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.feature-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.feature-header h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.feature-tabs {
|
||||
display: flex;
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.feature-description {
|
||||
padding: var(--spacing-lg);
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.demo-container,
|
||||
.code-container {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.code-container {
|
||||
background: var(--bg-tertiary);
|
||||
overflow-x: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Component registry */
|
||||
.components-section {
|
||||
margin-top: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
width: 300px;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.components-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.component-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-lg);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.component-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.component-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.component-header h4 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.component-type {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.props-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prop-tag {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Demo specific styles */
|
||||
.source-map-demo {
|
||||
text-align: center;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.demo-element {
|
||||
display: inline-block;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.demo-element:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.component-tree {
|
||||
padding: var(--spacing-lg);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
margin: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.tree-node.root>.node-label {
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.children {
|
||||
margin-left: var(--spacing-lg);
|
||||
border-left: 2px solid var(--border-color);
|
||||
padding-left: var(--spacing-md);
|
||||
}
|
||||
|
||||
.node-label {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.live-edit-demo {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.editable-element {
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.editable-element:hover {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.style-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.style-btn {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.style-btn.primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.style-btn.secondary {
|
||||
background: var(--accent-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.style-btn.danger {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.style-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.app-footer {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: var(--spacing-xl);
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* App error state */
|
||||
.app-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 50vh;
|
||||
padding: var(--spacing-xl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-error h1 {
|
||||
color: var(--danger);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.app-error button {
|
||||
margin-top: var(--spacing-lg);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.app-error button:hover {
|
||||
background: var(--accent-secondary);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-content h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.components-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Design mode specific styles */
|
||||
[data-design-mode-enabled="true"] [data-source-info] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-design-mode-enabled="true"] [data-source-info]:hover {
|
||||
outline: 2px dashed var(--accent-primary) !important;
|
||||
outline-offset: 2px !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
|
||||
.nav,
|
||||
.app-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Interactive Demo Styles */
|
||||
.interactive-demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
background: var(--bg-secondary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.design-mode-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.design-mode-toggle.active {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.element-editor {
|
||||
background: var(--bg-primary);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-xl);
|
||||
margin-top: var(--spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.demo-workspace {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: var(--spacing-xl);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.preview-area {
|
||||
background: var(--bg-secondary);
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-xl);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.preview-area.design-mode-active {
|
||||
border-color: var(--accent-primary);
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
background: var(--bg-primary);
|
||||
padding: var(--spacing-2xl);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.modifications-panel {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
font-size: 0.875rem;
|
||||
color: var(--danger);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.reset-btn:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.reset-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.modifications-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.modification-item {
|
||||
background: var(--bg-primary);
|
||||
padding: var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mod-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.property {
|
||||
color: var(--text-secondary);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.mod-values {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.value-change {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.new-value {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.mod-time {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.demo-footer {
|
||||
background: var(--bg-secondary);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.instructions ol {
|
||||
margin-left: var(--spacing-xl);
|
||||
margin-top: var(--spacing-md);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Demo Elements */
|
||||
.demo-title {
|
||||
font-size: 2.5rem;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.demo-description {
|
||||
font-size: 1.125rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.demo-button {
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
border-radius: var(--radius-md);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.demo-button.primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demo-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
background: white;
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-top: var(--spacing-xl);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Selected state for design mode */
|
||||
.selected {
|
||||
outline: 2px solid var(--accent-primary) !important;
|
||||
outline-offset: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selected::after {
|
||||
content: 'Selected';
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
left: 0;
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import './index.css';
|
||||
|
||||
// 开发模式下的额外日志
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('🚀 Advanced AppDev Design Mode Example');
|
||||
console.log('🔧 Plugin Configuration:', {
|
||||
enabled: true,
|
||||
prefix: 'design-mode',
|
||||
verbose: true,
|
||||
typescript: true,
|
||||
react: true,
|
||||
routing: true,
|
||||
stateManagement: true
|
||||
});
|
||||
}
|
||||
|
||||
// 错误边界组件
|
||||
class ErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode },
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: { children: React.ReactNode }) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): { hasError: boolean } {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('Error caught by boundary:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={{ padding: '2rem', textAlign: 'center', color: '#e53e3e' }}>
|
||||
<h1>Something went wrong.</h1>
|
||||
<p>Please refresh the page to try again.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,197 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const ComponentShowcase: React.FC = () => {
|
||||
const [activeCategory, setActiveCategory] = useState('buttons');
|
||||
const [selectedComponent, setSelectedComponent] = useState<string | null>(null);
|
||||
|
||||
const categories = [
|
||||
{ id: 'buttons', label: 'Buttons', icon: '🔘' },
|
||||
{ id: 'forms', label: 'Forms', icon: '📝' },
|
||||
{ id: 'cards', label: 'Cards', icon: '🃏' },
|
||||
{ id: 'navigation', label: 'Navigation', icon: '🧭' }
|
||||
];
|
||||
|
||||
const components = {
|
||||
buttons: [
|
||||
{
|
||||
id: 'primary-btn',
|
||||
name: 'Primary Button',
|
||||
description: 'Main action button with primary styling',
|
||||
component: (
|
||||
<button className="btn-primary">Primary Action</button>
|
||||
),
|
||||
code: `<button className="btn-primary">Primary Action</button>`
|
||||
},
|
||||
{
|
||||
id: 'secondary-btn',
|
||||
name: 'Secondary Button',
|
||||
description: 'Secondary action button',
|
||||
component: (
|
||||
<button className="btn-secondary">Secondary Action</button>
|
||||
),
|
||||
code: `<button className="btn-secondary">Secondary Action</button>`
|
||||
},
|
||||
{
|
||||
id: 'danger-btn',
|
||||
name: 'Danger Button',
|
||||
description: 'Destructive action button',
|
||||
component: (
|
||||
<button className="btn-danger">Delete Item</button>
|
||||
),
|
||||
code: `<button className="btn-danger">Delete Item</button>`
|
||||
}
|
||||
],
|
||||
forms: [
|
||||
{
|
||||
id: 'input-field',
|
||||
name: 'Input Field',
|
||||
description: 'Standard text input with label',
|
||||
component: (
|
||||
<div className="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" placeholder="Enter username" className="form-input" />
|
||||
</div>
|
||||
),
|
||||
code: `<div className="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" placeholder="Enter username" className="form-input" />
|
||||
</div>`
|
||||
},
|
||||
{
|
||||
id: 'checkbox-group',
|
||||
name: 'Checkbox Group',
|
||||
description: 'Multiple choice checkboxes',
|
||||
component: (
|
||||
<div className="form-group">
|
||||
<label>Preferences</label>
|
||||
<div className="checkbox-group">
|
||||
<label><input type="checkbox" /> Email notifications</label>
|
||||
<label><input type="checkbox" /> SMS alerts</label>
|
||||
<label><input type="checkbox" /> Push notifications</label>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
code: `<div className="form-group">
|
||||
<label>Preferences</label>
|
||||
<div className="checkbox-group">
|
||||
<label><input type="checkbox" /> Email notifications</label>
|
||||
<label><input type="checkbox" /> SMS alerts</label>
|
||||
<label><input type="checkbox" /> Push notifications</label>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
],
|
||||
cards: [
|
||||
{
|
||||
id: 'info-card',
|
||||
name: 'Info Card',
|
||||
description: 'Card with title, content and actions',
|
||||
component: (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3>Card Title</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>This is a sample card component with some descriptive content.</p>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<button className="btn-text">Learn More</button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
code: `<div className="card">
|
||||
<div className="card-header">
|
||||
<h3>Card Title</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>This is a sample card component with some descriptive content.</p>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<button className="btn-text">Learn More</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
],
|
||||
navigation: [
|
||||
{
|
||||
id: 'breadcrumb',
|
||||
name: 'Breadcrumb',
|
||||
description: 'Navigation breadcrumb trail',
|
||||
component: (
|
||||
<nav className="breadcrumb">
|
||||
<a href="#">Home</a>
|
||||
<span>/</span>
|
||||
<a href="#">Products</a>
|
||||
<span>/</span>
|
||||
<span>Current Page</span>
|
||||
</nav>
|
||||
),
|
||||
code: `<nav className="breadcrumb">
|
||||
<a href="#">Home</a>
|
||||
<span>/</span>
|
||||
<a href="#">Products</a>
|
||||
<span>/</span>
|
||||
<span>Current Page</span>
|
||||
</nav>`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="component-showcase" data-page="showcase">
|
||||
<header className="showcase-header">
|
||||
<h1>Component Showcase</h1>
|
||||
<p>Explore various UI components and their source mappings</p>
|
||||
</header>
|
||||
|
||||
<div className="showcase-content">
|
||||
<aside className="category-sidebar">
|
||||
<h3>Categories</h3>
|
||||
<nav className="category-nav">
|
||||
{categories.map(category => (
|
||||
<button
|
||||
key={category.id}
|
||||
className={`category-btn ${activeCategory === category.id ? 'active' : ''}`}
|
||||
onClick={() => setActiveCategory(category.id)}
|
||||
data-category={category.id}
|
||||
>
|
||||
<span className="icon">{category.icon}</span>
|
||||
<span className="label">{category.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main className="components-display">
|
||||
<div className="components-grid">
|
||||
{components[activeCategory as keyof typeof components]?.map(component => (
|
||||
<div
|
||||
key={component.id}
|
||||
className={`component-item ${selectedComponent === component.id ? 'selected' : ''}`}
|
||||
data-component={component.id}
|
||||
onClick={() => setSelectedComponent(
|
||||
selectedComponent === component.id ? null : component.id
|
||||
)}
|
||||
>
|
||||
<div className="component-preview" data-element="preview">
|
||||
{component.component}
|
||||
</div>
|
||||
<div className="component-info">
|
||||
<h4 data-element="name">{component.name}</h4>
|
||||
<p data-element="description">{component.description}</p>
|
||||
</div>
|
||||
{selectedComponent === component.id && (
|
||||
<div className="component-code" data-element="code">
|
||||
<pre><code>{component.code}</code></pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentShowcase;
|
||||
@@ -0,0 +1,292 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const ConfigurationGuide: React.FC = () => {
|
||||
const [activeSection, setActiveSection] = useState('basic');
|
||||
|
||||
const sections = [
|
||||
{ id: 'basic', label: 'Basic Setup', icon: '⚙️' },
|
||||
{ id: 'options', label: 'Configuration Options', icon: '🔧' },
|
||||
{ id: 'advanced', label: 'Advanced Usage', icon: '🚀' },
|
||||
{ id: 'troubleshooting', label: 'Troubleshooting', icon: '🛠️' }
|
||||
];
|
||||
|
||||
const configExamples = {
|
||||
basic: {
|
||||
title: 'Basic Setup',
|
||||
description: 'Get started with the plugin in minutes',
|
||||
code: `// vite.config.ts
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import appdevDesignMode from 'vite-plugin-appdev-design-mode';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
appdevDesignMode() // Basic configuration
|
||||
]
|
||||
});`,
|
||||
explanation: 'This is the minimal configuration needed to get started. The plugin will use all default options.'
|
||||
},
|
||||
options: {
|
||||
title: 'Configuration Options',
|
||||
description: 'Customize the plugin behavior with various options',
|
||||
code: `// vite.config.ts
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import appdevDesignMode from 'vite-plugin-appdev-design-mode';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
appdevDesignMode({
|
||||
// Enable/disable the plugin
|
||||
enabled: true,
|
||||
|
||||
// Only enable in development
|
||||
enableInProduction: false,
|
||||
|
||||
// Custom attribute prefix
|
||||
attributePrefix: 'design-mode',
|
||||
|
||||
// Enable verbose logging
|
||||
verbose: true,
|
||||
|
||||
// File patterns to exclude
|
||||
exclude: [
|
||||
'node_modules',
|
||||
'.git',
|
||||
'dist',
|
||||
'**/*.test.{ts,tsx,js,jsx}'
|
||||
],
|
||||
|
||||
// File patterns to include
|
||||
include: [
|
||||
'src/**/*.{ts,tsx,js,jsx}',
|
||||
'components/**/*.{ts,tsx,js,jsx}'
|
||||
]
|
||||
})
|
||||
]
|
||||
});`,
|
||||
explanation: 'This configuration shows all available options and their default values.'
|
||||
},
|
||||
advanced: {
|
||||
title: 'Advanced Usage',
|
||||
description: 'Advanced patterns and integration techniques',
|
||||
code: `// vite.config.ts
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import appdevDesignMode from 'vite-plugin-appdev-design-mode';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
// Multiple configurations for different environments
|
||||
appdevDesignMode({
|
||||
enabled: true,
|
||||
attributePrefix: 'dev-mode',
|
||||
verbose: process.env.NODE_ENV === 'development',
|
||||
|
||||
// Conditional inclusion based on environment
|
||||
include: process.env.NODE_ENV === 'development'
|
||||
? ['src/**/*.{ts,tsx,js,jsx}']
|
||||
: [], // Disable in production
|
||||
|
||||
// Custom exclude patterns
|
||||
exclude: [
|
||||
'node_modules',
|
||||
'.git',
|
||||
'dist',
|
||||
'build',
|
||||
'**/*.test.{ts,tsx,js,jsx}',
|
||||
'**/__tests__/**',
|
||||
'stories/**',
|
||||
'**/*.stories.{ts,tsx}'
|
||||
]
|
||||
})
|
||||
],
|
||||
|
||||
// Server configuration for API endpoints
|
||||
server: {
|
||||
port: 5173,
|
||||
host: true,
|
||||
middlewareMode: false
|
||||
}
|
||||
});
|
||||
|
||||
// Environment-specific configuration
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: isDevelopment ? [
|
||||
react(),
|
||||
appdevDesignMode({
|
||||
verbose: true,
|
||||
attributePrefix: 'dev'
|
||||
})
|
||||
] : [react()]
|
||||
});`,
|
||||
explanation: 'This example shows how to configure the plugin differently based on environment and use cases.'
|
||||
},
|
||||
troubleshooting: {
|
||||
title: 'Troubleshooting',
|
||||
description: 'Common issues and their solutions',
|
||||
code: `# Common Issues and Solutions
|
||||
|
||||
## Plugin not injecting source information
|
||||
|
||||
### Problem: Elements don't have data attributes
|
||||
### Solution: Check file inclusion patterns
|
||||
\`\`\`js
|
||||
appdevDesignMode({
|
||||
include: ['src/**/*.{ts,tsx,js,jsx}'] // Make sure this matches your project structure
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## Performance issues
|
||||
|
||||
### Problem: Plugin slowing down development server
|
||||
### Solution: Exclude unnecessary files
|
||||
\`\`\`js
|
||||
appdevDesignMode({
|
||||
exclude: [
|
||||
'node_modules',
|
||||
'**/*.test.{ts,tsx,js,jsx}',
|
||||
'**/__tests__/**',
|
||||
'stories/**'
|
||||
]
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## TypeScript errors
|
||||
|
||||
### Problem: Cannot find module '@babel/core'
|
||||
### Solution: Install peer dependencies
|
||||
\`\`\`bash
|
||||
npm install --save-dev @babel/core @babel/traverse @babel/types
|
||||
\`\`\`
|
||||
|
||||
## Custom configuration not working
|
||||
|
||||
### Problem: Plugin using default configuration
|
||||
### Solution: Check import path and plugin options
|
||||
\`\`\`js
|
||||
// Correct import
|
||||
import appdevDesignMode from 'vite-plugin-appdev-design-mode';
|
||||
|
||||
// Make sure options are passed correctly
|
||||
appdevDesignMode({
|
||||
enabled: true, // This should not be undefined
|
||||
attributePrefix: 'custom'
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## API endpoints not accessible
|
||||
|
||||
### Problem: Cannot access plugin API endpoints
|
||||
### Solution: Check server configuration and CORS
|
||||
\`\`\`js
|
||||
export default defineConfig({
|
||||
server: {
|
||||
cors: true, // Enable CORS for API access
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
}
|
||||
})
|
||||
\`\`\``,
|
||||
explanation: 'This section covers common problems developers encounter and their solutions.'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="config-guide" data-page="config">
|
||||
<header className="guide-header">
|
||||
<h1>Configuration Guide</h1>
|
||||
<p>Learn how to configure and use the AppDev Design Mode plugin effectively</p>
|
||||
</header>
|
||||
|
||||
<div className="guide-content">
|
||||
<nav className="guide-sidebar">
|
||||
<h3>Sections</h3>
|
||||
<ul className="guide-nav">
|
||||
{Object.entries(configExamples).map(([key, section]) => (
|
||||
<li key={key}>
|
||||
<button
|
||||
className={`nav-item ${activeSection === key ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection(key)}
|
||||
data-section={key}
|
||||
>
|
||||
<span className="icon">{sections.find(s => s.id === key)?.icon}</span>
|
||||
<span className="label">{section.title}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main className="guide-main">
|
||||
<div className="guide-section">
|
||||
<div className="section-header">
|
||||
<h2 data-element="section-title">
|
||||
{configExamples[activeSection as keyof typeof configExamples].title}
|
||||
</h2>
|
||||
<p className="section-description" data-element="section-description">
|
||||
{configExamples[activeSection as keyof typeof configExamples].description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="content-blocks">
|
||||
<div className="code-block" data-element="code-block">
|
||||
<div className="code-header">
|
||||
<h4>Configuration</h4>
|
||||
<button
|
||||
className="copy-btn"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(configExamples[activeSection as keyof typeof configExamples].code);
|
||||
}}
|
||||
data-action="copy-code"
|
||||
>
|
||||
📋 Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre className="code-content">
|
||||
<code>{configExamples[activeSection as keyof typeof configExamples].code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div className="explanation-block" data-element="explanation">
|
||||
<h4>Explanation</h4>
|
||||
<p>{configExamples[activeSection as keyof typeof configExamples].explanation}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer className="guide-footer">
|
||||
<div className="additional-resources">
|
||||
<h4>Additional Resources</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vitejs.dev/config/" target="_blank" rel="noopener noreferrer">
|
||||
📖 Vite Configuration Documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://babeljs.io/docs/babel-plugin-there/" target="_blank" rel="noopener noreferrer">
|
||||
🔧 Babel Plugin API Documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/vite-plugin-appdev-design-mode" target="_blank" rel="noopener noreferrer">
|
||||
📦 Plugin Repository
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigurationGuide;
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAppStore } from '../store/appStore';
|
||||
|
||||
interface MetricCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
change?: string;
|
||||
trend?: 'up' | 'down' | 'neutral';
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const MetricCard: React.FC<MetricCardProps> = ({ title, value, change, trend, icon }) => (
|
||||
<div className="metric-card" data-component="metric-card">
|
||||
<div className="metric-header" data-element="metric-header">
|
||||
<h3 data-element="metric-title">{title}</h3>
|
||||
<span className="metric-icon" data-element="metric-icon">{icon}</span>
|
||||
</div>
|
||||
<div className="metric-value" data-element="metric-value">{value}</div>
|
||||
{change && (
|
||||
<div className={`metric-change ${trend}`} data-element="metric-change">
|
||||
{trend === 'up' && '↗️'}
|
||||
{trend === 'down' && '↘️'}
|
||||
{trend === 'neutral' && '➡️'}
|
||||
{change}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FeatureDemoProps {
|
||||
title: string;
|
||||
description: string;
|
||||
code: string;
|
||||
demo: React.ReactNode;
|
||||
}
|
||||
|
||||
const FeatureDemo: React.FC<FeatureDemoProps> = ({ title, description, code, demo }) => {
|
||||
const [showCode, setShowCode] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<'demo' | 'code'>('demo');
|
||||
|
||||
return (
|
||||
<div className="feature-demo" data-component="feature-demo">
|
||||
<div className="feature-header" data-element="feature-header">
|
||||
<h3 data-element="feature-title">{title}</h3>
|
||||
<div className="feature-tabs" data-element="tabs">
|
||||
<button
|
||||
className={`tab ${activeTab === 'demo' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('demo')}
|
||||
data-tab="demo"
|
||||
>
|
||||
Demo
|
||||
</button>
|
||||
<button
|
||||
className={`tab ${activeTab === 'code' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('code')}
|
||||
data-tab="code"
|
||||
>
|
||||
Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="feature-description" data-element="description">{description}</p>
|
||||
|
||||
<div className="feature-content" data-element="content">
|
||||
{activeTab === 'demo' ? (
|
||||
<div className="demo-container" data-element="demo">
|
||||
{demo}
|
||||
</div>
|
||||
) : (
|
||||
<pre className="code-container" data-element="code">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const { theme, user, components } = useAppStore();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const metrics = [
|
||||
{ title: 'Total Components', value: components.length, change: '+2', trend: 'up' as const, icon: '🧩' },
|
||||
{ title: 'Active Elements', value: 24, change: '+5', trend: 'up' as const, icon: '⚡' },
|
||||
{ title: 'Design Mode Sessions', value: 156, change: '+12', trend: 'up' as const, icon: '🎨' },
|
||||
{ title: 'Code Quality Score', value: '94%', change: '+2%', trend: 'up' as const, icon: '📊' }
|
||||
];
|
||||
|
||||
const componentList = components.filter(comp =>
|
||||
comp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
comp.type.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="dashboard" data-page="dashboard">
|
||||
{/* Welcome Section */}
|
||||
<section className="welcome-section" data-section="welcome">
|
||||
<div className="welcome-content" data-element="content">
|
||||
<h1 data-element="title">
|
||||
Welcome back, {user.name}!
|
||||
</h1>
|
||||
<p data-element="subtitle">
|
||||
Here's what's happening with your design mode plugin today.
|
||||
</p>
|
||||
</div>
|
||||
<div className="status-indicators" data-element="indicators">
|
||||
<div className="status-item" data-status="plugin-active">
|
||||
<span className="status-dot active"></span>
|
||||
<span>Plugin Active</span>
|
||||
</div>
|
||||
<div className="status-item" data-status="theme">
|
||||
<span className="theme-indicator">{theme === 'light' ? '☀️' : '🌙'}</span>
|
||||
<span>{theme.charAt(0).toUpperCase() + theme.slice(1)} Mode</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Metrics Grid */}
|
||||
<section className="metrics-section" data-section="metrics">
|
||||
<h2 className="section-title" data-element="section-title">System Metrics</h2>
|
||||
<div className="metrics-grid">
|
||||
{metrics.map((metric, index) => (
|
||||
<MetricCard key={index} {...metric} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Feature Demonstrations */}
|
||||
<section className="features-section" data-section="features">
|
||||
<h2 className="section-title" data-element="section-title">Plugin Features</h2>
|
||||
<div className="features-grid">
|
||||
<FeatureDemo
|
||||
title="Source Mapping"
|
||||
description="Automatic injection of source location information into DOM elements during compilation."
|
||||
code={`// 插件自动为JSX元素添加源码信息
|
||||
<div className="example" data-source-info="...">
|
||||
Content here
|
||||
</div>`}
|
||||
demo={
|
||||
<div className="source-map-demo">
|
||||
<div className="demo-element" data-element="mapped">
|
||||
Hover over me to see source mapping info
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<FeatureDemo
|
||||
title="Component Tree"
|
||||
description="Visual representation of component hierarchy with source locations."
|
||||
code={`// 自动生成的组件树
|
||||
App (App.tsx:12)
|
||||
├── Header (Header.tsx:5)
|
||||
├── Navigation (Nav.tsx:15)
|
||||
└── MainContent (Main.tsx:8)`}
|
||||
demo={
|
||||
<div className="component-tree">
|
||||
<div className="tree-node root" data-node="app">
|
||||
<div className="node-label">App</div>
|
||||
<div className="children">
|
||||
<div className="tree-node" data-node="header">
|
||||
<div className="node-label">Header</div>
|
||||
</div>
|
||||
<div className="tree-node" data-node="main">
|
||||
<div className="node-label">MainContent</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<FeatureDemo
|
||||
title="Live Editing"
|
||||
description="Real-time style modifications with automatic HMR integration."
|
||||
code={`// 修改样式实时生效
|
||||
const modifyStyles = async (elementId, newStyles) => {
|
||||
await fetch('/__modify_element_source', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ elementId, newStyles })
|
||||
});
|
||||
};`}
|
||||
demo={
|
||||
<div className="live-edit-demo">
|
||||
<div className="editable-element" data-editable="true">
|
||||
Click to edit styles
|
||||
</div>
|
||||
<div className="style-controls">
|
||||
<button className="style-btn primary">Primary</button>
|
||||
<button className="style-btn secondary">Secondary</button>
|
||||
<button className="style-btn danger">Danger</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Component Registry */}
|
||||
<section className="components-section" data-section="components">
|
||||
<div className="section-header" data-element="header">
|
||||
<h2 className="section-title" data-element="title">Component Registry</h2>
|
||||
<div className="search-container" data-element="search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search components..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="search-input"
|
||||
data-element="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="components-grid">
|
||||
{componentList.map((component) => (
|
||||
<div key={component.id} className="component-card" data-component={component.type}>
|
||||
<div className="component-header">
|
||||
<h4 data-element="name">{component.name}</h4>
|
||||
<span className="component-type" data-element="type">{component.type}</span>
|
||||
</div>
|
||||
<div className="component-info" data-element="info">
|
||||
<small>ID: {component.id}</small>
|
||||
<div className="props-preview">
|
||||
{Object.entries(component.props).slice(0, 2).map(([key, value]) => (
|
||||
<span key={key} className="prop-tag">
|
||||
{key}: {String(value)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { DemoElement } from '../components/DemoElement';
|
||||
|
||||
const InteractiveDemo: React.FC = () => {
|
||||
// No local state or context needed - Design Mode is handled globally by the plugin
|
||||
|
||||
const demoElements = [
|
||||
{
|
||||
id: 'hero-title',
|
||||
tag: 'h1',
|
||||
content: 'Interactive Design Mode Demo',
|
||||
className: 'text-4xl font-bold text-slate-900 mb-4'
|
||||
},
|
||||
{
|
||||
id: 'hero-description',
|
||||
tag: 'p',
|
||||
content: 'Click on elements in design mode to modify their properties in real-time',
|
||||
className: 'text-lg text-slate-600 mb-8 max-w-2xl'
|
||||
},
|
||||
{
|
||||
id: 'demo-button',
|
||||
tag: 'button',
|
||||
content: 'Click Me',
|
||||
className: 'px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors shadow-sm'
|
||||
},
|
||||
{
|
||||
id: 'demo-card',
|
||||
tag: 'div',
|
||||
content: 'Sample Card Content',
|
||||
className: 'mt-8 p-6 bg-white rounded-xl shadow-md border border-slate-200'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8" data-page="interactive">
|
||||
<header className="flex flex-col gap-6 bg-slate-50 p-8 rounded-xl border border-slate-200">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-slate-900">Interactive Design Mode</h1>
|
||||
<p className="text-sm text-slate-500">
|
||||
Design Mode is now injected globally. Use the floating toggle in the bottom right to enable it.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 min-h-[600px]">
|
||||
<div className="bg-slate-50 border-2 border-dashed border-slate-300 rounded-xl p-8 relative">
|
||||
<div className="bg-white p-12 rounded-lg shadow-sm min-h-full">
|
||||
{demoElements.map(element => (
|
||||
<DemoElement
|
||||
key={element.id}
|
||||
element={element}
|
||||
isDesignMode={false} // The global manager handles selection visuals
|
||||
onSelect={() => {}} // Handled globally
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className="bg-slate-50 p-6 rounded-xl border border-slate-200">
|
||||
<div className="max-w-3xl">
|
||||
<h4 className="font-semibold text-slate-900 mb-4">Instructions:</h4>
|
||||
<ol className="list-decimal list-inside space-y-2 text-slate-600 text-sm">
|
||||
<li>Toggle <strong>Design Mode</strong> using the floating button</li>
|
||||
<li>Click on any element to select and edit it</li>
|
||||
<li>Modifications are applied globally</li>
|
||||
</ol>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InteractiveDemo;
|
||||
@@ -0,0 +1,173 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
|
||||
interface AppState {
|
||||
// UI状态
|
||||
theme: 'light' | 'dark';
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
|
||||
// 应用状态
|
||||
currentPage: string;
|
||||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
preferences: {
|
||||
showTooltips: boolean;
|
||||
animations: boolean;
|
||||
compactMode: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// 组件状态
|
||||
components: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
props: Record<string, any>;
|
||||
children?: string[];
|
||||
parent?: string;
|
||||
}>;
|
||||
|
||||
// 设计模式状态
|
||||
designMode: {
|
||||
enabled: boolean;
|
||||
selectedElement: string | null;
|
||||
showGrid: boolean;
|
||||
showInfo: boolean;
|
||||
};
|
||||
|
||||
// Actions
|
||||
setTheme: (theme: 'light' | 'dark') => void;
|
||||
toggleTheme: () => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
setCurrentPage: (page: string) => void;
|
||||
updateUserPreferences: (preferences: Partial<AppState['user']['preferences']>) => void;
|
||||
|
||||
// 组件操作
|
||||
addComponent: (component: Omit<AppState['components'][0], 'id'>) => void;
|
||||
updateComponent: (id: string, updates: Partial<AppState['components'][0]>) => void;
|
||||
removeComponent: (id: string) => void;
|
||||
|
||||
// 设计模式操作
|
||||
setDesignModeEnabled: (enabled: boolean) => void;
|
||||
setSelectedElement: (element: string | null) => void;
|
||||
toggleGrid: () => void;
|
||||
toggleInfo: () => void;
|
||||
|
||||
// 重置状态
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
theme: 'light' as const,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
currentPage: 'dashboard',
|
||||
user: {
|
||||
name: 'Demo User',
|
||||
email: 'demo@example.com',
|
||||
preferences: {
|
||||
showTooltips: true,
|
||||
animations: true,
|
||||
compactMode: false
|
||||
}
|
||||
},
|
||||
components: [
|
||||
{
|
||||
id: 'header-1',
|
||||
name: 'AppHeader',
|
||||
type: 'header',
|
||||
props: { title: 'Advanced Demo', showNav: true }
|
||||
},
|
||||
{
|
||||
id: 'card-1',
|
||||
name: 'InfoCard',
|
||||
type: 'card',
|
||||
props: { title: 'Welcome', content: 'This is an advanced example' }
|
||||
}
|
||||
],
|
||||
designMode: {
|
||||
enabled: false,
|
||||
selectedElement: null,
|
||||
showGrid: false,
|
||||
showInfo: true
|
||||
}
|
||||
};
|
||||
|
||||
export const useAppStore = create<AppState>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
|
||||
// UI Actions
|
||||
setTheme: (theme) => set({ theme }, false, 'setTheme'),
|
||||
toggleTheme: () => set((state) => ({
|
||||
theme: state.theme === 'light' ? 'dark' : 'light'
|
||||
}), false, 'toggleTheme'),
|
||||
|
||||
setLoading: (loading) => set({ isLoading: loading }, false, 'setLoading'),
|
||||
setError: (error) => set({ error }, false, 'setError'),
|
||||
setCurrentPage: (page) => set({ currentPage: page }, false, 'setCurrentPage'),
|
||||
|
||||
updateUserPreferences: (preferences) => set((state) => ({
|
||||
user: {
|
||||
...state.user,
|
||||
preferences: { ...state.user.preferences, ...preferences }
|
||||
}
|
||||
}), false, 'updateUserPreferences'),
|
||||
|
||||
// Component Actions
|
||||
addComponent: (component) => set((state) => ({
|
||||
components: [
|
||||
...state.components,
|
||||
{ ...component, id: `${component.name}-${Date.now()}` }
|
||||
]
|
||||
}), false, 'addComponent'),
|
||||
|
||||
updateComponent: (id, updates) => set((state) => ({
|
||||
components: state.components.map(comp =>
|
||||
comp.id === id ? { ...comp, ...updates } : comp
|
||||
)
|
||||
}), false, 'updateComponent'),
|
||||
|
||||
removeComponent: (id) => set((state) => ({
|
||||
components: state.components.filter(comp => comp.id !== id)
|
||||
}), false, 'removeComponent'),
|
||||
|
||||
// Design Mode Actions
|
||||
setDesignModeEnabled: (enabled) => set((state) => ({
|
||||
designMode: { ...state.designMode, enabled }
|
||||
}), false, 'setDesignModeEnabled'),
|
||||
|
||||
setSelectedElement: (element) => set((state) => ({
|
||||
designMode: { ...state.designMode, selectedElement: element }
|
||||
}), false, 'setSelectedElement'),
|
||||
|
||||
toggleGrid: () => set((state) => ({
|
||||
designMode: { ...state.designMode, showGrid: !state.designMode.showGrid }
|
||||
}), false, 'toggleGrid'),
|
||||
|
||||
toggleInfo: () => set((state) => ({
|
||||
designMode: { ...state.designMode, showInfo: !state.designMode.showInfo }
|
||||
}), false, 'toggleInfo'),
|
||||
|
||||
// Reset
|
||||
reset: () => set(initialState, false, 'reset')
|
||||
}),
|
||||
{
|
||||
name: 'advanced-design-mode-storage',
|
||||
partialize: (state) => ({
|
||||
theme: state.theme,
|
||||
user: state.user,
|
||||
designMode: state.designMode
|
||||
})
|
||||
}
|
||||
),
|
||||
{
|
||||
name: 'app-store'
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user