mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Generated by Spark: Add split screen code example with live preview
This commit is contained in:
30
PRD.md
30
PRD.md
@@ -26,11 +26,18 @@ A code snippet management application with an integrated component library showc
|
||||
- Success criteria: Snippet appears in grid immediately with proper syntax highlighting
|
||||
|
||||
**Snippet Viewing**
|
||||
- Functionality: Full-screen viewer with syntax highlighting and copy functionality
|
||||
- Purpose: Easy reading and copying of saved snippets
|
||||
- Functionality: Full-screen viewer with syntax highlighting, split-screen live preview for React code, and copy functionality
|
||||
- Purpose: Easy reading, testing, and copying of saved snippets
|
||||
- Trigger: Click on any snippet card
|
||||
- Progression: User clicks snippet → Full viewer opens → User reads code → Copies if needed → Closes viewer
|
||||
- Success criteria: Code displays with proper formatting, copy works reliably
|
||||
- Progression: User clicks snippet → Full viewer opens → User reads code → Toggles preview if available → Copies if needed → Closes viewer
|
||||
- Success criteria: Code displays with proper formatting, live preview renders React components accurately, copy works reliably
|
||||
|
||||
**Split-Screen Code Editor with Live Preview**
|
||||
- Functionality: Interactive code editor with live React component preview, resizable panels, and view mode switching (code-only, split, preview-only)
|
||||
- Purpose: Enable real-time testing and visualization of React code while editing
|
||||
- Trigger: Enable "Enable split-screen preview" checkbox when creating/editing JSX/TSX/JavaScript/TypeScript snippets
|
||||
- Progression: User enables preview → Split editor appears → User types code → Preview updates in real-time → User adjusts panel sizes or switches view modes → Saves snippet
|
||||
- Success criteria: Preview updates within 100ms of code changes, no lag during typing, error messages display clearly with AI help option
|
||||
|
||||
**Snippet Organization**
|
||||
- Functionality: Real-time search across title, description, language, and code content
|
||||
@@ -90,21 +97,27 @@ Animations should feel **smooth and purposeful**, enhancing navigation and feedb
|
||||
- **Components**:
|
||||
- Router (react-router-dom) for page navigation and routing
|
||||
- Custom Navigation component with hamburger menu using Framer Motion for slide-in animations
|
||||
- ResizablePanel (shadcn) for split-screen editor with adjustable panel sizes
|
||||
- Button (shadcn) for all interactive actions with hover effects
|
||||
- Card (shadcn) for snippet cards with hover elevation
|
||||
- Dialog (shadcn) for snippet creation and viewing
|
||||
- Input (shadcn) for search and form fields
|
||||
- Select (shadcn) for language picker dropdown
|
||||
- Alert Dialog (shadcn) for delete confirmations
|
||||
- Checkbox (shadcn) for preview toggle
|
||||
- Monaco Editor (@monaco-editor/react) for code editing with syntax highlighting
|
||||
- **Customizations**:
|
||||
- Custom Navigation drawer slides from left with backdrop overlay
|
||||
- Custom SplitScreenEditor with three view modes (code-only, split-screen, preview-only)
|
||||
- Active navigation items show bold text and filled background
|
||||
- Page transitions use fade and slide animations
|
||||
- Buttons: Rest → Hover (brightness increase) → Active (scale 0.98) → Disabled (opacity 50%)
|
||||
- ResizableHandle shows interactive draggable separator with visual feedback
|
||||
- **States**:
|
||||
- Navigation items: Default → Hover (background muted) → Active (background primary, bold text)
|
||||
- Buttons: Rest → Hover (brightness increase) → Active (scale down) → Disabled (opacity 50%)
|
||||
- Cards: Rest → Hover (slight elevation and border glow)
|
||||
- Split Editor view toggle: Default → Hover (subtle highlight) → Active (filled background)
|
||||
- **Icon Selection**:
|
||||
- House (regular/bold) for home navigation
|
||||
- Atom (regular/bold) for atoms page
|
||||
@@ -114,17 +127,24 @@ Animations should feel **smooth and purposeful**, enhancing navigation and feedb
|
||||
- X (bold) for close menu
|
||||
- Trash (regular) for delete
|
||||
- MagnifyingGlass (regular) for search
|
||||
- Code (regular) for code-only view
|
||||
- Eye (regular) for preview-only view
|
||||
- SplitHorizontal (regular) for split-screen view
|
||||
- SplitVertical (regular) for toggle preview in viewer
|
||||
- Sparkle (fill) for AI error helper
|
||||
- **Spacing**:
|
||||
- Navigation menu: p-4 for nav container, space-y-2 for items
|
||||
- Navigation items: px-4 py-3 (touch-friendly 44px+ targets)
|
||||
- Page container: px-6 py-8
|
||||
- Section margins: mb-8 for headers
|
||||
- Grid: gap-6 (24px) for snippet cards
|
||||
- Split editor controls: gap-2 for view mode buttons
|
||||
- **Mobile**:
|
||||
- Navigation menu expands to full-width overlay on mobile
|
||||
- Touch-optimized button sizes (min 44px tap targets)
|
||||
- Responsive grid adapts from multi-column to single column
|
||||
- Stack navigation items vertically with full-width buttons
|
||||
- Stack navigation items vertically with full-width buttons
|
||||
- Split-screen editor stacks vertically on small screens or switches to single view mode
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { Snippet } from '@/lib/types'
|
||||
import { MonacoEditor } from '@/components/MonacoEditor'
|
||||
import { SplitScreenEditor } from '@/components/SplitScreenEditor'
|
||||
import { strings, appConfig, LANGUAGES } from '@/lib/config'
|
||||
|
||||
interface SnippetDialogProps {
|
||||
@@ -166,14 +167,25 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
|
||||
<Label htmlFor="code">
|
||||
{strings.snippetDialog.fields.code.label}{strings.snippetDialog.fields.code.required ? ' *' : ''}
|
||||
</Label>
|
||||
<div className={`rounded-md border overflow-hidden ${errors.code ? 'border-destructive ring-2 ring-destructive/20' : 'border-border'}`}>
|
||||
<MonacoEditor
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
language={language}
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
{hasPreview && appConfig.previewEnabledLanguages.includes(language) ? (
|
||||
<div className={errors.code ? 'ring-2 ring-destructive/20 rounded-md' : ''}>
|
||||
<SplitScreenEditor
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
language={language}
|
||||
height="500px"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`rounded-md border overflow-hidden ${errors.code ? 'border-destructive ring-2 ring-destructive/20' : 'border-border'}`}>
|
||||
<MonacoEditor
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
language={language}
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{errors.code && (
|
||||
<p className="text-sm text-destructive">{errors.code}</p>
|
||||
)}
|
||||
|
||||
108
src/components/SplitScreenEditor.tsx
Normal file
108
src/components/SplitScreenEditor.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useState } from 'react'
|
||||
import { MonacoEditor } from '@/components/MonacoEditor'
|
||||
import { ReactPreview } from '@/components/ReactPreview'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
|
||||
import { Code, Eye, SplitHorizontal } from '@phosphor-icons/react'
|
||||
|
||||
interface SplitScreenEditorProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
language: string
|
||||
height?: string
|
||||
}
|
||||
|
||||
type ViewMode = 'split' | 'code' | 'preview'
|
||||
|
||||
export function SplitScreenEditor({
|
||||
value,
|
||||
onChange,
|
||||
language,
|
||||
height = '500px'
|
||||
}: SplitScreenEditorProps) {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('split')
|
||||
|
||||
const isPreviewSupported = ['JSX', 'TSX', 'JavaScript', 'TypeScript'].includes(language)
|
||||
|
||||
if (!isPreviewSupported) {
|
||||
return (
|
||||
<MonacoEditor
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
language={language}
|
||||
height={height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<div className="flex items-center gap-1 p-1 bg-muted rounded-md">
|
||||
<Button
|
||||
variant={viewMode === 'code' ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('code')}
|
||||
className="gap-2 h-8"
|
||||
>
|
||||
<Code className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Code</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'split' ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('split')}
|
||||
className="gap-2 h-8"
|
||||
>
|
||||
<SplitHorizontal className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Split</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'preview' ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('preview')}
|
||||
className="gap-2 h-8"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Preview</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="rounded-md border border-border overflow-hidden bg-card"
|
||||
style={{ height }}
|
||||
>
|
||||
{viewMode === 'code' && (
|
||||
<MonacoEditor
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
language={language}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
|
||||
{viewMode === 'preview' && (
|
||||
<ReactPreview code={value} language={language} />
|
||||
)}
|
||||
|
||||
{viewMode === 'split' && (
|
||||
<ResizablePanelGroup direction="horizontal" className="h-full">
|
||||
<ResizablePanel defaultSize={50} minSize={30}>
|
||||
<MonacoEditor
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
language={language}
|
||||
height="100%"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={50} minSize={30}>
|
||||
<ReactPreview code={value} language={language} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user