Theme

Advanced Forms

Combobox

Apple
Banana
Cherry
Date
Elderberry
Fig
Grape
Honeydew
Kiwi
Lemon
Mango
Nectarine
Orange
Papaya
Quince

DatePicker

For typed date entry, use DateField instead of DatePicker.

PIN Input

Tags Input

Svelte TypeScript

Tags: Svelte, TypeScript

Rating

Standard:
3/5
Half stars:
3.5/5
Readonly:
Sizes:

File Upload

Drag & drop files here

Color Picker

Color: #3b82f6 (alpha: 1.00)

Source

This page is rendered from the real demo file shown below.

svelte
<script lang="ts">
  import {
    Combobox,
    DatePicker,
    FileUpload,
    ColorPicker,
    PinInput,
    TagsInput,
    Rating,
  } from '@dryui/ui';

  // Combobox state
  let comboboxValue = $state('');
  let comboboxOpen = $state(false);
  let comboboxInput = $state('');
  const fruits = [
    'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
    'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon',
    'Mango', 'Nectarine', 'Orange', 'Papaya', 'Quince',
  ];
  let filteredFruits = $derived(
    comboboxInput
      ? fruits.filter((f) => f.toLowerCase().includes(comboboxInput.toLowerCase()))
      : fruits,
  );

  // DatePicker state
  let dateValue: Date | null = $state(null);
  let dateOpen = $state(false);

  // FileUpload state
  let uploadedFiles: File[] = $state([]);

  // ColorPicker state
  let colorValue = $state('#3b82f6');
  let colorAlpha = $state(1);

  // PINInput state
  let pinValue = $state('');

  // TagsInput state
  let tags: string[] = $state(['Svelte', 'TypeScript']);

  // Rating state
  let ratingValue = $state(3);
  let halfRatingValue = $state(3.5);

  function formatFileSize(bytes: number): string {
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
    return `${(bytes / 1048576).toFixed(1)} MB`;
  }
</script>

<h2>Advanced Forms</h2>

<div class="demo-grid">
  <!-- Combobox -->
  <div class="demo-section">
    <h3>Combobox</h3>
    <Combobox.Root bind:value={comboboxValue} bind:open={comboboxOpen} name="framework">
      <Combobox.Input placeholder="Search frameworks..." oninput={(e: Event) => {
        comboboxInput = (e.target as HTMLInputElement).value;
      }} />
      <Combobox.Content>
        {#each filteredFruits as fruit, index (fruit)}
          <Combobox.Item value={fruit} {index}>{fruit}</Combobox.Item>
        {/each}
        {#if filteredFruits.length === 0}
          <Combobox.Empty>No frameworks found</Combobox.Empty>
        {/if}
      </Combobox.Content>
    </Combobox.Root>
    {#if comboboxValue}
      <p class="value-display">Selected: {comboboxValue}</p>
    {/if}
  </div>

  <!-- DatePicker -->
  <div class="demo-section">
    <h3>DatePicker</h3>
    <DatePicker.Root bind:value={dateValue} bind:open={dateOpen}>
      <DatePicker.Trigger placeholder="Select a date..." />
      <DatePicker.Content>
        <DatePicker.Calendar />
      </DatePicker.Content>
    </DatePicker.Root>
    {#if dateValue}
      <p class="value-display">Selected: {dateValue.toLocaleDateString()}</p>
    {/if}
    <p class="value-display">For typed date entry, use DateField instead of DatePicker.</p>
  </div>

  <!-- PINInput -->
  <div class="demo-section">
    <h3>PIN Input</h3>
    <div class="stack-sm">
      <PinInput.Root
        bind:value={pinValue}
        length={6}
        oncomplete={(v) => console.log('PIN complete:', v)}
      />
      <PinInput.Root bind:value={pinValue} length={4} mask size="lg" />
    </div>
    {#if pinValue}
      <p class="value-display">Value: {pinValue}</p>
    {/if}
  </div>

  <!-- TagsInput -->
  <div class="demo-section">
    <h3>Tags Input</h3>
    <TagsInput.Root bind:value={tags} maxTags={8}>
      <TagsInput.List>
        {#each tags as tag, i (tag)}
          <TagsInput.Tag index={i} value={tag}>
            {tag}
            <TagsInput.TagDelete index={i} />
          </TagsInput.Tag>
        {/each}
      </TagsInput.List>
      <TagsInput.Input placeholder="Add tag..." />
    </TagsInput.Root>
    <p class="value-display">Tags: {tags.join(', ')}</p>
  </div>

  <!-- Rating -->
  <div class="demo-section">
    <h3>Rating</h3>
    <div class="stack-sm">
      <div class="rating-row">
        <span class="rating-label">Standard:</span>
        <Rating bind:value={ratingValue} />
        <span class="value-display">{ratingValue}/5</span>
      </div>
      <div class="rating-row">
        <span class="rating-label">Half stars:</span>
        <Rating bind:value={halfRatingValue} allowHalf />
        <span class="value-display">{halfRatingValue}/5</span>
      </div>
      <div class="rating-row">
        <span class="rating-label">Readonly:</span>
        <Rating value={4} readonly />
      </div>
      <div class="rating-row">
        <span class="rating-label">Sizes:</span>
        <Rating value={3} size="sm" readonly />
        <Rating value={3} size="md" readonly />
        <Rating value={3} size="lg" readonly />
      </div>
    </div>
  </div>

  <!-- FileUpload -->
  <div class="demo-section">
    <h3>File Upload</h3>
    <FileUpload.Root bind:files={uploadedFiles} multiple accept="image/*,.pdf" maxSize={5242880}>
      <FileUpload.Dropzone>
        <div class="upload-content">
          <p>Drag & drop files here</p>
          <FileUpload.Trigger>Browse files</FileUpload.Trigger>
        </div>
      </FileUpload.Dropzone>
      {#if uploadedFiles.length > 0}
        <FileUpload.List>
          {#snippet children({ file, index }: { file: File; index: number })}
            <FileUpload.Item {file} {index}>
              <span>{file.name} ({formatFileSize(file.size)})</span>
              <FileUpload.ItemDelete {index} />
            </FileUpload.Item>
          {/snippet}
        </FileUpload.List>
      {/if}
    </FileUpload.Root>
  </div>

  <!-- ColorPicker -->
  <div class="demo-section">
    <h3>Color Picker</h3>
    <ColorPicker.Root bind:value={colorValue} bind:alpha={colorAlpha}>
      <ColorPicker.Area />
      <ColorPicker.HueSlider />
      <ColorPicker.AlphaSlider />
      <div class="color-row">
        <ColorPicker.Swatch />
        <ColorPicker.Input format="hex" />
        <ColorPicker.EyeDropper />
      </div>
      <div class="color-presets">
        <ColorPicker.Swatch color="#ef4444" />
        <ColorPicker.Swatch color="#f59e0b" />
        <ColorPicker.Swatch color="#22c55e" />
        <ColorPicker.Swatch color="#3b82f6" />
        <ColorPicker.Swatch color="#8b5cf6" />
        <ColorPicker.Swatch color="#ec4899" />
      </div>
    </ColorPicker.Root>
    <p class="value-display">Color: {colorValue} (alpha: {colorAlpha.toFixed(2)})</p>
  </div>
</div>

<style>
  .demo-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  .demo-section {
    padding: 1.5rem;
    border: 1px solid var(--dry-color-border);
    border-radius: var(--dry-radius-lg);
    background: var(--dry-color-surface);
  }

  .demo-section h3 {
    margin: 0 0 1rem;
  }

  .stack-sm {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }

  .value-display {
    margin-top: 0.5rem;
    font-size: var(--dry-text-sm-size);
    color: var(--dry-color-muted);
  }

  .upload-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
  }

  .upload-content p {
    margin: 0;
    color: var(--dry-color-muted);
  }

  .color-row {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-top: 0.5rem;
  }

  .color-presets {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.5rem;
  }

  .rating-label {
    font-size: var(--dry-text-sm-size);
    color: var(--dry-color-muted);
    white-space: nowrap;
  }

  .rating-row {
    display: flex;
    align-items: center;
    gap: 0.25rem;
  }
</style>