Theme
← Examples
Advanced Interactions
A sandbox for rich interactions like virtual lists, drag-and-drop, transfers, tours, and editors.
apps/docs/src/lib/demos/AdvancedComponentsDemo.svelte
Preview
Open in new tabAdvanced Components
Clipboard
Hello from dryui! Marquee
dryui — 73 components, zero dependencies, native browser APIs, Svelte 5 runes •
QR Code
Code Block
svelte
1import { Button, Card } from '@dryui/ui';
2
3function App() {
4 let count = $state(0);
5
6 return (
7 <Card.Root>
8 <Card.Content>
9 <Button onclick={() => count++}>
10 Count: {count}
11 </Button>
12 </Card.Content>
13 </Card.Root>
14 );
15}Markdown Renderer
Hello World
This is a markdown renderer with italic and inline code.
- Item one
- Item two
- Item three
A blockquote with some wisdom.
js
const greeting = "Hello, dryui!";
console.log(greeting);VirtualList (10,000 items)
Item #1 This is virtual list item number 1
Item #2 This is virtual list item number 2
Item #3 This is virtual list item number 3
Item #4 This is virtual list item number 4
Item #5 This is virtual list item number 5
Infinite Scroll
Loaded item 1
Loaded item 2
Loaded item 3
Loaded item 4
Loaded item 5
Loaded item 6
Loaded item 7
Loaded item 8
Loaded item 9
Loaded item 10
Loaded item 11
Loaded item 12
Loaded item 13
Loaded item 14
Loaded item 15
Loaded item 16
Loaded item 17
Loaded item 18
Loaded item 19
Loaded item 20
DataGrid
| Alice | Engineer | Frontend | $120,000 |
| Bob | Designer | Design | $95,000 |
| Carol | Manager | Engineering | $150,000 |
| Dave | Engineer | Backend | $130,000 |
| Eve | Analyst | Data | $110,000 |
Drag and Drop
First item
Second item
Third item
Fourth item
Fifth item
Transfer
0/6
0/0
Rich Text Editor
Tour demonstrates step-by-step guidance
Source
This page is rendered from the real demo file shown below.
svelte
<script lang="ts">
import {
Clipboard,
ScrollToTop,
FloatButton,
Marquee,
QRCode,
CodeBlock,
MarkdownRenderer,
VirtualList,
InfiniteScroll,
DataGrid,
DragAndDrop,
Transfer,
RichTextEditor,
Tour,
VisuallyHidden,
Button,
} from '@dryui/ui';
import type { TourStep } from '@dryui/ui';
import type { TransferItem } from '@dryui/primitives';
// VirtualList data
const virtualItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
label: `Item #${i + 1}`,
description: `This is virtual list item number ${i + 1}`,
}));
// InfiniteScroll state
let infiniteItems = $state(Array.from({ length: 20 }, (_, i) => `Loaded item ${i + 1}`));
let hasMore = $state(true);
let loading = $state(false);
function loadMore() {
loading = true;
setTimeout(() => {
const start = infiniteItems.length;
const newItems = Array.from({ length: 10 }, (_, i) => `Loaded item ${start + i + 1}`);
infiniteItems = [...infiniteItems, ...newItems];
loading = false;
if (infiniteItems.length >= 60) hasMore = false;
}, 800);
}
// DataGrid data
type GridRow = {
name: string;
role: string;
department: string;
salary: number;
};
const gridData: GridRow[] = [
{ name: 'Alice', role: 'Engineer', department: 'Frontend', salary: 120000 },
{ name: 'Bob', role: 'Designer', department: 'Design', salary: 95000 },
{ name: 'Carol', role: 'Manager', department: 'Engineering', salary: 150000 },
{ name: 'Dave', role: 'Engineer', department: 'Backend', salary: 130000 },
{ name: 'Eve', role: 'Analyst', department: 'Data', salary: 110000 },
{ name: 'Frank', role: 'Designer', department: 'Design', salary: 98000 },
{ name: 'Grace', role: 'Engineer', department: 'Frontend', salary: 125000 },
{ name: 'Hank', role: 'DevOps', department: 'Infrastructure', salary: 135000 },
];
// DragAndDrop state
let dndItems = $state(['First item', 'Second item', 'Third item', 'Fourth item', 'Fifth item']);
// Transfer state
let sourceItems: TransferItem[] = $state([
{ key: '1', label: 'JavaScript' },
{ key: '2', label: 'TypeScript' },
{ key: '3', label: 'Python' },
{ key: '4', label: 'Rust' },
{ key: '5', label: 'Go' },
{ key: '6', label: 'Java' },
]);
let targetItems: TransferItem[] = $state([]);
// RichTextEditor state
let editorValue = $state('<p>Edit this <strong>rich text</strong> content...</p>');
// Tour state
let tourActive = $state(false);
const tourSteps: TourStep[] = [
{ target: '[data-tour="qr"]', title: 'QR Code', content: 'Generate QR codes from any text.', placement: 'bottom' },
{ target: '[data-tour="code"]', title: 'Code Block', content: 'Display code with syntax highlighting.', placement: 'bottom' },
{ target: '[data-tour="markdown"]', title: 'Markdown', content: 'Render markdown as styled HTML.', placement: 'top' },
];
// Markdown content
const markdownContent = `# Hello World
This is a **markdown** renderer with *italic* and \`inline code\`.
- Item one
- Item two
- Item three
> A blockquote with some wisdom.
\`\`\`js
const greeting = "Hello, dryui!";
console.log(greeting);
\`\`\`
[Visit dryui docs](https://example.com)`;
const sampleCode = `import { Button, Card } from '@dryui/ui';
function App() {
let count = $state(0);
return (
<Card.Root>
<Card.Content>
<Button onclick={() => count++}>
Count: {count}
</Button>
</Card.Content>
</Card.Root>
);
}`;
</script>
<h2>Advanced Components</h2>
<!-- Clipboard -->
<h3>Clipboard</h3>
<Clipboard value="Hello from dryui!">
{#snippet children({ copied, copy })}
<div class="row">
<code>Hello from dryui!</code>
<Button size="sm" variant="outline" onclick={copy}>
{copied ? 'Copied!' : 'Copy'}
</Button>
</div>
{/snippet}
</Clipboard>
<!-- Marquee -->
<h3>Marquee</h3>
<Marquee speed={60} pauseOnHover>
<span class="marquee-content">
dryui — 73 components, zero dependencies, native browser APIs, Svelte 5 runes •
</span>
</Marquee>
<!-- QR Code -->
<h3 data-tour="qr">QR Code</h3>
<QRCode value="https://github.com/dryui" size={160} />
<!-- Code Block -->
<h3 data-tour="code">Code Block</h3>
<CodeBlock
code={sampleCode}
language="svelte"
showLineNumbers
showCopyButton
/>
<!-- Markdown Renderer -->
<h3 data-tour="markdown">Markdown Renderer</h3>
<div class="markdown-container">
<MarkdownRenderer content={markdownContent} />
</div>
<!-- VirtualList -->
<h3>VirtualList (10,000 items)</h3>
<div class="virtual-container">
<VirtualList items={virtualItems} itemHeight={48}>
{#snippet children({ item, style })}
<div {style} class="virtual-item">
<strong>{item.label}</strong>
<span class="muted">{item.description}</span>
</div>
{/snippet}
</VirtualList>
</div>
<!-- InfiniteScroll -->
<h3>Infinite Scroll</h3>
<div class="infinite-container">
<InfiniteScroll onLoadMore={loadMore} {hasMore} {loading}>
{#snippet children()}
{#each infiniteItems as item}
<div class="infinite-item">{item}</div>
{/each}
{/snippet}
</InfiniteScroll>
</div>
<!-- DataGrid -->
<h3>DataGrid</h3>
<DataGrid.Root items={gridData} pageSize={5}>
<DataGrid.Table>
<DataGrid.Header>
<DataGrid.Column key="name" sortable>Name</DataGrid.Column>
<DataGrid.Column key="role" sortable filterable>Role</DataGrid.Column>
<DataGrid.Column key="department" sortable>Department</DataGrid.Column>
<DataGrid.Column key="salary" sortable>Salary</DataGrid.Column>
</DataGrid.Header>
<DataGrid.Body>
{#snippet children({ items })}
{@const rows = items as GridRow[]}
{#each rows as item}
<DataGrid.Row>
<DataGrid.Cell>{item.name}</DataGrid.Cell>
<DataGrid.Cell>{item.role}</DataGrid.Cell>
<DataGrid.Cell>{item.department}</DataGrid.Cell>
<DataGrid.Cell>${item.salary.toLocaleString()}</DataGrid.Cell>
</DataGrid.Row>
{/each}
{/snippet}
</DataGrid.Body>
</DataGrid.Table>
<DataGrid.Pagination />
</DataGrid.Root>
<!-- DragAndDrop -->
<h3>Drag and Drop</h3>
<DragAndDrop.Root items={dndItems} onReorder={(items) => dndItems = items}>
{#snippet children()}
{#each dndItems as item, i}
<DragAndDrop.Item index={i}>
{#snippet children({ isDragging, isOver })}
<div class="dnd-item" class:dragging={isDragging} class:over={isOver}>
<DragAndDrop.Handle index={i} />
<span>{item}</span>
</div>
{/snippet}
</DragAndDrop.Item>
{/each}
{/snippet}
</DragAndDrop.Root>
<!-- Transfer -->
<h3>Transfer</h3>
<Transfer.Root {sourceItems} bind:targetItems>
{#snippet children()}
<div class="transfer-layout">
<Transfer.List type="source" title="Available Languages" />
<Transfer.Actions />
<Transfer.List type="target" title="Selected Languages" />
</div>
{/snippet}
</Transfer.Root>
<!-- RichTextEditor -->
<h3>Rich Text Editor</h3>
<RichTextEditor.Root bind:value={editorValue}>
<RichTextEditor.Toolbar />
<RichTextEditor.Content />
</RichTextEditor.Root>
<!-- FloatButton -->
<FloatButton.Root>
<FloatButton.Trigger>+</FloatButton.Trigger>
<FloatButton.Action onclick={() => alert('Action 1')}>1</FloatButton.Action>
<FloatButton.Action onclick={() => alert('Action 2')}>2</FloatButton.Action>
</FloatButton.Root>
<!-- Tour -->
<div class="row">
<Button variant="outline" onclick={() => tourActive = true}>Start Tour</Button>
<VisuallyHidden>Tour demonstrates step-by-step guidance</VisuallyHidden>
</div>
<Tour.Root steps={tourSteps} bind:active={tourActive}>
{#snippet children()}
<Tour.Tooltip />
{/snippet}
</Tour.Root>
<style>
h2 { margin: 2rem 0 1rem; }
h3 { margin: 1.5rem 0 0.75rem; }
.row {
display: flex;
align-items: center;
gap: 0.75rem;
}
.marquee-content {
font-size: var(--dry-text-sm-size);
color: var(--dry-color-muted);
}
.markdown-container {
max-width: 600px;
padding: 1rem;
border: 1px solid var(--dry-color-border);
border-radius: var(--dry-radius-md);
}
.virtual-container {
height: 300px;
border: 1px solid var(--dry-color-border);
border-radius: var(--dry-radius-md);
position: relative;
isolation: isolate;
overflow: hidden;
}
.virtual-item {
display: flex;
flex-direction: column;
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--dry-color-border);
}
.muted {
font-size: var(--dry-text-xs-size);
color: var(--dry-color-muted);
}
.infinite-container {
height: 250px;
overflow-y: auto;
border: 1px solid var(--dry-color-border);
border-radius: var(--dry-radius-md);
}
.infinite-item {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--dry-color-border);
}
.dnd-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
margin-bottom: 0.25rem;
background: var(--dry-color-surface);
border: 1px solid var(--dry-color-border);
border-radius: var(--dry-radius-md);
}
.dnd-item.dragging {
opacity: 0.5;
}
.dnd-item.over {
border-color: var(--dry-color-primary);
}
.transfer-layout {
display: flex;
gap: 0.5rem;
align-items: flex-start;
}
</style>