diff --git a/docs/COMPONENT_IMPLEMENTATION.md b/docs/COMPONENT_IMPLEMENTATION.md new file mode 100644 index 0000000..f0461a5 --- /dev/null +++ b/docs/COMPONENT_IMPLEMENTATION.md @@ -0,0 +1,433 @@ +# Component Implementation Standards + +This document outlines the core implementation patterns for creating components in this library, optimized for AI website builders. + +## Component Structure Template + +Every component should follow this structure: + +```tsx +"use client"; + +import React from "react"; +import { cls } from "@/lib/utils"; + +interface ComponentProps { + // Required props first + text: string; + // Optional props with explicit types + onClick?: () => void; + className?: string; + disabled?: boolean; + ariaLabel?: string; + type?: "button" | "submit" | "reset"; +} + +const Component = ({ + text, + onClick, + className = "", + disabled = false, + ariaLabel, + type = "button", +}: ComponentProps) => { + return ( + + {text} + + ); +}; + +Component.displayName = "Component"; + +export default React.memo(Component); +``` + +**Key Requirements:** +- `"use client"` directive when needed (interactive components, hooks) +- Named exports with `displayName` for debugging +- Wrap in `React.memo()` for performance optimization +- Use `cls()` utility for class composition (never plain string concatenation) + +## Prop Structure & Defaults + +### Required Props +Core content props should be **required** with no default values: +- Section components: `title`, `description` +- Button components: `text` +- Media components: `imageSrc` or `videoSrc` (when applicable) + +### Optional Props with Defaults + +**Standard className defaults:** +```tsx +className = "", +textClassName = "", +iconClassName = "", +containerClassName = "", +``` + +Empty string defaults prevent undefined checks and are standard practice. + +**Common optional props:** +```tsx +disabled = false, +type = "button", +ariaLabel, // No default, falls back to sensible value in component +``` + +**Component-specific props:** +Document defaults clearly in both code and registry: +```tsx +strengthFactor = 20, +carouselMode = "buttons", +uniformGridCustomHeightClasses = "min-h-80 2xl:min-h-90", +``` + +## Naming Conventions + +### Section Components (Hero, About, Feature, etc.) + +**✅ CORRECT:** +```tsx +interface HeroProps { + title: string; // Primary heading + description: string; // Supporting text + buttons?: ButtonConfig[]; +} +``` + +**❌ WRONG:** +```tsx +interface HeroProps { + heading: string; // Should be "title" + subtitle: string; // Should be "description" + text: string; // Ambiguous +} +``` + +### Button Components + +**✅ CORRECT:** +```tsx +interface ButtonProps { + text: string; // Button label + onClick?: () => void; +} +``` + +**❌ WRONG:** +```tsx +interface ButtonProps { + title: string; // Should be "text" + label: string; // Should be "text" +} +``` + +### Button Config (for sections) + +```tsx +interface ButtonConfig { + text: string; // Button label (not "title" or "label") + href?: string; + onClick?: () => void; + props?: Partial>; + // NO variant property - controlled by ThemeProvider +} +``` + +**Consistency is critical:** +- All hero sections must use the same prop names +- All about sections must use the same prop names +- Registry documentation must match component prop names exactly + +## Component Customizability + +Provide className props for **all major elements** to allow full styling control: + +```tsx +interface SectionProps { + title: string; + description: string; + // Main wrapper + className?: string; + // Inner container + containerClassName?: string; + // Content areas + textClassName?: string; + mediaWrapperClassName?: string; + imageClassName?: string; +} + +const Section = ({ + title, + description, + className = "", + containerClassName = "", + textClassName = "", + mediaWrapperClassName = "", + imageClassName = "", +}: SectionProps) => { + return ( +
+
+
+ {/* content */} +
+
+ +
+
+
+ ); +}; +``` + +**Naming convention:** +- `className` - Main wrapper element +- `containerClassName` - Inner container +- `[element]ClassName` - Specific elements (e.g., `textClassName`, `imageClassName`) + +## Component Composition & Base Styles + +When composing higher-level components from base components, **set sensible base styles** while accepting className overrides: + +```tsx +interface HeroProps { + title: string; + description: string; + titleClassName?: string; + descriptionClassName?: string; + textBoxClassName?: string; +} + +const Hero = ({ + title, + description, + titleClassName = "", + descriptionClassName = "", + textBoxClassName = "", +}: HeroProps) => { + return ( +
+ +
+ ); +}; +``` + +**Key principles:** +- Base styles come first in `cls()`, overrides second +- This ensures good defaults while maintaining full customizability +- AI builders can use components without styling knowledge, but advanced users can override +- Use `cls()` utility for proper class merging (prevents Tailwind conflicts) + +## Type Safety + +### Use Explicit Prop Interfaces +```tsx +// ✅ CORRECT - Clear and explicit +interface ButtonProps { + text: string; + onClick?: () => void; + variant?: "primary" | "secondary"; +} + +// ❌ WRONG - Over-complicated +interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> { + // ... harder for AI to understand +} +``` + +### Use Discriminated Unions for Variants +```tsx +type MediaProps = + | { + imageSrc: string; + imageAlt?: string; + videoSrc?: never; + } + | { + videoSrc: string; + videoAriaLabel?: string; + imageSrc?: never; + }; +``` + +### Export Reusable Types +```tsx +export type ButtonConfig = { + text: string; + href?: string; + onClick?: () => void; +}; + +export type GridVariant = + | "uniform-all-items-equal" + | "two-columns-alternating-heights" + | "asymmetric-60-wide-40-narrow" + // ... etc +``` + +## Responsive Design + +### Mobile-First Approach + +**Default styles apply to mobile devices:** +```tsx +// ✅ CORRECT - Mobile until md breakpoint (768px) +
+ +
+ +// ❌ WRONG - Using lg: breakpoint +
+ +
+``` + +**Breakpoint Rules:** +- **Mobile styles**: No prefix (default) +- **Desktop styles**: `md:` prefix only (768px breakpoint) +- **Never use**: `lg:`, `xl:`, `2xl:` breakpoints for layout changes + +**Exceptions:** Only use larger breakpoints for minor tweaks: +```tsx +// Acceptable for minor adjustments +className="min-h-80 2xl:min-h-90" +``` + +## Content Width Pattern + +All section content must follow this structure: + +```tsx +
+
+ {/* content */} +
+
+``` + +**Rules:** +- Section: `w-full py-20` (full width with vertical padding) +- Inner div: `w-content-width mx-auto` (centered content with max width) +- `w-content-width` is controlled by ThemeProvider (small/medium/large) + +**Exceptions:** +- Heroes and footers do NOT use `py-20` (they have custom spacing) +- Full-bleed sections may skip inner wrapper + +## Vertical Spacing + +**Standard sections:** +```tsx +className="w-full py-20" +``` + +**Exceptions (NO py-20):** +- Hero sections (custom spacing) +- Footer sections (custom spacing) +- Full-bleed sections with background colors + +## Text Constraints + +For button text and short labels: +```tsx +{ + "text": { + "required": true, + "example": "Get Started", + "minChars": 2, + "maxChars": 15 + } +} +``` + +**Button text rules:** +- Minimum: 2 characters +- Maximum: 15 characters +- Single-line only (no multiline support) + +## Section Structure Pattern + +```tsx +
+
+ + + {/* Section-specific content */} +
+
+``` + +**Key Pattern Notes:** +- `useInvertedBackground` is a required prop: `"noInvert" | "invertDefault"` +- `"invertDefault"` creates a full-width inverted section with `bg-foreground` +- `"noInvert"` is the standard section with no background +- Always use explicit string equality checks (not truthy/falsy) +- Text colors should check for `"invertDefault"` mode + +## Implementation Checklist + +### Core Requirements +- [ ] Add `"use client"` directive if needed (hooks, interactivity) +- [ ] Use explicit prop interfaces (no over-complicated types) +- [ ] Set appropriate defaults for optional props +- [ ] Add `displayName` for debugging +- [ ] Wrap in `React.memo()` for performance +- [ ] Use semantic HTML tags (`
`, `