This document provides practical examples of how to use the common components, hooks, and utilities in the WebGUI application.
The Button component provides a flexible button with multiple variants and sizes.
import { Button } from "@/components/common"
function MyComponent() {
return <Button onClick={() => console.log("clicked")}>Click Me</Button>
}
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button variant="danger">Danger Button</Button>
<Button size="xs">Extra Small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium (default)</Button>
<Button size="lg">Large</Button>
function SaveButton() {
const [isSaving, setIsSaving] = useState(false)
const handleSave = async () => {
setIsSaving(true)
try {
await saveData()
} finally {
setIsSaving(false)
}
}
return (
<Button loading={isSaving} onClick={handleSave}>
Save Changes
</Button>
)
}
<Button disabled>Disabled Button</Button>
The IconButton component is designed for icon-only buttons with consistent sizing.
import { IconButton } from "@/components/common"
function CloseButton({ onClose }: { onClose: () => void }) {
return <IconButton icon={<XMarkIcon />} aria-label="Close dialog" onClick={onClose} />
}
<IconButton
size="sm"
icon={<TrashIcon />}
aria-label="Delete"
onClick={handleDelete}
/>
<IconButton
size="md"
icon={<EditIcon />}
aria-label="Edit"
onClick={handleEdit}
/>
<IconButton
size="lg"
icon={<SettingsIcon />}
aria-label="Settings"
onClick={handleSettings}
/>
The Card component provides a container with optional header, body, and footer sections.
import { Card, CardHeader, CardBody, CardFooter } from "@/components/common"
function UserProfile() {
return (
<Card>
<CardHeader>
<h3>User Profile</h3>
</CardHeader>
<CardBody>
<p>Name: John Doe</p>
<p>Email: [email protected]</p>
</CardBody>
<CardFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Save</Button>
</CardFooter>
</Card>
)
}
<Card hoverable onClick={handleCardClick}>
<CardBody>
<p>Click me!</p>
</CardBody>
</Card>
<Card padding="none">
<div className="p-6">Custom content with manual padding</div>
</Card>
<Card padding="lg">
<p>Large padding card</p>
</Card>
The Modal component provides a dialog with backdrop and keyboard support.
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "@/components/common"
function DeleteConfirmation({ isOpen, onClose, onConfirm }: Props) {
return (
<Modal isOpen={isOpen} onClose={onClose} size="md">
<ModalHeader onClose={onClose}>
<h3>Confirm Deletion</h3>
</ModalHeader>
<ModalBody>
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
</ModalBody>
<ModalFooter>
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={onConfirm}>
Delete
</Button>
</ModalFooter>
</Modal>
)
}
function EditUserModal({ isOpen, onClose, user }: Props) {
const [name, setName] = useState(user.name)
const [email, setEmail] = useState(user.email)
const [isSaving, setIsSaving] = useState(false)
const handleSubmit = async () => {
setIsSaving(true)
try {
await updateUser({ name, email })
onClose()
} finally {
setIsSaving(false)
}
}
return (
<Modal isOpen={isOpen} onClose={onClose} size="lg">
<ModalHeader onClose={onClose}>
<h3>Edit User</h3>
</ModalHeader>
<ModalBody>
<Input label="Name" value={name} onChange={(e) => setName(e.target.value)} />
<Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</ModalBody>
<ModalFooter>
<Button variant="secondary" onClick={onClose} disabled={isSaving}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit} loading={isSaving}>
Save Changes
</Button>
</ModalFooter>
</Modal>
)
}
// Don't close on backdrop click
<Modal isOpen={isOpen} onClose={onClose} closeOnBackdropClick={false} closeOnEscape={false}>
{/* Modal content */}
</Modal>
The Input component provides a text input with label, error, and icon support.
import { Input } from "@/components/common"
function LoginForm() {
const [email, setEmail] = useState("")
return (
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
)
}
function Form() {
const [email, setEmail] = useState("")
const [error, setError] = useState("")
const handleBlur = () => {
if (!email.includes("@")) {
setError("Please enter a valid email address")
} else {
setError("")
}
}
return (
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onBlur={handleBlur}
error={error}
helperText="We'll never share your email with anyone"
/>
)
}
<Input
label="Search"
placeholder="Search..."
leftIcon={<SearchIcon />}
/>
<Input
label="Password"
type="password"
rightIcon={<EyeIcon />}
/>
<Input inputSize="sm" placeholder="Small input" />
<Input inputSize="md" placeholder="Medium input (default)" />
<Input inputSize="lg" placeholder="Large input" />
The Select component provides a dropdown with label and error support.
import { Select } from "@/components/common"
function CountrySelector() {
const [country, setCountry] = useState("")
const countries = [
{ value: "us", label: "United States" },
{ value: "uk", label: "United Kingdom" },
{ value: "ca", label: "Canada" },
]
return (
<Select
label="Country"
options={countries}
value={country}
onChange={(e) => setCountry(e.target.value)}
placeholder="Select a country..."
/>
)
}
function Form() {
const [role, setRole] = useState("")
const [error, setError] = useState("")
const roles = [
{ value: "admin", label: "Administrator" },
{ value: "user", label: "User" },
{ value: "guest", label: "Guest", disabled: true },
]
return (
<Select
label="Role"
options={roles}
value={role}
onChange={(e) => setRole(e.target.value)}
error={error}
helperText="Select the user's role"
/>
)
}
The useKeyboard hook manages keyboard shortcuts.
import { useKeyboard } from "@/hooks"
function Editor() {
useKeyboard({
handlers: [
{
key: "s",
modKey: true, // Cmd/Ctrl
handler: () => save(),
},
{
key: "Escape",
handler: () => closeModal(),
},
],
})
return <div>Editor content</div>
}
import { useKeyboardShortcut } from "@/hooks"
function Component() {
useKeyboardShortcut("s", () => save(), { modKey: true })
}
useKeyboard({
handlers: [
{
key: "s",
modKey: true,
shiftKey: true,
handler: () => saveAs(),
},
],
})
The useDebounce hook delays value updates.
import { useDebounce } from "@/hooks"
import { useState, useEffect } from "react"
function SearchComponent() {
const [searchQuery, setSearchQuery] = useState("")
const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {
if (debouncedQuery) {
performSearch(debouncedQuery)
}
}, [debouncedQuery])
return <Input placeholder="Search..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} />
}
import { useDebouncedCallback } from "@/hooks"
function AutoSave() {
const handleSave = useDebouncedCallback((content: string) => {
saveToServer(content)
}, 1000)
return <textarea onChange={(e) => handleSave(e.target.value)} />
}
The useLocalStorage hook manages localStorage with React state.
import { useLocalStorage } from "@/hooks"
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage("theme", "light")
return <Button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme ({theme})</Button>
}
function Settings() {
const [settings, setSettings] = useLocalStorage(
"app-settings",
{ notifications: true, language: "en" },
{
onError: (error) => {
console.error("Failed to save settings:", error)
showToast("Failed to save settings")
},
},
)
return (
<div>
<label>
<input
type="checkbox"
checked={settings.notifications}
onChange={(e) =>
setSettings({
...settings,
notifications: e.target.checked,
})
}
/>
Enable Notifications
</label>
</div>
)
}
function SyncedCounter() {
const [count, setCount] = useLocalStorage("counter", 0, {
syncAcrossTabs: true, // Sync state across browser tabs
})
return (
<div>
<p>Count: {count}</p>
<Button onClick={() => setCount(count + 1)}>Increment</Button>
</div>
)
}
The useClickOutside hook detects clicks outside an element.
import { useClickOutside } from "@/hooks"
import { useRef, useState } from "react"
function Dropdown() {
const [isOpen, setIsOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
useClickOutside(dropdownRef, () => {
setIsOpen(false)
})
return (
<div>
<Button onClick={() => setIsOpen(!isOpen)}>Toggle Dropdown</Button>
{isOpen && (
<div ref={dropdownRef} className="dropdown-menu">
<button>Option 1</button>
<button>Option 2</button>
</div>
)}
</div>
)
}
import { useClickOutsideWithEscape } from "@/hooks"
function Modal() {
const modalRef = useRef<HTMLDivElement>(null)
useClickOutsideWithEscape(modalRef, () => {
closeModal()
})
return <div ref={modalRef}>Modal content</div>
}
The cn utility conditionally joins class names.
import { cn } from "@/utils"
function Component({ isActive, isDisabled }: Props) {
return <div className={cn("base-class", isActive && "active-class", isDisabled && "disabled-class")}>Content</div>
}
<div
className={cn("base", {
"text-red-500": hasError,
"text-green-500": isSuccess,
"font-bold": isImportant,
})}
>
Text
</div>
const baseClasses = ['flex', 'items-center', 'gap-2']
const conditionalClasses = isLarge ? ['text-lg', 'p-4'] : ['text-sm', 'p-2']
<div className={cn(baseClasses, conditionalClasses)}>
Content
</div>
import { formatK, formatKM, formatCost, formatPercentage } from "@/utils"
function Stats({ tokens, cost, accuracy }: Props) {
return (
<div>
<p>Tokens: {formatK(tokens)}</p>
<p>Cost: {formatCost(cost)}</p>
<p>Accuracy: {formatPercentage(accuracy)}</p>
</div>
)
}
import { formatDate, formatDateTime, formatRelativeTime, formatDuration } from "@/utils"
function Timestamps({ createdAt, duration }: Props) {
return (
<div>
<p>Created: {formatDateTime(createdAt)}</p>
<p>Time ago: {formatRelativeTime(createdAt)}</p>
<p>Duration: {formatDuration(duration)}</p>
</div>
)
}
import { formatFileSize } from "@/utils"
function FileInfo({ file }: Props) {
return <p>Size: {formatFileSize(file.size)}</p>
}
import { truncate, capitalize, toTitleCase } from "@/utils"
function TextDisplay({ text }: Props) {
return (
<div>
<p>Short: {truncate(text, 50)}</p>
<p>Title: {toTitleCase(text)}</p>
<p>Capitalized: {capitalize(text)}</p>
</div>
)
}
import { isValidEmail, isValidUrl, isValidHttpUrl } from "@/utils"
function ContactForm() {
const [email, setEmail] = useState("")
const [website, setWebsite] = useState("")
const [errors, setErrors] = useState({})
const handleSubmit = () => {
const newErrors = {}
if (!isValidEmail(email)) {
newErrors.email = "Invalid email address"
}
if (website && !isValidHttpUrl(website)) {
newErrors.website = "Invalid website URL"
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
// Submit form
}
return (
<form onSubmit={handleSubmit}>
<Input label="Email" value={email} onChange={(e) => setEmail(e.target.value)} error={errors.email} />
<Input label="Website" value={website} onChange={(e) => setWebsite(e.target.value)} error={errors.website} />
<Button type="submit">Submit</Button>
</form>
)
}
import { isEmpty, isNotEmpty, isLengthInRange, matches } from "@/utils"
function validateUsername(username: string): string | null {
if (isEmpty(username)) {
return "Username is required"
}
if (!isLengthInRange(username, 3, 20)) {
return "Username must be between 3 and 20 characters"
}
if (!matches(username, /^[a-zA-Z0-9_]+$/)) {
return "Username can only contain letters, numbers, and underscores"
}
return null
}
import { isStrongPassword } from "@/utils"
function PasswordInput({ password, onChange }: Props) {
const isStrong = isStrongPassword(password, {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireDigits: true,
requireSpecialChars: true,
})
return (
<div>
<Input type="password" label="Password" value={password} onChange={onChange} />
{!isStrong && password && (
<p className="text-red-500 text-sm">
Password must be at least 8 characters with uppercase, lowercase, digits, and special characters
</p>
)}
</div>
)
}
import { normalizePath, toDisplayPath, dirname, basename, extname, join } from "@/utils"
function FileExplorer({ filePath, projectRoot }: Props) {
const displayPath = toDisplayPath(filePath, projectRoot)
const fileName = basename(filePath)
const directory = dirname(filePath)
const extension = extname(filePath)
return (
<div>
<p>File: {fileName}</p>
<p>Directory: {directory}</p>
<p>Type: {extension}</p>
<p>Path: {displayPath}</p>
</div>
)
}
import { join } from "@/utils"
function buildPath(base: string, ...segments: string[]) {
return join(base, ...segments)
}
// Example
const configPath = buildPath("src", "config", "settings.json")
// => "src/config/settings.json"
Here's a complete example combining multiple components, hooks, and utilities:
import { useState } from "react"
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Input, Select } from "@/components/common"
import { useKeyboard, useLocalStorage } from "@/hooks"
import { isValidEmail, isEmpty } from "@/utils"
interface User {
name: string
email: string
role: string
}
function UserManagementForm() {
const [isOpen, setIsOpen] = useState(false)
const [users, setUsers] = useLocalStorage<User[]>("users", [])
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [role, setRole] = useState("")
const [errors, setErrors] = useState<Record<string, string>>({})
const [isSaving, setIsSaving] = useState(false)
// Keyboard shortcut to open modal
useKeyboard({
handlers: [
{
key: "n",
modKey: true,
handler: () => setIsOpen(true),
},
],
})
const roleOptions = [
{ value: "admin", label: "Administrator" },
{ value: "user", label: "User" },
{ value: "viewer", label: "Viewer" },
]
const validate = (): boolean => {
const newErrors: Record<string, string> = {}
if (isEmpty(name)) {
newErrors.name = "Name is required"
}
if (isEmpty(email)) {
newErrors.email = "Email is required"
} else if (!isValidEmail(email)) {
newErrors.email = "Invalid email address"
}
if (isEmpty(role)) {
newErrors.role = "Role is required"
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async () => {
if (!validate()) return
setIsSaving(true)
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000))
setUsers([...users, { name, email, role }])
// Reset form
setName("")
setEmail("")
setRole("")
setIsOpen(false)
} finally {
setIsSaving(false)
}
}
const handleClose = () => {
if (!isSaving) {
setIsOpen(false)
setErrors({})
}
}
return (
<div>
<Button variant="primary" onClick={() => setIsOpen(true)}>
Add User (Cmd+N)
</Button>
<Modal isOpen={isOpen} onClose={handleClose} size="lg" closeOnBackdropClick={!isSaving} closeOnEscape={!isSaving}>
<ModalHeader onClose={handleClose}>
<h3 className="text-lg font-semibold">Add New User</h3>
</ModalHeader>
<ModalBody>
<div className="space-y-4">
<Input
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
error={errors.name}
placeholder="Enter user's name"
disabled={isSaving}
/>
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
error={errors.email}
placeholder="Enter user's email"
disabled={isSaving}
/>
<Select
label="Role"
options={roleOptions}
value={role}
onChange={(e) => setRole(e.target.value)}
error={errors.role}
placeholder="Select a role..."
disabled={isSaving}
/>
</div>
</ModalBody>
<ModalFooter>
<Button variant="secondary" onClick={handleClose} disabled={isSaving}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit} loading={isSaving}>
Add User
</Button>
</ModalFooter>
</Modal>
{/* Display users */}
<div className="mt-4 space-y-2">
{users.map((user, index) => (
<div key={index} className="p-4 border rounded">
<p className="font-semibold">{user.name}</p>
<p className="text-sm text-gray-600">{user.email}</p>
<p className="text-xs text-gray-500">{user.role}</p>
</div>
))}
</div>
</div>
)
}
@/components/common, @/hooks, @/utilsFor more detailed documentation, see: