152 lines
3.9 KiB
TypeScript
152 lines
3.9 KiB
TypeScript
/**
|
|
* Component Template
|
|
*
|
|
* Usage: Copy this file to create new components
|
|
* 1. Replace TEMPLATE_NAME with your component name
|
|
* 2. Define the props interface
|
|
* 3. Implement your component logic
|
|
*/
|
|
|
|
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
// Component props interface
|
|
interface TemplateComponentProps {
|
|
// Define your props here
|
|
className?: string
|
|
children?: React.ReactNode
|
|
// Add other props as needed
|
|
title?: string
|
|
description?: string
|
|
variant?: 'default' | 'secondary' | 'outline'
|
|
size?: 'sm' | 'md' | 'lg'
|
|
disabled?: boolean
|
|
onClick?: () => void
|
|
}
|
|
|
|
/**
|
|
* TEMPLATE_NAME Component
|
|
*
|
|
* @param props - Component properties
|
|
* @returns JSX element
|
|
*/
|
|
export function TemplateComponent({
|
|
className,
|
|
children,
|
|
title,
|
|
description,
|
|
variant = 'default',
|
|
size = 'md',
|
|
disabled = false,
|
|
onClick,
|
|
...props
|
|
}: TemplateComponentProps) {
|
|
// State management
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
// Event handlers
|
|
const handleClick = async () => {
|
|
if (disabled || isLoading) return
|
|
|
|
setIsLoading(true)
|
|
try {
|
|
await onClick?.()
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
// CSS class variations
|
|
const variants = {
|
|
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
}
|
|
|
|
const sizes = {
|
|
sm: 'h-8 px-3 text-sm',
|
|
md: 'h-10 px-4 py-2',
|
|
lg: 'h-12 px-8 text-lg',
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
// Base styles
|
|
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
'disabled:pointer-events-none disabled:opacity-50',
|
|
// Variants
|
|
variants[variant],
|
|
sizes[size],
|
|
// Loading state
|
|
isLoading && 'cursor-not-allowed opacity-70',
|
|
className
|
|
)}
|
|
onClick={handleClick}
|
|
{...props}
|
|
>
|
|
{/* Loading spinner */}
|
|
{isLoading && (
|
|
<svg
|
|
className="mr-2 h-4 w-4 animate-spin"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<div className="flex flex-col items-center">
|
|
{title && <h3 className="font-semibold">{title}</h3>}
|
|
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Alternative component patterns:
|
|
*
|
|
* 1. Server Component (remove 'use client' and useState)
|
|
* 2. Compound Components:
|
|
*
|
|
* export function TemplateComponent({ children }: { children: React.ReactNode }) {
|
|
* return <div className="template-wrapper">{children}</div>;
|
|
* }
|
|
*
|
|
* TemplateComponent.Header = function Header({ children }: { children: React.ReactNode }) {
|
|
* return <header className="template-header">{children}</header>;
|
|
* };
|
|
*
|
|
* TemplateComponent.Body = function Body({ children }: { children: React.ReactNode }) {
|
|
* return <main className="template-body">{children}</main>;
|
|
* };
|
|
*
|
|
* 3. Forward Ref:
|
|
*
|
|
* export const TemplateComponent = forwardRef<HTMLDivElement, TemplateComponentProps>(
|
|
* ({ className, ...props }, ref) => {
|
|
* return <div ref={ref} className={cn('template-base', className)} {...props} />;
|
|
* }
|
|
* );
|
|
* TemplateComponent.displayName = "TemplateComponent";
|
|
*/
|