Merge pull request #7 from johndoe6345789/copilot/refactor-large-components

Refactor monolithic files into focused modules (db.ts 1085→37 LOC, component-code-snippets.ts 1006→9 LOC)
This commit is contained in:
2026-01-17 23:35:17 +00:00
committed by GitHub
14 changed files with 2196 additions and 2090 deletions

File diff suppressed because it is too large Load Diff

8
src/lib/db-constants.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* Database constants shared across modules
*/
export const DB_KEY = 'codesnippet-db'
export const IDB_NAME = 'CodeSnippetDB'
export const IDB_STORE = 'database'
export const IDB_VERSION = 1

178
src/lib/db-core.ts Normal file
View File

@@ -0,0 +1,178 @@
/**
* Core database initialization and management
*/
import initSqlJs, { Database } from 'sql.js'
import { loadFromIndexedDB, saveToIndexedDB, openIndexedDB, deleteFromIndexedDB } from './db-indexeddb'
import { loadFromLocalStorage, saveToLocalStorage, deleteFromLocalStorage } from './db-localstorage'
import { validateSchema, createTables } from './db-schema'
import { getStorageConfig, FlaskStorageAdapter, loadStorageConfig } from './storage'
import { DB_KEY } from './db-constants'
let dbInstance: Database | null = null
let sqlInstance: any = null
let flaskAdapter: FlaskStorageAdapter | null = null
let configLoaded = false
async function wipeAndRecreateDB(): Promise<void> {
console.warn('Wiping corrupted database and creating fresh schema...')
await saveToIndexedDB(new Uint8Array())
saveToLocalStorage(new Uint8Array())
await deleteFromIndexedDB()
deleteFromLocalStorage()
dbInstance = null
}
export async function initDB(): Promise<Database> {
if (dbInstance) return dbInstance
if (!sqlInstance) {
sqlInstance = await initSqlJs({
locateFile: (file) => `https://sql.js.org/dist/${file}`
})
}
let loadedData: Uint8Array | null = null
let schemaValid = false
loadedData = await loadFromIndexedDB()
if (!loadedData) {
loadedData = loadFromLocalStorage()
}
if (loadedData && loadedData.length > 0) {
try {
const testDb = new sqlInstance.Database(loadedData)
schemaValid = await validateSchema(testDb)
if (schemaValid) {
dbInstance = testDb
} else {
console.warn('Schema validation failed, wiping database')
testDb.close()
await wipeAndRecreateDB()
dbInstance = new sqlInstance.Database()
}
} catch (error) {
console.error('Failed to load saved database, creating new one:', error)
await wipeAndRecreateDB()
dbInstance = new sqlInstance.Database()
}
} else {
dbInstance = new sqlInstance.Database()
}
if (!dbInstance) {
throw new Error('Failed to initialize database')
}
createTables(dbInstance)
await saveDB()
return dbInstance
}
export async function saveDB() {
if (!dbInstance) return
try {
const data = dbInstance.export()
const savedToIDB = await saveToIndexedDB(data)
if (!savedToIDB) {
saveToLocalStorage(data)
}
} catch (error) {
console.error('Failed to save database:', error)
}
}
export function getFlaskAdapter(): FlaskStorageAdapter | null {
if (!configLoaded) {
loadStorageConfig()
configLoaded = true
}
const config = getStorageConfig()
if (config.backend === 'flask' && config.flaskUrl) {
try {
if (!flaskAdapter || flaskAdapter['baseUrl'] !== config.flaskUrl) {
flaskAdapter = new FlaskStorageAdapter(config.flaskUrl)
}
return flaskAdapter
} catch (error) {
console.warn('Failed to create Flask adapter:', error)
return null
}
}
return null
}
export async function exportDatabase(): Promise<Uint8Array> {
const db = await initDB()
return db.export()
}
export async function importDatabase(data: Uint8Array): Promise<void> {
if (!sqlInstance) {
sqlInstance = await initSqlJs({
locateFile: (file) => `https://sql.js.org/dist/${file}`
})
}
try {
dbInstance = new sqlInstance.Database(data)
await saveDB()
} catch (error) {
console.error('Failed to import database:', error)
throw error
}
}
export async function getDatabaseStats(): Promise<{
snippetCount: number
templateCount: number
storageType: 'indexeddb' | 'localstorage' | 'none'
databaseSize: number
}> {
const db = await initDB()
const snippetResult = db.exec('SELECT COUNT(*) as count FROM snippets')
const templateResult = db.exec('SELECT COUNT(*) as count FROM snippet_templates')
const snippetCount = snippetResult[0]?.values[0]?.[0] as number || 0
const templateCount = templateResult[0]?.values[0]?.[0] as number || 0
const data = db.export()
const databaseSize = data.length
const hasIDB = await openIndexedDB()
const hasLocalStorage = typeof localStorage !== 'undefined' && localStorage.getItem(DB_KEY) !== null
const storageType = hasIDB ? 'indexeddb' : (hasLocalStorage ? 'localstorage' : 'none')
return {
snippetCount,
templateCount,
storageType,
databaseSize
}
}
export async function clearDatabase(): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
await adapter.wipeDatabase()
return
}
await deleteFromIndexedDB()
deleteFromLocalStorage()
dbInstance = null
await initDB()
}

100
src/lib/db-indexeddb.ts Normal file
View File

@@ -0,0 +1,100 @@
/**
* IndexedDB operations for database persistence
*/
import { DB_KEY, IDB_NAME, IDB_STORE, IDB_VERSION } from './db-constants'
export async function openIndexedDB(): Promise<IDBDatabase | null> {
if (typeof indexedDB === 'undefined') return null
return new Promise((resolve) => {
try {
const request = indexedDB.open(IDB_NAME, IDB_VERSION)
request.onerror = () => {
console.warn('IndexedDB not available, falling back to localStorage')
resolve(null)
}
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
if (!db.objectStoreNames.contains(IDB_STORE)) {
db.createObjectStore(IDB_STORE)
}
}
request.onsuccess = (event) => {
resolve((event.target as IDBOpenDBRequest).result)
}
} catch (error) {
console.warn('IndexedDB error:', error)
resolve(null)
}
})
}
export async function loadFromIndexedDB(): Promise<Uint8Array | null> {
const db = await openIndexedDB()
if (!db) return null
return new Promise((resolve) => {
try {
const transaction = db.transaction([IDB_STORE], 'readonly')
const store = transaction.objectStore(IDB_STORE)
const request = store.get(DB_KEY)
request.onsuccess = () => {
const data = request.result
resolve(data ? new Uint8Array(data) : null)
}
request.onerror = () => {
console.warn('Failed to load from IndexedDB')
resolve(null)
}
} catch (error) {
console.warn('IndexedDB read error:', error)
resolve(null)
}
})
}
export async function saveToIndexedDB(data: Uint8Array): Promise<boolean> {
const db = await openIndexedDB()
if (!db) return false
return new Promise((resolve) => {
try {
const transaction = db.transaction([IDB_STORE], 'readwrite')
const store = transaction.objectStore(IDB_STORE)
const request = store.put(data, DB_KEY)
request.onsuccess = () => resolve(true)
request.onerror = () => {
console.warn('Failed to save to IndexedDB')
resolve(false)
}
} catch (error) {
console.warn('IndexedDB write error:', error)
resolve(false)
}
})
}
export async function deleteFromIndexedDB(): Promise<void> {
const db = await openIndexedDB()
if (!db) return
return new Promise((resolve) => {
try {
const transaction = db.transaction([IDB_STORE], 'readwrite')
const store = transaction.objectStore(IDB_STORE)
const request = store.delete(DB_KEY)
request.onsuccess = () => resolve()
request.onerror = () => resolve()
} catch (error) {
console.warn('Error clearing IndexedDB:', error)
resolve()
}
})
}

View File

@@ -0,0 +1,36 @@
/**
* LocalStorage operations for database persistence
*/
import { DB_KEY } from './db-constants'
export function loadFromLocalStorage(): Uint8Array | null {
try {
const savedData = localStorage.getItem(DB_KEY)
if (savedData) {
return new Uint8Array(JSON.parse(savedData))
}
} catch (error) {
console.warn('Failed to load from localStorage:', error)
}
return null
}
export function saveToLocalStorage(data: Uint8Array): boolean {
try {
const dataArray = Array.from(data)
localStorage.setItem(DB_KEY, JSON.stringify(dataArray))
return true
} catch (error) {
console.warn('Failed to save to localStorage (quota exceeded?):', error)
return false
}
}
export function deleteFromLocalStorage(): void {
try {
localStorage.removeItem(DB_KEY)
} catch (error) {
console.warn('Error clearing localStorage:', error)
}
}

108
src/lib/db-namespaces.ts Normal file
View File

@@ -0,0 +1,108 @@
/**
* Namespace operations for organizing snippets
*/
import type { Namespace } from './types'
import { initDB, saveDB, getFlaskAdapter } from './db-core'
import { mapRowToObject, mapRowsToObjects } from './db-mapper'
export async function getAllNamespaces(): Promise<Namespace[]> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.getAllNamespaces()
}
const db = await initDB()
const results = db.exec('SELECT * FROM namespaces ORDER BY isDefault DESC, name ASC')
return mapRowsToObjects<Namespace>(results)
}
export async function createNamespace(name: string): Promise<Namespace> {
const namespace: Namespace = {
id: Date.now().toString(),
name,
createdAt: Date.now(),
isDefault: false
}
const adapter = getFlaskAdapter()
if (adapter) {
await adapter.createNamespace(namespace)
return namespace
}
const db = await initDB()
db.run(
`INSERT INTO namespaces (id, name, createdAt, isDefault)
VALUES (?, ?, ?, ?)`,
[namespace.id, namespace.name, namespace.createdAt, namespace.isDefault ? 1 : 0]
)
await saveDB()
return namespace
}
export async function deleteNamespace(id: string): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.deleteNamespace(id)
}
const db = await initDB()
const defaultNamespace = db.exec('SELECT id FROM namespaces WHERE isDefault = 1')
if (defaultNamespace.length === 0 || defaultNamespace[0].values.length === 0) {
throw new Error('Default namespace not found')
}
const defaultId = defaultNamespace[0].values[0][0] as string
const checkDefault = db.exec('SELECT isDefault FROM namespaces WHERE id = ?', [id])
if (checkDefault.length > 0 && checkDefault[0].values[0]?.[0] === 1) {
throw new Error('Cannot delete default namespace')
}
db.run('UPDATE snippets SET namespaceId = ? WHERE namespaceId = ?', [defaultId, id])
db.run('DELETE FROM namespaces WHERE id = ?', [id])
await saveDB()
}
export async function ensureDefaultNamespace(): Promise<void> {
const db = await initDB()
const results = db.exec('SELECT COUNT(*) as count FROM namespaces WHERE isDefault = 1')
const count = results[0]?.values[0]?.[0] as number || 0
if (count === 0) {
const defaultNamespace: Namespace = {
id: 'default',
name: 'Default',
createdAt: Date.now(),
isDefault: true
}
db.run(
`INSERT INTO namespaces (id, name, createdAt, isDefault)
VALUES (?, ?, ?, ?)`,
[defaultNamespace.id, defaultNamespace.name, defaultNamespace.createdAt, 1]
)
await saveDB()
}
}
export async function getNamespaceById(id: string): Promise<Namespace | null> {
const db = await initDB()
const results = db.exec('SELECT * FROM namespaces WHERE id = ?', [id])
if (results.length === 0 || results[0].values.length === 0) return null
const columns = results[0].columns
const row = results[0].values[0]
return mapRowToObject<Namespace>(row, columns)
}

106
src/lib/db-schema.ts Normal file
View File

@@ -0,0 +1,106 @@
/**
* Database schema management and validation
*/
import type { Database } from 'sql.js'
export async function validateSchema(db: Database): Promise<boolean> {
try {
const snippetsCheck = db.exec("PRAGMA table_info(snippets)")
if (snippetsCheck.length === 0) return true
const columns = snippetsCheck[0].values.map(row => row[1] as string)
const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt']
for (const col of requiredColumns) {
if (!columns.includes(col)) {
console.warn(`Schema validation failed: missing column '${col}'`)
return false
}
}
const namespacesCheck = db.exec("PRAGMA table_info(namespaces)")
if (namespacesCheck.length === 0) {
console.warn('Schema validation failed: namespaces table missing')
return false
}
return true
} catch (error) {
console.error('Schema validation error:', error)
return false
}
}
export function createTables(db: Database): void {
db.run(`
CREATE TABLE IF NOT EXISTS namespaces (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
createdAt INTEGER NOT NULL,
isDefault INTEGER DEFAULT 0
)
`)
db.run(`
CREATE TABLE IF NOT EXISTS snippets (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
code TEXT NOT NULL,
language TEXT NOT NULL,
category TEXT NOT NULL,
namespaceId TEXT,
hasPreview INTEGER DEFAULT 0,
functionName TEXT,
inputParameters TEXT,
createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL,
FOREIGN KEY (namespaceId) REFERENCES namespaces(id)
)
`)
db.run(`
CREATE TABLE IF NOT EXISTS snippet_templates (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
code TEXT NOT NULL,
language TEXT NOT NULL,
category TEXT NOT NULL,
hasPreview INTEGER DEFAULT 0,
functionName TEXT,
inputParameters TEXT
)
`)
}
export async function validateDatabaseSchema(db: Database): Promise<{ valid: boolean; issues: string[] }> {
try {
const issues: string[] = []
const snippetsCheck = db.exec("PRAGMA table_info(snippets)")
if (snippetsCheck.length === 0) {
issues.push('Snippets table missing')
return { valid: false, issues }
}
const columns = snippetsCheck[0].values.map(row => row[1] as string)
const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt']
for (const col of requiredColumns) {
if (!columns.includes(col)) {
issues.push(`Missing column '${col}' in snippets table`)
}
}
const namespacesCheck = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='namespaces'")
if (namespacesCheck.length === 0) {
issues.push('Namespaces table missing')
}
return { valid: issues.length === 0, issues }
} catch (error) {
return { valid: false, issues: ['Failed to validate schema: ' + (error as Error).message] }
}
}

585
src/lib/db-snippets.ts Normal file
View File

@@ -0,0 +1,585 @@
/**
* Snippet CRUD operations and templates management
*/
import type { Snippet, SnippetTemplate } from './types'
import { initDB, saveDB, getFlaskAdapter } from './db-core'
import { mapRowToObject, mapRowsToObjects } from './db-mapper'
import { ensureDefaultNamespace } from './db-namespaces'
export async function getAllSnippets(): Promise<Snippet[]> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.getAllSnippets()
}
const db = await initDB()
const results = db.exec('SELECT * FROM snippets ORDER BY updatedAt DESC')
return mapRowsToObjects<Snippet>(results)
}
export async function getSnippet(id: string): Promise<Snippet | null> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.getSnippet(id)
}
const db = await initDB()
const results = db.exec('SELECT * FROM snippets WHERE id = ?', [id])
if (results.length === 0 || results[0].values.length === 0) return null
const columns = results[0].columns
const row = results[0].values[0]
return mapRowToObject<Snippet>(row, columns)
}
export async function createSnippet(snippet: Snippet): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.createSnippet(snippet)
}
const db = await initDB()
db.run(
`INSERT INTO snippets (id, title, description, code, language, category, namespaceId, hasPreview, functionName, inputParameters, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
snippet.id,
snippet.title,
snippet.description,
snippet.code,
snippet.language,
snippet.category,
snippet.namespaceId || null,
snippet.hasPreview ? 1 : 0,
snippet.functionName || null,
snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null,
snippet.createdAt,
snippet.updatedAt
]
)
await saveDB()
}
export async function updateSnippet(snippet: Snippet): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.updateSnippet(snippet)
}
const db = await initDB()
db.run(
`UPDATE snippets
SET title = ?, description = ?, code = ?, language = ?, category = ?, namespaceId = ?, hasPreview = ?, functionName = ?, inputParameters = ?, updatedAt = ?
WHERE id = ?`,
[
snippet.title,
snippet.description,
snippet.code,
snippet.language,
snippet.category,
snippet.namespaceId || null,
snippet.hasPreview ? 1 : 0,
snippet.functionName || null,
snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null,
snippet.updatedAt,
snippet.id
]
)
await saveDB()
}
export async function deleteSnippet(id: string): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
return await adapter.deleteSnippet(id)
}
const db = await initDB()
db.run('DELETE FROM snippets WHERE id = ?', [id])
await saveDB()
}
export async function getSnippetsByNamespace(namespaceId: string): Promise<Snippet[]> {
const db = await initDB()
const results = db.exec('SELECT * FROM snippets WHERE namespaceId = ? OR (namespaceId IS NULL AND ? = (SELECT id FROM namespaces WHERE isDefault = 1)) ORDER BY updatedAt DESC', [namespaceId, namespaceId])
return mapRowsToObjects<Snippet>(results)
}
export async function moveSnippetToNamespace(snippetId: string, targetNamespaceId: string): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
const snippet = await adapter.getSnippet(snippetId)
if (snippet) {
snippet.namespaceId = targetNamespaceId
snippet.updatedAt = Date.now()
await adapter.updateSnippet(snippet)
}
return
}
const db = await initDB()
db.run(
'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?',
[targetNamespaceId, Date.now(), snippetId]
)
await saveDB()
}
export async function bulkMoveSnippets(snippetIds: string[], targetNamespaceId: string): Promise<void> {
const adapter = getFlaskAdapter()
if (adapter) {
await adapter.bulkMoveSnippets(snippetIds, targetNamespaceId)
return
}
const db = await initDB()
const now = Date.now()
for (const snippetId of snippetIds) {
db.run(
'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?',
[targetNamespaceId, now, snippetId]
)
}
await saveDB()
}
export async function getAllTemplates(): Promise<SnippetTemplate[]> {
const db = await initDB()
const results = db.exec('SELECT * FROM snippet_templates')
return mapRowsToObjects<SnippetTemplate>(results)
}
export async function createTemplate(template: SnippetTemplate): Promise<void> {
const db = await initDB()
db.run(
`INSERT INTO snippet_templates (id, title, description, code, language, category, hasPreview, functionName, inputParameters)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
template.id,
template.title,
template.description,
template.code,
template.language,
template.category,
template.hasPreview ? 1 : 0,
template.functionName || null,
template.inputParameters ? JSON.stringify(template.inputParameters) : null
]
)
await saveDB()
}
export async function syncTemplatesFromJSON(templates: SnippetTemplate[]): Promise<void> {
const db = await initDB()
const existingTemplates = db.exec('SELECT id FROM snippet_templates')
const existingIds = new Set(
existingTemplates[0]?.values.map(row => row[0] as string) || []
)
let addedCount = 0
for (const template of templates) {
if (!existingIds.has(template.id)) {
await createTemplate(template)
addedCount++
}
}
}
export async function seedDatabase(): Promise<void> {
const db = await initDB()
await ensureDefaultNamespace()
const checkSnippets = db.exec('SELECT COUNT(*) as count FROM snippets')
const snippetCount = checkSnippets[0]?.values[0]?.[0] as number
if (snippetCount > 0) {
return
}
const now = Date.now()
const seedSnippets: Snippet[] = [
{
id: 'seed-1',
title: 'React Counter Hook',
description: 'Basic state management with useState',
code: `import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div className="p-6 space-y-4">
<p className="text-2xl font-bold">Count: {count}</p>
<div className="flex gap-2">
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-primary text-primary-foreground rounded"
>
Increment
</button>
<button
onClick={() => setCount(count - 1)}
className="px-4 py-2 bg-secondary text-secondary-foreground rounded"
>
Decrement
</button>
</div>
</div>
)
}
export default Counter`,
language: 'tsx',
category: 'React Hooks',
hasPreview: true,
functionName: 'Counter',
inputParameters: [],
createdAt: now,
updatedAt: now
},
{
id: 'seed-2',
title: 'Todo List Component',
description: 'Complete todo list with add, toggle, and delete',
code: `import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
import { Trash2 } from '@phosphor-icons/react'
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', completed: false },
{ id: 2, text: 'Build a project', completed: false }
])
const [input, setInput] = useState('')
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input, completed: false }])
setInput('')
}
}
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
))
}
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<Card className="p-6 max-w-md mx-auto">
<h2 className="text-2xl font-bold mb-4">My Todos</h2>
<div className="flex gap-2 mb-4">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="Add a new todo..."
/>
<Button onClick={addTodo}>Add</Button>
</div>
<div className="space-y-2">
{todos.map(todo => (
<div key={todo.id} className="flex items-center gap-2 p-2 hover:bg-muted rounded">
<Checkbox
checked={todo.completed}
onCheckedChange={() => toggleTodo(todo.id)}
/>
<span className={todo.completed ? 'line-through text-muted-foreground flex-1' : 'flex-1'}>
{todo.text}
</span>
<Button
variant="ghost"
size="icon"
onClick={() => deleteTodo(todo.id)}
>
<Trash2 size={16} />
</Button>
</div>
))}
</div>
</Card>
)
}
export default TodoList`,
language: 'tsx',
category: 'Components',
hasPreview: true,
functionName: 'TodoList',
inputParameters: [],
createdAt: now - 1000,
updatedAt: now - 1000
},
{
id: 'seed-3',
title: 'Fetch Data Hook',
description: 'Custom hook for API data fetching',
code: `import { useState, useEffect } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch(url)
if (!response.ok) throw new Error('Network response was not ok')
const json = await response.json()
setData(json)
setError(null)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}`,
language: 'tsx',
category: 'React Hooks',
hasPreview: false,
createdAt: now - 2000,
updatedAt: now - 2000
},
{
id: 'seed-4',
title: 'Animated Card',
description: 'Card with hover animation using Framer Motion',
code: `import { motion } from 'framer-motion'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'
function AnimatedCard({ title = 'Animated Card', description = 'Hover to see the effect' }) {
return (
<motion.div
whileHover={{ scale: 1.05, rotateZ: 2 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
>
<Card className="cursor-pointer">
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<p>This card has smooth animations on hover and tap!</p>
</CardContent>
</Card>
</motion.div>
)
}
export default AnimatedCard`,
language: 'tsx',
category: 'Components',
hasPreview: true,
functionName: 'AnimatedCard',
inputParameters: [
{
name: 'title',
type: 'string',
defaultValue: 'Animated Card',
description: 'Card title'
},
{
name: 'description',
type: 'string',
defaultValue: 'Hover to see the effect',
description: 'Card description'
}
],
createdAt: now - 3000,
updatedAt: now - 3000
},
{
id: 'seed-5',
title: 'Form Validation',
description: 'Form with react-hook-form validation',
code: `import { useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Card } from '@/components/ui/card'
function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm()
const onSubmit = (data) => {
console.log('Form data:', data)
alert('Form submitted successfully!')
}
return (
<Card className="p-6 max-w-md mx-auto">
<h2 className="text-2xl font-bold mb-4">Contact Form</h2>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<Label htmlFor="name">Name</Label>
<Input
id="name"
{...register('name', { required: 'Name is required' })}
/>
{errors.name && (
<p className="text-destructive text-sm mt-1">{errors.name.message}</p>
)}
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i,
message: 'Invalid email address'
}
})}
/>
{errors.email && (
<p className="text-destructive text-sm mt-1">{errors.email.message}</p>
)}
</div>
<Button type="submit" className="w-full">Submit</Button>
</form>
</Card>
)
}
export default ContactForm`,
language: 'tsx',
category: 'Forms',
hasPreview: true,
functionName: 'ContactForm',
inputParameters: [],
createdAt: now - 4000,
updatedAt: now - 4000
}
]
for (const snippet of seedSnippets) {
await createSnippet(snippet)
}
const seedTemplates: SnippetTemplate[] = [
{
id: 'template-1',
title: 'Basic React Component',
description: 'Simple functional component template',
code: `function MyComponent() {
return (
<div className="p-4">
<h2 className="text-xl font-bold">Hello World</h2>
<p>This is a basic component.</p>
</div>
)
}
export default MyComponent`,
language: 'tsx',
category: 'Templates',
hasPreview: true,
functionName: 'MyComponent',
inputParameters: []
},
{
id: 'template-2',
title: 'Component with Props',
description: 'Component template with configurable props',
code: `function Greeting({ name = 'World', message = 'Hello' }) {
return (
<div className="p-4">
<h2 className="text-xl font-bold">{message}, {name}!</h2>
</div>
)
}
export default Greeting`,
language: 'tsx',
category: 'Templates',
hasPreview: true,
functionName: 'Greeting',
inputParameters: [
{
name: 'name',
type: 'string',
defaultValue: 'World',
description: 'Name to greet'
},
{
name: 'message',
type: 'string',
defaultValue: 'Hello',
description: 'Greeting message'
}
]
},
{
id: 'template-3',
title: 'useState Hook Template',
description: 'Component with state management',
code: `import { useState } from 'react'
import { Button } from '@/components/ui/button'
function StatefulComponent() {
const [value, setValue] = useState(0)
return (
<div className="p-4 space-y-4">
<p className="text-lg">Value: {value}</p>
<Button onClick={() => setValue(value + 1)}>
Increment
</Button>
</div>
)
}
export default StatefulComponent`,
language: 'tsx',
category: 'Templates',
hasPreview: true,
functionName: 'StatefulComponent',
inputParameters: []
}
]
for (const template of seedTemplates) {
await createTemplate(template)
}
}

File diff suppressed because it is too large Load Diff

209
src/lib/snippets/atoms.ts Normal file
View File

@@ -0,0 +1,209 @@
/**
* Atoms - Basic UI building blocks code snippets
*/
export const atomsCodeSnippets = {
buttonDefault: `interface ButtonProps {
children?: string
onClick?: () => void
}
function DefaultButton({ children = "Default", onClick }: ButtonProps) {
return <Button onClick={onClick}>{children}</Button>
}`,
buttonSecondary: `interface ButtonProps {
children?: string
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link"
onClick?: () => void
}
function CustomButton({ children = "Secondary", variant = "secondary", onClick }: ButtonProps) {
return <Button variant={variant} onClick={onClick}>{children}</Button>
}`,
buttonDestructive: `interface DestructiveButtonProps {
children?: string
onClick?: () => void
}
function DestructiveButton({ children = "Destructive", onClick }: DestructiveButtonProps) {
return <Button variant="destructive" onClick={onClick}>{children}</Button>
}`,
buttonOutline: `interface OutlineButtonProps {
children?: string
onClick?: () => void
}
function OutlineButton({ children = "Outline", onClick }: OutlineButtonProps) {
return <Button variant="outline" onClick={onClick}>{children}</Button>
}`,
buttonGhost: `interface GhostButtonProps {
children?: string
onClick?: () => void
}
function GhostButton({ children = "Ghost", onClick }: GhostButtonProps) {
return <Button variant="ghost" onClick={onClick}>{children}</Button>
}`,
buttonLink: `interface LinkButtonProps {
children?: string
onClick?: () => void
}
function LinkButton({ children = "Link", onClick }: LinkButtonProps) {
return <Button variant="link" onClick={onClick}>{children}</Button>
}`,
buttonSizes: `interface ButtonSizesProps {
smallText?: string
defaultText?: string
largeText?: string
onSmallClick?: () => void
onDefaultClick?: () => void
onLargeClick?: () => void
onIconClick?: () => void
}
function ButtonSizes({
smallText = "Small",
defaultText = "Default",
largeText = "Large",
onSmallClick,
onDefaultClick,
onLargeClick,
onIconClick
}: ButtonSizesProps) {
return (
<>
<Button size="sm" onClick={onSmallClick}>{smallText}</Button>
<Button size="default" onClick={onDefaultClick}>{defaultText}</Button>
<Button size="lg" onClick={onLargeClick}>{largeText}</Button>
<Button size="icon" onClick={onIconClick}>
<Heart weight="fill" />
</Button>
</>
)
}`,
buttonWithIcons: `interface IconButtonProps {
primaryText?: string
secondaryText?: string
onPrimaryClick?: () => void
onSecondaryClick?: () => void
}
function IconButtons({
primaryText = "Favorite",
secondaryText = "Add Item",
onPrimaryClick,
onSecondaryClick
}: IconButtonProps) {
return (
<>
<Button onClick={onPrimaryClick}>
<Star weight="fill" />
{primaryText}
</Button>
<Button variant="outline" onClick={onSecondaryClick}>
<Plus weight="bold" />
{secondaryText}
</Button>
</>
)
}`,
badgeVariants: `interface BadgeVariantsProps {
defaultText?: string
secondaryText?: string
destructiveText?: string
outlineText?: string
}
function BadgeVariants({
defaultText = "Default",
secondaryText = "Secondary",
destructiveText = "Destructive",
outlineText = "Outline"
}: BadgeVariantsProps) {
return (
<>
<Badge>{defaultText}</Badge>
<Badge variant="secondary">{secondaryText}</Badge>
<Badge variant="destructive">{destructiveText}</Badge>
<Badge variant="outline">{outlineText}</Badge>
</>
)
}`,
badgeWithIcons: `interface IconBadgeProps {
completedText?: string
failedText?: string
}
function IconBadges({
completedText = "Completed",
failedText = "Failed"
}: IconBadgeProps) {
return (
<>
<Badge>
<Check weight="bold" className="mr-1" />
{completedText}
</Badge>
<Badge variant="destructive">
<X weight="bold" className="mr-1" />
{failedText}
</Badge>
</>
)
}`,
inputBasic: `interface InputProps {
placeholder?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function BasicInput({ placeholder = "Default input", value, onChange }: InputProps) {
return <Input placeholder={placeholder} value={value} onChange={onChange} />
}`,
inputWithIcon: `interface SearchInputProps {
placeholder?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function SearchInput({ placeholder = "Search...", value, onChange }: SearchInputProps) {
return (
<div className="relative">
<MagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input placeholder={placeholder} className="pl-10" value={value} onChange={onChange} />
</div>
)
}`,
inputTypes: `interface TypedInputsProps {
textPlaceholder?: string
emailPlaceholder?: string
passwordPlaceholder?: string
numberPlaceholder?: string
onTextChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
onEmailChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
onPasswordChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
onNumberChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function TypedInputs({
textPlaceholder = "Text input",
emailPlaceholder = "email@example.com",
passwordPlaceholder = "Password",
numberPlaceholder = "123",
onTextChange,
onEmailChange,
onPasswordChange,
onNumberChange
}: TypedInputsProps) {
return (
<>
<Input type="text" placeholder={textPlaceholder} onChange={onTextChange} />
<Input type="email" placeholder={emailPlaceholder} onChange={onEmailChange} />
<Input type="password" placeholder={passwordPlaceholder} onChange={onPasswordChange} />
<Input type="number" placeholder={numberPlaceholder} onChange={onNumberChange} />
</>
)
}`,
}

View File

@@ -0,0 +1,8 @@
/**
* Component code snippets - Re-exports from focused modules
*/
export { atomsCodeSnippets } from './atoms'
export { moleculesCodeSnippets } from './molecules'
export { organismsCodeSnippets } from './organisms'
export { templatesCodeSnippets } from './templates'

View File

@@ -0,0 +1,219 @@
/**
* Molecules - Composite UI components code snippets
*/
export const moleculesCodeSnippets = {
formField: `interface FormFieldProps {
label?: string
placeholder?: string
helperText?: string
id?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function EmailFormField({
label = "Email Address",
placeholder = "john@example.com",
helperText = "We'll never share your email with anyone else.",
id = "email",
value,
onChange
}: FormFieldProps) {
return (
<div className="space-y-2">
<Label htmlFor={id}>{label}</Label>
<div className="relative">
<Envelope className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input id={id} type="email" placeholder={placeholder} className="pl-10" value={value} onChange={onChange} />
</div>
<p className="text-sm text-muted-foreground">
{helperText}
</p>
</div>
)
}`,
searchBar: `interface SearchBarProps {
placeholder?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function SearchBar({ placeholder = "Search...", value, onChange }: SearchBarProps) {
return (
<div className="relative">
<MagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input placeholder={placeholder} className="pl-10" value={value} onChange={onChange} />
</div>
)
}`,
searchBarWithButton: `interface SearchBarWithButtonProps {
placeholder?: string
buttonText?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
onSearch?: () => void
}
function SearchBarWithButton({
placeholder = "Search...",
buttonText = "Search",
value,
onChange,
onSearch
}: SearchBarWithButtonProps) {
return (
<div className="flex gap-2">
<div className="relative flex-1">
<MagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input placeholder={placeholder} className="pl-10" value={value} onChange={onChange} />
</div>
<Button onClick={onSearch}>{buttonText}</Button>
</div>
)
}`,
userCard: `interface UserCardProps {
name?: string
username?: string
bio?: string
avatarUrl?: string
avatarFallback?: string
buttonText?: string
onButtonClick?: () => void
}
function UserCard({
name = "Alex Morgan",
username = "@alexmorgan",
bio = "Product designer passionate about creating delightful user experiences.",
avatarUrl = "https://i.pravatar.cc/150?img=1",
avatarFallback = "AM",
buttonText = "Follow",
onButtonClick
}: UserCardProps) {
return (
<Card className="p-6">
<div className="flex items-start gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src={avatarUrl} />
<AvatarFallback>{avatarFallback}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-lg">{name}</h3>
<p className="text-sm text-muted-foreground">{username}</p>
<p className="text-sm mt-2">
{bio}
</p>
</div>
<Button size="sm" variant="outline" onClick={onButtonClick}>
{buttonText}
</Button>
</div>
</Card>
)
}`,
socialActions: `interface SocialActionsProps {
likeText?: string
commentText?: string
shareText?: string
onLike?: () => void
onComment?: () => void
onShare?: () => void
}
function SocialActions({
likeText = "Like",
commentText = "Comment",
shareText = "Share",
onLike,
onComment,
onShare
}: SocialActionsProps) {
return (
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={onLike}>
<Heart className="mr-2" />
{likeText}
</Button>
<Button variant="ghost" size="sm" onClick={onComment}>
<ChatCircle className="mr-2" />
{commentText}
</Button>
<Button variant="ghost" size="sm" onClick={onShare}>
<Share className="mr-2" />
{shareText}
</Button>
</div>
)
}`,
statusIndicator: `interface StatusIndicatorProps {
statusText?: string
badgeText?: string
isActive?: boolean
}
function StatusIndicator({
statusText = "System Online",
badgeText = "Active",
isActive = true
}: StatusIndicatorProps) {
return (
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={cn(
"h-3 w-3 rounded-full bg-accent",
isActive && "animate-pulse"
)} />
<span className="font-medium">{statusText}</span>
</div>
<Badge>{badgeText}</Badge>
</div>
)
}`,
contentCard: `interface ContentCardProps {
title?: string
description?: string
date?: string
readTime?: string
tags?: string[]
}
function ContentCard({
title = "Building Scalable Design Systems",
description = "Learn how to create and maintain design systems that grow with your team.",
date = "Mar 15, 2024",
readTime = "5 min read",
tags = ["Design", "System"]
}: ContentCardProps) {
return (
<Card className="p-6 hover:shadow-lg transition-shadow">
<div className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<h3 className="font-semibold text-lg line-clamp-2">
{title}
</h3>
<p className="text-sm text-muted-foreground">
{description}
</p>
</div>
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
<span>{date}</span>
</div>
<span>•</span>
<span>{readTime}</span>
</div>
<div className="flex gap-2">
{tags.map((tag) => (
<Badge key={tag} variant="outline">{tag}</Badge>
))}
</div>
</div>
</Card>
)
}`,
}

View File

@@ -0,0 +1,338 @@
/**
* Organisms - Complex UI patterns code snippets
*/
export const organismsCodeSnippets = {
navigationBar: `interface NavigationBarProps {
brandName?: string
navItems?: Array<{ label: string; icon: React.ReactNode; onClick?: () => void }>
avatarUrl?: string
avatarFallback?: string
onNotificationClick?: () => void
onSettingsClick?: () => void
}
function NavigationBar({
brandName = "BrandName",
navItems = [
{ label: "Home", icon: <House className="mr-2" /> },
{ label: "Analytics", icon: <ChartBar className="mr-2" /> },
{ label: "Projects", icon: <Folder className="mr-2" /> }
],
avatarUrl = "https://i.pravatar.cc/150?img=3",
avatarFallback = "U",
onNotificationClick,
onSettingsClick
}: NavigationBarProps) {
return (
<div className="border-b border-border bg-card p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-6">
<h3 className="text-xl font-bold">{brandName}</h3>
<nav className="hidden md:flex items-center gap-1">
{navItems.map((item) => (
<Button key={item.label} variant="ghost" size="sm" onClick={item.onClick}>
{item.icon}
{item.label}
</Button>
))}
</nav>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={onNotificationClick}>
<Bell />
</Button>
<Button variant="ghost" size="icon" onClick={onSettingsClick}>
<Gear />
</Button>
<Avatar className="h-8 w-8">
<AvatarImage src={avatarUrl} />
<AvatarFallback>{avatarFallback}</AvatarFallback>
</Avatar>
</div>
</div>
</div>
)
}`,
dataTable: `interface Transaction {
id: string
status: string
statusVariant?: "default" | "secondary" | "destructive" | "outline"
description: string
date: string
amount: string
isNegative?: boolean
}
interface DataTableProps {
title?: string
exportButtonText?: string
transactions?: Transaction[]
onExport?: () => void
}
function DataTable({
title = "Recent Transactions",
exportButtonText = "Export",
transactions = [
{ id: "1", status: "Completed", description: "Payment received", date: "Mar 15, 2024", amount: "$250.00" }
],
onExport
}: DataTableProps) {
return (
<Card>
<div className="p-4 border-b border-border">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-lg">{title}</h3>
<Button variant="outline" size="sm" onClick={onExport}>
{exportButtonText}
</Button>
</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead>Status</TableHead>
<TableHead>Transaction</TableHead>
<TableHead>Date</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{transactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell>
<Badge variant={transaction.statusVariant}>{transaction.status}</Badge>
</TableCell>
<TableCell className="font-medium">{transaction.description}</TableCell>
<TableCell>{transaction.date}</TableCell>
<TableCell className={cn(
"text-right",
transaction.isNegative && "text-destructive"
)}>{transaction.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
)
}`,
form: `interface CreateAccountFormProps {
title?: string
description?: string
firstNameLabel?: string
lastNameLabel?: string
emailLabel?: string
firstNamePlaceholder?: string
lastNamePlaceholder?: string
emailPlaceholder?: string
cancelButtonText?: string
submitButtonText?: string
onCancel?: () => void
onSubmit?: (data: { firstName: string; lastName: string; email: string }) => void
}
function CreateAccountForm({
title = "Create Account",
description = "Fill in your details to get started",
firstNameLabel = "First Name",
lastNameLabel = "Last Name",
emailLabel = "Email",
firstNamePlaceholder = "John",
lastNamePlaceholder = "Doe",
emailPlaceholder = "john@example.com",
cancelButtonText = "Cancel",
submitButtonText = "Create Account",
onCancel,
onSubmit
}: CreateAccountFormProps) {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [email, setEmail] = useState("")
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
onSubmit?.({ firstName, lastName, email })
}
return (
<Card className="p-6">
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<h3 className="text-xl font-semibold mb-4">{title}</h3>
<p className="text-sm text-muted-foreground">
{description}
</p>
</div>
<Separator />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">{firstNameLabel}</Label>
<Input
id="firstName"
placeholder={firstNamePlaceholder}
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">{lastNameLabel}</Label>
<Input
id="lastName"
placeholder={lastNamePlaceholder}
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">{emailLabel}</Label>
<div className="relative">
<Envelope className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder={emailPlaceholder}
className="pl-10"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</div>
<Separator />
<div className="flex items-center justify-between gap-4">
<Button variant="outline" type="button" onClick={onCancel}>
{cancelButtonText}
</Button>
<Button type="submit">
{submitButtonText}
<ArrowRight className="ml-2" />
</Button>
</div>
</form>
</Card>
)
}`,
taskList: `interface Task {
id: string
title: string
description: string
status: string
statusColor?: "accent" | "destructive"
badgeText: string
badgeVariant?: "default" | "secondary" | "destructive" | "outline"
icon: React.ReactNode
}
interface TaskListProps {
title?: string
addButtonText?: string
tasks?: Task[]
onAddTask?: () => void
}
function TaskList({
title = "Project Tasks",
addButtonText = "Add Task",
tasks = [
{
id: "1",
title: "Design system documentation",
description: "Complete the component library documentation",
status: "Completed",
statusColor: "accent" as const,
badgeText: "Design",
badgeVariant: "secondary" as const,
icon: <CheckCircle weight="fill" className="h-6 w-6 text-accent mt-0.5" />
}
],
onAddTask
}: TaskListProps) {
return (
<Card>
<div className="p-4 border-b border-border">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-lg">{title}</h3>
<Button size="sm" onClick={onAddTask}>
<Plus className="mr-2" />
{addButtonText}
</Button>
</div>
</div>
<div className="divide-y divide-border">
{tasks.map((task) => (
<div key={task.id} className="p-4 hover:bg-muted/50 transition-colors">
<div className="flex items-start gap-4">
{task.icon}
<div className="flex-1 min-w-0">
<h4 className="font-medium">{task.title}</h4>
<p className="text-sm text-muted-foreground mt-1">
{task.description}
</p>
<div className="flex items-center gap-4 mt-3">
<Badge variant={task.badgeVariant}>{task.badgeText}</Badge>
<span className="text-xs text-muted-foreground">{task.status}</span>
</div>
</div>
</div>
</div>
))}
</div>
</Card>
)
}`,
sidebarNavigation: `interface SidebarNavItem {
label: string
icon: React.ReactNode
variant?: "default" | "ghost"
onClick?: () => void
}
interface SidebarNavigationProps {
title?: string
navItems?: SidebarNavItem[]
contentText?: string
}
function SidebarNavigation({
title = "Dashboard",
navItems = [
{ label: "Home", icon: <House className="mr-2" />, variant: "ghost" as const },
{ label: "Analytics", icon: <ChartBar className="mr-2" />, variant: "default" as const },
{ label: "Projects", icon: <Folder className="mr-2" />, variant: "ghost" as const }
],
contentText = "Main content area"
}: SidebarNavigationProps) {
return (
<div className="flex">
<aside className="w-64 border-r border-border bg-card/50 p-4">
<div className="space-y-6">
<div className="flex items-center gap-2 px-2">
<div className="h-8 w-8 rounded-lg bg-accent" />
<span className="font-bold">{title}</span>
</div>
<nav className="space-y-1">
{navItems.map((item) => (
<Button
key={item.label}
variant={item.variant || "ghost"}
className="w-full justify-start"
onClick={item.onClick}
>
{item.icon}
{item.label}
</Button>
))}
</nav>
</div>
</aside>
<div className="flex-1 p-6">
<p className="text-sm text-muted-foreground">
{contentText}
</p>
</div>
</div>
)
}`,
}

View File

@@ -0,0 +1,256 @@
/**
* Templates - Complete page layouts code snippets
*/
export const templatesCodeSnippets = {
dashboardLayout: `interface StatCard {
label: string
value: string
trend?: string
}
interface DashboardLayoutProps {
title?: string
navItems?: Array<{ label: string; icon: React.ReactNode; variant?: "default" | "ghost" }>
avatarUrl?: string
avatarFallback?: string
stats?: StatCard[]
onNotificationClick?: () => void
}
function DashboardLayout({
title = "Dashboard",
navItems = [
{ label: "Overview", icon: <House className="mr-2" />, variant: "default" as const },
{ label: "Analytics", icon: <ChartBar className="mr-2" />, variant: "ghost" as const }
],
avatarUrl,
avatarFallback = "U",
stats = [
{ label: "Total Revenue", value: "$45,231", trend: "+20.1% from last month" }
],
onNotificationClick
}: DashboardLayoutProps) {
return (
<div className="min-h-screen bg-background">
<div className="border-b border-border bg-card p-4">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold">{title}</h3>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={onNotificationClick}>
<Bell />
</Button>
<Avatar className="h-8 w-8">
{avatarUrl && <AvatarImage src={avatarUrl} />}
<AvatarFallback>{avatarFallback}</AvatarFallback>
</Avatar>
</div>
</div>
</div>
<div className="flex">
<aside className="w-64 border-r border-border bg-card/30 p-4">
<nav className="space-y-1">
{navItems.map((item) => (
<Button
key={item.label}
variant={item.variant || "ghost"}
className="w-full justify-start"
>
{item.icon}
{item.label}
</Button>
))}
</nav>
</aside>
<main className="flex-1 p-6">
<h1 className="text-3xl font-bold">Overview</h1>
<div className="grid grid-cols-3 gap-6 mt-6">
{stats.map((stat) => (
<Card key={stat.label} className="p-6">
<p className="text-sm text-muted-foreground">{stat.label}</p>
<p className="text-3xl font-bold mt-2">{stat.value}</p>
{stat.trend && (
<p className="text-sm text-accent mt-2">{stat.trend}</p>
)}
</Card>
))}
</div>
</main>
</div>
</div>
)
}`,
landingPage: `interface LandingPageProps {
brandName?: string
navItems?: string[]
badge?: string
headline?: string
description?: string
primaryCta?: string
secondaryCta?: string
onPrimaryCta?: () => void
onSecondaryCta?: () => void
}
function LandingPage({
brandName = "ProductName",
navItems = ["Features", "Sign Up"],
badge = "New Release",
headline = "Build Amazing Products Faster",
description = "The complete toolkit for modern product development.",
primaryCta = "Get Started",
secondaryCta,
onPrimaryCta,
onSecondaryCta
}: LandingPageProps) {
return (
<div className="min-h-screen">
<div className="border-b border-border bg-card p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-accent" />
<h3 className="text-xl font-bold">{brandName}</h3>
</div>
<div className="flex items-center gap-2">
{navItems.map((item) => (
<Button key={item} variant="ghost" size="sm">{item}</Button>
))}
</div>
</div>
</div>
<div className="p-12 text-center bg-gradient-to-br from-primary/20 to-accent/20">
<Badge className="mb-4">{badge}</Badge>
<h1 className="text-5xl font-bold mb-6">
{headline}
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
{description}
</p>
<div className="flex items-center justify-center gap-4">
<Button size="lg" onClick={onPrimaryCta}>
{primaryCta}
<ArrowRight className="ml-2" />
</Button>
{secondaryCta && (
<Button size="lg" variant="outline" onClick={onSecondaryCta}>
{secondaryCta}
</Button>
)}
</div>
</div>
</div>
)
}`,
ecommercePage: `interface EcommercePageProps {
storeName?: string
productBadge?: string
productName?: string
productPrice?: string
originalPrice?: string
onAddToCart?: () => void
}
function EcommercePage({
storeName = "Store",
productBadge = "New Arrival",
productName = "Premium Product",
productPrice = "$299.00",
originalPrice,
onAddToCart
}: EcommercePageProps) {
return (
<div className="min-h-screen">
<div className="border-b border-border bg-card p-4">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold">{storeName}</h3>
<Button variant="ghost" size="icon">
<ShoppingCart />
</Button>
</div>
</div>
<div className="p-8">
<div className="grid grid-cols-2 gap-12">
<div className="aspect-square rounded-lg bg-gradient-to-br from-primary to-accent" />
<div className="space-y-6">
<Badge>{productBadge}</Badge>
<h1 className="text-4xl font-bold">{productName}</h1>
<div className="flex items-baseline gap-3">
<span className="text-3xl font-bold">{productPrice}</span>
{originalPrice && (
<span className="text-lg text-muted-foreground line-through">
{originalPrice}
</span>
)}
</div>
<Button size="lg" className="w-full" onClick={onAddToCart}>
<ShoppingCart className="mr-2" />
Add to Cart
</Button>
</div>
</div>
</div>
</div>
)
}`,
blogArticle: `interface BlogArticleProps {
blogName?: string
tags?: string[]
title?: string
authorName?: string
authorAvatar?: string
authorFallback?: string
date?: string
readTime?: string
excerpt?: string
}
function BlogArticle({
blogName = "Blog",
tags = ["Design", "Tutorial"],
title = "Building a Comprehensive Component Library",
authorName = "Alex Writer",
authorAvatar,
authorFallback = "AW",
date = "March 15, 2024",
readTime = "10 min read",
excerpt = "Design systems have become an essential part of modern product development."
}: BlogArticleProps) {
return (
<div className="min-h-screen">
<div className="border-b border-border bg-card p-4">
<h3 className="text-xl font-bold">{blogName}</h3>
</div>
<div className="p-8">
<div className="max-w-4xl mx-auto space-y-6">
<div className="flex gap-2">
{tags.map((tag, idx) => (
<Badge key={tag} variant={idx === 0 ? "default" : "secondary"}>
{tag}
</Badge>
))}
</div>
<h1 className="text-5xl font-bold">
{title}
</h1>
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
{authorAvatar && <AvatarImage src={authorAvatar} />}
<AvatarFallback>{authorFallback}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{authorName}</p>
<p className="text-sm text-muted-foreground">
{date} · {readTime}
</p>
</div>
</div>
<div className="aspect-video rounded-lg bg-gradient-to-br from-primary to-accent" />
<p className="text-lg text-muted-foreground leading-relaxed">
{excerpt}
</p>
</div>
</div>
</div>
)
}`,
}