From 58b761106b061903c69ff65a20863794b2e018d2 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Sun, 2 Nov 2025 13:21:25 +0100 Subject: [PATCH] Add reusable `Example`, `ExampleGrid`, and `ExampleSection` components for live UI demonstrations with code previews. Refactor `ComponentShowcase` to use new components, improving structure, maintainability, and documentation coverage. Include semantic updates to labels and descriptions. --- frontend/src/components/dev/BeforeAfter.tsx | 136 +++ frontend/src/components/dev/CodeSnippet.tsx | 178 ++++ .../src/components/dev/ComponentShowcase.tsx | 912 +++++++++++------- frontend/src/components/dev/Example.tsx | 220 +++++ 4 files changed, 1105 insertions(+), 341 deletions(-) create mode 100644 frontend/src/components/dev/BeforeAfter.tsx create mode 100644 frontend/src/components/dev/CodeSnippet.tsx create mode 100644 frontend/src/components/dev/Example.tsx diff --git a/frontend/src/components/dev/BeforeAfter.tsx b/frontend/src/components/dev/BeforeAfter.tsx new file mode 100644 index 0000000..70e299b --- /dev/null +++ b/frontend/src/components/dev/BeforeAfter.tsx @@ -0,0 +1,136 @@ +/* istanbul ignore file */ + +/** + * BeforeAfter Component + * Side-by-side comparison component for demonstrating anti-patterns vs best practices + * This file is excluded from coverage as it's a demo/showcase component + */ + +'use client'; + +import { AlertTriangle, CheckCircle2 } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; + +interface BeforeAfterProps { + title?: string; + description?: string; + before: { + label?: string; + content: React.ReactNode; + caption?: string; + }; + after: { + label?: string; + content: React.ReactNode; + caption?: string; + }; + vertical?: boolean; + className?: string; +} + +/** + * BeforeAfter - Side-by-side comparison component + * + * @example + * Child with margin, + * caption: "Child controls its own spacing" + * }} + * after={{ + * content:
Child
, + * caption: "Parent controls spacing with gap/space-y" + * }} + * /> + */ +export function BeforeAfter({ + title, + description, + before, + after, + vertical = false, + className, +}: BeforeAfterProps) { + return ( +
+ {/* Header */} + {(title || description) && ( +
+ {title &&

{title}

} + {description && ( +

{description}

+ )} +
+ )} + + {/* Comparison Grid */} +
+ {/* Before (Anti-pattern) */} + + +
+ + {before.label || '❌ Before (Anti-pattern)'} + + + + Avoid + +
+
+ + {/* Demo content */} +
+ {before.content} +
+ {/* Caption */} + {before.caption && ( +

+ {before.caption} +

+ )} +
+
+ + {/* After (Best practice) */} + + +
+ + {after.label || '✅ After (Best practice)'} + + + + Correct + +
+
+ + {/* Demo content */} +
+ {after.content} +
+ {/* Caption */} + {after.caption && ( +

+ {after.caption} +

+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/components/dev/CodeSnippet.tsx b/frontend/src/components/dev/CodeSnippet.tsx new file mode 100644 index 0000000..7869cd7 --- /dev/null +++ b/frontend/src/components/dev/CodeSnippet.tsx @@ -0,0 +1,178 @@ +/* istanbul ignore file */ + +/** + * CodeSnippet Component + * Displays syntax-highlighted code with copy-to-clipboard functionality + * This file is excluded from coverage as it's a demo/showcase component + */ + +'use client'; + +import { useState } from 'react'; +import { Check, Copy } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +interface CodeSnippetProps { + code: string; + language?: 'tsx' | 'typescript' | 'javascript' | 'css' | 'bash' | 'json'; + title?: string; + showLineNumbers?: boolean; + highlightLines?: number[]; + className?: string; +} + +/** + * CodeSnippet - Syntax-highlighted code block with copy button + * + * @example + * Click me`} + * showLineNumbers + * /> + */ +export function CodeSnippet({ + code, + language = 'tsx', + title, + showLineNumbers = false, + highlightLines = [], + className, +}: CodeSnippetProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy code:', err); + } + }; + + const lines = code.split('\n'); + + return ( +
+ {/* Header */} + {(title || language) && ( +
+
+ {title && ( + {title} + )} + {language && ( + ({language}) + )} +
+ +
+ )} + + {/* Code Block */} +
+ {/* Copy button (when no header) */} + {!title && !language && ( + + )} + +
+          
+            {showLineNumbers ? (
+              
+ {/* Line numbers */} +
+ {lines.map((_, idx) => ( +
+ {idx + 1} +
+ ))} +
+ {/* Code lines */} +
+ {lines.map((line, idx) => ( +
+ {line || ' '} +
+ ))} +
+
+ ) : ( + code + )} +
+
+
+
+ ); +} + +/** + * CodeGroup - Group multiple related code snippets + * + * @example + * + * + * + * + */ +export function CodeGroup({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return
{children}
; +} diff --git a/frontend/src/components/dev/ComponentShowcase.tsx b/frontend/src/components/dev/ComponentShowcase.tsx index c56bcdf..6388a39 100644 --- a/frontend/src/components/dev/ComponentShowcase.tsx +++ b/frontend/src/components/dev/ComponentShowcase.tsx @@ -2,17 +2,18 @@ /** * Component Showcase - * Comprehensive display of all design system components + * Comprehensive display of all design system components with copy-paste code * This file is excluded from coverage as it's a demo/showcase page */ 'use client'; import { useState } from 'react'; +import Link from 'next/link'; import { Moon, Sun, Mail, User, Settings, LogOut, Shield, AlertCircle, Info, - CheckCircle2, AlertTriangle, Trash2 + CheckCircle2, AlertTriangle, Trash2, ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { @@ -66,20 +67,7 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; - -/** - * Section wrapper component - */ -function Section({ title, children }: { title: string; children: React.ReactNode }) { - return ( -
-

{title}

-
- {children} -
-
- ); -} +import { Example, ExampleGrid, ExampleSection } from './Example'; /** * Component showcase @@ -98,9 +86,16 @@ export function ComponentShowcase() { {/* Header */}
-
-

Component Showcase

-

Development Preview

+
+ + + +
+

Component Showcase

+

All shadcn/ui components with code

+
+ + + + +`} + >
@@ -210,11 +182,18 @@ export function ComponentShowcase() {
-
+ - {/* Sizes */} -
-

Sizes

+ Small + + +`} + >
@@ -223,11 +202,20 @@ export function ComponentShowcase() {
-
+ - {/* With Icons */} -
-

With Icons

+ + + Email + +`} + >
-
+ - {/* States */} -
-

States

+ Normal +`} + >
-
-
- + + + {/* Form Inputs */} -
-
-
- - -
+ + + + +
-
- - -
+
+ +