Initial commit
This commit is contained in:
437
docs/COMPONENT_IMPLEMENTATION.md
Normal file
437
docs/COMPONENT_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,437 @@
|
||||
# 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 (
|
||||
<element
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel || text}
|
||||
className={cls("base-classes", "disabled-states", className)}
|
||||
>
|
||||
{text}
|
||||
</element>
|
||||
);
|
||||
};
|
||||
|
||||
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<ButtonPropsForVariant<CTAButtonVariant>>;
|
||||
// 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 (
|
||||
<section className={cls("base-section-styles", className)}>
|
||||
<div className={cls("base-container-styles", containerClassName)}>
|
||||
<div className={cls("base-text-styles", textClassName)}>
|
||||
{/* content */}
|
||||
</div>
|
||||
<div className={cls("base-media-wrapper-styles", mediaWrapperClassName)}>
|
||||
<img className={cls("base-image-styles", imageClassName)} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**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 (
|
||||
<section>
|
||||
<TextBox
|
||||
title={title}
|
||||
description={description}
|
||||
// Set base styles, allow overrides
|
||||
className={cls("flex flex-col gap-3 md:gap-1", textBoxClassName)}
|
||||
titleClassName={cls("text-6xl font-medium", titleClassName)}
|
||||
descriptionClassName={cls("text-lg leading-[1.2]", descriptionClassName)}
|
||||
center={true}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**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)
|
||||
<div className="flex-col md:flex-row">
|
||||
<img className="w-full h-auto md:h-8 md:w-auto" />
|
||||
</div>
|
||||
|
||||
// ❌ WRONG - Using lg: breakpoint
|
||||
<div className="flex-col lg:flex-row">
|
||||
<img className="w-full h-auto lg:h-8 lg:w-auto" />
|
||||
</div>
|
||||
```
|
||||
|
||||
**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
|
||||
<section aria-label={ariaLabel || "Section name"} className="w-full py-20">
|
||||
<div className="w-content-width mx-auto">
|
||||
{/* content */}
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
**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
|
||||
aria-label={ariaLabel || "Default section label"}
|
||||
className={cls(
|
||||
"relative py-20",
|
||||
useInvertedBackground === "invertCard"
|
||||
? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground"
|
||||
: "w-full",
|
||||
useInvertedBackground === "invertDefault" && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="w-content-width mx-auto">
|
||||
<TextBox
|
||||
title={title}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
// ... className overrides
|
||||
/>
|
||||
|
||||
{/* Section-specific content */}
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
**Key Pattern Notes:**
|
||||
- `useInvertedBackground` is a required prop: `"noInvert" | "invertDefault" | "invertCard"`
|
||||
- `"invertCard"` creates a card-style section with expanded width and rounded corners
|
||||
- `"invertDefault"` creates a full-width inverted section
|
||||
- `"noInvert"` is the standard section with no background
|
||||
- Always use explicit string equality checks (not truthy/falsy)
|
||||
- Text colors must check for both `"invertDefault"` and `"invertCard"` modes
|
||||
|
||||
## 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 (`<section>`, `<button>`, etc.)
|
||||
|
||||
### Customizability
|
||||
- [ ] Provide className props for all major elements
|
||||
- [ ] Use `cls()` utility for class composition
|
||||
- [ ] Set base styles with override capability
|
||||
- [ ] Follow naming convention (className, containerClassName, [element]ClassName)
|
||||
|
||||
### Responsive Design
|
||||
- [ ] Mobile-first styles (no prefix)
|
||||
- [ ] Desktop styles with `md:` prefix only
|
||||
- [ ] Avoid `lg:`, `xl:`, `2xl:` for layout changes
|
||||
- [ ] Use `w-content-width mx-auto` pattern
|
||||
|
||||
### Naming Conventions
|
||||
- [ ] Section components: Use `title` and `description`
|
||||
- [ ] Button components: Use `text`
|
||||
- [ ] Button configs: Use `text` (not variant - controlled by theme)
|
||||
- [ ] Consistent naming across similar component types
|
||||
|
||||
### Structure
|
||||
- [ ] Required props first in interface
|
||||
- [ ] Optional props with defaults after
|
||||
- [ ] Empty string defaults for className props
|
||||
- [ ] Document default values clearly
|
||||
Reference in New Issue
Block a user