# Workflow: Block Extensions

Add layout variants and custom fields to built-in system blocks without creating entirely new custom blocks.

## When to Use Block Extensions

**Use block extensions when:**
- You want a system block's core functionality (hero, bodyText, cta, etc.)
- You need site-specific variants or modes (e.g., different visual styles for the same block)
- You want to add site-specific options (colors, animations, conditional content)

**Use custom blocks instead when:**
- The block structure is fundamentally different
- You need completely custom fields and logic
- The system block doesn't fit your use case at all

### Prefer built-in `variant` when available

Some system blocks already support variants via a built-in `variant` select field (e.g. `block.image-gallery`).
In those cases, prefer using the existing `variant` field instead of adding your own `layout` select.

See `.riverbank/docs/block-types.md` for which system blocks support variants and what the options are.

## Prerequisites

- `riverbank.config.ts` exists
- Block overrides file set up (e.g., `src/lib/block-overrides.tsx`)
- Understanding of the system block you're extending

## Steps

### Step 1: Add blockFieldExtensions in Config

In `riverbank.config.ts`, add your extensions:

```typescript
import { defineConfig } from '@riverbankcms/sdk/config';

export default defineConfig({
  siteId: '...',

  blockFieldExtensions: {
    'block.hero': {
      fields: [
        {
          id: 'layout',
          type: 'select',
          label: 'Layout',
          options: [
            { value: 'default', label: 'Default' },
            { value: 'topography', label: 'Topography Background' },
          ],
          defaultValue: 'default',
        },
        {
          id: 'topographyColor',
          type: 'select',
          label: 'Topography Color',
          options: [
            { value: 'teal', label: 'Teal' },
            { value: 'green', label: 'Green' },
          ],
          defaultValue: 'teal',
          // Only show when layout is 'topography'
          ui: {
            visibleWhen: {
              field: 'layout',
              equals: 'topography',
            },
          },
        },
      ],
    },
  },
});
```

### Step 2: Create Override Component with Layout Switching

Create a component that reads the extended fields and switches rendering:

```typescript
// src/components/CustomHero.tsx
import type { BlockOverrideProps, HeroContent } from '@riverbankcms/sdk/rendering';
import { DefaultHeroLayout } from './DefaultHeroLayout';
import { TopographyHeroLayout } from './TopographyHeroLayout';

// Extend the base content type with your custom fields
type ExtendedHeroContent = HeroContent & {
  layout?: 'default' | 'topography';
  topographyColor?: 'teal' | 'green';
};

export function CustomHero({ content, ...props }: BlockOverrideProps<ExtendedHeroContent>) {
  const layout = content.layout || 'default';

  // Switch based on layout value
  if (layout === 'topography') {
    return <TopographyHeroLayout content={content} topographyColor={content.topographyColor} {...props} />;
  }

  return <DefaultHeroLayout content={content} {...props} />;
}
```

### Step 3: Register in blockOverrides

Add your override component to the block overrides:

```typescript
// src/lib/block-overrides.tsx
import type { BlockOverrides } from '@riverbankcms/sdk/rendering';
import { CustomHero } from '@/components/CustomHero';

export const blockOverrides: BlockOverrides = {
  hero: CustomHero,
};
```

### Step 4: Push Config

Push the configuration to make the new fields available in the CMS:

```bash
riverbankcms push-config
```

### Step 5: Use in Content

Now when editing hero blocks in the CMS, you'll see the new Layout field. Create content using the CLI:

```bash
riverbankcms block upsert home main-hero \
  --kind block.hero \
  --data '{"heading": "Welcome", "layout": "topography", "topographyColor": "teal"}'
```

## Extension Patterns

### Pattern 1: Layout Variants

The most common pattern - add a `layout` field to switch between visual styles:

```typescript
'block.body-text': {
  fields: [
    {
      id: 'layout',
      type: 'select',
      label: 'Layout',
      options: [
        { value: 'default', label: 'Default' },
        { value: 'green-banner', label: 'Green Banner' },
        { value: 'quote-highlight', label: 'Quote Highlight' },
      ],
      defaultValue: 'default',
    },
  ],
}
```

### Pattern 2: Conditional Fields

Show/hide fields based on other field values using `ui.visibleWhen`:

```typescript
'block.cta-full': {
  fields: [
    {
      id: 'layout',
      type: 'select',
      label: 'Layout',
      options: [
        { value: 'default', label: 'Default' },
        { value: 'banner', label: 'Colored Banner' },
        { value: 'dark', label: 'Dark Theme' },
      ],
      defaultValue: 'default',
    },
    {
      id: 'backgroundColor',
      type: 'select',
      label: 'Background Color',
      options: [
        { value: 'green', label: 'Green' },
        { value: 'tan', label: 'Tan' },
      ],
      defaultValue: 'green',
      ui: {
        visibleWhen: {
          field: 'layout',
          equals: 'banner',
        },
      },
    },
    {
      id: 'quoteType',
      type: 'select',
      label: 'Quote Type',
      options: [
        { value: 'websites', label: 'Websites' },
        { value: 'ecommerce', label: 'Ecommerce' },
      ],
      defaultValue: 'websites',
      ui: {
        visibleWhen: {
          field: 'layout',
          equals: 'dark',
        },
      },
    },
  ],
}
```

### Pattern 3: Color Theming

Add color selection to match your brand palette:

```typescript
'block.quote': {
  fields: [
    {
      id: 'accentColor',
      type: 'select',
      label: 'Accent Color',
      options: [
        { value: 'teal', label: 'Teal' },
        { value: 'green', label: 'Green' },
        { value: 'tan', label: 'Tan' },
        { value: 'brown', label: 'Brown' },
      ],
      defaultValue: 'teal',
    },
  ],
}
```

## blockFieldOptions vs blockFieldExtensions

### blockFieldExtensions

Add **new fields** to system blocks:

```typescript
blockFieldExtensions: {
  'block.hero': {
    fields: [
      { id: 'layout', type: 'select', ... },  // New field
    ],
  },
}
```

### blockFieldOptions

Override **options for existing select fields** on system blocks:

```typescript
blockFieldOptions: {
  'block.embed': {
    layout: {
      options: [
        { value: 'showcase', label: 'Services Showcase' },
        { value: 'list', label: 'Simple List' },
        { value: 'service-tiles', label: 'Service Tiles' },
      ],
    },
  },
}
```

Use `blockFieldOptions` when the system block already has a select field (like `layout`) and you want to customize which options are available.

## Verification

1. Check the field appears in the CMS editor for the block
2. Verify conditional fields show/hide correctly
3. Preview the block: `riverbankcms preview block.hero --data '{"layout": "topography"}' --open`
4. Verify the override component renders the correct layout

## Complete Example: Hero with Topography

**Config (`riverbank.config.ts`):**

```typescript
blockFieldExtensions: {
  'block.hero': {
    fields: [
      {
        id: 'layout',
        type: 'select',
        label: 'Layout',
        options: [
          { value: 'default', label: 'Default (Canvas)' },
          { value: 'topography', label: 'Topography' },
        ],
        defaultValue: 'default',
      },
      {
        id: 'topographyColor',
        type: 'select',
        label: 'Topography Color',
        options: [
          { value: 'teal', label: 'Teal' },
          { value: 'green', label: 'Green' },
          { value: 'tan', label: 'Tan' },
          { value: 'brown', label: 'Brown' },
        ],
        defaultValue: 'teal',
        ui: {
          visibleWhen: {
            field: 'layout',
            equals: 'topography',
          },
        },
      },
    ],
  },
}
```

**Override component:**

```typescript
type ExtendedHeroContent = HeroContent & {
  layout?: 'default' | 'topography';
  topographyColor?: 'teal' | 'green' | 'tan' | 'brown';
};

const HeroOverride = (props: BlockOverrideProps<ExtendedHeroContent>) => {
  const { content } = props;
  const layout = content.layout || 'default';

  if (layout === 'topography') {
    return <HeroTopography {...props} />;
  }
  return <CustomHeroWithCanvas {...props} />;
};

export const blockOverrides: BlockOverrides = {
  hero: HeroOverride,
};
```

## Troubleshooting

### Field not appearing in CMS

- Verify the block kind is correct (e.g., `block.hero` not just `hero`)
- Run `riverbankcms push-config` to sync configuration
- Check the field ID doesn't conflict with existing block fields

### Conditional field always hidden

- Verify `visibleWhen.field` matches the exact field ID
- Check `visibleWhen.equals` matches the exact option value
- Ensure the controlling field has the expected value selected

### Override component not rendering

- Verify the override is registered in `blockOverrides`
- Check the key matches the block name (e.g., `hero` for `block.hero`)
- Ensure the component is exported correctly

### Extended fields not in content

- Extended fields are added to `content` alongside default block fields
- Access them via `props.content.yourFieldId`
- TypeScript: Create an extended type that adds your fields to the base content type
