# UI Components Playbook

This playbook provides comprehensive guidance for creating and maintaining UI components in the @design.estate/dees-catalog library. Follow these patterns and best practices to ensure consistency, maintainability, and quality.

## Table of Contents

1. [Component Creation Checklist](#component-creation-checklist)
2. [Architectural Patterns](#architectural-patterns)
3. [Component Types and Base Classes](#component-types-and-base-classes)
4. [Theming System](#theming-system)
5. [Event Handling](#event-handling)
6. [State Management](#state-management)
7. [Form Components](#form-components)
8. [Overlay Components](#overlay-components)
9. [Complex Components](#complex-components)
10. [Performance Optimization](#performance-optimization)
11. [Focus Management](#focus-management)
12. [Demo System](#demo-system)
13. [Common Pitfalls and Anti-patterns](#common-pitfalls-and-anti-patterns)
14. [Code Examples](#code-examples)

## Component Creation Checklist

When creating a new component, follow this checklist:

- [ ] Choose the appropriate base class (`DeesElement` or `DeesInputBase`)
- [ ] Use `@customElement('dees-componentname')` decorator
- [ ] Implement consistent theming with `cssManager.bdTheme()`
- [ ] Create demo function in separate `.demo.ts` file
- [ ] Export component from `ts_web/elements/index.ts`
- [ ] Use proper TypeScript types and interfaces (prefix with `I` for interfaces, `T` for types)
- [ ] Implement proper event handling with bubbling and composition
- [ ] Consider mobile responsiveness
- [ ] Add focus states for accessibility
- [ ] Clean up resources in `destroy()` method
- [ ] Follow lowercase naming convention for files
- [ ] Add z-index registry support if it's an overlay component

## Architectural Patterns

### Base Component Structure

```typescript
import { customElement, property, state, css, TemplateResult, html } from '@design.estate/dees-element';
import { DeesElement } from '@design.estate/dees-element';
import * as cssManager from './00colors.js';
import * as demoFunc from './dees-componentname.demo.js';

@customElement('dees-componentname')
export class DeesComponentName extends DeesElement {
  // Static demo reference
  public static demo = demoFunc.demoFunc;

  // Public properties (reactive, can be set via attributes)
  @property({ type: String })
  public label: string = '';

  @property({ type: Boolean, reflect: true })
  public disabled: boolean = false;

  // Internal state (reactive, but not exposed as attributes)
  @state()
  private internalState: string = '';

  // Static styles with theme support
  public static styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        background: ${cssManager.bdTheme('#ffffff', '#09090b')};
      }
    `
  ];

  // Render method
  public render(): TemplateResult {
    return html`
      <div class="main-container">
        <!-- Component content -->
      </div>
    `;
  }

  // Lifecycle methods
  public connectedCallback() {
    super.connectedCallback();
    // Setup that needs DOM access
  }

  public async firstUpdated() {
    // One-time initialization after first render
  }

  // Cleanup
  public destroy() {
    // Clean up listeners, observers, registrations
    super.destroy();
  }
}
```

### Advanced Patterns

#### 1. Separation of Concerns (Complex Components)

For complex components like WYSIWYG editors, separate concerns into handler classes:

```typescript
export class DeesComplexComponent extends DeesElement {
  // Orchestrator pattern - main component coordinates handlers
  private inputHandler: InputHandler;
  private stateHandler: StateHandler;
  private renderHandler: RenderHandler;

  constructor() {
    super();
    this.inputHandler = new InputHandler(this);
    this.stateHandler = new StateHandler(this);
    this.renderHandler = new RenderHandler(this);
  }
}
```

#### 2. Singleton Pattern (Global Components)

For global UI elements like menus:

```typescript
export class DeesGlobalMenu extends DeesElement {
  private static instance: DeesGlobalMenu;

  public static getInstance(): DeesGlobalMenu {
    if (!DeesGlobalMenu.instance) {
      DeesGlobalMenu.instance = new DeesGlobalMenu();
      document.body.appendChild(DeesGlobalMenu.instance);
    }
    return DeesGlobalMenu.instance;
  }
}
```

#### 3. Registry Pattern (Z-Index Management)

Use centralized registries for global state:

```typescript
class ComponentRegistry {
  private static instance: ComponentRegistry;
  private registry = new WeakMap<HTMLElement, number>();
  
  public register(element: HTMLElement, value: number) {
    this.registry.set(element, value);
  }
  
  public unregister(element: HTMLElement) {
    this.registry.delete(element);
  }
}
```

## Component Types and Base Classes

### Standard Component (extends DeesElement)

Use for most UI components:
- Buttons, badges, icons
- Layout components
- Data display components
- Overlay components

### Form Input Component (extends DeesInputBase)

Use for all form inputs:
- Text inputs, dropdowns, checkboxes
- Date pickers, file uploads
- Rich text editors

**Required implementations:**
```typescript
export class DeesInputCustom extends DeesInputBase<ValueType> {
  // Required: Get current value
  public getValue(): ValueType {
    return this.value;
  }

  // Required: Set value programmatically
  public setValue(value: ValueType): void {
    this.value = value;
    this.changeSubject.next(this); // Notify form
  }

  // Optional: Custom validation
  public async validate(): Promise<boolean> {
    // Custom validation logic
    return true;
  }
}
```

## Theming System

### DO: Use Theme Functions

Always use `cssManager.bdTheme()` for colors that change between themes:

```typescript
// ✅ CORRECT
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333333')};

// ❌ INCORRECT
background: #ffffff; // Hard-coded color
color: var(--custom-color); // Custom CSS variable
```

### DO: Use Consistent Color Values

Reference shared color constants when possible:

```typescript
// From 00colors.ts
background: ${cssManager.bdTheme(colors.bright.background, colors.dark.background)};
```

## Event Handling

### DO: Dispatch Custom Events Properly

```typescript
// ✅ CORRECT - Events bubble and cross shadow DOM
this.dispatchEvent(new CustomEvent('dees-componentname-change', {
  detail: { value: this.value },
  bubbles: true,
  composed: true
}));

// ❌ INCORRECT - Event won't propagate properly
this.dispatchEvent(new CustomEvent('change', {
  detail: { value: this.value }
  // Missing bubbles and composed
}));
```

### DO: Use Event Delegation

For dynamic content, use event delegation:

```typescript
// ✅ CORRECT - Single listener for all items
this.addEventListener('click', (e: MouseEvent) => {
  const item = (e.target as HTMLElement).closest('.item');
  if (item) {
    this.handleItemClick(item);
  }
});

// ❌ INCORRECT - Multiple listeners
this.items.forEach(item => {
  item.addEventListener('click', () => this.handleItemClick(item));
});
```

## State Management

### DO: Use Appropriate Property Decorators

```typescript
// Public API - use @property
@property({ type: String })
public label: string;

// Internal state - use @state
@state()
private isLoading: boolean = false;

// Reflect to attribute when needed
@property({ type: Boolean, reflect: true })
public disabled: boolean = false;
```

### DON'T: Manipulate State in Render

```typescript
// ❌ INCORRECT - Side effects in render
public render() {
  this.counter++; // Don't modify state
  return html`<div>${this.counter}</div>`;
}

// ✅ CORRECT - Pure render function
public render() {
  return html`<div>${this.counter}</div>`;
}
```

## Form Components

### DO: Extend DeesInputBase

All form inputs must extend the base class:

```typescript
export class DeesInputNew extends DeesInputBase<string> {
  // Inherits: key, label, value, required, disabled, validationState
}
```

### DO: Emit Changes Consistently

```typescript
private handleInput(e: Event) {
  this.value = (e.target as HTMLInputElement).value;
  this.changeSubject.next(this); // Notify form system
}
```

### DO: Support Standard Form Properties

```typescript
// All form inputs should support:
@property() public key: string;
@property() public label: string;
@property() public required: boolean = false;
@property() public disabled: boolean = false;
@property() public validationState: 'valid' | 'warn' | 'invalid';
```

## Overlay Components

### DO: Use Z-Index Registry

Never hardcode z-index values:

```typescript
// ✅ CORRECT
import { zIndexRegistry } from './00zindex.js';

public async show() {
  this.modalZIndex = zIndexRegistry.getNextZIndex();
  zIndexRegistry.register(this, this.modalZIndex);
  this.style.zIndex = `${this.modalZIndex}`;
}

public async hide() {
  zIndexRegistry.unregister(this);
}

// ❌ INCORRECT
public async show() {
  this.style.zIndex = '9999'; // Hardcoded z-index
}
```

### DO: Use Window Layers

For modal backdrops:

```typescript
import { DeesWindowLayer } from './dees-windowlayer.js';

private windowLayer: DeesWindowLayer;

public async show() {
  this.windowLayer = new DeesWindowLayer();
  this.windowLayer.zIndex = zIndexRegistry.getNextZIndex();
  document.body.append(this.windowLayer);
}
```

## Complex Components

### DO: Use Handler Classes

For complex logic, separate into specialized handlers:

```typescript
// wysiwyg/handlers/input.handler.ts
export class InputHandler {
  constructor(private component: DeesInputWysiwyg) {}
  
  public handleInput(event: InputEvent) {
    // Specialized input handling
  }
}

// Main component orchestrates
export class DeesInputWysiwyg extends DeesInputBase {
  private inputHandler = new InputHandler(this);
}
```

### DO: Use Programmatic Rendering

For performance-critical updates that shouldn't trigger re-renders:

```typescript
// ✅ CORRECT - Direct DOM manipulation when needed
private updateBlockContent(blockId: string, content: string) {
  const blockElement = this.shadowRoot.querySelector(`#${blockId}`);
  if (blockElement) {
    blockElement.textContent = content; // Direct update
  }
}

// ❌ INCORRECT - Triggering full re-render
private updateBlockContent(blockId: string, content: string) {
  this.blocks.find(b => b.id === blockId).content = content;
  this.requestUpdate(); // Unnecessary re-render
}
```

## Performance Optimization

### DO: Debounce Expensive Operations

```typescript
private resizeTimeout: number;

private handleResize = () => {
  clearTimeout(this.resizeTimeout);
  this.resizeTimeout = window.setTimeout(() => {
    this.updateLayout();
  }, 250);
};
```

### DO: Use Observers Efficiently

```typescript
// Clean up observers
public disconnectedCallback() {
  super.disconnectedCallback();
  this.resizeObserver?.disconnect();
  this.mutationObserver?.disconnect();
}
```

### DO: Implement Virtual Scrolling

For large lists:

```typescript
// Only render visible items
private getVisibleItems() {
  const scrollTop = this.scrollContainer.scrollTop;
  const containerHeight = this.scrollContainer.clientHeight;
  const itemHeight = 50;
  
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.ceil((scrollTop + containerHeight) / itemHeight);
  
  return this.items.slice(startIndex, endIndex);
}
```

## Focus Management

### DO: Handle Focus Timing

```typescript
// ✅ CORRECT - Wait for render
async focusInput() {
  await this.updateComplete;
  await new Promise(resolve => requestAnimationFrame(resolve));
  this.inputElement?.focus();
}

// ❌ INCORRECT - Focus too early
focusInput() {
  this.inputElement?.focus(); // Element might not exist
}
```

### DO: Prevent Focus Loss

```typescript
// For global menus
constructor() {
  super();
  // Prevent focus loss when clicking menu
  this.addEventListener('mousedown', (e) => {
    e.preventDefault();
  });
}
```

### DO: Implement Blur Debouncing

```typescript
private blurTimeout: number;

private handleBlur = () => {
  clearTimeout(this.blurTimeout);
  this.blurTimeout = window.setTimeout(() => {
    // Check if truly blurred
    if (!this.contains(document.activeElement)) {
      this.handleTrueBlur();
    }
  }, 100);
};
```

## Demo System

### DO: Create Comprehensive Demos

Every component needs a demo:

```typescript
// dees-button.demo.ts
import { html } from '@design.estate/dees-element';

export const demoFunc = () => html`
  <dees-button>Default Button</dees-button>
  <dees-button type="primary">Primary Button</dees-button>
  <dees-button type="danger" disabled>Disabled Danger</dees-button>
`;

// In component file
import * as demoFunc from './dees-button.demo.js';

export class DeesButton extends DeesElement {
  public static demo = demoFunc.demoFunc;
}
```

### DO: Include All Variants

Show all component states and variations in demos:
- Default state
- Different types/variants
- Disabled state
- Loading state
- Error states
- Edge cases (long text, empty content)

## Common Pitfalls and Anti-patterns

### ❌ DON'T: Hardcode Z-Index Values

```typescript
// ❌ WRONG
this.style.zIndex = '9999';

// ✅ CORRECT
this.style.zIndex = `${zIndexRegistry.getNextZIndex()}`;
```

### ❌ DON'T: Skip Base Classes

```typescript
// ❌ WRONG - Form input without base class
export class DeesInputCustom extends DeesElement {
  // Missing standard form functionality
}

// ✅ CORRECT
export class DeesInputCustom extends DeesInputBase<string> {
  // Inherits all form functionality
}
```

### ❌ DON'T: Forget Theme Support

```typescript
// ❌ WRONG
background-color: #ffffff;
color: #000000;

// ✅ CORRECT
background-color: ${cssManager.bdTheme('#ffffff', '#09090b')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
```

### ❌ DON'T: Create Components Without Demos

```typescript
// ❌ WRONG
export class DeesComponent extends DeesElement {
  // No demo property
}

// ✅ CORRECT
export class DeesComponent extends DeesElement {
  public static demo = demoFunc.demoFunc;
}
```

### ❌ DON'T: Emit Non-Bubbling Events

```typescript
// ❌ WRONG
this.dispatchEvent(new CustomEvent('change', {
  detail: this.value
}));

// ✅ CORRECT
this.dispatchEvent(new CustomEvent('change', {
  detail: this.value,
  bubbles: true,
  composed: true
}));
```

### ❌ DON'T: Skip Cleanup

```typescript
// ❌ WRONG
public connectedCallback() {
  window.addEventListener('resize', this.handleResize);
}

// ✅ CORRECT
public connectedCallback() {
  super.connectedCallback();
  window.addEventListener('resize', this.handleResize);
}

public disconnectedCallback() {
  super.disconnectedCallback();
  window.removeEventListener('resize', this.handleResize);
}
```

### ❌ DON'T: Use Inline Styles for Theming

```typescript
// ❌ WRONG
<div style="background-color: ${this.darkMode ? '#000' : '#fff'}">

// ✅ CORRECT
<div class="themed-container">
// In styles:
.themed-container {
  background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
}
```

### ❌ DON'T: Forget Mobile Responsiveness

```typescript
// ❌ WRONG
:host {
  width: 800px; // Fixed width
}

// ✅ CORRECT
:host {
  width: 100%;
  max-width: 800px;
}

@media (max-width: 768px) {
  :host {
    /* Mobile adjustments */
  }
}
```

## Code Examples

### Example: Creating a New Button Variant

```typescript
// dees-special-button.ts
import { customElement, property, css, html } from '@design.estate/dees-element';
import { DeesElement } from '@design.estate/dees-element';
import * as cssManager from './00colors.js';
import * as demoFunc from './dees-special-button.demo.js';

@customElement('dees-special-button')
export class DeesSpecialButton extends DeesElement {
  public static demo = demoFunc.demoFunc;

  @property({ type: String })
  public text: string = 'Click me';

  @property({ type: Boolean, reflect: true })
  public loading: boolean = false;

  public static styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: inline-block;
      }

      .button {
        padding: 8px 16px;
        background: ${cssManager.bdTheme('#0066ff', '#0044cc')};
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.2s;
      }

      .button:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 8px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
      }

      :host([loading]) .button {
        opacity: 0.7;
        cursor: not-allowed;
      }
    `
  ];

  public render() {
    return html`
      <button class="button" ?disabled=${this.loading} @click=${this.handleClick}>
        ${this.loading ? html`<dees-spinner size="small"></dees-spinner>` : this.text}
      </button>
    `;
  }

  private handleClick() {
    this.dispatchEvent(new CustomEvent('special-click', {
      bubbles: true,
      composed: true
    }));
  }
}
```

### Example: Creating a Form Input

```typescript
// dees-input-special.ts
export class DeesInputSpecial extends DeesInputBase<string> {
  public static demo = demoFunc.demoFunc;

  public render() {
    return html`
      <dees-label .label=${this.label} .required=${this.required}>
        <input
          type="text"
          .value=${this.value || ''}
          ?disabled=${this.disabled}
          @input=${this.handleInput}
          @blur=${this.handleBlur}
        />
      </dees-label>
    `;
  }

  private handleInput(e: Event) {
    this.value = (e.target as HTMLInputElement).value;
    this.changeSubject.next(this);
  }

  private handleBlur() {
    this.dispatchEvent(new CustomEvent('blur', {
      bubbles: true,
      composed: true
    }));
  }

  public getValue(): string {
    return this.value;
  }

  public setValue(value: string): void {
    this.value = value;
    this.changeSubject.next(this);
  }
}
```

## Summary

This playbook represents the collective wisdom and patterns found in the @design.estate/dees-catalog component library. Following these guidelines will help you create components that are:

- **Consistent**: Following established patterns
- **Maintainable**: Easy to understand and modify
- **Performant**: Optimized for real-world use
- **Accessible**: Usable by everyone
- **Theme-aware**: Supporting light and dark modes
- **Well-integrated**: Working seamlessly with the component ecosystem

Remember: When in doubt, look at existing components for examples. The codebase itself is the best documentation of these patterns in action.