"添加前端模板和运行代码模块"

This commit is contained in:
Codex
2026-06-01 13:43:09 +08:00
parent 24045274ad
commit 8092c4b1f8
587 changed files with 99761 additions and 0 deletions

View 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;

View File

@@ -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>
);
};

View 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;
}

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'
}
)
);

View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}