Variants — outlined / filled / ghost
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'fr', label: 'France', icon: FlagIcon(), subLabel: 'Europe' },
];
const App = () => {
const outlined = atom([]);
const filled = atom([]);
return () => html`
<x-col gap="4" style="max-width:32rem;padding:1rem;">
${Select({ items: COUNTRIES, label: 'Outlined (default)', placeholder: 'Choose…', selected: outlined(), onChange: ids => outlined.set(ids) })}
${Select({ items: COUNTRIES, label: 'Filled', variant: 'filled', placeholder: 'Choose…', selected: filled(), onChange: ids => filled.set(ids) })}
${Select({ items: [], label: 'Empty list', placeholder: 'No options' })}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
Sizes — sm / md / lg
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
];
const App = () => {
const sel = atom([]);
return () => html`
<x-col gap="4" style="max-width:32rem;padding:1rem;">
<x-row gap="3" align="end">
${Select({ items: COUNTRIES, label: 'Small', size: 'sm', placeholder: 'Select…', selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: COUNTRIES, label: 'Medium', size: 'md', placeholder: 'Select…', selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: COUNTRIES, label: 'Large', size: 'lg', placeholder: 'Select…', selected: sel(), onChange: ids => sel.set(ids) })}
</x-row>
<x-row gap="3" align="end">
${Select({ items: COUNTRIES, variant: 'filled', size: 'sm', placeholder: 'Small filled', selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: COUNTRIES, variant: 'filled', size: 'md', placeholder: 'Medium filled', selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: COUNTRIES, variant: 'filled', size: 'lg', placeholder: 'Large filled', selected: sel(), onChange: ids => sel.set(ids) })}
</x-row>
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
States — helper text / error / disabled
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { AdminPanelSettingsIcon, EditIcon, VisibilityIcon, CommentIcon } from 'mates-icons';
const ROLES = [
{ id: 'admin', label: 'Admin', icon: AdminPanelSettingsIcon(), subLabel: 'Full access' },
{ id: 'editor', label: 'Editor', icon: EditIcon(), subLabel: 'Can edit content' },
{ id: 'viewer', label: 'Viewer', icon: VisibilityIcon(), subLabel: 'Read only' },
{ id: 'commenter', label: 'Commenter', icon: CommentIcon(), subLabel: 'Can comment' },
];
const App = () => {
const helper = atom([]);
const error = atom([]);
return () => html`
<x-row gap="4" style="max-width:40rem;padding:1rem;" align="start">
${Select({ items: ROLES, label: 'With helper', placeholder: 'Select role', helper: 'Controls what the user can do', selected: helper(), onChange: ids => helper.set(ids) })}
${Select({ items: ROLES, label: 'With error', placeholder: 'Select role', error: 'This field is required', selected: error(), onChange: ids => error.set(ids) })}
${Select({ items: ROLES, label: 'Disabled', placeholder: 'Not available', disabled: true })}
</x-row>
`;
};
renderApp(App, document.getElementById('app'));
Loading skeleton
import { html, renderApp } from 'mates';
import { Select } from 'mates-ui';
const App = () => () => html`
<div style="max-width:16rem;padding:1rem;">
${Select({ items: [], label: 'Async data', placeholder: 'Loading…', loading: true, skeletonCount: 4 })}
</div>
`;
renderApp(App, document.getElementById('app'));
With icons and subLabel
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'fr', label: 'France', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'jp', label: 'Japan', icon: FlagIcon(), subLabel: 'Asia' },
{ id: 'au', label: 'Australia', icon: FlagIcon(), subLabel: 'Oceania' },
];
const App = () => {
const sel = atom([]);
return () => html`
<div style="max-width:18rem;padding:1rem;">
${Select({ items: COUNTRIES, label: 'Country', placeholder: 'Choose a country', helper: 'Items include an icon and a subLabel', selected: sel(), onChange: ids => sel.set(ids) })}
${sel().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">Selected: <strong>${sel()[0]}</strong></p>` : ''}
</div>
`;
};
renderApp(App, document.getElementById('app'));
autoComplete — searchable select
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'fr', label: 'France', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'jp', label: 'Japan', icon: FlagIcon(), subLabel: 'Asia' },
];
const TECH_TAGS = [
{ id: 'ts', label: 'TypeScript' }, { id: 'js', label: 'JavaScript' },
{ id: 'react', label: 'React' }, { id: 'vue', label: 'Vue' },
{ id: 'svelte', label: 'Svelte' }, { id: 'css', label: 'CSS' },
{ id: 'html', label: 'HTML' }, { id: 'node',label: 'Node.js' },
];
const App = () => {
const acSingle = atom([]);
const acMulti = atom([]);
return () => html`
<x-row gap="4" style="max-width:38rem;padding:1rem;" align="start">
<div>
${Select({ items: COUNTRIES, selected: acSingle(), label: 'Country (single)', placeholder: 'Search countries…', autoComplete: true, searchPlaceholder: 'Type to filter…', onChange: ids => acSingle.set(ids) })}
${acSingle().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">Selected: <strong>${acSingle()[0]}</strong></p>` : ''}
</div>
<div>
${Select({ items: TECH_TAGS, selected: acMulti(), label: 'Tech stack (multi)', placeholder: 'Search tags…', autoComplete: true, multiple: true, searchPlaceholder: 'Filter tags…', onChange: ids => acMulti.set(ids) })}
${acMulti().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">${acMulti().length} selected</p>` : ''}
</div>
</x-row>
`;
};
renderApp(App, document.getElementById('app'));
Controlled — single and filled variant
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon, AdminPanelSettingsIcon, EditIcon, VisibilityIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
];
const ROLES = [
{ id: 'admin', label: 'Admin', icon: AdminPanelSettingsIcon(), subLabel: 'Full access' },
{ id: 'editor', label: 'Editor', icon: EditIcon(), subLabel: 'Can edit' },
{ id: 'viewer', label: 'Viewer', icon: VisibilityIcon(), subLabel: 'Read only' },
];
const App = () => {
const country = atom([]);
const role = atom([]);
return () => html`
<x-row gap="4" style="max-width:36rem;padding:1rem;" align="start">
<div>
${Select({ items: COUNTRIES, selected: country(), label: 'Country', placeholder: 'Choose a country', onChange: ids => country.set(ids) })}
${country().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">Selected: <strong>${country()[0]}</strong></p>` : ''}
</div>
<div>
${Select({ items: ROLES, selected: role(), label: 'Role', variant: 'filled', placeholder: 'Assign a role', onChange: ids => role.set(ids) })}
${role().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">Selected: <strong>${role()[0]}</strong></p>` : ''}
</div>
</x-row>
`;
};
renderApp(App, document.getElementById('app'));
Multi-select
import { html, atom, renderApp } from 'mates';
import { Select, Badge } from 'mates-ui';
import { VisibilityIcon, EditIcon, DeleteIcon, DownloadIcon, ManageAccountsIcon } from 'mates-icons';
const TECH_TAGS = [
{ id: 'ts', label: 'TypeScript' }, { id: 'js', label: 'JavaScript' },
{ id: 'react', label: 'React' }, { id: 'vue', label: 'Vue' },
{ id: 'css', label: 'CSS' }, { id: 'node',label: 'Node.js' },
];
const PERMISSIONS = [
{ id: 'read', label: 'Read', icon: VisibilityIcon(), subLabel: 'View all content' },
{ id: 'write', label: 'Write', icon: EditIcon(), subLabel: 'Create & modify' },
{ id: 'delete', label: 'Delete', icon: DeleteIcon(), subLabel: 'Remove records' },
{ id: 'export', label: 'Export', icon: DownloadIcon(), subLabel: 'Download data' },
{ id: 'manage', label: 'Manage', icon: ManageAccountsIcon(), subLabel: 'Administer users' },
];
const App = () => {
const tags = atom([]);
const permissions = atom(['read', 'write']);
return () => html`
<x-col gap="4" style="max-width:22rem;padding:1rem;">
<div>
${Select({ items: TECH_TAGS, selected: tags(), label: 'Tech stack', multiple: true, placeholder: 'Pick technologies…', helper: 'Select all that apply', onChange: ids => tags.set(ids) })}
${tags().length ? html`<div style="margin-top:.5rem;display:flex;flex-wrap:wrap;gap:.25rem;">${tags().map(id => Badge(TECH_TAGS.find(t => t.id === id)?.label ?? id))}</div>` : ''}
</div>
${Select({ items: PERMISSIONS, selected: permissions(), label: 'Permissions', multiple: true, placeholder: 'No permissions selected', helper: 'Grant access levels', onChange: ids => permissions.set(ids) })}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
PopupList — check positions
import { html, atom, renderApp } from 'mates';
import { PopupList, CascadingPopupList } from 'mates-ui';
import { VisibilityIcon, EditIcon, DeleteIcon, DownloadIcon, ManageAccountsIcon, AdminPanelSettingsIcon, CommentIcon, NoteAddIcon, FolderOpenIcon, SaveIcon, CloseIcon } from 'mates-icons';
const PERMISSIONS = [
{ id: 'read', label: 'Read', icon: VisibilityIcon(), subLabel: 'View content' },
{ id: 'write', label: 'Write', icon: EditIcon(), subLabel: 'Create & modify' },
{ id: 'delete', label: 'Delete', icon: DeleteIcon(), subLabel: 'Remove records' },
{ id: 'manage', label: 'Manage', icon: ManageAccountsIcon(), subLabel: 'Administer users' },
];
const ROLES = [
{ id: 'admin', label: 'Admin', icon: AdminPanelSettingsIcon(), subLabel: 'Full access' },
{ id: 'editor', label: 'Editor', icon: EditIcon(), subLabel: 'Can edit' },
{ id: 'viewer', label: 'Viewer', icon: VisibilityIcon(), subLabel: 'Read only' },
{ id: 'commenter', label: 'Commenter', icon: CommentIcon(), subLabel: 'Can comment' },
];
const FILE_ACTIONS = [
{ id: 'new', label: 'New File', icon: NoteAddIcon() },
{ id: 'open', label: 'Open Recent', icon: FolderOpenIcon(), children: [
{ id: 'r1', label: 'project-alpha' }, { id: 'r2', label: 'project-beta' },
]},
{ id: 'save', label: 'Save', icon: SaveIcon() },
{ type: 'divider', label: '' },
{ id: 'close', label: 'Close', icon: CloseIcon() },
];
const App = () => {
const permissions = atom(['read']);
const role = atom([]);
const last = atom('—');
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);';
return () => html`
<x-col gap="3" style="padding:1rem;">
<x-row gap="4" align="start">
<div>
<p style="font-size:.75rem;color:var(--md-color-text-muted);margin-bottom:.5rem;">check · start</p>
CascadingPopupList — nested sub-panels
import { html, atom, renderApp } from 'mates';
import { CascadingPopupList } from 'mates-ui';
import { NoteAddIcon, FolderOpenIcon, SaveIcon, DownloadIcon, ImageIcon, CloseIcon } from 'mates-icons';
const FILE_ACTIONS = [
{ id: 'new', label: 'New File', icon: NoteAddIcon() },
{ id: 'open-recent', label: 'Open Recent', icon: FolderOpenIcon(), children: [
{ id: 'r1', label: 'project-alpha' },
{ id: 'r2', label: 'project-beta' },
{ id: 'r3', label: 'project-gamma' },
]},
{ id: 'save', label: 'Save', icon: SaveIcon() },
{ id: 'export', label: 'Export As…', icon: DownloadIcon(), children: [
{ id: 'ex-pdf', label: 'PDF Document' },
{ id: 'ex-png', label: 'PNG Image', icon: ImageIcon() },
{ id: 'ex-svg', label: 'SVG Vector' },
]},
{ type: 'divider', label: '' },
{ id: 'close', label: 'Close', icon: CloseIcon() },
];
const App = () => {
const last = atom('—');
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);display:inline-block;min-width:12rem;';
return () => html`
<div style="padding:1rem;">
<div style=${box}>${CascadingPopupList({ items: FILE_ACTIONS, autoFocus: false, onSelect: (_ids, item) => last.set(item?.label ?? '—') })}</div>
<p style="margin-top:.75rem;font-size:.875rem;color:var(--md-color-text-muted);">Last selected: <strong>${last()}</strong></p>
</div>
`;
};
renderApp(App, document.getElementById('app'));
Select trigger + CascadingPopupList panel
import { html, atom, popup, eleHook, renderApp } from 'mates';
import { CascadingPopupList } from 'mates-ui';
import { NoteAddIcon, FolderOpenIcon, SaveIcon, DownloadIcon, ImageIcon, CloseIcon, KeyboardArrowDownIcon } from 'mates-icons';
const FILE_ACTIONS = [
{ id: 'new', label: 'New File', icon: NoteAddIcon() },
{ id: 'open', label: 'Open Recent', icon: FolderOpenIcon(), children: [
{ id: 'r1', label: 'project-alpha' }, { id: 'r2', label: 'project-beta' },
]},
{ id: 'save', label: 'Save', icon: SaveIcon() },
{ id: 'export', label: 'Export As…', icon: DownloadIcon(), children: [
{ id: 'ex-pdf', label: 'PDF Document' }, { id: 'ex-png', label: 'PNG Image', icon: ImageIcon() },
]},
{ type: 'divider', label: '' },
{ id: 'close', label: 'Close', icon: CloseIcon() },
];
const App = () => {
const selected = atom('');
const triggerRef = { el: null };
const captureRef = eleHook($ => {
triggerRef.el = $.el;
return { onCleanup() { triggerRef.el = null; } };
});
const panel = CascadingPopupList({
items: FILE_ACTIONS, autoFocus: true,
onSelect: (_ids, item) => {
selected.set(item?.label ?? '');
requestAnimationFrame(() => document.dispatchEvent(new MouseEvent('click', { bubbles: true })));
},
onEscape: () => requestAnimationFrame(() => document.dispatchEvent(new MouseEvent('click', { bubbles: true }))),
});
const pop = popup(panel, {
position: 'bottom-start', gap: 4,
onOpen(_p) { triggerRef.el?.setAttribute('aria-expanded', 'true'); },
onClose(a) { a.setAttribute('aria-expanded', 'false'); a.focus(); },
Multi-select CascadingPopupList panel
import { html, atom, popup, eleHook, renderApp } from 'mates';
import { CascadingPopupList } from 'mates-ui';
import { CodeIcon, VisibilityIcon, EditIcon, DeleteIcon, ManageAccountsIcon, PersonIcon, SettingsIcon, AdminPanelSettingsIcon, KeyboardArrowDownIcon } from 'mates-icons';
const PERMISSION_TREE = [
{ id: 'content', label: 'Content', icon: CodeIcon(), children: [
{ id: 'content-read', label: 'Read', icon: VisibilityIcon() },
{ id: 'content-write', label: 'Write', icon: EditIcon() },
{ id: 'content-delete', label: 'Delete', icon: DeleteIcon() },
]},
{ id: 'users', label: 'Users', icon: ManageAccountsIcon(), children: [
{ id: 'users-view', label: 'View', icon: PersonIcon() },
{ id: 'users-invite', label: 'Invite', icon: PersonIcon() },
]},
{ id: 'settings', label: 'Settings', icon: SettingsIcon(), children: [
{ id: 'settings-read', label: 'Read', icon: VisibilityIcon() },
{ id: 'settings-write', label: 'Write', icon: EditIcon() },
]},
{ type: 'divider', label: '' },
{ id: 'admin', label: 'Admin (all)', icon: AdminPanelSettingsIcon() },
];
const App = () => {
const selected = atom([]);
const triggerRef = { el: null };
const captureRef = eleHook($ => {
triggerRef.el = $.el;
return { onCleanup() { triggerRef.el = null; } };
});
const panel = CascadingPopupList({
items: PERMISSION_TREE, checkType: 'check', selected: selected(), autoFocus: true,
onSelect: ids => selected.set(ids),
onEscape: () => requestAnimationFrame(() => document.dispatchEvent(new MouseEvent('click', { bubbles: true }))),
});
const pop = popup(panel, {
position: 'bottom-start', gap: 4,
PopupList — checkType radio / check / none
import { html, atom, renderApp } from 'mates';
import { PopupList } from 'mates-ui';
import { AdminPanelSettingsIcon, EditIcon, VisibilityIcon, CommentIcon } from 'mates-icons';
const ROLES = [
{ id: 'admin', label: 'Admin', icon: AdminPanelSettingsIcon(), subLabel: 'Full access' },
{ id: 'editor', label: 'Editor', icon: EditIcon(), subLabel: 'Can edit' },
{ id: 'viewer', label: 'Viewer', icon: VisibilityIcon(), subLabel: 'Read only' },
{ id: 'commenter', label: 'Commenter', icon: CommentIcon(), subLabel: 'Can comment' },
];
const TAGS = [
{ id: 'ts', label: 'TypeScript' }, { id: 'js', label: 'JavaScript' },
{ id: 'react', label: 'React' }, { id: 'vue', label: 'Vue' },
{ id: 'css', label: 'CSS' },
];
const App = () => {
const noneS = atom(['editor']);
const radioS = atom(['ts']);
const checkS = atom(['ts', 'react', 'css']);
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);min-width:11rem;';
return () => html`
<x-row gap="4" style="padding:1rem;" align="start">
<div>
<p style="font-size:.75rem;color:var(--md-color-text-muted);margin-bottom:.5rem;">checkType: none</p>
<div style=${box}>${PopupList({ items: ROLES, selected: noneS(), checkType: 'none', autoFocus: false, onSelect: ids => noneS.set(ids) })}</div>
</div>
<div>
<p style="font-size:.75rem;color:var(--md-color-text-muted);margin-bottom:.5rem;">checkType: radio</p>
<div style=${box}>${PopupList({ items: TAGS, selected: radioS(), checkType: 'radio', autoFocus: false, onSelect: ids => radioS.set(ids) })}</div>
</div>
<div>
<p style="font-size:.75rem;color:var(--md-color-text-muted);margin-bottom:.5rem;">checkType: check</p>
<div style=${box}>${PopupList({ items: TAGS, selected: checkS(), checkType: 'check', autoFocus: false, onSelect: ids => checkS.set(ids) })}</div>
</div>
</x-row>
PopupListItem — all states
import { html, renderApp } from 'mates';
import { PopupListItem } from 'mates-ui';
import { PersonIcon, SettingsIcon, NotificationsIcon, ChevronRightIcon, BlockIcon, LogoutIcon } from 'mates-icons';
const App = () => () => html`
<div style="padding:1rem;">
<div style="border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);max-width:18rem;">
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
</div>
</div>
`;
renderApp(App, document.getElementById('app'));
Grouped items
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { CodeIcon, DnsIcon } from 'mates-icons';
const GROUPED_ITEMS = [
{ id: '_h1', label: 'Frontend', type: 'header' },
{ id: 'react', label: 'React', icon: CodeIcon() },
{ id: 'vue', label: 'Vue', icon: CodeIcon() },
{ id: 'svelte', label: 'Svelte', icon: CodeIcon() },
{ id: '_h2', label: 'Backend', type: 'header' },
{ id: 'node', label: 'Node.js', icon: DnsIcon() },
{ id: 'python', label: 'Python', icon: DnsIcon() },
{ id: 'go', label: 'Go', icon: DnsIcon() },
];
const App = () => {
const sel = atom([]);
return () => html`
<div style="max-width:18rem;padding:1rem;">
${Select({ items: GROUPED_ITEMS, label: 'Framework', placeholder: 'Choose a framework', helper: 'Grouped by category', selected: sel(), onChange: ids => sel.set(ids) })}
${sel().length ? html`<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">Selected: <strong>${sel()[0]}</strong></p>` : ''}
</div>
`;
};
renderApp(App, document.getElementById('app'));
Virtualised — plain (2 000 items with state)
import { html, atom, renderApp } from 'mates';
import { PopupListVirtualised } from 'mates-ui';
const LARGE_LIST = Array.from({ length: 2000 }, (_, i) => ({
id: 'item-' + i,
label: 'Item ' + (i + 1),
subLabel: 'Row index ' + i,
icon: i % 3 === 0 ? 'star' : i % 3 === 1 ? 'circle' : 'favorite',
}));
const App = () => {
const sel = atom([]);
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);width:16rem;';
return () => html`
<div style="padding:1rem;">
<div style=${box}>${PopupListVirtualised({ items: LARGE_LIST, selected: sel(), checkType: 'none', autoFocus: false, onSelect: ids => sel.set(ids) })}</div>
<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">${sel().length ? 'Selected: ' + sel()[0] : 'None selected'}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));
Virtualised — multi-select checkbox (2 000 items)
import { html, atom, renderApp } from 'mates';
import { PopupListVirtualised } from 'mates-ui';
const LARGE_LIST = Array.from({ length: 2000 }, (_, i) => ({
id: 'item-' + i,
label: 'Item ' + (i + 1),
subLabel: 'Row index ' + i,
icon: i % 3 === 0 ? 'star' : i % 3 === 1 ? 'circle' : 'favorite',
}));
const App = () => {
const sel = atom([]);
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);width:16rem;';
return () => html`
<div style="padding:1rem;">
<div style=${box}>${PopupListVirtualised({ items: LARGE_LIST, selected: sel(), checkType: 'check', checkPosition: 'start', autoFocus: false, onSelect: ids => sel.set(ids) })}</div>
<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">${sel().length} of 2000 selected</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));
Virtualised — grouped (4 groups × 500 items)
import { html, atom, renderApp } from 'mates';
import { PopupListVirtualised } from 'mates-ui';
const LARGE_LIST = Array.from({ length: 2000 }, (_, i) => ({
id: 'item-' + i,
label: 'Item ' + (i + 1),
subLabel: 'Row index ' + i,
icon: i % 3 === 0 ? 'star' : i % 3 === 1 ? 'circle' : 'favorite',
}));
const App = () => {
const sel = atom([]);
const box = 'border:1px solid var(--md-color-border);border-radius:var(--md-radius-lg);overflow:hidden;background:var(--md-color-surface-raised);box-shadow:var(--md-shadow-1);width:16rem;';
return () => html`
<div style="padding:1rem;">
<div style=${box}>${PopupListVirtualised({ groups: [
{ label: 'Frontend', items: LARGE_LIST.slice(0, 500) },
{ label: 'Backend', items: LARGE_LIST.slice(500, 1000) },
{ label: 'DevOps', items: LARGE_LIST.slice(1000, 1500) },
{ label: 'Data', items: LARGE_LIST.slice(1500, 2000) },
], selected: sel(), checkType: 'check', autoFocus: false, onSelect: ids => sel.set(ids) })}</div>
<p style="margin-top:.5rem;font-size:.75rem;color:var(--md-color-text-muted);">${sel().length} of 2000 selected</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));
Open on hover
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon, AdminPanelSettingsIcon, EditIcon, VisibilityIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'fr', label: 'France', icon: FlagIcon(), subLabel: 'Europe' },
];
const App = () => {
const sel = atom([]);
return () => html`
<x-row gap="4" style="max-width:28rem;padding:1rem;" align="start">
${Select({ items: COUNTRIES, label: 'Hover to open', placeholder: 'Hover over me…', openOnHover: true, selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: COUNTRIES, label: 'Filled — hover to open', variant: 'filled', placeholder: 'Hover over me…', openOnHover: true, selected: sel(), onChange: ids => sel.set(ids) })}
</x-row>
`;
};
renderApp(App, document.getElementById('app'));
Hide chevron
import { html, atom, renderApp } from 'mates';
import { Select } from 'mates-ui';
import { FlagIcon, AdminPanelSettingsIcon, EditIcon, VisibilityIcon } from 'mates-icons';
const COUNTRIES = [
{ id: 'us', label: 'United States', icon: FlagIcon(), subLabel: 'North America' },
{ id: 'uk', label: 'United Kingdom', icon: FlagIcon(), subLabel: 'Europe' },
{ id: 'de', label: 'Germany', icon: FlagIcon(), subLabel: 'Europe' },
];
const ROLES = [
{ id: 'admin', label: 'Admin', icon: AdminPanelSettingsIcon(), subLabel: 'Full access' },
{ id: 'editor', label: 'Editor', icon: EditIcon(), subLabel: 'Can edit' },
{ id: 'viewer', label: 'Viewer', icon: VisibilityIcon(), subLabel: 'Read only' },
];
const App = () => {
const sel = atom([]);
return () => html`
<x-row gap="4" style="max-width:28rem;padding:1rem;" align="start">
${Select({ items: COUNTRIES, label: 'No chevron — outlined', placeholder: 'Choose a country…', hideChevron: true, selected: sel(), onChange: ids => sel.set(ids) })}
${Select({ items: ROLES, label: 'No chevron — filled', variant: 'filled', placeholder: 'Choose a role…', hideChevron: true, selected: sel(), onChange: ids => sel.set(ids) })}
</x-row>
`;
};
renderApp(App, document.getElementById('app'));