438 lines
11 KiB
Markdown
438 lines
11 KiB
Markdown
# 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
|