# Component Checklist and Examples

This document provides a comprehensive checklist for creating components and complete reference examples.

## Summary Checklist

When creating a new component in componentLibrary, ensure:

### Structure & Naming
- [ ] Component folder follows naming convention (PascalCase)
- [ ] Files follow structure: `index.tsx`, `index.module.scss`, `types.ts`, `ContentFields.tsx`, `StyleFields.tsx`
- [ ] Static class name follows pattern: `hscl-componentName` (kebab-case)

### CSS & Styling
- [ ] CSS variables follow pattern: `--hscl-componentName-property-state`
- [ ] CSS variables include fallback values
- [ ] CSS module uses logical properties (`margin-block`, `padding-inline`, etc.)
- [ ] `defaultClasses` includes both CSS module class and static class
- [ ] `className` prop is merged into final classes using `cx()`
- [ ] `style` prop is merged with CSS variables
- [ ] Pseudo-states (`:hover`, `:focus`, `:disabled`) use state-suffixed variables
- [ ] Variant system structure is in place (even if not fully implemented)

### TypeScript
- [ ] Types are defined in separate `types.ts` file
- [ ] Field props follow `{field}Label`, `{field}Name`, `{field}Default` pattern
- [ ] Field props should always be in the order specified in this documentation
- [ ] Union types used for string literals (e.g., `'primary' | 'secondary'`)
- [ ] Discriminated unions used for multi-variant components (if applicable)
- [ ] Template literal types used for value validation (e.g., `PixelValue`)

### Component Implementation
- [ ] Component exports as compound component with field attachments
- [ ] Props destructured inline in function signature with defaults
- [ ] Helper functions defined outside component (when reusable or for readability)
- [ ] Proper use of early returns for conditional rendering
- [ ] `{...rest}` spread to underlying HTML element

### Field Components
- [ ] ContentFields and StyleFields export as default functions
- [ ] Field props destructured inline in function signature with defaults
- [ ] Field props support label, name, and default customization
- [ ] ChoiceField choices use `[value, displayLabel]` format
- [ ] NumberFields include appropriate `suffix`, `min`, `max` props
- [ ] Visibility rules properly configured with `id` on controlling fields

### Storybook (if applicable)

**File Structure:**
- [ ] Stories organized in `stories/` subdirectory
- [ ] Separate files: `ComponentName.stories.tsx`, `ComponentNameArgs.ts`, `ComponentNameDecorator.tsx`
- [ ] Multiple behavioral variants get separate story files (e.g., `Button.AsButton.stories.tsx`, `Button.AsLink.stories.tsx`)

**Meta Configuration:**
- [ ] Title follows pattern: `'Component Library/ComponentName'` or `'Component Library/ComponentName/Variant'`
- [ ] Appropriate layout specified: `'centered'`, `'fullscreen'`, or `'padded'`
- [ ] Component description added in `parameters.docs.description.component`
- [ ] Decorators applied at meta level for all stories

**Story Types:**
- [ ] **Default** story: Basic usage with all args (playground)
- [ ] **States** story: Demonstrates different component states (hover, disabled, etc.)
- [ ] **Edge Cases** story: Long text, empty content, special characters
- [ ] **Bespoke** stories: Component-specific features (no need to go overboard)

**Utilities & Helpers:**
- [ ] `SBContainer` used for consistent layout and spacing
- [ ] Import `SBContainer` via alias: `@sb-utils/SBContainer.js`
- [ ] Additional helper components created in `.storybook/utils/` and exported from index
- [ ] Related examples grouped in single story when appropriate

**CSS Variables & Decorators:**
- [ ] Decorator created with default CSS variable values
- [ ] CSS variables follow naming: `--hscl-[component]-[property]-[state]`
- [ ] Decorator applied to provide visual consistency

**Mocks:**
- [ ] Icon mock used automatically (any text in `iconFieldPath` works)
- [ ] Additional mocks created in `.storybook/mocks/` if needed
- [ ] Mock behavior documented in argTypes descriptions

### Islands (if component needs client-side interactivity)
- [ ] Interactive logic lives in `islands/ComponentNameIsland.tsx` with a default export
- [ ] Wrapper `index.tsx` imports island with `?island` suffix and `@ts-expect-error` comment
- [ ] Wrapper renders `<Island module={...} />` from `@hubspot/cms-components`
- [ ] Server-side data (if any) fetched in the wrapper and passed as props through the Island component
- [ ] Island styles live either in `../index.module.scss` (shared with wrapper) or a dedicated `islands/index.module.scss`
- [ ] Field components (ContentFields, StyleFields) attached to the wrapper, not the island

### Accessibility
- [ ] Semantic HTML elements used appropriately
- [ ] ARIA roles added when needed (e.g., `role="separator"`)
- [ ] Icon purpose properly set (SEMANTIC vs DECORATIVE)
- [ ] Use `Text` with `textFeatureSet="heading"` for editable heading fields rather than raw HTML heading elements

## Reference Examples

### Complete Component Example Structure

```typescript
// index.tsx
import styles from './index.module.scss';
import ContentFields from './ContentFields.js';
import StyleFields from './StyleFields.js';
import cx from '../utils/classname.js';
import { ComponentProps } from './types.js';

const ComponentNameComponent = ({
  variant = 'primary',
  className = '',
  style = {},
  children,
  ...rest
}: ComponentProps) => {

  const cssVariables: React.CSSProperties & {
    [key: `--${string}`]: string | undefined;
  } = {
    '--hscl-componentName-property': 'value',
  };

  const variantClass = styles[`hscl-componentName-${variant}`];
  const defaultClasses = cx(styles.componentName, variantClass, 'hscl-componentName');
  const combinedClasses = cx(defaultClasses, className);

  return (
    <div
      className={combinedClasses}
      style={{ ...cssVariables, ...style }}
      {...rest}
    >
      {children}
    </div>
  );
};

type ComponentNameComponent = typeof ComponentNameComponent & {
  ContentFields: typeof ContentFields;
  StyleFields: typeof StyleFields;
};

const ComponentName = ComponentNameComponent as ComponentNameComponent;
ComponentName.ContentFields = ContentFields;
ComponentName.StyleFields = StyleFields;

export default ComponentName;
```

```scss
// index.module.scss
.componentName {
  property: var(--hscl-componentName-property);

  &:hover {
    property: var(--hscl-componentName-property-hover);
  }

  &:focus {
    property: var(--hscl-componentName-property-focus);
  }
}

.hscl-componentName-primary {
  // Variant styles
}
```

```typescript
// types.ts
export type ComponentProps = {
  variant?: 'primary' | 'secondary' | 'tertiary';
  className?: string;
  style?: React.CSSProperties;
  children?: React.ReactNode;
};

export type ContentFieldsProps = {
  fieldLabel?: string;
  fieldName?: string;
  fieldDefault?: string;
};

export type StyleFieldsProps = {
  styleFieldLabel?: string;
  styleFieldName?: string;
  styleFieldDefault?: string;
};
```

```typescript
// ContentFields.tsx
import { TextField } from '@hubspot/cms-components/fields';
import { ContentFieldsProps } from './types.js';

const ContentFields = ({
  fieldLabel = 'Field',
  fieldName = 'field',
  fieldDefault = 'Default',
}: ContentFieldsProps) => {
  return (
    <TextField
      label={fieldLabel}
      name={fieldName}
      default={fieldDefault}
    />
  );
};

export default ContentFields;
```
