summaryrefslogtreecommitdiff
path: root/ui/tailwindcss/.claude/commands/create-component.md
diff options
context:
space:
mode:
Diffstat (limited to 'ui/tailwindcss/.claude/commands/create-component.md')
-rw-r--r--ui/tailwindcss/.claude/commands/create-component.md716
1 files changed, 716 insertions, 0 deletions
diff --git a/ui/tailwindcss/.claude/commands/create-component.md b/ui/tailwindcss/.claude/commands/create-component.md
new file mode 100644
index 0000000..fab23f5
--- /dev/null
+++ b/ui/tailwindcss/.claude/commands/create-component.md
@@ -0,0 +1,716 @@
+---
+name: create-component
+description: Create reusable components using TailwindCSS utilities with proper patterns and best practices
+tools: Write, Edit, Read, Grep, Glob
+---
+
+# Create TailwindCSS Component
+
+This command helps create well-structured, reusable components using TailwindCSS utilities following best practices and design system patterns.
+
+## What This Command Does
+
+1. **Component Architecture**
+ - Creates component files with proper TailwindCSS utility composition
+ - Implements responsive design patterns
+ - Sets up proper TypeScript/PropTypes definitions
+ - Follows accessibility best practices
+
+2. **Utility Composition**
+ - Uses semantic utility class combinations
+ - Implements proper state management (hover, focus, active)
+ - Creates responsive variants using breakpoint prefixes
+ - Follows mobile-first methodology
+
+3. **Design System Integration**
+ - Uses design tokens from TailwindCSS configuration
+ - Implements consistent spacing and typography scales
+ - Applies proper color palette and semantic colors
+ - Follows component variant patterns
+
+4. **Performance Optimization**
+ - Uses efficient utility combinations
+ - Optimizes for CSS purging
+ - Implements proper class composition strategies
+ - Avoids unnecessary custom CSS
+
+## Component Templates
+
+### Button Component
+
+```jsx
+// components/Button.jsx
+import React from 'react'
+import { cva } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ // Base styles
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm 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: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
+ size?: 'default' | 'sm' | 'lg' | 'icon'
+ loading?: boolean
+ leftIcon?: React.ReactNode
+ rightIcon?: React.ReactNode
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+ ({ className, variant, size, loading, leftIcon, rightIcon, children, ...props }, ref) => {
+ return (
+ <button
+ className={cn(buttonVariants({ variant, size }), className)}
+ ref={ref}
+ disabled={loading || props.disabled}
+ {...props}
+ >
+ {loading ? (
+ <svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24">
+ <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" className="opacity-25" />
+ <path fill="currentColor" className="opacity-75" 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>
+ ) : leftIcon ? (
+ <span className="mr-2">{leftIcon}</span>
+ ) : null}
+
+ {children}
+
+ {rightIcon && !loading && (
+ <span className="ml-2">{rightIcon}</span>
+ )}
+ </button>
+ )
+ }
+)
+
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
+```
+
+### Card Component
+
+```jsx
+// components/Card.jsx
+import React from 'react'
+import { cn } from '@/lib/utils'
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement> & {
+ hover?: boolean
+ padding?: 'none' | 'sm' | 'md' | 'lg'
+ }
+>(({ className, hover = false, padding = 'md', children, ...props }, ref) => {
+ const paddingMap = {
+ none: '',
+ sm: 'p-4',
+ md: 'p-6',
+ lg: 'p-8'
+ }
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
+ hover && "transition-shadow hover:shadow-md",
+ paddingMap[padding],
+ className
+ )}
+ {...props}
+ >
+ {children}
+ </div>
+ )
+})
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
+ {...props}
+ />
+))
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+ <h3
+ ref={ref}
+ className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
+ {...props}
+ />
+))
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+ <p
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
+))
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("flex items-center p-6 pt-0", className)}
+ {...props}
+ />
+))
+
+Card.displayName = "Card"
+CardHeader.displayName = "CardHeader"
+CardTitle.displayName = "CardTitle"
+CardDescription.displayName = "CardDescription"
+CardContent.displayName = "CardContent"
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+```
+
+### Input Component
+
+```jsx
+// components/Input.jsx
+import React from 'react'
+import { cva } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const inputVariants = cva(
+ "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ {
+ variants: {
+ size: {
+ sm: "h-8 px-2 text-xs",
+ default: "h-10 px-3",
+ lg: "h-12 px-4 text-base",
+ },
+ state: {
+ default: "",
+ error: "border-destructive focus-visible:ring-destructive",
+ success: "border-green-500 focus-visible:ring-green-500",
+ },
+ },
+ defaultVariants: {
+ size: "default",
+ state: "default",
+ },
+ }
+)
+
+export interface InputProps
+ extends React.InputHTMLAttributes<HTMLInputElement> {
+ size?: 'sm' | 'default' | 'lg'
+ state?: 'default' | 'error' | 'success'
+ label?: string
+ helperText?: string
+ error?: string
+ leftIcon?: React.ReactNode
+ rightIcon?: React.ReactNode
+}
+
+const Input = React.forwardRef<HTMLInputElement, InputProps>(
+ ({
+ className,
+ type,
+ size,
+ state,
+ label,
+ helperText,
+ error,
+ leftIcon,
+ rightIcon,
+ ...props
+ }, ref) => {
+ const inputState = error ? 'error' : state
+
+ return (
+ <div className="space-y-1">
+ {label && (
+ <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
+ {label}
+ </label>
+ )}
+
+ <div className="relative">
+ {leftIcon && (
+ <div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
+ {leftIcon}
+ </div>
+ )}
+
+ <input
+ type={type}
+ className={cn(
+ inputVariants({ size, state: inputState }),
+ leftIcon && "pl-9",
+ rightIcon && "pr-9",
+ className
+ )}
+ ref={ref}
+ {...props}
+ />
+
+ {rightIcon && (
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground">
+ {rightIcon}
+ </div>
+ )}
+ </div>
+
+ {(helperText || error) && (
+ <p className={cn(
+ "text-xs",
+ error ? "text-destructive" : "text-muted-foreground"
+ )}>
+ {error || helperText}
+ </p>
+ )}
+ </div>
+ )
+ }
+)
+
+Input.displayName = "Input"
+
+export { Input, inputVariants }
+```
+
+### Badge Component
+
+```jsx
+// components/Badge.jsx
+import React from 'react'
+import { cva } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+ success: "border-transparent bg-green-500 text-white hover:bg-green-600",
+ warning: "border-transparent bg-yellow-500 text-white hover:bg-yellow-600",
+ outline: "text-foreground",
+ },
+ size: {
+ sm: "px-2 py-0.5 text-xs",
+ default: "px-2.5 py-0.5 text-xs",
+ lg: "px-3 py-1 text-sm",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes<HTMLDivElement> {
+ variant?: 'default' | 'secondary' | 'destructive' | 'success' | 'warning' | 'outline'
+ size?: 'sm' | 'default' | 'lg'
+ removable?: boolean
+ onRemove?: () => void
+}
+
+const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
+ ({ className, variant, size, removable, onRemove, children, ...props }, ref) => {
+ return (
+ <div
+ className={cn(badgeVariants({ variant, size }), className)}
+ ref={ref}
+ {...props}
+ >
+ {children}
+ {removable && (
+ <button
+ onClick={onRemove}
+ className="ml-1 -mr-1 rounded-full p-0.5 hover:bg-black/10 focus:outline-none"
+ aria-label="Remove badge"
+ >
+ <svg className="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+ <path d="M18 6L6 18M6 6l12 12" />
+ </svg>
+ </button>
+ )}
+ </div>
+ )
+ }
+)
+
+Badge.displayName = "Badge"
+
+export { Badge, badgeVariants }
+```
+
+### Alert Component
+
+```jsx
+// components/Alert.jsx
+import React from 'react'
+import { cva } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ success: "border-green-500/50 text-green-700 dark:text-green-400 [&>svg]:text-green-600",
+ warning: "border-yellow-500/50 text-yellow-700 dark:text-yellow-400 [&>svg]:text-yellow-600",
+ info: "border-blue-500/50 text-blue-700 dark:text-blue-400 [&>svg]:text-blue-600",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement> & {
+ variant?: 'default' | 'destructive' | 'success' | 'warning' | 'info'
+ dismissible?: boolean
+ onDismiss?: () => void
+ }
+>(({ className, variant, dismissible, onDismiss, children, ...props }, ref) => (
+ <div
+ ref={ref}
+ role="alert"
+ className={cn(alertVariants({ variant }), className)}
+ {...props}
+ >
+ {children}
+ {dismissible && (
+ <button
+ onClick={onDismiss}
+ className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
+ >
+ <svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+ <path d="M18 6L6 18M6 6l12 12" />
+ </svg>
+ <span className="sr-only">Close</span>
+ </button>
+ )}
+ </div>
+))
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+ <h5
+ ref={ref}
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
+ {...props}
+ />
+))
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
+ {...props}
+ />
+))
+
+Alert.displayName = "Alert"
+AlertTitle.displayName = "AlertTitle"
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
+```
+
+## Layout Components
+
+### Container Component
+
+```jsx
+// components/Container.jsx
+import React from 'react'
+import { cn } from '@/lib/utils'
+
+export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'
+ padding?: boolean
+}
+
+const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
+ ({ className, size = 'lg', padding = true, ...props }, ref) => {
+ const sizeClasses = {
+ sm: 'max-w-2xl',
+ md: 'max-w-4xl',
+ lg: 'max-w-6xl',
+ xl: 'max-w-7xl',
+ '2xl': 'max-w-8xl',
+ full: 'max-w-full'
+ }
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ 'mx-auto',
+ sizeClasses[size],
+ padding && 'px-4 sm:px-6 lg:px-8',
+ className
+ )}
+ {...props}
+ />
+ )
+ }
+)
+
+Container.displayName = 'Container'
+
+export { Container }
+```
+
+### Grid Component
+
+```jsx
+// components/Grid.jsx
+import React from 'react'
+import { cn } from '@/lib/utils'
+
+export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
+ cols?: 1 | 2 | 3 | 4 | 5 | 6 | 12
+ gap?: 'none' | 'sm' | 'md' | 'lg' | 'xl'
+ responsive?: boolean
+}
+
+const Grid = React.forwardRef<HTMLDivElement, GridProps>(
+ ({ className, cols = 1, gap = 'md', responsive = true, ...props }, ref) => {
+ const gapClasses = {
+ none: 'gap-0',
+ sm: 'gap-2',
+ md: 'gap-4',
+ lg: 'gap-6',
+ xl: 'gap-8'
+ }
+
+ const getResponsiveCols = (cols: number) => {
+ if (!responsive) return `grid-cols-${cols}`
+
+ switch (cols) {
+ case 1: return 'grid-cols-1'
+ case 2: return 'grid-cols-1 md:grid-cols-2'
+ case 3: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
+ case 4: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'
+ case 5: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5'
+ case 6: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6'
+ case 12: return 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-12'
+ default: return `grid-cols-${cols}`
+ }
+ }
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ 'grid',
+ getResponsiveCols(cols),
+ gapClasses[gap],
+ className
+ )}
+ {...props}
+ />
+ )
+ }
+)
+
+Grid.displayName = 'Grid'
+
+export { Grid }
+```
+
+## Utility Functions
+
+### Class Name Utility
+
+```typescript
+// lib/utils.ts
+import { type ClassValue, clsx } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
+
+// Responsive utility
+export function responsive(
+ base: string,
+ sm?: string,
+ md?: string,
+ lg?: string,
+ xl?: string,
+ xxl?: string
+) {
+ return cn(
+ base,
+ sm && `sm:${sm}`,
+ md && `md:${md}`,
+ lg && `lg:${lg}`,
+ xl && `xl:${xl}`,
+ xxl && `2xl:${xxl}`
+ )
+}
+
+// Focus ring utility
+export function focusRing(color: string = 'ring-primary') {
+ return `focus:outline-none focus:ring-2 ${color} focus:ring-offset-2`
+}
+```
+
+## Component Generation Script
+
+### Auto-generate Component
+
+```javascript
+// scripts/create-component.js
+const fs = require('fs')
+const path = require('path')
+
+function createComponent(name, type = 'basic') {
+ const componentName = name.charAt(0).toUpperCase() + name.slice(1)
+ const fileName = `${componentName}.tsx`
+ const componentDir = `./components/${componentName}`
+
+ // Create component directory
+ if (!fs.existsSync(componentDir)) {
+ fs.mkdirSync(componentDir, { recursive: true })
+ }
+
+ const templates = {
+ basic: basicComponentTemplate,
+ form: formComponentTemplate,
+ layout: layoutComponentTemplate,
+ interactive: interactiveComponentTemplate
+ }
+
+ const template = templates[type] || templates.basic
+ const componentCode = template(componentName, name)
+
+ // Write component file
+ fs.writeFileSync(path.join(componentDir, fileName), componentCode)
+
+ // Create index file
+ const indexContent = `export { ${componentName} } from './${componentName}'\nexport type { ${componentName}Props } from './${componentName}'`
+ fs.writeFileSync(path.join(componentDir, 'index.ts'), indexContent)
+
+ console.log(`✅ Component ${componentName} created successfully!`)
+ console.log(`📁 Location: ${componentDir}`)
+ console.log(`📝 Files created:`)
+ console.log(` - ${fileName}`)
+ console.log(` - index.ts`)
+}
+
+function basicComponentTemplate(componentName, kebabName) {
+ return `import React from 'react'
+import { cn } from '@/lib/utils'
+
+export interface ${componentName}Props extends React.HTMLAttributes<HTMLDivElement> {
+ variant?: 'default' | 'secondary'
+ size?: 'sm' | 'md' | 'lg'
+}
+
+const ${componentName} = React.forwardRef<HTMLDivElement, ${componentName}Props>(
+ ({ className, variant = 'default', size = 'md', children, ...props }, ref) => {
+ const variants = {
+ default: 'bg-background text-foreground',
+ secondary: 'bg-secondary text-secondary-foreground'
+ }
+
+ const sizes = {
+ sm: 'p-2 text-sm',
+ md: 'p-4 text-base',
+ lg: 'p-6 text-lg'
+ }
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ 'rounded-lg border transition-colors',
+ variants[variant],
+ sizes[size],
+ className
+ )}
+ {...props}
+ >
+ {children}
+ </div>
+ )
+ }
+)
+
+${componentName}.displayName = '${componentName}'
+
+export { ${componentName} }
+`
+}
+
+// Usage: node scripts/create-component.js MyComponent basic
+const [,, name, type] = process.argv
+if (!name) {
+ console.error('Please provide a component name')
+ process.exit(1)
+}
+
+createComponent(name, type)
+```
+
+Remember: **Focus on utility composition, responsive design, accessibility, and performance optimization when creating TailwindCSS components!**