Merge pull request #54 from johndoe6345789/codex/refactor-comprehensivedemopage-into-subcomponents

Refactor ComprehensiveDemoPage into subcomponents and externalize UI strings
This commit is contained in:
2026-01-18 00:42:11 +00:00
committed by GitHub
8 changed files with 404 additions and 264 deletions

View File

@@ -1,26 +1,15 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Checkbox } from '@/components/ui/checkbox'
import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import { Progress } from '@/components/ui/progress'
import { useCRUD, useSearch } from '@/hooks/data'
import { useState } from 'react'
import { useCRUD } from '@/hooks/data'
import { useDialog } from '@/hooks/ui'
import { useKV } from '@/hooks/use-kv'
import { SearchBar } from '@/components/molecules/SearchBar'
import { DataList, ActionButton, IconButton } from '@/components/atoms'
import { Plus, Trash, Check, Clock } from '@phosphor-icons/react'
import { toast } from 'sonner'
import { cn } from '@/lib/utils'
interface Todo {
id: number
text: string
completed: boolean
priority: 'low' | 'medium' | 'high'
createdAt: string
}
import strings from '@/data/comprehensive-demo.json'
import { ComprehensiveDemoHeader } from '@/components/comprehensive-demo/ComprehensiveDemoHeader'
import { ComprehensiveDemoStatsRow } from '@/components/comprehensive-demo/ComprehensiveDemoStatsRow'
import { ComprehensiveDemoTaskList } from '@/components/comprehensive-demo/ComprehensiveDemoTaskList'
import { ComprehensiveDemoArchitectureHighlights } from '@/components/comprehensive-demo/ComprehensiveDemoArchitectureHighlights'
import { ComprehensiveDemoDialogs } from '@/components/comprehensive-demo/ComprehensiveDemoDialogs'
import type { Priority, Todo } from '@/components/comprehensive-demo/types'
export function ComprehensiveDemoPage() {
const [todos, setTodos] = useKV<Todo[]>('json-demo-todos', [])
@@ -30,21 +19,9 @@ export function ComprehensiveDemoPage() {
setItems: (updater) => setTodos(updater),
})
const { query, setQuery, filtered } = useSearch({
items: todos,
searchFields: ['text' as keyof Todo],
})
const newTodoDialog = useDialog()
const [newTodoText, setNewTodoText] = useState('')
const [newTodoPriority, setNewTodoPriority] = useState<'low' | 'medium' | 'high'>('medium')
const stats = {
total: todos.length,
completed: todos.filter(t => t.completed).length,
pending: todos.filter(t => !t.completed).length,
completionRate: todos.length > 0 ? (todos.filter(t => t.completed).length / todos.length) * 100 : 0,
}
const [newTodoPriority, setNewTodoPriority] = useState<Priority>('medium')
const handleAddTodo = () => {
if (newTodoText.trim()) {
@@ -58,7 +35,7 @@ export function ComprehensiveDemoPage() {
setNewTodoText('')
setNewTodoPriority('medium')
newTodoDialog.close()
toast.success('Task added successfully!')
toast.success(strings.toast.added)
}
}
@@ -66,248 +43,38 @@ export function ComprehensiveDemoPage() {
const todo = todos.find(t => t.id === id)
if (todo) {
crud.update(id, { completed: !todo.completed })
toast.success(todo.completed ? 'Task marked as pending' : 'Task completed!')
toast.success(todo.completed ? strings.toast.pending : strings.toast.completed)
}
}
const handleDeleteTodo = (id: number) => {
crud.delete(id)
toast.success('Task deleted')
}
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'bg-red-500/10 text-red-600 border-red-500/20'
case 'medium': return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'
case 'low': return 'bg-blue-500/10 text-blue-600 border-blue-500/20'
default: return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
}
toast.success(strings.toast.deleted)
}
return (
<div className="h-full overflow-auto p-6 bg-gradient-to-br from-background via-background to-primary/5">
<div className="max-w-5xl mx-auto space-y-6">
{/* Header */}
<div className="space-y-2">
<h1 className="text-4xl font-bold bg-gradient-to-r from-primary via-primary to-accent bg-clip-text text-transparent">
Advanced Task Manager
</h1>
<p className="text-muted-foreground">
Demonstrating atomic components, custom hooks, and reactive state management
</p>
</div>
{/* Stats Row */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-card/50 backdrop-blur">
<CardContent className="pt-6">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">Total Tasks</p>
<p className="text-3xl font-bold">{stats.total}</p>
</div>
</CardContent>
</Card>
<Card className="bg-green-500/5 backdrop-blur border-green-500/20">
<CardContent className="pt-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">Completed</p>
<p className="text-3xl font-bold text-green-600">{stats.completed}</p>
</div>
<Check className="text-green-600" size={24} weight="duotone" />
</div>
</CardContent>
</Card>
<Card className="bg-blue-500/5 backdrop-blur border-blue-500/20">
<CardContent className="pt-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">Pending</p>
<p className="text-3xl font-bold text-blue-600">{stats.pending}</p>
</div>
<Clock className="text-blue-600" size={24} weight="duotone" />
</div>
</CardContent>
</Card>
<Card className="bg-primary/5 backdrop-blur border-primary/20">
<CardContent className="pt-6">
<div className="space-y-2">
<p className="text-sm text-muted-foreground">Completion</p>
<p className="text-3xl font-bold text-primary">{Math.round(stats.completionRate)}%</p>
<Progress value={stats.completionRate} className="h-2" />
</div>
</CardContent>
</Card>
</div>
{/* Main Content */}
<Card>
<CardHeader>
<div className="flex items-start justify-between">
<div>
<CardTitle>Your Tasks</CardTitle>
<CardDescription>Manage your tasks with advanced features</CardDescription>
</div>
<ActionButton
icon={<Plus size={16} weight="bold" />}
label="Add Task"
onClick={newTodoDialog.open}
/>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Search */}
<SearchBar
value={query}
onChange={setQuery}
placeholder="Search tasks..."
/>
<Separator />
{/* Task List */}
<DataList
items={filtered}
emptyMessage={query ? 'No tasks match your search' : 'No tasks yet. Click "Add Task" to get started!'}
renderItem={(todo) => (
<Card className="bg-card/50 backdrop-blur hover:bg-card transition-colors">
<CardContent className="pt-6">
<div className="flex items-start gap-3">
<Checkbox
checked={todo.completed}
onCheckedChange={() => handleToggleTodo(todo.id)}
className="mt-1"
/>
<div className="flex-1 min-w-0">
<p className={cn(
'font-medium',
todo.completed && 'line-through text-muted-foreground'
)}>
{todo.text}
</p>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className={getPriorityColor(todo.priority)}>
{todo.priority}
</Badge>
<span className="text-xs text-muted-foreground">
{new Date(todo.createdAt).toLocaleDateString()}
</span>
</div>
</div>
<IconButton
icon={<Trash size={16} />}
onClick={() => handleDeleteTodo(todo.id)}
variant="ghost"
title="Delete task"
/>
</div>
</CardContent>
</Card>
)}
/>
</CardContent>
</Card>
{/* Architecture Info */}
<Card className="bg-accent/5 border-accent/20">
<CardHeader>
<CardTitle>Architecture Highlights</CardTitle>
<CardDescription>What makes this demo special</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-start gap-3">
<Check className="text-accent mt-1" size={16} weight="bold" />
<div>
<p className="font-medium">Custom Hooks</p>
<p className="text-sm text-muted-foreground">
useCRUD for data management, useSearch for filtering, useDialog for modals
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Check className="text-accent mt-1" size={16} weight="bold" />
<div>
<p className="font-medium">Atomic Components</p>
<p className="text-sm text-muted-foreground">
ActionButton, IconButton, DataList, SearchBar - all under 150 LOC
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Check className="text-accent mt-1" size={16} weight="bold" />
<div>
<p className="font-medium">KV Persistence</p>
<p className="text-sm text-muted-foreground">
All data persists between sessions using the Spark KV store
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Check className="text-accent mt-1" size={16} weight="bold" />
<div>
<p className="font-medium">Reactive State</p>
<p className="text-sm text-muted-foreground">
Computed stats update automatically when todos change
</p>
</div>
</div>
</CardContent>
</Card>
<ComprehensiveDemoHeader />
<ComprehensiveDemoStatsRow todos={todos} />
<ComprehensiveDemoTaskList
todos={todos}
onAdd={newTodoDialog.open}
onToggle={handleToggleTodo}
onDelete={handleDeleteTodo}
/>
<ComprehensiveDemoArchitectureHighlights />
</div>
{/* Add Todo Dialog */}
{newTodoDialog.isOpen && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Add New Task</CardTitle>
<CardDescription>Create a new task with priority</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">Task Description</label>
<Input
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="What needs to be done?"
onKeyDown={(e) => e.key === 'Enter' && handleAddTodo()}
autoFocus
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Priority</label>
<div className="flex gap-2">
{(['low', 'medium', 'high'] as const).map((priority) => (
<Button
key={priority}
variant={newTodoPriority === priority ? 'default' : 'outline'}
size="sm"
onClick={() => setNewTodoPriority(priority)}
className="flex-1"
>
{priority}
</Button>
))}
</div>
</div>
<div className="flex gap-2 pt-4">
<Button onClick={handleAddTodo} className="flex-1" disabled={!newTodoText.trim()}>
Add Task
</Button>
<Button onClick={newTodoDialog.close} variant="outline">
Cancel
</Button>
</div>
</CardContent>
</Card>
</div>
)}
<ComprehensiveDemoDialogs
isOpen={newTodoDialog.isOpen}
newTodoText={newTodoText}
newTodoPriority={newTodoPriority}
onNewTodoTextChange={setNewTodoText}
onNewTodoPriorityChange={setNewTodoPriority}
onAdd={handleAddTodo}
onClose={newTodoDialog.close}
/>
</div>
)
}
// Missing import
import { useState } from 'react'

View File

@@ -0,0 +1,25 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Check } from '@phosphor-icons/react'
import strings from '@/data/comprehensive-demo.json'
export function ComprehensiveDemoArchitectureHighlights() {
return (
<Card className="bg-accent/5 border-accent/20">
<CardHeader>
<CardTitle>{strings.architecture.title}</CardTitle>
<CardDescription>{strings.architecture.description}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{strings.architecture.items.map((item) => (
<div key={item.title} className="flex items-start gap-3">
<Check className="text-accent mt-1" size={16} weight="bold" />
<div>
<p className="font-medium">{item.title}</p>
<p className="text-sm text-muted-foreground">{item.description}</p>
</div>
</div>
))}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,78 @@
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import strings from '@/data/comprehensive-demo.json'
import type { Priority } from './types'
interface ComprehensiveDemoDialogsProps {
isOpen: boolean
newTodoText: string
newTodoPriority: Priority
onNewTodoTextChange: (value: string) => void
onNewTodoPriorityChange: (value: Priority) => void
onAdd: () => void
onClose: () => void
}
const priorities: Priority[] = ['low', 'medium', 'high']
export function ComprehensiveDemoDialogs({
isOpen,
newTodoText,
newTodoPriority,
onNewTodoTextChange,
onNewTodoPriorityChange,
onAdd,
onClose,
}: ComprehensiveDemoDialogsProps) {
if (!isOpen) {
return null
}
return (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>{strings.dialog.title}</CardTitle>
<CardDescription>{strings.dialog.description}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">{strings.dialog.taskDescriptionLabel}</label>
<Input
value={newTodoText}
onChange={(event) => onNewTodoTextChange(event.target.value)}
placeholder={strings.dialog.taskDescriptionPlaceholder}
onKeyDown={(event) => event.key === 'Enter' && onAdd()}
autoFocus
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">{strings.dialog.priorityLabel}</label>
<div className="flex gap-2">
{priorities.map((priority) => (
<Button
key={priority}
variant={newTodoPriority === priority ? 'default' : 'outline'}
size="sm"
onClick={() => onNewTodoPriorityChange(priority)}
className="flex-1"
>
{strings.priorityLabels[priority]}
</Button>
))}
</div>
</div>
<div className="flex gap-2 pt-4">
<Button onClick={onAdd} className="flex-1" disabled={!newTodoText.trim()}>
{strings.dialog.addButton}
</Button>
<Button onClick={onClose} variant="outline">
{strings.dialog.cancelButton}
</Button>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,12 @@
import strings from '@/data/comprehensive-demo.json'
export function ComprehensiveDemoHeader() {
return (
<div className="space-y-2">
<h1 className="text-4xl font-bold bg-gradient-to-r from-primary via-primary to-accent bg-clip-text text-transparent">
{strings.header.title}
</h1>
<p className="text-muted-foreground">{strings.header.subtitle}</p>
</div>
)
}

View File

@@ -0,0 +1,68 @@
import { useMemo } from 'react'
import { Card, CardContent } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress'
import { Check, Clock } from '@phosphor-icons/react'
import strings from '@/data/comprehensive-demo.json'
import type { Todo } from './types'
interface ComprehensiveDemoStatsRowProps {
todos: Todo[]
}
export function ComprehensiveDemoStatsRow({ todos }: ComprehensiveDemoStatsRowProps) {
const stats = useMemo(() => {
const total = todos.length
const completed = todos.filter((todo) => todo.completed).length
const pending = total - completed
const completionRate = total > 0 ? (completed / total) * 100 : 0
return { total, completed, pending, completionRate }
}, [todos])
return (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-card/50 backdrop-blur">
<CardContent className="pt-6">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">{strings.stats.total}</p>
<p className="text-3xl font-bold">{stats.total}</p>
</div>
</CardContent>
</Card>
<Card className="bg-green-500/5 backdrop-blur border-green-500/20">
<CardContent className="pt-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">{strings.stats.completed}</p>
<p className="text-3xl font-bold text-green-600">{stats.completed}</p>
</div>
<Check className="text-green-600" size={24} weight="duotone" />
</div>
</CardContent>
</Card>
<Card className="bg-blue-500/5 backdrop-blur border-blue-500/20">
<CardContent className="pt-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">{strings.stats.pending}</p>
<p className="text-3xl font-bold text-blue-600">{stats.pending}</p>
</div>
<Clock className="text-blue-600" size={24} weight="duotone" />
</div>
</CardContent>
</Card>
<Card className="bg-primary/5 backdrop-blur border-primary/20">
<CardContent className="pt-6">
<div className="space-y-2">
<p className="text-sm text-muted-foreground">{strings.stats.completion}</p>
<p className="text-3xl font-bold text-primary">{Math.round(stats.completionRate)}%</p>
<Progress value={stats.completionRate} className="h-2" />
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,116 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import { SearchBar } from '@/components/molecules/SearchBar'
import { DataList, ActionButton, IconButton } from '@/components/atoms'
import { Trash, Plus } from '@phosphor-icons/react'
import { useSearch } from '@/hooks/data'
import { cn } from '@/lib/utils'
import strings from '@/data/comprehensive-demo.json'
import type { Todo } from './types'
interface ComprehensiveDemoTaskListProps {
todos: Todo[]
onAdd: () => void
onToggle: (id: number) => void
onDelete: (id: number) => void
}
const priorityLabels = strings.priorityLabels as Record<Todo['priority'], string>
const getPriorityColor = (priority: Todo['priority']) => {
switch (priority) {
case 'high':
return 'bg-red-500/10 text-red-600 border-red-500/20'
case 'medium':
return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'
case 'low':
return 'bg-blue-500/10 text-blue-600 border-blue-500/20'
default:
return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
}
}
export function ComprehensiveDemoTaskList({
todos,
onAdd,
onToggle,
onDelete,
}: ComprehensiveDemoTaskListProps) {
const { query, setQuery, filtered } = useSearch({
items: todos,
searchFields: ['text' as keyof Todo],
})
return (
<Card>
<CardHeader>
<div className="flex items-start justify-between">
<div>
<CardTitle>{strings.taskCard.title}</CardTitle>
<CardDescription>{strings.taskCard.description}</CardDescription>
</div>
<ActionButton
icon={<Plus size={16} weight="bold" />}
label={strings.taskCard.addTask}
onClick={onAdd}
/>
</div>
</CardHeader>
<CardContent className="space-y-4">
<SearchBar
value={query}
onChange={setQuery}
placeholder={strings.taskCard.searchPlaceholder}
/>
<Separator />
<DataList
items={filtered}
emptyMessage={
query ? strings.taskCard.empty.noMatch : strings.taskCard.empty.noTasks
}
renderItem={(todo) => (
<Card className="bg-card/50 backdrop-blur hover:bg-card transition-colors">
<CardContent className="pt-6">
<div className="flex items-start gap-3">
<Checkbox
checked={todo.completed}
onCheckedChange={() => onToggle(todo.id)}
className="mt-1"
/>
<div className="flex-1 min-w-0">
<p
className={cn(
'font-medium',
todo.completed && 'line-through text-muted-foreground'
)}
>
{todo.text}
</p>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className={getPriorityColor(todo.priority)}>
{priorityLabels[todo.priority]}
</Badge>
<span className="text-xs text-muted-foreground">
{new Date(todo.createdAt).toLocaleDateString()}
</span>
</div>
</div>
<IconButton
icon={<Trash size={16} />}
onClick={() => onDelete(todo.id)}
variant="ghost"
title={strings.taskCard.deleteTitle}
/>
</div>
</CardContent>
</Card>
)}
/>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,9 @@
export type Priority = 'low' | 'medium' | 'high'
export interface Todo {
id: number
text: string
completed: boolean
priority: Priority
createdAt: string
}

View File

@@ -0,0 +1,65 @@
{
"header": {
"title": "Advanced Task Manager",
"subtitle": "Demonstrating atomic components, custom hooks, and reactive state management"
},
"stats": {
"total": "Total Tasks",
"completed": "Completed",
"pending": "Pending",
"completion": "Completion"
},
"taskCard": {
"title": "Your Tasks",
"description": "Manage your tasks with advanced features",
"addTask": "Add Task",
"searchPlaceholder": "Search tasks...",
"empty": {
"noMatch": "No tasks match your search",
"noTasks": "No tasks yet. Click \"Add Task\" to get started!"
},
"deleteTitle": "Delete task"
},
"architecture": {
"title": "Architecture Highlights",
"description": "What makes this demo special",
"items": [
{
"title": "Custom Hooks",
"description": "useCRUD for data management, useSearch for filtering, useDialog for modals"
},
{
"title": "Atomic Components",
"description": "ActionButton, IconButton, DataList, SearchBar - all under 150 LOC"
},
{
"title": "KV Persistence",
"description": "All data persists between sessions using the Spark KV store"
},
{
"title": "Reactive State",
"description": "Computed stats update automatically when todos change"
}
]
},
"dialog": {
"title": "Add New Task",
"description": "Create a new task with priority",
"taskDescriptionLabel": "Task Description",
"taskDescriptionPlaceholder": "What needs to be done?",
"priorityLabel": "Priority",
"addButton": "Add Task",
"cancelButton": "Cancel"
},
"priorityLabels": {
"low": "low",
"medium": "medium",
"high": "high"
},
"toast": {
"added": "Task added successfully!",
"pending": "Task marked as pending",
"completed": "Task completed!",
"deleted": "Task deleted"
}
}