Skip to main content

Configuration Overview

The Vertical Template uses a TypeScript-based configuration system where each tenant has its own config file defining the UI layout, state management, and component behavior.

Configuration System

Each tenant config defines:

  • Store slices: Global state shape and initial values
  • Layout structure: Which components render in which slots
  • Component props: Static props and store bindings
  • Visibility conditions: When components should show/hide
// src/eai.config/tenants/my-tenant.config.ts
export const myTenantConfig: EAIConfig = {
tenantId: 'my-tenant',
displayName: 'My Application',

// State management
store: {
user: {
initialState: { name: null, email: null },
persist: true,
},
},

// UI layout
layout: {
header: [{ component: 'Header', priority: 1 }],
leftPane: [{ component: 'Sidebar', priority: 1 }],
middlePane: [{ component: 'Dashboard', priority: 1 }],
rightPane: [],
},
};

File Structure

src/eai.config/
├── index.ts # Registry and getTenantConfig()
├── types.ts # TypeScript types
├── utils/
│ └── getTenantFromHost.ts # Subdomain detection
└── tenants/
├── template.config.ts # Example tenant
└── [tenant].config.ts # Your tenant configs

How Configs Are Used

  1. Registration: Configs are registered in src/eai.config/index.ts
  2. Selection: getTenantConfig(tenantId) returns the matching config
  3. Rendering: EAIConfigProvider creates the store and renders components
  4. Slots: SlotRenderer renders components in each layout slot
// src/eai.config/index.ts
import { templateConfig } from './tenants/template.config';
import { myTenantConfig } from './tenants/my-tenant.config';

const configs: Record<string, EAIConfig> = {
template: templateConfig,
'my-tenant': myTenantConfig,
};

export function getTenantConfig(tenantId: string): EAIConfig {
return configs[tenantId] || configs.template;
}

Configuration Types

The main types are defined in src/eai.config/types.ts:

interface EAIConfig {
tenantId: string;
displayName: string;
store: Record<string, StoreSlice>;
layout: LayoutConfig;
}

interface StoreSlice {
initialState: Record<string, unknown>;
persist: boolean;
}

interface LayoutConfig {
header: ComponentConfig[];
leftPane: ComponentConfig[];
middlePane: ComponentConfig[];
rightPane: ComponentConfig[];
}

interface ComponentConfig {
component: string; // Component name in registry
priority: number; // Render order (lower = first)
props?: Record<string, any>; // Static props
storeBindings?: StoreBinding[];
showWhen?: (state: any) => boolean;
}

Key Concepts

Store Slices

Define the shape and persistence of global state:

store: {
property: {
initialState: { selectedAddress: null },
persist: true, // Survives page refresh
},
ui: {
initialState: { sidebarOpen: true },
persist: false, // Resets on refresh
},
}

Layout Slots

Four slots for component placement:

SlotDescriptionTypical Use
headerTop of the pageNavigation, user menu
leftPaneLeft sidebarNavigation, filters
middlePaneMain content areaPrimary features
rightPaneRight sidebarContext panels

Store Bindings

Map store values to component props:

{
component: 'UserCard',
storeBindings: [
{ prop: 'name', storePath: 'user.name' },
{ prop: 'email', storePath: 'user.email' },
],
}
// UserCard receives props: { name: 'John', email: 'john@example.com' }

Visibility Conditions

Show components based on state:

{
component: 'WelcomeMessage',
showWhen: (state) => !state.user?.name,
}

Priority

Control render order within a slot:

middlePane: [
{ component: 'Header', priority: 1 }, // Renders first
{ component: 'Content', priority: 2 }, // Renders second
{ component: 'Footer', priority: 10 }, // Renders last
]

Platform & CLI