Overview
DrawerContext is a comprehensive list/menu component that supports complex interactions including drag-and-drop reordering, collapsible groups, keyboard navigation, and both single and multiple selection modes. It’s ideal for dropdowns, context menus, file lists, and any interface requiring sophisticated list management.
Import
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption, DrawerGroup } from '@invopop/popui'
Basic Usage
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
const items: DrawerOption[] = [
{ label: 'Profile', value: 'profile' },
{ label: 'Settings', value: 'settings' },
{ label: 'Logout', value: 'logout', destructive: true }
]
function handleClick(value: any) {
console.log('Selected:', value)
}
</script>
<DrawerContext
bind:items
onclick={handleClick}
/>
With Icons
<script>
import { DrawerContext } from '@invopop/popui'
import { User, Cog, ArrowRightOnRectangle } from '@steeze-ui/heroicons'
import type { DrawerOption } from '@invopop/popui'
const items: DrawerOption[] = [
{ label: 'Profile', value: 'profile', icon: User },
{ label: 'Settings', value: 'settings', icon: Cog },
{ label: 'Logout', value: 'logout', icon: ArrowRightOnRectangle, destructive: true }
]
</script>
<DrawerContext bind:items onclick={handleClick} />
Multiple Selection Mode
Enable checkbox selection for multiple items:
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
let items: DrawerOption[] = [
{ label: 'Email notifications', value: 'email', selected: true },
{ label: 'SMS notifications', value: 'sms', selected: false },
{ label: 'Push notifications', value: 'push', selected: true }
]
function handleSelection(selected: DrawerOption[]) {
console.log('Selected items:', selected)
}
</script>
<DrawerContext
bind:items
multiple={true}
onselect={handleSelection}
/>
Drag-and-Drop Reordering
Enable draggable items for manual reordering:
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
let items: DrawerOption[] = [
{ label: 'Task 1', value: 'task-1' },
{ label: 'Task 2', value: 'task-2' },
{ label: 'Task 3', value: 'task-3' },
{ label: 'Task 4', value: 'task-4', locked: true } // Cannot be moved
]
function handleReorder(reorderedItems: DrawerOption[]) {
console.log('New order:', reorderedItems)
}
</script>
<DrawerContext
bind:items
draggable={true}
onreorder={handleReorder}
/>
Items with locked: true cannot be dragged and maintain their position.
Grouping and Collapsible Groups
Organize items into collapsible groups:
<script>
import { DrawerContext } from '@invopop/popui'
import { Folder, FolderOpen } from '@steeze-ui/heroicons'
import type { DrawerOption, DrawerGroup } from '@invopop/popui'
const groups: DrawerGroup[] = [
{
label: 'Work',
slug: 'work',
emptyIcon: FolderOpen,
emptyTitle: 'No work items',
emptyDescription: 'Add your first work item'
},
{
label: 'Personal',
slug: 'personal',
hideCounter: false
}
]
let items: DrawerOption[] = [
{ label: 'Project Alpha', value: 'alpha', groupBy: 'work' },
{ label: 'Project Beta', value: 'beta', groupBy: 'work' },
{ label: 'Shopping List', value: 'shopping', groupBy: 'personal' },
{ label: 'Vacation Planning', value: 'vacation', groupBy: 'personal' }
]
</script>
<DrawerContext
bind:items
{groups}
collapsibleGroups={true}
onclick={handleClick}
/>
Drag Between Groups
Move items between groups with drag-and-drop:
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption, DrawerGroup } from '@invopop/popui'
const groups: DrawerGroup[] = [
{ label: 'To Do', slug: 'todo' },
{ label: 'In Progress', slug: 'progress' },
{ label: 'Done', slug: 'done' }
]
let items: DrawerOption[] = [
{ label: 'Task A', value: 'a', groupBy: 'todo' },
{ label: 'Task B', value: 'b', groupBy: 'progress' },
{ label: 'Task C', value: 'c', groupBy: 'done' }
]
function handleGroupChange(distribution: Record<string, DrawerOption[]>) {
console.log('Group distribution:', distribution)
// distribution = { todo: [...], progress: [...], done: [...] }
}
</script>
<DrawerContext
bind:items
{groups}
draggable={true}
ondropitem={handleGroupChange}
/>
Keyboard Navigation
DrawerContext includes built-in keyboard navigation:
- Arrow Up/Down: Navigate through items
- Enter or Space: Select focused item
- Tab: Move focus to next element
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
let items: DrawerOption[] = [
{ label: 'Option 1', value: '1', focused: false },
{ label: 'Option 2', value: '2', focused: true }, // Initially focused
{ label: 'Option 3', value: '3', focused: false }
]
</script>
<DrawerContext bind:items onclick={handleClick} />
The keyboard helpers automatically filter out disabled, locked, and separator items.
Advanced Item Features
With Avatars and Pictures
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
const users: DrawerOption[] = [
{
label: 'John Doe',
value: 'john',
picture: 'https://example.com/john.jpg',
useAvatar: true
},
{
label: 'Jane Smith',
value: 'jane',
picture: 'https://example.com/jane.jpg',
useAvatar: true
}
]
</script>
<DrawerContext bind:items={users} onclick={handleClick} />
With Country Flags
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
const countries: DrawerOption[] = [
{ label: 'United States', value: 'us', country: 'US', flagPosition: 'after' },
{ label: 'United Kingdom', value: 'uk', country: 'GB', flagPosition: 'after' },
{ label: 'Germany', value: 'de', country: 'DE', flagPosition: 'after' }
]
</script>
<DrawerContext
bind:items={countries}
flagPosition="after"
onclick={handleClick}
/>
With Color Dots
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption, StatusType } from '@invopop/popui'
const items: DrawerOption[] = [
{ label: 'Active', value: 'active', color: 'green' as StatusType },
{ label: 'Pending', value: 'pending', color: 'yellow' as StatusType },
{ label: 'Inactive', value: 'inactive', color: 'grey' as StatusType }
]
</script>
<DrawerContext bind:items onclick={handleClick} />
With Custom Actions
<script>
import { DrawerContext, BaseButton } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
const items: DrawerOption[] = [
{
label: 'Item with action',
value: 'item-1',
action: (item: DrawerOption) => {
return `<button onclick="console.log('Action clicked')">Edit</button>`
}
}
]
</script>
<DrawerContext bind:items />
With Separators
<script>
import { DrawerContext } from '@invopop/popui'
import type { DrawerOption } from '@invopop/popui'
const items: DrawerOption[] = [
{ label: 'Cut', value: 'cut' },
{ label: 'Copy', value: 'copy' },
{ label: 'Paste', value: 'paste' },
{ separator: true, label: '', value: 'sep1' },
{ label: 'Delete', value: 'delete', destructive: true }
]
</script>
<DrawerContext bind:items onclick={handleClick} />
Props
items
DrawerOption[]
default:"[]"
Array of items to display in the drawer. Bindable for two-way data sync.
Enable multiple selection mode with checkboxes
Enable drag-and-drop reordering of items
Tailwind width class for the drawer container
Allow groups to be collapsed/expanded. When false, all groups remain open.
flagPosition
'before' | 'after'
default:"'after'"
Position of country flags relative to label text
Callback when an item is clicked (single selection mode)
onselect
(selected: DrawerOption[]) => void
Callback when selection changes (multiple selection mode)
onreorder
(items: DrawerOption[]) => void
Callback when items are reordered via drag-and-drop
ondropitem
(groups: Record<string, DrawerOption[]>) => void
Callback when items are moved between groups. Receives object with group slugs as keys.
Svelte snippet for custom header content at the top of the drawer
Array of group definitions for organizing items
TypeScript Interfaces
DrawerOption
export type DrawerOption = SelectOption & {
separator?: boolean
destructive?: boolean
selected?: boolean
focused?: boolean
icon?: IconSource | string | undefined
rightIcon?: IconSource | undefined
country?: string
color?: StatusType
picture?: string
sandbox?: boolean
iconClass?: string
disabled?: boolean
locked?: boolean
groupBy?: string
useAvatar?: boolean
action?: Snippet<[DrawerOption]>
flagPosition?: 'before' | 'after'
}
export type SelectOption = {
label: string
value: AnyProp
}
DrawerGroup
export type DrawerGroup = {
label: string
slug: string
emptyIcon?: IconSource
emptyTitle?: string
emptyDescription?: string
hideCounter?: boolean
}
DrawerContextProps
export interface DrawerContextProps {
items?: DrawerOption[]
multiple?: boolean
draggable?: boolean
widthClass?: string
collapsibleGroups?: boolean
flagPosition?: 'before' | 'after'
onclick?: (value: AnyProp) => void
onselect?: (selected: DrawerOption[]) => void
onreorder?: (items: DrawerOption[]) => void
ondropitem?: (groups: Record<string, DrawerOption[]>) => void
children?: Snippet
groups?: DrawerGroup[]
}
Keyboard Helpers
The drawer includes keyboard navigation utilities from drawer-keyboard-helpers.ts:
getFocusableItems
Filters items to only those that can receive focus (excludes separators, disabled, and locked items).
function getFocusableItems(items: DrawerOption[]): DrawerOption[]
getNextFocusedIndex
Calculates the next focused index based on arrow key direction.
function getNextFocusedIndex(
currentIndex: number,
direction: 'up' | 'down',
itemsCount: number
): number
selectFocusedItem
Handles selection of the currently focused item.
function selectFocusedItem(
items: DrawerOption[],
focusedIndex: number,
multiple: boolean
): { item: DrawerOption; shouldUpdate: boolean } | null
Styling
- Border and shadow for container
- Rounded corners (
rounded-2xl)
- Max height with scrolling (
max-h-[568px])
- Hover states for items
- Selected state highlighting
- Smooth transitions for drag operations
- Drop indicators during drag-and-drop
Accessibility
- Keyboard navigation support (Arrow keys, Enter, Space)
- Proper focus management
- ARIA attributes for screen readers
- Disabled and locked states properly communicated
- Scroll behavior for selected items
Examples
File Manager
<script>
import { DrawerContext } from '@invopop/popui'
import { Folder, Document, Photo } from '@steeze-ui/heroicons'
import type { DrawerOption, DrawerGroup } from '@invopop/popui'
const groups: DrawerGroup[] = [
{ label: 'Folders', slug: 'folders' },
{ label: 'Files', slug: 'files' }
]
let items: DrawerOption[] = [
{ label: 'Documents', value: 'docs', icon: Folder, groupBy: 'folders' },
{ label: 'Images', value: 'images', icon: Folder, groupBy: 'folders' },
{ label: 'report.pdf', value: 'report', icon: Document, groupBy: 'files' },
{ label: 'photo.jpg', value: 'photo', icon: Photo, groupBy: 'files' }
]
function handleFileClick(value: any) {
console.log('Opening:', value)
}
</script>
<DrawerContext
bind:items
{groups}
draggable={true}
onclick={handleFileClick}
widthClass="w-80"
/>
Task Board
<script>
import { DrawerContext } from '@invopop/popui'
import { CheckCircle, Clock, XCircle } from '@steeze-ui/heroicons'
import type { DrawerOption, DrawerGroup } from '@invopop/popui'
const groups: DrawerGroup[] = [
{ label: 'To Do', slug: 'todo', emptyTitle: 'No tasks', emptyDescription: 'Add a new task' },
{ label: 'In Progress', slug: 'progress' },
{ label: 'Completed', slug: 'done' }
]
let tasks: DrawerOption[] = [
{ label: 'Design homepage', value: 't1', groupBy: 'todo', color: 'blue' },
{ label: 'Build API', value: 't2', groupBy: 'progress', color: 'yellow' },
{ label: 'Write tests', value: 't3', groupBy: 'done', color: 'green' }
]
function handleTaskMove(distribution: Record<string, DrawerOption[]>) {
console.log('Tasks by status:', distribution)
// Update backend with new task statuses
}
</script>
<DrawerContext
bind:items={tasks}
{groups}
draggable={true}
collapsibleGroups={false}
ondropitem={handleTaskMove}
widthClass="w-96"
/>