docs/guides/cookbook

#Cookbook

Complete page-level recipes showing how to build real applications with HyperKit. Each recipe is a full page component, ready to drop into a SolidJS app. All styling uses --sk-* design tokens. No hardcoded colors or spacing.


#Recipe 1: Admin Dashboard

A complete admin dashboard with metrics strip, tabbed content area, a sortable data table, and a collapsible sidebar navigation.

Components used: Flex, Stack, Grid, Card, MetricCard, Text, Button, Badge, StatusDot, Sidebar, Tabs, Table, Tooltip, Separator, EmptyState

import {
  Flex,
  Stack,
  Grid,
  Card,
  MetricCard,
  Text,
  Button,
  Badge,
  StatusDot,
  Sidebar,
  Tabs,
  Table,
  Tooltip,
  Separator,
  EmptyState,
  type TabItem,
  type TableColumn,
} from '@ybouhjira/hyperkit';
import { createSignal, For } from 'solid-js';

interface User {
  id: string;
  name: string;
  email: string;
  role: string;
  status: 'active' | 'inactive' | 'pending';
  joined: string;
}

const NAV_ITEMS = [
  { id: 'dashboard', label: 'Dashboard', icon: '⊞' },
  { id: 'users', label: 'Users', icon: '👥' },
  { id: 'orders', label: 'Orders', icon: '📦' },
  { id: 'analytics', label: 'Analytics', icon: '📊' },
  { id: 'settings', label: 'Settings', icon: '⚙️' },
];

const USERS: User[] = [
  {
    id: '1',
    name: 'Alice Martin',
    email: 'alice@example.com',
    role: 'Admin',
    status: 'active',
    joined: '2024-01-15',
  },
  {
    id: '2',
    name: 'Bob Chen',
    email: 'bob@example.com',
    role: 'Editor',
    status: 'active',
    joined: '2024-02-20',
  },
  {
    id: '3',
    name: 'Carol Davis',
    email: 'carol@example.com',
    role: 'Viewer',
    status: 'inactive',
    joined: '2024-03-05',
  },
  {
    id: '4',
    name: 'Dan Kim',
    email: 'dan@example.com',
    role: 'Editor',
    status: 'pending',
    joined: '2024-04-10',
  },
];

const USER_COLUMNS: TableColumn<User>[] = [
  { key: 'name', header: 'Name', sortable: true },
  { key: 'email', header: 'Email', sortable: true },
  { key: 'role', header: 'Role' },
  {
    key: 'status',
    header: 'Status',
    render: (user) => {
      const statusMap = {
        active: 'success',
        inactive: 'danger',
        pending: 'warning',
      } as const;
      return (
        <StatusDot
          status={statusMap[user.status]}
          label={user.status}
          pulse={user.status === 'active'}
        />
      );
    },
  },
  { key: 'joined', header: 'Joined', sortable: true },
];

export function AdminDashboard() {
  const [activeNav, setActiveNav] = createSignal('dashboard');
  const [sidebarOpen, setSidebarOpen] = createSignal(true);
  const [sortColumn, setSortColumn] = createSignal<string | undefined>();
  const [sortDirection, setSortDirection] = createSignal<'asc' | 'desc'>('asc');
  const [selectedUser, setSelectedUser] = createSignal<string | null>(null);

  const tabItems: TabItem[] = [
    {
      value: 'users',
      label: 'Users',
      content: (
        <Table
          columns={USER_COLUMNS}
          data={USERS}
          getRowKey={(u) => u.id}
          onRowClick={(u) => setSelectedUser(u.id)}
          selectedKey={selectedUser()}
          sortColumn={sortColumn()}
          sortDirection={sortDirection()}
          onSort={(col, dir) => {
            setSortColumn(col);
            setSortDirection(dir);
          }}
          emptyState={
            <EmptyState
              icon="users"
              title="No users found"
              description="Add your first user to get started."
              action={<Button size="sm">Add User</Button>}
            />
          }
        />
      ),
    },
    {
      value: 'activity',
      label: 'Recent Activity',
      content: (
        <EmptyState
          icon="activity"
          title="No recent activity"
          description="Activity events will appear here."
        />
      ),
    },
  ];

  return (
    <Flex style={{ height: '100vh', overflow: 'hidden' }}>
      {/* Sidebar */}
      <Sidebar
        open={sidebarOpen()}
        onToggle={() => setSidebarOpen((v) => !v)}
        width="220px"
        header={
          <Flex align="center" gap="sm" style={{ padding: 'var(--sk-space-md)' }}>
            <Text size="lg" weight="bold" color="primary">
              AdminKit
            </Text>
          </Flex>
        }
      >
        <Stack gap="xs" style={{ padding: 'var(--sk-space-sm)' }}>
          <For each={NAV_ITEMS}>
            {(item) => (
              <Button
                variant={activeNav() === item.id ? 'secondary' : 'ghost'}
                fullWidth
                onClick={() => setActiveNav(item.id)}
                style={{ 'justify-content': 'flex-start', gap: 'var(--sk-space-sm)' }}
              >
                <span>{item.icon}</span>
                {item.label}
              </Button>
            )}
          </For>
        </Stack>
      </Sidebar>

      {/* Main Content */}
      <Flex direction="column" style={{ flex: 1, overflow: 'hidden', 'min-width': 0 }}>
        {/* Top Bar */}
        <Flex
          align="center"
          justify="between"
          style={{
            padding: 'var(--sk-space-sm) var(--sk-space-lg)',
            'border-bottom': '1px solid var(--sk-border)',
            'background-color': 'var(--sk-bg-primary)',
            'flex-shrink': 0,
          }}
        >
          <Text as="h1" size="lg" weight="semibold" color="primary">
            Dashboard
          </Text>
          <Flex gap="sm" align="center">
            <Tooltip content="3 pending notifications" placement="bottom">
              <Badge variant="warning" type="count" count={3}>
                Alerts
              </Badge>
            </Tooltip>
            <Button variant="primary" size="sm">
              + New User
            </Button>
          </Flex>
        </Flex>

        {/* Scrollable Body */}
        <Flex
          direction="column"
          gap="lg"
          style={{
            flex: 1,
            overflow: 'auto',
            padding: 'var(--sk-space-lg)',
            'background-color': 'var(--sk-bg-secondary)',
          }}
        >
          {/* Metrics Row */}
          <Grid columns={4} gap="md">
            <MetricCard
              label="Total Users"
              value="12,847"
              trend="+12%"
              trendDirection="up"
              variant="info"
            />
            <MetricCard
              label="Monthly Revenue"
              value="$48,290"
              trend="+8.3%"
              trendDirection="up"
              variant="success"
            />
            <MetricCard
              label="Open Issues"
              value="34"
              trend="-6"
              trendDirection="down"
              variant="warning"
            />
            <MetricCard
              label="Uptime"
              value="99.97%"
              trend="stable"
              trendDirection="neutral"
              variant="default"
            />
          </Grid>

          <Separator />

          {/* Tabbed Content */}
          <Card padding="none">
            <Flex
              align="center"
              justify="between"
              style={{ padding: 'var(--sk-space-md) var(--sk-space-lg)' }}
            >
              <Text size="base" weight="semibold" color="primary">
                Management
              </Text>
              <Button variant="ghost" size="sm">
                Export CSV
              </Button>
            </Flex>
            <Tabs items={tabItems} />
          </Card>
        </Flex>
      </Flex>
    </Flex>
  );
}

#Recipe 2: Chat Application

Full-featured chat interface with a conversation list sidebar, message area showing real messages, and a rich composer at the bottom. Supports slash commands and file attachments.

Components used: Flex, Stack, Text, Badge, SearchInput, Separator, StatusDot, MessageList, MessageInput, EmptyState, Button, Spinner

import {
  Flex,
  Stack,
  Text,
  Badge,
  SearchInput,
  Separator,
  StatusDot,
  MessageList,
  MessageInput,
  EmptyState,
  Button,
  Spinner,
  type Message,
  type SlashCommand,
} from '@ybouhjira/hyperkit';
import { createSignal, Show, For } from 'solid-js';

interface Conversation {
  id: string;
  name: string;
  preview: string;
  unread: number;
  online: boolean;
  lastTime: string;
}

const CONVERSATIONS: Conversation[] = [
  {
    id: '1',
    name: 'Alice Martin',
    preview: "Sounds great, let's meet at 3pm",
    unread: 2,
    online: true,
    lastTime: '2m',
  },
  {
    id: '2',
    name: 'Design Team',
    preview: 'Updated the mockups',
    unread: 0,
    online: true,
    lastTime: '15m',
  },
  {
    id: '3',
    name: 'Bob Chen',
    preview: 'Can you review the PR?',
    unread: 1,
    online: false,
    lastTime: '1h',
  },
  {
    id: '4',
    name: 'DevOps',
    preview: 'Deploy scheduled for Friday',
    unread: 0,
    online: false,
    lastTime: '3h',
  },
];

const SLASH_COMMANDS: SlashCommand[] = [
  { id: 'help', name: 'help', description: 'Show available commands' },
  { id: 'clear', name: 'clear', description: 'Clear conversation history' },
  { id: 'image', name: 'image', description: 'Send an image' },
  { id: 'file', name: 'file', description: 'Attach a file' },
];

const INITIAL_MESSAGES: Message[] = [
  {
    id: '1',
    role: 'assistant',
    content: 'Hey! How can I help you today?',
    timestamp: new Date(Date.now() - 60000),
  },
  {
    id: '2',
    role: 'user',
    content: 'I need to review the latest design specs.',
    timestamp: new Date(Date.now() - 30000),
  },
  {
    id: '3',
    role: 'assistant',
    content:
      "Sure! I've pulled up the latest specs. The main changes are in the navigation component and the dashboard layout. Want me to walk you through them?",
    timestamp: new Date(),
  },
];

export function ChatApplication() {
  const [activeConv, setActiveConv] = createSignal<Conversation | null>(CONVERSATIONS[0] ?? null);
  const [messages, setMessages] = createSignal<Message[]>(INITIAL_MESSAGES);
  const [search, setSearch] = createSignal('');
  const [isStreaming, setIsStreaming] = createSignal(false);

  const filteredConvs = () => {
    const q = search().toLowerCase();
    return CONVERSATIONS.filter(
      (c) => c.name.toLowerCase().includes(q) || c.preview.toLowerCase().includes(q)
    );
  };

  const handleSend = (text: string) => {
    const userMsg: Message = {
      id: String(Date.now()),
      role: 'user',
      content: text,
      timestamp: new Date(),
    };
    const streamId = String(Date.now() + 1);

    setMessages((prev) => [...prev, userMsg]);
    setIsStreaming(true);

    // Simulate assistant response
    setTimeout(() => {
      const assistantMsg: Message = {
        id: streamId,
        role: 'assistant',
        content:
          "Thanks for your message! I'm processing your request and will have an answer for you shortly.",
        timestamp: new Date(),
      };
      setMessages((prev) => [...prev, assistantMsg]);
      setIsStreaming(false);
    }, 1500);
  };

  return (
    <Flex style={{ height: '100vh', overflow: 'hidden' }}>
      {/* Conversation List */}
      <Flex
        direction="column"
        style={{
          width: '300px',
          'flex-shrink': 0,
          'border-right': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-primary)',
          overflow: 'hidden',
        }}
      >
        {/* Header */}
        <Flex
          align="center"
          justify="between"
          style={{ padding: 'var(--sk-space-md)', 'flex-shrink': 0 }}
        >
          <Text size="lg" weight="bold" color="primary">
            Messages
          </Text>
          <Button variant="ghost" size="sm">
            + New
          </Button>
        </Flex>

        {/* Search */}
        <div style={{ padding: '0 var(--sk-space-md) var(--sk-space-sm)' }}>
          <SearchInput
            value={search()}
            onChange={setSearch}
            placeholder="Search conversations..."
          />
        </div>

        <Separator />

        {/* Conversation Items */}
        <Stack gap="0" style={{ flex: 1, overflow: 'auto', padding: 'var(--sk-space-xs)' }}>
          <For each={filteredConvs()}>
            {(conv) => (
              <button
                type="button"
                onClick={() => setActiveConv(conv)}
                style={{
                  display: 'flex',
                  'align-items': 'flex-start',
                  gap: 'var(--sk-space-sm)',
                  padding: 'var(--sk-space-sm) var(--sk-space-md)',
                  background:
                    activeConv()?.id === conv.id ? 'var(--sk-bg-secondary)' : 'transparent',
                  border: 'none',
                  cursor: 'pointer',
                  'border-radius': 'var(--sk-radius-md)',
                  width: '100%',
                  'text-align': 'left',
                  transition: 'background var(--sk-duration-fast) var(--sk-ease-default)',
                }}
              >
                {/* Avatar */}
                <div
                  style={{
                    width: '36px',
                    height: '36px',
                    'border-radius': '50%',
                    'background-color': 'var(--sk-accent)',
                    color: 'var(--sk-text-on-accent)',
                    display: 'flex',
                    'align-items': 'center',
                    'justify-content': 'center',
                    'font-size': 'var(--sk-font-size-sm)',
                    'font-weight': '600',
                    'flex-shrink': 0,
                  }}
                >
                  {conv.name[0]}
                </div>

                <Flex direction="column" gap="xs" style={{ flex: 1, 'min-width': 0 }}>
                  <Flex align="center" justify="between">
                    <Text size="sm" weight="semibold" color="primary" truncate>
                      {conv.name}
                    </Text>
                    <Flex align="center" gap="xs">
                      <StatusDot status={conv.online ? 'success' : 'default'} size="sm" />
                      <Text size="xs" color="muted">
                        {conv.lastTime}
                      </Text>
                    </Flex>
                  </Flex>
                  <Flex align="center" justify="between">
                    <Text size="xs" color="muted" truncate>
                      {conv.preview}
                    </Text>
                    <Show when={conv.unread > 0}>
                      <Badge variant="info" type="count" count={conv.unread} />
                    </Show>
                  </Flex>
                </Flex>
              </button>
            )}
          </For>
        </Stack>
      </Flex>

      {/* Chat Area */}
      <Flex direction="column" style={{ flex: 1, 'min-width': 0, overflow: 'hidden' }}>
        <Show
          when={activeConv()}
          fallback={
            <Flex align="center" justify="center" style={{ flex: 1 }}>
              <EmptyState
                icon="message-circle"
                title="Select a conversation"
                description="Choose a conversation from the list to start messaging."
              />
            </Flex>
          }
        >
          {(conv) => (
            <>
              {/* Conversation Header */}
              <Flex
                align="center"
                justify="between"
                style={{
                  padding: 'var(--sk-space-sm) var(--sk-space-lg)',
                  'border-bottom': '1px solid var(--sk-border)',
                  'background-color': 'var(--sk-bg-primary)',
                  'flex-shrink': 0,
                }}
              >
                <Flex align="center" gap="sm">
                  <StatusDot status={conv().online ? 'success' : 'default'} pulse={conv().online} />
                  <Text size="base" weight="semibold" color="primary">
                    {conv().name}
                  </Text>
                  <Text size="xs" color="muted">
                    {conv().online ? 'Online' : 'Offline'}
                  </Text>
                </Flex>
                <Flex gap="xs">
                  <Button variant="ghost" size="sm">
                    📞
                  </Button>
                  <Button variant="ghost" size="sm">

                  </Button>
                </Flex>
              </Flex>

              {/* Messages */}
              <MessageList
                messages={messages()}
                streamingMessageId={isStreaming() ? 'streaming' : undefined}
                style={{ flex: 1 }}
              />

              {/* Composer */}
              <div style={{ 'border-top': '1px solid var(--sk-border)', 'flex-shrink': 0 }}>
                <MessageInput
                  onSend={handleSend}
                  isStreaming={isStreaming()}
                  placeholder={`Message ${conv().name}…`}
                  slashCommands={SLASH_COMMANDS}
                  enableAttachments
                  enableMarkdownToolbar
                  showShortcutHints
                />
              </div>
            </>
          )}
        </Show>
      </Flex>
    </Flex>
  );
}

#Recipe 3: Document Editor

Full document editor with application menu bar, format toolbar, page-tree sidebar, main editing canvas, and a VS Code–style status bar at the bottom.

Components used: Flex, Stack, Box, Text, Button, Tooltip, MenuBar, StatusBar, Sidebar, Tabs, Separator, Badge, Input, Spinner

import {
  Flex,
  Stack,
  Box,
  Text,
  Button,
  Tooltip,
  MenuBar,
  StatusBar,
  Sidebar,
  Tabs,
  Separator,
  Badge,
  Input,
  Spinner,
  type MenuDefinition,
  type StatusBarItem,
  type TabItem,
} from '@ybouhjira/hyperkit';
import { createSignal, Show } from 'solid-js';

const APP_MENUS: MenuDefinition[] = [
  {
    id: 'file',
    label: 'File',
    items: [
      { id: 'new', label: 'New Document', shortcut: '⌘N', handler: () => {} },
      { id: 'open', label: 'Open…', shortcut: '⌘O', handler: () => {} },
      { id: 'save', label: 'Save', shortcut: '⌘S', handler: () => {} },
      { id: 'sep1', label: '', type: 'separator' },
      { id: 'export', label: 'Export as PDF', handler: () => {} },
      { id: 'print', label: 'Print…', shortcut: '⌘P', handler: () => {} },
    ],
  },
  {
    id: 'edit',
    label: 'Edit',
    items: [
      { id: 'undo', label: 'Undo', shortcut: '⌘Z', handler: () => {} },
      { id: 'redo', label: 'Redo', shortcut: '⌘⇧Z', handler: () => {} },
      { id: 'sep2', label: '', type: 'separator' },
      { id: 'cut', label: 'Cut', shortcut: '⌘X', handler: () => {} },
      { id: 'copy', label: 'Copy', shortcut: '⌘C', handler: () => {} },
      { id: 'paste', label: 'Paste', shortcut: '⌘V', handler: () => {} },
      { id: 'find', label: 'Find & Replace…', shortcut: '⌘H', handler: () => {} },
    ],
  },
  {
    id: 'view',
    label: 'View',
    items: [
      { id: 'outline', label: 'Show Outline', type: 'checkbox', checked: true, handler: () => {} },
      { id: 'ruler', label: 'Show Ruler', type: 'checkbox', checked: false, handler: () => {} },
      { id: 'fullscreen', label: 'Full Screen', shortcut: '⌘⇧F', handler: () => {} },
    ],
  },
];

const FORMAT_TOOLS = [
  { id: 'bold', label: 'Bold', key: '⌘B', symbol: '𝐁' },
  { id: 'italic', label: 'Italic', key: '⌘I', symbol: '𝐼' },
  { id: 'underline', label: 'Underline', key: '⌘U', symbol: 'U̲' },
  { id: 'strike', label: 'Strikethrough', symbol: 'S̶' },
];

export function DocumentEditor() {
  const [sidebarOpen, setSidebarOpen] = createSignal(true);
  const [wordCount] = createSignal(1248);
  const [isSaving, setIsSaving] = createSignal(false);
  const [zoom, setZoom] = createSignal(100);
  const [docTitle, setDocTitle] = createSignal('Untitled Document');

  const handleSave = () => {
    setIsSaving(true);
    setTimeout(() => setIsSaving(false), 800);
  };

  const statusItems: StatusBarItem[] = [
    { id: 'words', text: `${wordCount()} words`, icon: '📝', align: 'left', priority: 1 },
    { id: 'lang', text: 'English (US)', align: 'left', priority: 2 },
    {
      id: 'save-state',
      align: 'right',
      priority: 1,
      render: () => (
        <Show
          when={isSaving()}
          fallback={
            <span style={{ 'font-size': 'var(--sk-font-size-xs)', color: 'var(--sk-text-muted)' }}>
              Saved
            </span>
          }
        >
          <Flex align="center" gap="xs">
            <Spinner size="xs" />
            <span style={{ 'font-size': 'var(--sk-font-size-xs)' }}>Saving…</span>
          </Flex>
        </Show>
      ),
    },
    {
      id: 'zoom',
      text: `${zoom()}%`,
      align: 'right',
      priority: 2,
      onClick: () => setZoom((z) => (z === 100 ? 125 : 100)),
    },
  ];

  const outlineTabs: TabItem[] = [
    {
      value: 'outline',
      label: 'Outline',
      content: (
        <Stack gap="xs" style={{ padding: 'var(--sk-space-sm)' }}>
          {['Introduction', 'Background', 'Methodology', 'Results', 'Discussion', 'Conclusion'].map(
            (section, i) => (
              <Button
                key={section}
                variant="ghost"
                fullWidth
                style={{
                  'justify-content': 'flex-start',
                  'padding-left': `calc(var(--sk-space-md) * ${i === 0 ? 1 : 1.5})`,
                }}
              >
                <Text size="sm" color="secondary">
                  {i === 0 ? 'H1' : 'H2'}
                </Text>
                <Text size="sm">{section}</Text>
              </Button>
            )
          )}
        </Stack>
      ),
    },
    {
      value: 'comments',
      label: 'Comments',
      content: (
        <Flex align="center" justify="center" style={{ padding: 'var(--sk-space-xl)' }}>
          <Text size="sm" color="muted">
            No comments yet
          </Text>
        </Flex>
      ),
    },
  ];

  return (
    <Flex direction="column" style={{ height: '100vh', overflow: 'hidden' }}>
      {/* Application Menu Bar */}
      <MenuBar menus={APP_MENUS} />

      {/* Toolbar */}
      <Flex
        align="center"
        gap="xs"
        style={{
          padding: 'var(--sk-space-xs) var(--sk-space-md)',
          'border-bottom': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-primary)',
          'flex-shrink': 0,
        }}
      >
        {/* Undo/Redo */}
        <Tooltip content="Undo (⌘Z)">
          <Button variant="ghost" size="sm" onClick={() => {}}>

          </Button>
        </Tooltip>
        <Tooltip content="Redo (⌘⇧Z)">
          <Button variant="ghost" size="sm" onClick={() => {}}>

          </Button>
        </Tooltip>

        <Separator orientation="vertical" style={{ height: '20px' }} />

        {/* Format tools */}
        {FORMAT_TOOLS.map((tool) => (
          <Tooltip key={tool.id} content={`${tool.label}${tool.key ? ` (${tool.key})` : ''}`}>
            <Button variant="ghost" size="sm">
              {tool.symbol}
            </Button>
          </Tooltip>
        ))}

        <Separator orientation="vertical" style={{ height: '20px' }} />

        {/* Heading level */}
        <Button variant="ghost" size="sm">
          H1
        </Button>
        <Button variant="ghost" size="sm">
          H2
        </Button>
        <Button variant="ghost" size="sm">

        </Button>

        <Separator orientation="vertical" style={{ height: '20px' }} />

        {/* Alignment */}
        <Button variant="ghost" size="sm">

        </Button>
        <Button variant="ghost" size="sm">

        </Button>

        {/* Spacer */}
        <div style={{ flex: 1 }} />

        <Button variant="primary" size="sm" loading={isSaving()} onClick={handleSave}>
          Save
        </Button>
      </Flex>

      {/* Body */}
      <Flex style={{ flex: 1, overflow: 'hidden' }}>
        {/* Sidebar: Outline */}
        <Sidebar open={sidebarOpen()} onToggle={() => setSidebarOpen((v) => !v)} width="220px">
          <Tabs items={outlineTabs} />
        </Sidebar>

        {/* Page Canvas */}
        <Flex
          direction="column"
          align="center"
          style={{
            flex: 1,
            overflow: 'auto',
            'background-color': 'var(--sk-bg-secondary)',
            padding: 'var(--sk-space-xl) var(--sk-space-lg)',
          }}
        >
          {/* Document title */}
          <div
            style={{
              width: 'min(760px, 100%)',
              'margin-bottom': 'var(--sk-space-md)',
            }}
          >
            <Input
              value={docTitle()}
              onInput={setDocTitle}
              unstyled
              style={{
                width: '100%',
                'font-size': 'var(--sk-font-size-2xl)',
                'font-weight': '700',
                color: 'var(--sk-text-primary)',
                background: 'transparent',
                border: 'none',
                outline: 'none',
                padding: 0,
              }}
            />
          </div>

          {/* Page */}
          <Box
            style={{
              width: 'min(760px, 100%)',
              'min-height': '1040px',
              'background-color': 'var(--sk-bg-primary)',
              'border-radius': 'var(--sk-radius-md)',
              'box-shadow': 'var(--sk-shadow-lg)',
              padding: 'var(--sk-space-2xl)',
            }}
          >
            <Text as="p" color="secondary" lineHeight={1.75}>
              Start writing your document here. This area represents a single page in your document
              editor. The outline panel on the left reflects your heading structure for easy
              navigation.
            </Text>
          </Box>
        </Flex>
      </Flex>

      {/* Status Bar */}
      <StatusBar items={statusItems} />
    </Flex>
  );
}

#Recipe 4: E-Commerce Product Page

Product detail page with image gallery, variant selectors, price/rating area, a price-range filter for related items, and a tabbed review section.

Components used: Flex, Stack, Grid, Card, Text, Button, Badge, Select, RangeSlider, Tabs, ProgressBar, Separator, MetricCard, Tooltip, MessageBubble

import {
  Flex,
  Stack,
  Grid,
  Card,
  Text,
  Button,
  Badge,
  Select,
  RangeSlider,
  Tabs,
  ProgressBar,
  Separator,
  Tooltip,
  type TabItem,
  type SelectOption,
} from '@ybouhjira/hyperkit';
import { createSignal, For, Show } from 'solid-js';

interface Review {
  id: string;
  author: string;
  rating: number;
  date: string;
  body: string;
}

const SIZE_OPTIONS: SelectOption[] = [
  { value: 'xs', label: 'XS' },
  { value: 's', label: 'S' },
  { value: 'm', label: 'M' },
  { value: 'l', label: 'L' },
  { value: 'xl', label: 'XL' },
];

const COLOR_OPTIONS: SelectOption[] = [
  { value: 'midnight', label: 'Midnight Blue' },
  { value: 'stone', label: 'Stone Grey' },
  { value: 'forest', label: 'Forest Green' },
];

const REVIEWS: Review[] = [
  {
    id: '1',
    author: 'Alice M.',
    rating: 5,
    date: 'Mar 10, 2025',
    body: 'Incredible quality. The material is soft, breathable, and the fit is exactly as advertised. Will definitely buy again.',
  },
  {
    id: '2',
    author: 'Bob C.',
    rating: 4,
    date: 'Feb 28, 2025',
    body: 'Great product overall. Shipping was fast and packaging was solid. Sizing runs slightly large.',
  },
  {
    id: '3',
    author: 'Carol D.',
    rating: 5,
    date: 'Feb 14, 2025',
    body: 'Perfect gift. My partner loved it. The colour in person matches the photos exactly.',
  },
];

const RATING_DISTRIBUTION = [5, 4, 3, 2, 1].map((star) => ({
  star,
  count: [18, 7, 3, 1, 0][5 - star] ?? 0,
  pct: [62, 24, 10, 3, 0][5 - star] ?? 0,
}));

export function ProductPage() {
  const [selectedSize, setSelectedSize] = createSignal('m');
  const [selectedColor, setSelectedColor] = createSignal('midnight');
  const [quantity, setQuantity] = createSignal(1);
  const [priceRange, setPriceRange] = createSignal<[number, number]>([20, 80]);
  const [addedToCart, setAddedToCart] = createSignal(false);

  const handleAddToCart = () => {
    setAddedToCart(true);
    setTimeout(() => setAddedToCart(false), 2000);
  };

  const reviewTabs: TabItem[] = [
    {
      value: 'reviews',
      label: `Reviews (${REVIEWS.length})`,
      content: (
        <Stack gap="md" style={{ padding: 'var(--sk-space-md) 0' }}>
          {/* Rating summary */}
          <Flex gap="xl" align="start">
            <Stack gap="xs" align="center" style={{ 'flex-shrink': 0 }}>
              <Text size="4xl" weight="bold" color="primary">
                4.7
              </Text>
              <Text size="sm" color="muted">
                out of 5
              </Text>
            </Stack>
            <Stack gap="xs" style={{ flex: 1 }}>
              <For each={RATING_DISTRIBUTION}>
                {(row) => (
                  <Flex align="center" gap="sm">
                    <Text size="xs" color="muted" style={{ width: '16px', 'text-align': 'right' }}>
                      {row.star}★
                    </Text>
                    <ProgressBar value={row.pct} size="sm" style={{ flex: 1 }} />
                    <Text size="xs" color="muted" style={{ width: '28px' }}>
                      {row.count}
                    </Text>
                  </Flex>
                )}
              </For>
            </Stack>
          </Flex>

          <Separator />

          {/* Review list */}
          <For each={REVIEWS}>
            {(review) => (
              <Stack gap="xs">
                <Flex align="center" justify="between">
                  <Flex align="center" gap="sm">
                    <div
                      style={{
                        width: '32px',
                        height: '32px',
                        'border-radius': '50%',
                        'background-color': 'var(--sk-accent)',
                        color: 'var(--sk-text-on-accent)',
                        display: 'flex',
                        'align-items': 'center',
                        'justify-content': 'center',
                        'font-size': 'var(--sk-font-size-sm)',
                        'font-weight': '600',
                      }}
                    >
                      {review.author[0]}
                    </div>
                    <Stack gap="0">
                      <Text size="sm" weight="semibold" color="primary">
                        {review.author}
                      </Text>
                      <Text size="xs" color="muted">
                        {review.date}
                      </Text>
                    </Stack>
                  </Flex>
                  <Text size="sm" color="secondary">
                    {'★'.repeat(review.rating)}
                    {'☆'.repeat(5 - review.rating)}
                  </Text>
                </Flex>
                <Text size="sm" color="secondary" lineHeight={1.6}>
                  {review.body}
                </Text>
                <Separator />
              </Stack>
            )}
          </For>
        </Stack>
      ),
    },
    {
      value: 'specs',
      label: 'Specifications',
      content: (
        <Grid columns={2} gap="sm" style={{ padding: 'var(--sk-space-md) 0' }}>
          {[
            ['Material', '100% Organic Cotton'],
            ['Weight', '180 GSM'],
            ['Fit', 'Regular'],
            ['Care', 'Machine wash cold'],
            ['Origin', 'Made in Portugal'],
            ['Certification', 'GOTS Certified'],
          ].map(([label, value]) => (
            <Flex key={label} justify="between" style={{ padding: 'var(--sk-space-xs) 0' }}>
              <Text size="sm" color="muted">
                {label}
              </Text>
              <Text size="sm" weight="medium" color="primary">
                {value}
              </Text>
            </Flex>
          ))}
        </Grid>
      ),
    },
  ];

  return (
    <div style={{ 'background-color': 'var(--sk-bg-secondary)', 'min-height': '100vh' }}>
      <div
        style={{
          'max-width': '1200px',
          margin: '0 auto',
          padding: 'var(--sk-space-xl) var(--sk-space-lg)',
        }}
      >
        <Grid columns="1fr 480px" gap="2xl">
          {/* Left: Images */}
          <Stack gap="md">
            {/* Main image */}
            <div
              style={{
                'aspect-ratio': '4/3',
                'border-radius': 'var(--sk-radius-lg)',
                'background-color': 'var(--sk-bg-tertiary)',
                border: '1px solid var(--sk-border)',
                display: 'flex',
                'align-items': 'center',
                'justify-content': 'center',
              }}
            >
              <Text color="muted" size="2xl">
                🖼
              </Text>
            </div>
            {/* Thumbnails */}
            <Grid columns={4} gap="sm">
              {[1, 2, 3, 4].map((i) => (
                <div
                  key={i}
                  style={{
                    'aspect-ratio': '1',
                    'border-radius': 'var(--sk-radius-md)',
                    'background-color': 'var(--sk-bg-tertiary)',
                    border: `2px solid ${i === 1 ? 'var(--sk-accent)' : 'var(--sk-border)'}`,
                    cursor: 'pointer',
                  }}
                />
              ))}
            </Grid>
          </Stack>

          {/* Right: Product Info */}
          <Stack gap="md">
            <Flex align="center" gap="sm">
              <Badge variant="success">In Stock</Badge>
              <Badge variant="info">Free Shipping</Badge>
            </Flex>

            <Text as="h1" size="2xl" weight="bold" color="primary">
              Premium Organic Cotton Tee
            </Text>

            <Flex align="center" gap="sm">
              <Text size="sm" color="secondary">
                {'★★★★★'}
              </Text>
              <Text size="sm" color="muted">
                4.7 · {REVIEWS.length} reviews
              </Text>
            </Flex>

            <Flex align="baseline" gap="sm">
              <Text size="2xl" weight="bold" color="primary">
                $49.00
              </Text>
              <Text size="base" color="muted" style={{ 'text-decoration': 'line-through' }}>
                $65.00
              </Text>
              <Badge variant="danger">25% off</Badge>
            </Flex>

            <Separator />

            {/* Variant selectors */}
            <Stack gap="sm">
              <Flex align="center" justify="between">
                <Text size="sm" weight="medium" color="primary">
                  Color
                </Text>
                <Text size="sm" color="muted">
                  {COLOR_OPTIONS.find((c) => c.value === selectedColor())?.label}
                </Text>
              </Flex>
              <Select options={COLOR_OPTIONS} value={selectedColor()} onChange={setSelectedColor} />

              <Flex align="center" justify="between">
                <Text size="sm" weight="medium" color="primary">
                  Size
                </Text>
                <Text
                  size="sm"
                  color="secondary"
                  style={{ cursor: 'pointer', 'text-decoration': 'underline' }}
                >
                  Size guide
                </Text>
              </Flex>
              <Select options={SIZE_OPTIONS} value={selectedSize()} onChange={setSelectedSize} />
            </Stack>

            {/* Quantity + Add to Cart */}
            <Flex gap="sm" align="center">
              <Flex
                align="center"
                style={{
                  border: '1px solid var(--sk-border)',
                  'border-radius': 'var(--sk-radius-md)',
                  overflow: 'hidden',
                }}
              >
                <Button
                  variant="ghost"
                  size="sm"
                  onClick={() => setQuantity((q) => Math.max(1, q - 1))}
                  disabled={quantity() <= 1}
                >

                </Button>
                <Text
                  size="sm"
                  weight="medium"
                  style={{
                    padding: '0 var(--sk-space-md)',
                    'min-width': '32px',
                    'text-align': 'center',
                  }}
                >
                  {quantity()}
                </Text>
                <Button variant="ghost" size="sm" onClick={() => setQuantity((q) => q + 1)}>
                  +
                </Button>
              </Flex>

              <Button variant="primary" fullWidth size="lg" onClick={handleAddToCart}>
                <Show when={addedToCart()} fallback="Add to Cart">
                  ✓ Added to Cart
                </Show>
              </Button>
            </Flex>

            {/* Price Range Filter for similar items */}
            <Card padding="md" variant="outlined">
              <Stack gap="sm">
                <Text size="sm" weight="medium" color="primary">
                  Browse similar: price range
                </Text>
                <RangeSlider
                  value={priceRange()}
                  onChange={setPriceRange}
                  min={0}
                  max={200}
                  step={5}
                  label="Price"
                />
              </Stack>
            </Card>
          </Stack>
        </Grid>

        {/* Reviews + Specs Tabs */}
        <Card padding="lg" style={{ 'margin-top': 'var(--sk-space-2xl)' }}>
          <Tabs items={reviewTabs} />
        </Card>
      </div>
    </div>
  );
}

#Recipe 5: Project Management Board

Kanban board with status columns, card assignment, a command palette for quick actions, and a top bar with filters.

Components used: Flex, Stack, Text, Button, Badge, SearchInput, Select, KanbanBoard, MetricCard, StatusDot, Separator, Tooltip

import {
  Flex,
  Stack,
  Text,
  Button,
  Badge,
  SearchInput,
  Select,
  KanbanBoard,
  MetricCard,
  StatusDot,
  Separator,
  Tooltip,
  type KanbanColumn,
  type KanbanCard,
  type SelectOption,
} from '@ybouhjira/hyperkit';
import { createSignal } from 'solid-js';

const PRIORITY_OPTIONS: SelectOption[] = [
  { value: 'all', label: 'All Priorities' },
  { value: 'high', label: 'High' },
  { value: 'medium', label: 'Medium' },
  { value: 'low', label: 'Low' },
];

const ASSIGNEE_OPTIONS: SelectOption[] = [
  { value: 'all', label: 'All Assignees' },
  { value: 'alice', label: 'Alice' },
  { value: 'bob', label: 'Bob' },
  { value: 'carol', label: 'Carol' },
];

const COLUMNS: KanbanColumn[] = [
  {
    id: 'backlog',
    label: 'Backlog',
    icon: '📋',
    color: 'var(--sk-text-muted)',
    cards: [
      {
        id: 'b1',
        title: 'Research competitor UX patterns',
        subtitle: 'Design · Low priority',
        badge: { text: 'Research', color: 'var(--sk-info)' },
        accent: 'var(--sk-info)',
      },
      {
        id: 'b2',
        title: 'Set up CI/CD pipeline',
        subtitle: 'DevOps · Medium priority',
        badge: { text: 'DevOps', color: 'var(--sk-warning)' },
        accent: 'var(--sk-warning)',
      },
      {
        id: 'b3',
        title: 'Audit accessibility compliance',
        subtitle: 'Frontend · Medium priority',
        badge: { text: 'A11y', color: 'var(--sk-accent)' },
        accent: 'var(--sk-accent)',
      },
    ],
  },
  {
    id: 'in-progress',
    label: 'In Progress',
    icon: '🔄',
    color: 'var(--sk-warning)',
    cards: [
      {
        id: 'p1',
        title: 'Redesign onboarding flow',
        subtitle: 'Alice · Due May 10',
        badge: { text: 'Design', color: 'var(--sk-accent)' },
        accent: 'var(--sk-accent)',
        icon: '🎨',
      },
      {
        id: 'p2',
        title: 'Implement dark mode tokens',
        subtitle: 'Bob · Due May 8',
        badge: { text: 'Frontend', color: 'var(--sk-info)' },
        accent: 'var(--sk-info)',
        icon: '🌙',
      },
    ],
  },
  {
    id: 'review',
    label: 'In Review',
    icon: '👁',
    color: 'var(--sk-info)',
    cards: [
      {
        id: 'r1',
        title: 'Add pagination to data tables',
        subtitle: 'Carol · PR #142',
        badge: { text: 'PR Open', color: 'var(--sk-success)' },
        accent: 'var(--sk-success)',
      },
    ],
  },
  {
    id: 'done',
    label: 'Done',
    icon: '✅',
    color: 'var(--sk-success)',
    cards: [
      {
        id: 'd1',
        title: 'Migrate to SolidJS',
        subtitle: 'Completed Mar 20',
        badge: { text: 'Done', color: 'var(--sk-success)' },
        accent: 'var(--sk-success)',
      },
      {
        id: 'd2',
        title: 'Set up design tokens',
        subtitle: 'Completed Mar 18',
        badge: { text: 'Done', color: 'var(--sk-success)' },
        accent: 'var(--sk-success)',
      },
    ],
  },
];

export function ProjectBoard() {
  const [search, setSearch] = createSignal('');
  const [priority, setPriority] = createSignal('all');
  const [assignee, setAssignee] = createSignal('all');
  const [selectedCard, setSelectedCard] = createSignal<KanbanCard | null>(null);

  const totalCards = COLUMNS.reduce((sum, col) => sum + col.cards.length, 0);
  const inProgressCount = COLUMNS.find((c) => c.id === 'in-progress')?.cards.length ?? 0;
  const doneCount = COLUMNS.find((c) => c.id === 'done')?.cards.length ?? 0;

  return (
    <Flex direction="column" style={{ height: '100vh', overflow: 'hidden' }}>
      {/* Top Bar */}
      <Flex
        align="center"
        justify="between"
        style={{
          padding: 'var(--sk-space-sm) var(--sk-space-lg)',
          'border-bottom': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-primary)',
          'flex-shrink': 0,
        }}
      >
        <Flex align="center" gap="md">
          <Text as="h1" size="lg" weight="bold" color="primary">
            Sprint 14
          </Text>
          <Flex align="center" gap="xs">
            <StatusDot status="success" pulse size="sm" />
            <Text size="xs" color="muted">
              Active sprint
            </Text>
          </Flex>
        </Flex>

        <Flex gap="sm" align="center">
          <Badge variant="default">{totalCards} tasks</Badge>
          <Badge variant="warning">{inProgressCount} in progress</Badge>
          <Badge variant="success">{doneCount} done</Badge>
        </Flex>

        <Button variant="primary" size="sm">
          + Add Task
        </Button>
      </Flex>

      {/* Filters Bar */}
      <Flex
        align="center"
        gap="sm"
        style={{
          padding: 'var(--sk-space-sm) var(--sk-space-lg)',
          'border-bottom': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-primary)',
          'flex-shrink': 0,
        }}
      >
        <SearchInput
          value={search()}
          onChange={setSearch}
          placeholder="Search tasks…"
          shortcut="/"
          style={{ width: '220px' }}
        />
        <Separator orientation="vertical" style={{ height: '20px' }} />
        <Select options={ASSIGNEE_OPTIONS} value={assignee()} onChange={setAssignee} />
        <Select options={PRIORITY_OPTIONS} value={priority()} onChange={setPriority} />
        <div style={{ flex: 1 }} />
        <Tooltip content="Group by assignee">
          <Button variant="ghost" size="sm">
            ⊞ Group
          </Button>
        </Tooltip>
        <Tooltip content="Board view">
          <Button variant="secondary" size="sm">
            ☰ Board
          </Button>
        </Tooltip>
      </Flex>

      {/* Metrics Strip */}
      <Flex
        gap="md"
        style={{
          padding: 'var(--sk-space-sm) var(--sk-space-lg)',
          'border-bottom': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-secondary)',
          'flex-shrink': 0,
        }}
      >
        <MetricCard label="Velocity" value="34 pts" trend="+6" trendDirection="up" size="sm" />
        <MetricCard
          label="Completion"
          value="71%"
          trend="+5%"
          trendDirection="up"
          variant="success"
          size="sm"
        />
        <MetricCard
          label="Blockers"
          value="0"
          trend="clear"
          trendDirection="neutral"
          variant="info"
          size="sm"
        />
        <MetricCard
          label="Overdue"
          value="1"
          trend="-2"
          trendDirection="down"
          variant="warning"
          size="sm"
        />
      </Flex>

      {/* Kanban Board */}
      <div style={{ flex: 1, overflow: 'auto', padding: 'var(--sk-space-lg)' }}>
        <KanbanBoard
          columns={COLUMNS}
          selectedCardId={selectedCard()?.id ?? null}
          onCardClick={(card) => setSelectedCard(card)}
          emptyState="No tasks in this column"
        />
      </div>
    </Flex>
  );
}

#Recipe 6: Settings & Preferences

Multi-section settings page with profile editing, appearance controls with live preview, notification toggles, and a danger zone with confirmation dialog.

Components used: Flex, Stack, Grid, Card, Text, Button, Input, Select, Switch, Tabs, Separator, ConfirmDialog, Badge, StatusDot, ErrorBanner

import {
  Flex,
  Stack,
  Grid,
  Card,
  Text,
  Button,
  Input,
  Select,
  Switch,
  Tabs,
  Separator,
  ConfirmDialog,
  Badge,
  ErrorBanner,
  type TabItem,
  type SelectOption,
} from '@ybouhjira/hyperkit';
import { createSignal, Show } from 'solid-js';

const TIMEZONE_OPTIONS: SelectOption[] = [
  { value: 'utc', label: 'UTC' },
  { value: 'est', label: 'Eastern (UTC-5)' },
  { value: 'cet', label: 'Central Europe (UTC+1)' },
  { value: 'jst', label: 'Japan (UTC+9)' },
];

const LANGUAGE_OPTIONS: SelectOption[] = [
  { value: 'en', label: 'English' },
  { value: 'fr', label: 'French' },
  { value: 'de', label: 'German' },
  { value: 'ja', label: 'Japanese' },
];

export function SettingsPage() {
  // Profile
  const [name, setName] = createSignal('Ada L.');
  const [email, setEmail] = createSignal('ada@example.com');
  const [timezone, setTimezone] = createSignal('cet');
  const [language, setLanguage] = createSignal('en');

  // Notifications
  const [emailNotifs, setEmailNotifs] = createSignal(true);
  const [pushNotifs, setPushNotifs] = createSignal(true);
  const [marketingNotifs, setMarketingNotifs] = createSignal(false);
  const [securityAlerts, setSecurityAlerts] = createSignal(true);

  // Privacy
  const [publicProfile, setPublicProfile] = createSignal(false);
  const [activityStatus, setActivityStatus] = createSignal(true);
  const [dataSharing, setDataSharing] = createSignal(false);

  // UI state
  const [isSaving, setIsSaving] = createSignal(false);
  const [saveError, setSaveError] = createSignal<string | null>(null);
  const [deleteOpen, setDeleteOpen] = createSignal(false);
  const [isDeleting, setIsDeleting] = createSignal(false);

  const handleSave = () => {
    setIsSaving(true);
    setSaveError(null);
    setTimeout(() => {
      setIsSaving(false);
    }, 800);
  };

  const handleDelete = () => {
    setIsDeleting(true);
    setTimeout(() => {
      setIsDeleting(false);
      setDeleteOpen(false);
    }, 1500);
  };

  const tabItems: TabItem[] = [
    {
      value: 'profile',
      label: 'Profile',
      content: (
        <Stack gap="lg" style={{ padding: 'var(--sk-space-lg) 0' }}>
          <Show when={saveError()}>
            {(msg) => <ErrorBanner message={msg()} onDismiss={() => setSaveError(null)} />}
          </Show>

          {/* Avatar */}
          <Flex align="center" gap="lg">
            <div
              style={{
                width: '72px',
                height: '72px',
                'border-radius': '50%',
                'background-color': 'var(--sk-accent)',
                color: 'var(--sk-text-on-accent)',
                display: 'flex',
                'align-items': 'center',
                'justify-content': 'center',
                'font-size': 'var(--sk-font-size-2xl)',
                'font-weight': '700',
                'flex-shrink': 0,
              }}
            >
              Y
            </div>
            <Stack gap="xs">
              <Button variant="outline" size="sm">
                Upload photo
              </Button>
              <Text size="xs" color="muted">
                JPG, PNG or GIF · Max 2 MB
              </Text>
            </Stack>
          </Flex>

          <Separator />

          <Grid columns={2} gap="md">
            <Stack gap="xs">
              <Text as="label" size="sm" weight="medium" color="primary">
                Full name
              </Text>
              <Input value={name()} onInput={setName} placeholder="Your name" />
            </Stack>
            <Stack gap="xs">
              <Text as="label" size="sm" weight="medium" color="primary">
                Email address
              </Text>
              <Input
                value={email()}
                onInput={setEmail}
                type="email"
                placeholder="you@example.com"
              />
            </Stack>
          </Grid>

          <Grid columns={2} gap="md">
            <Stack gap="xs">
              <Text as="label" size="sm" weight="medium" color="primary">
                Language
              </Text>
              <Select options={LANGUAGE_OPTIONS} value={language()} onChange={setLanguage} />
            </Stack>
            <Stack gap="xs">
              <Text as="label" size="sm" weight="medium" color="primary">
                Timezone
              </Text>
              <Select options={TIMEZONE_OPTIONS} value={timezone()} onChange={setTimezone} />
            </Stack>
          </Grid>

          <Flex justify="end">
            <Button variant="primary" loading={isSaving()} onClick={handleSave}>
              Save changes
            </Button>
          </Flex>
        </Stack>
      ),
    },
    {
      value: 'notifications',
      label: 'Notifications',
      content: (
        <Stack gap="md" style={{ padding: 'var(--sk-space-lg) 0' }}>
          <Card padding="md" variant="outlined">
            <Stack gap="md">
              <Text size="base" weight="semibold" color="primary">
                Email
              </Text>
              <Stack gap="sm">
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Product updates
                    </Text>
                    <Text size="xs" color="muted">
                      New features and release notes
                    </Text>
                  </Stack>
                  <Switch checked={emailNotifs()} onChange={setEmailNotifs} size="sm" />
                </Flex>
                <Separator />
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Marketing
                    </Text>
                    <Text size="xs" color="muted">
                      Tips, promotions, and surveys
                    </Text>
                  </Stack>
                  <Switch checked={marketingNotifs()} onChange={setMarketingNotifs} size="sm" />
                </Flex>
              </Stack>
            </Stack>
          </Card>

          <Card padding="md" variant="outlined">
            <Stack gap="md">
              <Text size="base" weight="semibold" color="primary">
                Push notifications
              </Text>
              <Stack gap="sm">
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Desktop push
                    </Text>
                    <Text size="xs" color="muted">
                      Alerts when the app is in the background
                    </Text>
                  </Stack>
                  <Switch checked={pushNotifs()} onChange={setPushNotifs} size="sm" />
                </Flex>
                <Separator />
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Flex align="center" gap="xs">
                      <Text size="sm" weight="medium" color="primary">
                        Security alerts
                      </Text>
                      <Badge variant="success" type="dot" />
                    </Flex>
                    <Text size="xs" color="muted">
                      Sign-ins from new devices
                    </Text>
                  </Stack>
                  <Switch checked={securityAlerts()} onChange={setSecurityAlerts} size="sm" />
                </Flex>
              </Stack>
            </Stack>
          </Card>
        </Stack>
      ),
    },
    {
      value: 'privacy',
      label: 'Privacy',
      content: (
        <Stack gap="md" style={{ padding: 'var(--sk-space-lg) 0' }}>
          <Card padding="md" variant="outlined">
            <Stack gap="md">
              <Text size="base" weight="semibold" color="primary">
                Visibility
              </Text>
              <Stack gap="sm">
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Public profile
                    </Text>
                    <Text size="xs" color="muted">
                      Anyone can view your profile page
                    </Text>
                  </Stack>
                  <Switch checked={publicProfile()} onChange={setPublicProfile} size="sm" />
                </Flex>
                <Separator />
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Activity status
                    </Text>
                    <Text size="xs" color="muted">
                      Show when you were last active
                    </Text>
                  </Stack>
                  <Switch checked={activityStatus()} onChange={setActivityStatus} size="sm" />
                </Flex>
                <Separator />
                <Flex align="center" justify="between">
                  <Stack gap="0">
                    <Text size="sm" weight="medium" color="primary">
                      Usage analytics
                    </Text>
                    <Text size="xs" color="muted">
                      Help improve the product with anonymous data
                    </Text>
                  </Stack>
                  <Switch checked={dataSharing()} onChange={setDataSharing} size="sm" />
                </Flex>
              </Stack>
            </Stack>
          </Card>

          {/* Danger Zone */}
          <Card
            padding="md"
            style={{ 'border-color': 'var(--sk-error)', border: '1px solid var(--sk-error)' }}
          >
            <Stack gap="sm">
              <Text size="base" weight="semibold" color="primary">
                Danger Zone
              </Text>
              <Text size="sm" color="secondary">
                Permanently delete your account and all associated data. This action cannot be
                undone.
              </Text>
              <Flex justify="end">
                <Button variant="danger" onClick={() => setDeleteOpen(true)}>
                  Delete Account
                </Button>
              </Flex>
            </Stack>
          </Card>

          {/* Confirm Dialog */}
          <ConfirmDialog
            open={deleteOpen()}
            onClose={() => setDeleteOpen(false)}
            onConfirm={handleDelete}
            title="Delete your account?"
            confirmLabel="Yes, delete my account"
            cancelLabel="Cancel"
            variant="danger"
            loading={isDeleting()}
          >
            <Text size="sm" color="secondary">
              All your data, projects, and settings will be permanently removed. You cannot undo
              this.
            </Text>
          </ConfirmDialog>
        </Stack>
      ),
    },
  ];

  return (
    <div
      style={{
        'min-height': '100vh',
        'background-color': 'var(--sk-bg-secondary)',
        padding: 'var(--sk-space-xl) var(--sk-space-lg)',
      }}
    >
      <div style={{ 'max-width': '720px', margin: '0 auto' }}>
        <Stack gap="lg">
          <Text as="h1" size="2xl" weight="bold" color="primary">
            Account Settings
          </Text>
          <Card padding="none">
            <Tabs items={tabItems} />
          </Card>
        </Stack>
      </div>
    </div>
  );
}

#Recipe 7: Data Explorer

Data-browsing interface with a filter sidebar, sortable/selectable table, row detail drawer, and toast feedback on row actions.

Components used: Flex, Stack, Card, Text, Button, SearchInput, Select, Table, Badge, StatusDot, Separator, EmptyState, Tooltip, ToastProvider, useToast

import {
  Flex,
  Stack,
  Card,
  Text,
  Button,
  SearchInput,
  Select,
  Table,
  Badge,
  StatusDot,
  Separator,
  EmptyState,
  Tooltip,
  ToastProvider,
  useToast,
  type TableColumn,
  type SelectOption,
} from '@ybouhjira/hyperkit';
import { createSignal, Show, For } from 'solid-js';

interface Dataset {
  id: string;
  name: string;
  source: string;
  records: number;
  status: 'active' | 'archived' | 'processing';
  updated: string;
  owner: string;
}

const STATUS_OPTIONS: SelectOption[] = [
  { value: 'all', label: 'All Statuses' },
  { value: 'active', label: 'Active' },
  { value: 'archived', label: 'Archived' },
  { value: 'processing', label: 'Processing' },
];

const SOURCE_OPTIONS: SelectOption[] = [
  { value: 'all', label: 'All Sources' },
  { value: 'api', label: 'API' },
  { value: 'upload', label: 'Upload' },
  { value: 'sync', label: 'Sync' },
];

const DATASETS: Dataset[] = [
  {
    id: 'ds1',
    name: 'User Events Q1 2025',
    source: 'api',
    records: 1480320,
    status: 'active',
    updated: '2025-03-01',
    owner: 'Alice',
  },
  {
    id: 'ds2',
    name: 'Product Catalogue v3',
    source: 'upload',
    records: 8420,
    status: 'active',
    updated: '2025-02-28',
    owner: 'Bob',
  },
  {
    id: 'ds3',
    name: 'Revenue Log 2024',
    source: 'sync',
    records: 52100,
    status: 'archived',
    updated: '2025-01-15',
    owner: 'Alice',
  },
  {
    id: 'ds4',
    name: 'Support Tickets Mar',
    source: 'api',
    records: 3210,
    status: 'processing',
    updated: '2025-03-05',
    owner: 'Carol',
  },
  {
    id: 'ds5',
    name: 'Ad Campaign Results',
    source: 'upload',
    records: 14500,
    status: 'active',
    updated: '2025-03-04',
    owner: 'Bob',
  },
];

function DataExplorerInner() {
  const toast = useToast();

  const [search, setSearch] = createSignal('');
  const [statusFilter, setStatusFilter] = createSignal('all');
  const [sourceFilter, setSourceFilter] = createSignal('all');
  const [sortColumn, setSortColumn] = createSignal<string | undefined>('updated');
  const [sortDirection, setSortDirection] = createSignal<'asc' | 'desc'>('desc');
  const [selectedId, setSelectedId] = createSignal<string | null>(null);
  const [detailOpen, setDetailOpen] = createSignal(false);

  const filtered = () => {
    const q = search().toLowerCase();
    return DATASETS.filter((ds) => {
      const matchSearch = ds.name.toLowerCase().includes(q) || ds.owner.toLowerCase().includes(q);
      const matchStatus = statusFilter() === 'all' || ds.status === statusFilter();
      const matchSource = sourceFilter() === 'all' || ds.source === sourceFilter();
      return matchSearch && matchStatus && matchSource;
    });
  };

  const selectedDataset = () => DATASETS.find((d) => d.id === selectedId()) ?? null;

  const columns: TableColumn<Dataset>[] = [
    {
      key: 'name',
      header: 'Dataset',
      sortable: true,
      render: (ds) => (
        <Stack gap="0">
          <Text size="sm" weight="medium" color="primary">
            {ds.name}
          </Text>
          <Text size="xs" color="muted">
            {ds.owner}
          </Text>
        </Stack>
      ),
    },
    {
      key: 'source',
      header: 'Source',
      render: (ds) => <Badge variant="default">{ds.source.toUpperCase()}</Badge>,
    },
    {
      key: 'records',
      header: 'Records',
      sortable: true,
      render: (ds) => (
        <Text size="sm" color="secondary">
          {ds.records.toLocaleString()}
        </Text>
      ),
    },
    {
      key: 'status',
      header: 'Status',
      render: (ds) => {
        const map = { active: 'success', archived: 'default', processing: 'warning' } as const;
        return (
          <StatusDot status={map[ds.status]} label={ds.status} pulse={ds.status === 'processing'} />
        );
      },
    },
    {
      key: 'updated',
      header: 'Updated',
      sortable: true,
      render: (ds) => (
        <Text size="xs" color="muted">
          {ds.updated}
        </Text>
      ),
    },
  ];

  const handleRowClick = (ds: Dataset) => {
    setSelectedId(ds.id);
    setDetailOpen(true);
  };

  const handleArchive = () => {
    toast.warning('Dataset archived.', 'Action');
    setDetailOpen(false);
  };

  const handleDownload = () => {
    toast.success("Export started. You'll receive an email when it's ready.", 'Download');
  };

  return (
    <Flex style={{ height: '100vh', overflow: 'hidden' }}>
      {/* Filter Sidebar */}
      <Flex
        direction="column"
        gap="lg"
        style={{
          width: '240px',
          'flex-shrink': 0,
          'border-right': '1px solid var(--sk-border)',
          'background-color': 'var(--sk-bg-primary)',
          padding: 'var(--sk-space-lg)',
          overflow: 'auto',
        }}
      >
        <Text size="base" weight="semibold" color="primary">
          Filters
        </Text>

        <Stack gap="sm">
          <Text size="sm" weight="medium" color="secondary">
            Status
          </Text>
          <Select options={STATUS_OPTIONS} value={statusFilter()} onChange={setStatusFilter} />
        </Stack>

        <Stack gap="sm">
          <Text size="sm" weight="medium" color="secondary">
            Source
          </Text>
          <Select options={SOURCE_OPTIONS} value={sourceFilter()} onChange={setSourceFilter} />
        </Stack>

        <Separator />

        <Stack gap="sm">
          <Text size="sm" weight="medium" color="secondary">
            Quick filters
          </Text>
          <For each={['My datasets', 'Shared with me', 'Recently updated', 'Large (1M+)']}>
            {(label) => (
              <Button
                variant="ghost"
                size="sm"
                fullWidth
                style={{ 'justify-content': 'flex-start' }}
              >
                {label}
              </Button>
            )}
          </For>
        </Stack>

        <div style={{ flex: 1 }} />

        <Text size="xs" color="muted">
          {filtered().length} of {DATASETS.length} datasets
        </Text>
      </Flex>

      {/* Main */}
      <Flex direction="column" style={{ flex: 1, 'min-width': 0, overflow: 'hidden' }}>
        {/* Toolbar */}
        <Flex
          align="center"
          gap="sm"
          style={{
            padding: 'var(--sk-space-sm) var(--sk-space-lg)',
            'border-bottom': '1px solid var(--sk-border)',
            'background-color': 'var(--sk-bg-primary)',
            'flex-shrink': 0,
          }}
        >
          <SearchInput
            value={search()}
            onChange={setSearch}
            placeholder="Search datasets…"
            shortcut="/"
          />
          <div style={{ flex: 1 }} />
          <Tooltip content="Import new dataset">
            <Button variant="outline" size="sm">
              Import
            </Button>
          </Tooltip>
          <Button variant="primary" size="sm">
            + New Dataset
          </Button>
        </Flex>

        {/* Table */}
        <div style={{ flex: 1, overflow: 'auto', padding: 'var(--sk-space-lg)' }}>
          <Card padding="none">
            <Show
              when={filtered().length > 0}
              fallback={
                <EmptyState
                  icon="database"
                  title="No datasets found"
                  description="Try adjusting your filters or search query."
                  action={
                    <Button
                      variant="ghost"
                      size="sm"
                      onClick={() => {
                        setSearch('');
                        setStatusFilter('all');
                        setSourceFilter('all');
                      }}
                    >
                      Clear filters
                    </Button>
                  }
                />
              }
            >
              <Table
                columns={columns}
                data={filtered()}
                getRowKey={(ds) => ds.id}
                onRowClick={handleRowClick}
                selectedKey={selectedId()}
                sortColumn={sortColumn()}
                sortDirection={sortDirection()}
                onSort={(col, dir) => {
                  setSortColumn(col);
                  setSortDirection(dir);
                }}
              />
            </Show>
          </Card>
        </div>
      </Flex>

      {/* Detail Drawer */}
      <Show when={detailOpen() && selectedDataset()}>
        {(ds) => (
          <Flex
            direction="column"
            style={{
              width: '320px',
              'flex-shrink': 0,
              'border-left': '1px solid var(--sk-border)',
              'background-color': 'var(--sk-bg-primary)',
              overflow: 'auto',
            }}
          >
            {/* Drawer Header */}
            <Flex
              align="center"
              justify="between"
              style={{
                padding: 'var(--sk-space-md) var(--sk-space-lg)',
                'border-bottom': '1px solid var(--sk-border)',
                'flex-shrink': 0,
              }}
            >
              <Text size="sm" weight="semibold" color="primary">
                Dataset Details
              </Text>
              <Button variant="ghost" size="sm" onClick={() => setDetailOpen(false)}>

              </Button>
            </Flex>

            {/* Drawer Body */}
            <Stack gap="lg" style={{ padding: 'var(--sk-space-lg)' }}>
              <Stack gap="xs">
                <Text size="lg" weight="bold" color="primary">
                  {ds().name}
                </Text>
                <StatusDot
                  status={
                    ds().status === 'active'
                      ? 'success'
                      : ds().status === 'processing'
                        ? 'warning'
                        : 'default'
                  }
                  label={ds().status}
                  pulse={ds().status === 'processing'}
                />
              </Stack>

              <Separator />

              <Stack gap="sm">
                {[
                  { label: 'Owner', value: ds().owner },
                  { label: 'Source', value: ds().source.toUpperCase() },
                  { label: 'Records', value: ds().records.toLocaleString() },
                  { label: 'Last updated', value: ds().updated },
                ].map(({ label, value }) => (
                  <Flex justify="between" key={label}>
                    <Text size="sm" color="muted">
                      {label}
                    </Text>
                    <Text size="sm" weight="medium" color="primary">
                      {value}
                    </Text>
                  </Flex>
                ))}
              </Stack>

              <Separator />

              <Stack gap="sm">
                <Button variant="primary" fullWidth onClick={handleDownload}>
                  Export dataset
                </Button>
                <Button variant="outline" fullWidth>
                  View schema
                </Button>
                <Button variant="ghost" fullWidth onClick={handleArchive}>
                  Archive
                </Button>
              </Stack>
            </Stack>
          </Flex>
        )}
      </Show>
    </Flex>
  );
}

export function DataExplorer() {
  return (
    <ToastProvider position="bottom-right">
      <DataExplorerInner />
    </ToastProvider>
  );
}

#Quick Reference

#Token cheat-sheet

Purpose Token
Smallest gap / padding var(--sk-space-xs)
Standard padding var(--sk-space-md)
Section spacing var(--sk-space-lg)
Page padding var(--sk-space-xl)
Body text var(--sk-font-size-base)
Label / caption var(--sk-font-size-sm)
Dividers var(--sk-border)
Primary bg var(--sk-bg-primary)
Subtle bg var(--sk-bg-secondary)
Hover / input bg var(--sk-bg-tertiary)

#Layout patterns

// Full-viewport shell with sidebar + main
<Flex style={{ height: "100vh", overflow: "hidden" }}>
  <Sidebar ...>...</Sidebar>
  <Flex direction="column" style={{ flex: 1, "min-width": 0, overflow: "hidden" }}>
    {/* top bar */}
    <Flex style={{ "flex-shrink": 0 }}>...</Flex>
    {/* scrollable body */}
    <div style={{ flex: 1, overflow: "auto" }}>...</div>
  </Flex>
</Flex>

// Responsive 2-column product layout
<Grid columns="1fr 480px" gap="2xl">
  <div>{/* media */}</div>
  <Stack gap="md">{/* info */}</Stack>
</Grid>

// Card with header + table
<Card padding="none">
  <Flex style={{ padding: "var(--sk-space-md) var(--sk-space-lg)" }}>
    <Text weight="semibold">Title</Text>
  </Flex>
  <Table ... />
</Card>

#SolidJS patterns used

// Controlled signal
const [value, setValue] = createSignal("");
<Input value={value()} onInput={setValue} />

// Conditional rendering
<Show when={isOpen()} fallback={<Placeholder />}>
  <Content />
</Show>

// List rendering
<For each={items()}>
  {(item) => <Card>{item.name}</Card>}
</For>

// Derived value from accessor
const active = () => CONVERSATIONS.find((c) => c.id === activeId());
<Show when={active()}>
  {(conv) => <Text>{conv().name}</Text>}
</Show>