(null)
useClickOutsideWithEscape(modalRef, () => {
closeModal()
})
return Modal content
}
```
---
## Utilities
### classNames (cn)
The `cn` utility conditionally joins class names.
#### Basic Usage
```tsx
import { cn } from "@/utils"
function Component({ isActive, isDisabled }: Props) {
return Content
}
```
#### With Objects
```tsx
Text
```
#### With Arrays
```tsx
const baseClasses = ['flex', 'items-center', 'gap-2']
const conditionalClasses = isLarge ? ['text-lg', 'p-4'] : ['text-sm', 'p-2']
Content
```
---
### Formatting
#### Number Formatting
```tsx
import { formatK, formatKM, formatCost, formatPercentage } from "@/utils"
function Stats({ tokens, cost, accuracy }: Props) {
return (
Tokens: {formatK(tokens)}
Cost: {formatCost(cost)}
Accuracy: {formatPercentage(accuracy)}
)
}
```
#### Date Formatting
```tsx
import { formatDate, formatDateTime, formatRelativeTime, formatDuration } from "@/utils"
function Timestamps({ createdAt, duration }: Props) {
return (
Created: {formatDateTime(createdAt)}
Time ago: {formatRelativeTime(createdAt)}
Duration: {formatDuration(duration)}
)
}
```
#### File Size Formatting
```tsx
import { formatFileSize } from "@/utils"
function FileInfo({ file }: Props) {
return Size: {formatFileSize(file.size)}
}
```
#### Text Formatting
```tsx
import { truncate, capitalize, toTitleCase } from "@/utils"
function TextDisplay({ text }: Props) {
return (
Short: {truncate(text, 50)}
Title: {toTitleCase(text)}
Capitalized: {capitalize(text)}
)
}
```
---
### Validation
#### Email and URL Validation
```tsx
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 (
)
}
```
#### String Validation
```tsx
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
}
```
#### Password Validation
```tsx
import { isStrongPassword } from "@/utils"
function PasswordInput({ password, onChange }: Props) {
const isStrong = isStrongPassword(password, {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireDigits: true,
requireSpecialChars: true,
})
return (
{!isStrong && password && (
Password must be at least 8 characters with uppercase, lowercase, digits, and special characters
)}
)
}
```
---
### Path Utilities
#### Path Manipulation
```tsx
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 (
File: {fileName}
Directory: {directory}
Type: {extension}
Path: {displayPath}
)
}
```
#### Joining Paths
```tsx
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"
```
---
## Complete Example: User Management Form
Here's a complete example combining multiple components, hooks, and utilities:
```tsx
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("users", [])
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [role, setRole] = useState("")
const [errors, setErrors] = useState>({})
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 = {}
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 (
Add New User
setName(e.target.value)}
error={errors.name}
placeholder="Enter user's name"
disabled={isSaving}
/>
setEmail(e.target.value)}
error={errors.email}
placeholder="Enter user's email"
disabled={isSaving}
/>
{/* Display users */}
{users.map((user, index) => (
{user.name}
{user.email}
{user.role}
))}
)
}
```
---
## Best Practices
1. **Always use TypeScript** - Components and utilities are fully typed
2. **Import from barrel exports** - Use `@/components/common`, `@/hooks`, `@/utils`
3. **Handle errors gracefully** - Use error states and validation
4. **Provide accessibility** - Use aria-labels, proper keyboard navigation
5. **Use loading states** - Show feedback during async operations
6. **Leverage hooks** - Use custom hooks for common patterns
7. **Keep components focused** - One component, one responsibility
8. **Test thoroughly** - Run tests after making changes
---
For more detailed documentation, see:
- [Component README](src/components/common/README.md)
- [Utilities README](src/utils/README.md)
- [Main Architecture README](README.md)