UI Components

Building reusable server-side components with TypeScript template strings.

Component Structure

MiniWork components are TypeScript functions that return HTML strings. They follow a consistent pattern:

// Component with props interface
interface ButtonProps {
  children: string;
  variant?: 'primary' | 'secondary' | 'danger';
  href?: string;
  size?: 'sm' | 'md' | 'lg';
}

export function button({ children, variant = 'primary', href, size = 'md' }: ButtonProps): string {
  const classes = `btn btn-${variant} btn-${size}`;
  
  if (href) {
    return `<a href="${href}" class="${classes}">${children}</a>`;
  }
  return `<button type="button" class="${classes}">${children}</button>`;
}

Composing Components

Components can be composed together to build complex UIs:

// Card component
export function card(content: string, title?: string): string {
  return `
    <div class="card">
      ${title ? `<h3 class="card-title">${title}</h3>` : ''}
      <div class="card-content">${content}</div>
    </div>
  `;
}

// Stats card using card component
export function statsCard(label: string, value: number, icon: string): string {
  return card(`
    <div class="stats-icon">${icon}</div>
    <div class="stats-value">${value}</div>
    <div class="stats-label">${label}</div>
  `);
}

Layout Components

Shared layout components ensure consistency across pages:

src/components/ui.ts
        // Site header for public pages
export function siteHeader({ currentPath }: { currentPath?: string }): string {
  return `
    <header class="site-header">
      <div class="site-header-inner">
        <a href="/" class="site-logo">MiniWork</a>
        <nav class="site-nav">
          <a href="/docs" class="${currentPath === '/docs' ? 'active' : ''}">Docs</a>
          <a href="/login" class="btn btn-primary btn-sm">Login</a>
        </nav>
      </div>
    </header>
  `;
}

// Admin sidebar with active state
export function adminSidebar({ activePage, userEmail }): string {
  const links = [
    { href: '/admin', id: 'dashboard', label: 'Dashboard', icon: '📊' },
    { href: '/admin/users', id: 'users', label: 'Users', icon: '👥' },
  ];
  
  return `
    <aside class="admin-sidebar">
      ${links.map(l => `
        <a href="${l.href}" class="sidebar-link ${activePage === l.id ? 'active' : ''}">
          ${l.icon} ${l.label}
        </a>
      `).join('')}
    </aside>
  `;
}
      

HTMX Integration

Add interactivity with HTMX attributes:

export function deleteButton(id: number, target: string): string {
  return `
    <button
      hx-delete="/api/items/${id}"
      hx-confirm="Are you sure?"
      hx-target="${target}"
      hx-swap="outerHTML"
      class="btn btn-danger btn-sm"
    >
      Delete
    </button>
  `;
}
HTML Escaping

Always escape user input with the escapeHtml() helper to prevent XSS attacks.