Common Components

Reusable UI components used throughout DesQTA

Common Components Overview

DesQTA includes a comprehensive set of reusable UI components located in src/lib/components/ui/. These components follow consistent design patterns and are used throughout the application.

Component Categories

Form Components

Button

A versatile button component with multiple variants, sizes, and states.

<script lang="ts">
  import { Button } from '$lib/components/ui';
  import { TrashIcon, SaveIcon } from 'svelte-hero-icons';
</script>

<!-- Basic usage -->
<Button variant="primary" size="md" onclick={() => console.log('clicked')}>
  Click me
</Button>

<!-- With icon -->
<Button variant="danger" size="lg" icon={TrashIcon} iconPosition="left">
  Delete
</Button>

<!-- Loading state -->
<Button loading={true} disabled={false}>
  Loading...
</Button>

<!-- Full width -->
<Button fullWidth={true} variant="primary">
  Submit
</Button>

Props:

  • variant: 'primary' | 'secondary' | 'ghost' | 'danger' | 'success' | 'warning' | 'info' - Button style variant
  • size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' - Button size
  • disabled: boolean - Disable button interaction
  • loading: boolean - Show loading spinner
  • icon: any - Heroicon component to display
  • iconPosition: 'left' | 'right' - Icon placement
  • fullWidth: boolean - Make button full width
  • type: 'button' | 'submit' | 'reset' - Button type
  • onclick: (e: MouseEvent) => void - Click handler
  • ariaLabel: string - Accessibility label

Variants:

  • primary: Accent color background
  • secondary: Neutral gray background
  • ghost: Transparent background
  • danger: Red background for destructive actions
  • success: Green background
  • warning: Yellow background
  • info: Blue background

Input

A comprehensive input component with validation, icons, and error states.

<script lang="ts">
  import { Input } from '$lib/components/ui';
  import { EnvelopeIcon, LockIcon } from 'svelte-hero-icons';
  
  let email = $state('');
  let password = $state('');
  let emailError = $state<string | null>(null);
</script>

<!-- Basic input -->
<Input 
  bind:value={email}
  type="email"
  label="Email Address"
  placeholder="Enter your email"
  leftIcon={EnvelopeIcon}
  error={emailError}
  required={true}
/>

<!-- Password input -->
<Input 
  bind:value={password}
  type="password"
  label="Password"
  placeholder="Enter your password"
  leftIcon={LockIcon}
  required={true}
/>

Props:

  • value: string - Input value (use bind:value)
  • type: string - Input type (text, email, password, etc.)
  • label: string - Label text
  • placeholder: string - Placeholder text
  • leftIcon: any - Icon on the left
  • rightIcon: any - Icon on the right
  • error: string | null - Error message
  • required: boolean - Required field indicator
  • disabled: boolean - Disable input
  • readonly: boolean - Read-only mode

SearchInput

A specialized input for search functionality with debouncing.

<script lang="ts">
  import { SearchInput } from '$lib/components/ui';
  
  let searchQuery = $state('');
  
  function handleSearch(query: string) {
    console.log('Searching for:', query);
  }
  
  function clearSearch() {
    searchQuery = '';
  }
</script>

<SearchInput
  bind:value={searchQuery}
  placeholder="Search items..."
  debounceMs={300}
  clearable={true}
  onSearch={handleSearch}
  onClear={clearSearch}
/>

Props:

  • value: string - Search query (use bind:value)
  • placeholder: string - Placeholder text
  • debounceMs: number - Debounce delay in milliseconds
  • clearable: boolean - Show clear button
  • onSearch: (query: string) => void - Search callback
  • onClear: () => void - Clear callback

Display Components

Card

A flexible container component with header/footer slots.

<script lang="ts">
  import { Card, Button } from '$lib/components/ui';
</script>

<Card variant="elevated" padding="lg" interactive={true}>
  {#snippet header()}
    <h3 class="text-lg font-semibold">Card Title</h3>
  {/snippet}
  
  <p class="text-zinc-600 dark:text-zinc-400">
    Card content goes here
  </p>
  
  {#snippet footer()}
    <div class="flex gap-2">
      <Button variant="primary">Action</Button>
      <Button variant="ghost">Cancel</Button>
    </div>
  {/snippet}
</Card>

Props:

  • variant: 'default' | 'elevated' | 'outlined' | 'ghost' - Card style
  • padding: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' - Content padding
  • interactive: boolean - Make card clickable
  • onclick: () => void - Click handler (when interactive)
  • header: Snippet - Header slot
  • footer: Snippet - Footer slot
  • children: Snippet - Main content slot

Variants:

  • default: Standard card with border
  • elevated: Card with shadow
  • outlined: Transparent background with border
  • ghost: Subtle background

Badge

Display status, categories, or counts with various styles.

<script lang="ts">
  import { Badge } from '$lib/components/ui';
  import { CheckIcon } from 'svelte-hero-icons';
</script>

<!-- Basic badge -->
<Badge variant="success" size="md">
  Active
</Badge>

<!-- Badge with dot -->
<Badge variant="primary" dot={true}>
  New
</Badge>

<!-- Badge with icon -->
<Badge variant="info" icon={CheckIcon}>
  Verified
</Badge>

<!-- Removable badge -->
<Badge variant="default" removable={true} onremove={() => console.log('removed')}>
  Removable Tag
</Badge>

Props:

  • variant: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' - Badge color
  • size: 'xs' | 'sm' | 'md' | 'lg' - Badge size
  • icon: any - Icon to display
  • dot: boolean - Show colored dot instead of icon
  • removable: boolean - Show remove button
  • onremove: () => void - Remove callback

Progress

Show progress with different variants and animations.

<script lang="ts">
  import { Progress } from '$lib/components/ui';
  
  let progress = $state(75);
</script>

<Progress 
  value={progress} 
  max={100}
  variant="success"
  size="md"
  showLabel={true}
  label="Processing..."
  animated={true}
  striped={true}
/>

Props:

  • value: number - Current progress value
  • max: number - Maximum value (default: 100)
  • variant: 'default' | 'primary' | 'success' | 'warning' | 'danger' - Progress color
  • size: 'sm' | 'md' | 'lg' - Progress bar height
  • showLabel: boolean - Show percentage label
  • label: string - Custom label text
  • animated: boolean - Animate progress bar
  • striped: boolean - Show striped pattern

Data Components

DataTable

A feature-rich table component with sorting, selection, and custom cells.

<script lang="ts">
  import { DataTable } from '$lib/components/ui';
  import { Button } from '$lib/components/ui';
  
  let users = $state([
    { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
  ]);
  
  let isLoading = $state(false);
  let selectedRows = $state<number[]>([]);
  
  const columns = [
    { key: 'name', title: 'Name', sortable: true },
    { key: 'email', title: 'Email', sortable: true },
    { key: 'role', title: 'Role' },
    { 
      key: 'actions', 
      title: 'Actions',
      cell: (value: any, row: any) => {
        return `<button class="btn btn-sm">Edit</button>`;
      }
    }
  ];
  
  function handleSort(column: string) {
    console.log('Sort by:', column);
  }
  
  function handleSelect(row: any, selected: boolean) {
    if (selected) {
      selectedRows = [...selectedRows, row.id];
    } else {
      selectedRows = selectedRows.filter(id => id !== row.id);
    }
  }
</script>

<DataTable
  columns={columns}
  data={users}
  loading={isLoading}
  selectable={true}
  onSort={handleSort}
  onRowSelect={handleSelect}
/>

Props:

  • columns: Column[] - Column definitions
  • data: any[] - Table data
  • loading: boolean - Show loading state
  • error: string | null - Error message
  • selectable: boolean - Enable row selection
  • onSort: (column: string) => void - Sort callback
  • onRowSelect: (row: any, selected: boolean) => void - Selection callback

Column Definition:

interface Column {
  key: string;
  title: string;
  sortable?: boolean;
  cell?: (value: any, row: any) => string | HTMLElement;
  width?: string;
  align?: 'left' | 'center' | 'right';
}

AsyncWrapper

Handle loading, error, and empty states for data components.

<script lang="ts">
  import { AsyncWrapper } from '$lib/components/ui';
  
  let items = $state<any[]>([]);
  let isLoading = $state(true);
  let error = $state<string | null>(null);
  
  async function refetch() {
    isLoading = true;
    error = null;
    try {
      items = await fetchItems();
    } catch (e) {
      error = e instanceof Error ? e.message : 'Failed to load';
    } finally {
      isLoading = false;
    }
  }
</script>

<AsyncWrapper 
  loading={isLoading}
  error={error}
  data={items}
  empty={items.length === 0}
  emptyTitle="No items found"
  emptyDescription="Try adjusting your filters or create a new item"
  onretry={refetch}
>
  {#snippet children(data)}
    {#each data as item}
      <div class="p-4 border rounded-lg">{item.name}</div>
    {/each}
  {/snippet}
</AsyncWrapper>

Props:

  • loading: boolean - Show loading state
  • error: string | null - Error message
  • data: any - Data to display
  • empty: boolean - Show empty state
  • emptyTitle: string - Empty state title
  • emptyDescription: string - Empty state description
  • onretry: () => void - Retry callback
  • children: (data: any) => Snippet - Content render function

Tabs

Create tabbed interfaces with different variants.

<script lang="ts">
  import { Tabs } from '$lib/components/ui';
  import { HomeIcon, CogIcon, HelpIcon } from 'svelte-hero-icons';
  
  let activeTab = $state('overview');
  
  const tabs = [
    { id: 'overview', label: 'Overview', icon: HomeIcon },
    { id: 'settings', label: 'Settings', icon: CogIcon },
    { id: 'help', label: 'Help', icon: HelpIcon, disabled: true }
  ];
</script>

<Tabs
  tabs={tabs}
  bind:activeTab
  variant="pills"
  fullWidth={true}
>
  {#snippet children(activeTab)}
    {#if activeTab === 'overview'}
      <div>Overview content</div>
    {:else if activeTab === 'settings'}
      <div>Settings content</div>
    {/if}
  {/snippet}
</Tabs>

Props:

  • tabs: Tab[] - Tab definitions
  • activeTab: string - Active tab ID (use bind:activeTab)
  • variant: 'default' | 'pills' | 'underline' - Tab style
  • fullWidth: boolean - Make tabs fill width
  • children: (activeTab: string) => Snippet - Content render function

Tab Definition:

interface Tab {
  id: string;
  label: string;
  icon?: any;
  disabled?: boolean;
  badge?: string | number;
}

Create dropdown menus with customizable items.

<script lang="ts">
  import { Dropdown } from '$lib/components/ui';
  import { PencilIcon, TrashIcon, DuplicateIcon } from 'svelte-hero-icons';
  
  function edit() {
    console.log('Edit');
  }
  
  function deleteItem() {
    console.log('Delete');
  }
  
  function duplicate() {
    console.log('Duplicate');
  }
  
  const items = [
    { id: 'edit', label: 'Edit', icon: PencilIcon, onClick: edit },
    { id: 'duplicate', label: 'Duplicate', icon: DuplicateIcon, onClick: duplicate },
    { id: 'sep1', separator: true },
    { id: 'delete', label: 'Delete', icon: TrashIcon, danger: true, onClick: deleteItem }
  ];
</script>

<Dropdown
  items={items}
  buttonText="Actions"
  showChevron={true}
/>

Props:

  • items: DropdownItem[] - Menu items
  • buttonText: string - Button label
  • showChevron: boolean - Show dropdown chevron
  • variant: 'default' | 'ghost' - Button variant
  • placement: 'bottom' | 'top' | 'left' | 'right' - Dropdown position

DropdownItem Definition:

interface DropdownItem {
  id: string;
  label: string;
  icon?: any;
  danger?: boolean;
  disabled?: boolean;
  separator?: boolean;
  onClick?: () => void;
}

Feedback Components

Tooltip

Display helpful information on hover/focus.

<script lang="ts">
  import { Tooltip } from '$lib/components/ui';
  import { Button } from '$lib/components/ui';
</script>

<Tooltip content="This is a helpful tooltip" placement="top">
  <Button>Hover me</Button>
</Tooltip>

Props:

  • content: string - Tooltip text
  • placement: 'top' | 'bottom' | 'left' | 'right' - Tooltip position
  • delay: number - Show delay in milliseconds
  • children: Snippet - Element to attach tooltip to

Toast

Display temporary notifications (used with ToastContainer).

<script lang="ts">
  import { toast } from '$lib/stores/toast';
  
  function showSuccess() {
    toast.success('Operation completed successfully!');
  }
  
  function showError() {
    toast.error('Something went wrong');
  }
  
  function showInfo() {
    toast.info('Here is some information');
  }
</script>

<Button onclick={showSuccess}>Show Success</Button>
<Button onclick={showError}>Show Error</Button>
<Button onclick={showInfo}>Show Info</Button>

Toast Methods:

  • toast.success(message: string, duration?: number)
  • toast.error(message: string, duration?: number)
  • toast.info(message: string, duration?: number)
  • toast.warning(message: string, duration?: number)
  • toast.dismiss(id: string)

Component Composition

Using Multiple Components Together

<script lang="ts">
  import { Card, Button, Badge, Input, SearchInput } from '$lib/components/ui';
  
  let searchQuery = $state('');
  let items = $state([
    { id: 1, name: 'Item 1', status: 'active' },
    { id: 2, name: 'Item 2', status: 'pending' }
  ]);
</script>

<Card variant="elevated" padding="lg">
  {#snippet header()}
    <div class="flex items-center justify-between">
      <h2 class="text-xl font-semibold">Items</h2>
      <Button variant="primary" size="sm">Add New</Button>
    </div>
  {/snippet}
  
  <div class="space-y-4">
    <SearchInput bind:value={searchQuery} placeholder="Search items..." />
    
    <div class="space-y-2">
      {#each items as item}
        <div class="flex items-center justify-between p-3 border rounded-lg">
          <div class="flex items-center gap-2">
            <span>{item.name}</span>
            <Badge variant={item.status === 'active' ? 'success' : 'warning'}>
              {item.status}
            </Badge>
          </div>
          <Button variant="ghost" size="sm">Edit</Button>
        </div>
      {/each}
    </div>
  </div>
</Card>

Design System

Colors

Components use CSS custom properties for theming:

  • --accent-* for primary colors
  • --zinc-* for neutral colors
  • Semantic colors: red, green, yellow, blue for states

Sizing

Consistent size scale across components:

  • xs: Extra small
  • sm: Small
  • md: Medium (default)
  • lg: Large
  • xl: Extra large

Transitions

All interactive elements use:

  • transition-all duration-200 for smooth transitions
  • hover:scale-105 for hover effects
  • active:scale-95 for click feedback
  • Focus rings for accessibility: focus:ring-2 focus:ring-offset-2

Best Practices

  1. Composition over Configuration: Use slots and snippets for flexibility
  2. Consistent Props: Similar props across components (size, variant, disabled, etc.)
  3. Error Handling: Always provide error states and fallbacks
  4. Loading States: Use AsyncWrapper for data-dependent components
  5. Accessibility: All components include proper ARIA attributes
  6. Type Safety: Use TypeScript interfaces for all props
  7. Dark Mode: All components support dark mode automatically

Import Paths

// Individual imports
import { Button } from '$lib/components/ui/Button.svelte';
import { Card } from '$lib/components/ui/Card.svelte';

// Barrel import (recommended)
import { Button, Card, Input, Badge } from '$lib/components/ui';

Next Steps