infinite-agents-public/src_enhanced/ui_enhanced_2.html

1172 lines
40 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dropdown Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
padding: 2rem;
line-height: 1.6;
}
main {
max-width: 800px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 3rem;
font-size: 2.5rem;
font-weight: 300;
letter-spacing: -0.5px;
}
.component-showcase {
display: flex;
flex-direction: column;
gap: 3rem;
align-items: center;
}
.dropdown-container {
width: 100%;
max-width: 400px;
}
.dropdown-label {
display: block;
margin-bottom: 0.5rem;
color: #4a5568;
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.025em;
}
.dropdown {
position: relative;
width: 100%;
}
.dropdown-trigger {
width: 100%;
min-height: 48px;
padding: 0.75rem 3rem 0.75rem 1rem;
background: white;
border: 2px solid #e2e8f0;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
font-size: 1rem;
color: #2d3748;
position: relative;
overflow: hidden;
}
.dropdown-trigger:hover {
border-color: #cbd5e0;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.dropdown-trigger:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15);
}
.dropdown.open .dropdown-trigger {
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15);
}
.dropdown-arrow {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
transition: transform 0.2s ease;
}
.dropdown.open .dropdown-arrow {
transform: translateY(-50%) rotate(180deg);
}
.dropdown-arrow svg {
width: 20px;
height: 20px;
color: #718096;
}
.dropdown-placeholder {
color: #a0aec0;
}
.dropdown-tag {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background: #edf2f7;
border-radius: 6px;
font-size: 0.875rem;
color: #4a5568;
animation: tagFadeIn 0.2s ease;
}
@keyframes tagFadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.dropdown-tag-remove {
cursor: pointer;
padding: 0.125rem;
margin-left: 0.25rem;
border-radius: 3px;
transition: all 0.15s ease;
}
.dropdown-tag-remove:hover {
background: #cbd5e0;
}
.dropdown-tag-remove svg {
width: 14px;
height: 14px;
}
.dropdown-menu {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
right: 0;
background: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12);
z-index: 1000;
opacity: 0;
transform: translateY(-10px);
pointer-events: none;
transition: all 0.2s ease;
max-height: 320px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.dropdown.open .dropdown-menu {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.dropdown-menu.position-top {
top: auto;
bottom: calc(100% + 0.5rem);
}
.dropdown-search {
padding: 1rem;
border-bottom: 1px solid #e2e8f0;
position: sticky;
top: 0;
background: white;
z-index: 10;
}
.dropdown-search-input {
width: 100%;
padding: 0.625rem 2.5rem 0.625rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 0.875rem;
transition: all 0.2s ease;
background: #f7fafc;
}
.dropdown-search-input:focus {
outline: none;
border-color: #4299e1;
background: white;
}
.dropdown-search-icon {
position: absolute;
right: 1.75rem;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #a0aec0;
}
.dropdown-search-icon svg {
width: 18px;
height: 18px;
}
.dropdown-options {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
scrollbar-width: thin;
scrollbar-color: #cbd5e0 transparent;
}
.dropdown-options::-webkit-scrollbar {
width: 6px;
}
.dropdown-options::-webkit-scrollbar-track {
background: transparent;
}
.dropdown-options::-webkit-scrollbar-thumb {
background-color: #cbd5e0;
border-radius: 3px;
}
.dropdown-group {
margin-bottom: 0.5rem;
}
.dropdown-group:last-child {
margin-bottom: 0;
}
.dropdown-group-header {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
color: #718096;
text-transform: uppercase;
letter-spacing: 0.05em;
user-select: none;
}
.dropdown-option {
padding: 0.75rem 1rem;
margin: 0.125rem 0;
border-radius: 8px;
cursor: pointer;
transition: all 0.15s ease;
display: flex;
align-items: center;
gap: 0.75rem;
position: relative;
overflow: hidden;
}
.dropdown-option:hover {
background: #f7fafc;
transform: translateX(2px);
}
.dropdown-option.focused {
background: #edf2f7;
box-shadow: inset 0 0 0 2px #4299e1;
}
.dropdown-option.selected {
background: #e6f4ff;
color: #2b6cb0;
font-weight: 500;
}
.dropdown-option.disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.dropdown-option-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-option-icon svg {
width: 20px;
height: 20px;
}
.dropdown-option-content {
flex: 1;
min-width: 0;
}
.dropdown-option-text {
font-size: 0.9375rem;
color: #2d3748;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dropdown-option-description {
font-size: 0.8125rem;
color: #718096;
margin-top: 0.125rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dropdown-option-checkbox {
position: absolute;
right: 1rem;
width: 20px;
height: 20px;
border: 2px solid #cbd5e0;
border-radius: 4px;
background: white;
transition: all 0.15s ease;
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-option.selected .dropdown-option-checkbox {
background: #4299e1;
border-color: #4299e1;
}
.dropdown-option-checkbox svg {
width: 14px;
height: 14px;
color: white;
opacity: 0;
transition: opacity 0.15s ease;
}
.dropdown-option.selected .dropdown-option-checkbox svg {
opacity: 1;
}
.dropdown-loading {
padding: 2rem;
text-align: center;
color: #718096;
}
.dropdown-loading-spinner {
width: 32px;
height: 32px;
margin: 0 auto 1rem;
border: 3px solid #e2e8f0;
border-top-color: #4299e1;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.dropdown-no-results {
padding: 2rem;
text-align: center;
color: #718096;
}
.dropdown-footer {
padding: 0.75rem 1rem;
border-top: 1px solid #e2e8f0;
font-size: 0.8125rem;
color: #718096;
background: #f7fafc;
text-align: center;
}
.keyboard-hint {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.125rem 0.375rem;
background: #e2e8f0;
border-radius: 4px;
font-family: monospace;
font-size: 0.75rem;
color: #4a5568;
}
@media (max-width: 640px) {
.dropdown-menu {
position: fixed;
left: 1rem;
right: 1rem;
top: 50%;
transform: translateY(-50%);
max-height: 80vh;
}
.dropdown.open .dropdown-menu {
transform: translateY(-50%);
}
.dropdown-menu.position-top {
top: 50%;
bottom: auto;
}
}
/* Highlight animation for search matches */
.highlight {
background: linear-gradient(to right, transparent 0%, #fef3c7 20%, #fef3c7 80%, transparent 100%);
padding: 0 2px;
border-radius: 2px;
animation: highlightPulse 1s ease infinite;
}
@keyframes highlightPulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
/* Demo specific styles */
.demo-section {
margin-top: 3rem;
padding: 2rem;
background: white;
border-radius: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}
.demo-title {
font-size: 1.25rem;
color: #2d3748;
margin-bottom: 1.5rem;
font-weight: 500;
}
.selected-values {
margin-top: 1rem;
padding: 1rem;
background: #f7fafc;
border-radius: 8px;
font-size: 0.875rem;
color: #4a5568;
font-family: monospace;
word-break: break-all;
}
</style>
</head>
<body>
<main>
<h1>Dropdown - Enhanced</h1>
<div class="component-showcase">
<!-- Single Select Dropdown -->
<div class="dropdown-container">
<label class="dropdown-label">Choose your favorite framework</label>
<div class="dropdown" id="single-select">
<button class="dropdown-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="dropdown-placeholder">Select a framework...</span>
<span class="dropdown-arrow">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</span>
</button>
</div>
</div>
<!-- Multi Select Dropdown -->
<div class="dropdown-container">
<label class="dropdown-label">Select team members</label>
<div class="dropdown" id="multi-select">
<button class="dropdown-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="dropdown-placeholder">Select team members...</span>
<span class="dropdown-arrow">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</span>
</button>
</div>
</div>
<!-- Async Loading Dropdown -->
<div class="dropdown-container">
<label class="dropdown-label">Search for a country</label>
<div class="dropdown" id="async-select">
<button class="dropdown-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="dropdown-placeholder">Type to search countries...</span>
<span class="dropdown-arrow">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</span>
</button>
</div>
</div>
</div>
<div class="demo-section">
<h2 class="demo-title">Selected Values</h2>
<div class="selected-values" id="selected-values">No selections yet</div>
</div>
</main>
<script>
// Enhanced Dropdown Class
class EnhancedDropdown {
constructor(element, options = {}) {
this.element = element;
this.trigger = element.querySelector('.dropdown-trigger');
this.isOpen = false;
this.isMultiple = options.multiple || false;
this.searchable = options.searchable !== false;
this.loadData = options.loadData || null;
this.options = options.options || [];
this.selectedValues = new Set();
this.focusedIndex = -1;
this.searchQuery = '';
this.loading = false;
this.loadingTimeout = null;
this.init();
}
init() {
this.createMenu();
this.bindEvents();
this.render();
}
createMenu() {
this.menu = document.createElement('div');
this.menu.className = 'dropdown-menu';
this.menu.setAttribute('role', 'listbox');
if (this.searchable) {
const searchContainer = document.createElement('div');
searchContainer.className = 'dropdown-search';
searchContainer.innerHTML = `
<input type="text" class="dropdown-search-input" placeholder="Search..." aria-label="Search options">
<span class="dropdown-search-icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</span>
`;
this.menu.appendChild(searchContainer);
this.searchInput = searchContainer.querySelector('.dropdown-search-input');
}
this.optionsContainer = document.createElement('div');
this.optionsContainer.className = 'dropdown-options';
this.menu.appendChild(this.optionsContainer);
const footer = document.createElement('div');
footer.className = 'dropdown-footer';
footer.innerHTML = `
Use <span class="keyboard-hint">↑↓</span> to navigate,
<span class="keyboard-hint">Enter</span> to select,
<span class="keyboard-hint">Esc</span> to close
`;
this.menu.appendChild(footer);
this.element.appendChild(this.menu);
}
bindEvents() {
// Trigger events
this.trigger.addEventListener('click', () => this.toggle());
this.trigger.addEventListener('keydown', (e) => this.handleTriggerKeydown(e));
// Search events
if (this.searchInput) {
this.searchInput.addEventListener('input', (e) => this.handleSearch(e.target.value));
this.searchInput.addEventListener('keydown', (e) => this.handleSearchKeydown(e));
}
// Click outside to close
document.addEventListener('click', (e) => {
if (!this.element.contains(e.target)) {
this.close();
}
});
// Window resize for positioning
window.addEventListener('resize', () => {
if (this.isOpen) this.updatePosition();
});
}
toggle() {
this.isOpen ? this.close() : this.open();
}
async open() {
this.isOpen = true;
this.element.classList.add('open');
this.trigger.setAttribute('aria-expanded', 'true');
this.updatePosition();
if (this.searchInput) {
setTimeout(() => this.searchInput.focus(), 100);
}
if (this.loadData && this.options.length === 0) {
await this.loadOptions();
}
this.focusedIndex = this.findFirstSelectedIndex();
this.updateFocus();
}
close() {
this.isOpen = false;
this.element.classList.remove('open');
this.trigger.setAttribute('aria-expanded', 'false');
this.searchQuery = '';
if (this.searchInput) {
this.searchInput.value = '';
}
this.render();
}
updatePosition() {
const rect = this.trigger.getBoundingClientRect();
const menuHeight = this.menu.offsetHeight;
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
if (spaceBelow < menuHeight && spaceAbove > spaceBelow) {
this.menu.classList.add('position-top');
} else {
this.menu.classList.remove('position-top');
}
}
async loadOptions() {
this.loading = true;
this.render();
try {
clearTimeout(this.loadingTimeout);
this.loadingTimeout = setTimeout(async () => {
const data = await this.loadData(this.searchQuery);
this.options = data;
this.loading = false;
this.render();
}, 300);
} catch (error) {
console.error('Error loading options:', error);
this.loading = false;
this.render();
}
}
handleSearch(query) {
this.searchQuery = query.toLowerCase();
this.focusedIndex = -1;
if (this.loadData) {
this.loadOptions();
} else {
this.render();
}
}
handleTriggerKeydown(e) {
switch (e.key) {
case 'Enter':
case ' ':
case 'ArrowDown':
e.preventDefault();
this.open();
break;
case 'ArrowUp':
e.preventDefault();
this.open();
this.focusedIndex = this.getFilteredOptions().length - 1;
this.updateFocus();
break;
}
}
handleSearchKeydown(e) {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
this.moveFocus(1);
break;
case 'ArrowUp':
e.preventDefault();
this.moveFocus(-1);
break;
case 'Enter':
e.preventDefault();
this.selectFocusedOption();
break;
case 'Escape':
e.preventDefault();
this.close();
this.trigger.focus();
break;
}
}
moveFocus(direction) {
const filtered = this.getFilteredOptions();
const maxIndex = filtered.length - 1;
this.focusedIndex += direction;
if (this.focusedIndex < 0) {
this.focusedIndex = maxIndex;
} else if (this.focusedIndex > maxIndex) {
this.focusedIndex = 0;
}
this.updateFocus();
}
updateFocus() {
const options = this.optionsContainer.querySelectorAll('.dropdown-option:not(.disabled)');
options.forEach((option, index) => {
if (index === this.focusedIndex) {
option.classList.add('focused');
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
} else {
option.classList.remove('focused');
}
});
}
selectFocusedOption() {
const filtered = this.getFilteredOptions();
if (this.focusedIndex >= 0 && this.focusedIndex < filtered.length) {
const option = filtered[this.focusedIndex];
this.selectOption(option);
}
}
selectOption(option) {
if (option.disabled) return;
if (this.isMultiple) {
if (this.selectedValues.has(option.value)) {
this.selectedValues.delete(option.value);
} else {
this.selectedValues.add(option.value);
}
this.render();
this.updateTrigger();
this.updateDemoValues();
} else {
this.selectedValues.clear();
this.selectedValues.add(option.value);
this.updateTrigger();
this.updateDemoValues();
this.close();
}
}
removeTag(value, event) {
event.stopPropagation();
this.selectedValues.delete(value);
this.updateTrigger();
this.updateDemoValues();
}
updateTrigger() {
const selected = this.options.filter(opt => this.selectedValues.has(opt.value));
if (selected.length === 0) {
this.trigger.innerHTML = `
<span class="dropdown-placeholder">${this.getPlaceholder()}</span>
${this.getArrowHtml()}
`;
} else if (this.isMultiple) {
const tags = selected.map(opt => `
<span class="dropdown-tag">
${opt.label}
<span class="dropdown-tag-remove" data-value="${opt.value}">
<svg fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
</span>
`).join('');
this.trigger.innerHTML = tags + this.getArrowHtml();
// Bind remove events
this.trigger.querySelectorAll('.dropdown-tag-remove').forEach(btn => {
btn.addEventListener('click', (e) => this.removeTag(btn.dataset.value, e));
});
} else {
this.trigger.innerHTML = `
<span>${selected[0].label}</span>
${this.getArrowHtml()}
`;
}
}
getPlaceholder() {
return this.trigger.querySelector('.dropdown-placeholder')?.textContent || 'Select...';
}
getArrowHtml() {
return `
<span class="dropdown-arrow">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</span>
`;
}
getFilteredOptions() {
if (!this.searchQuery) return this.options;
return this.options.filter(option => {
const searchTarget = `${option.label} ${option.description || ''}`.toLowerCase();
return this.fuzzyMatch(searchTarget, this.searchQuery);
});
}
fuzzyMatch(str, pattern) {
let patternIdx = 0;
let strIdx = 0;
while (strIdx < str.length && patternIdx < pattern.length) {
if (str[strIdx] === pattern[patternIdx]) {
patternIdx++;
}
strIdx++;
}
return patternIdx === pattern.length;
}
highlightMatch(text, query) {
if (!query) return text;
const regex = new RegExp(`(${query.split('').join('.*?')})`, 'gi');
return text.replace(regex, '<span class="highlight">$1</span>');
}
findFirstSelectedIndex() {
const filtered = this.getFilteredOptions();
return filtered.findIndex(opt => this.selectedValues.has(opt.value));
}
render() {
if (this.loading) {
this.optionsContainer.innerHTML = `
<div class="dropdown-loading">
<div class="dropdown-loading-spinner"></div>
<div>Loading options...</div>
</div>
`;
return;
}
const filtered = this.getFilteredOptions();
if (filtered.length === 0) {
this.optionsContainer.innerHTML = `
<div class="dropdown-no-results">
<div>No results found</div>
</div>
`;
return;
}
const groups = this.groupOptions(filtered);
let html = '';
let optionIndex = 0;
for (const [group, options] of Object.entries(groups)) {
if (group !== 'undefined') {
html += `<div class="dropdown-group-header">${group}</div>`;
}
html += `<div class="dropdown-group">`;
for (const option of options) {
const isSelected = this.selectedValues.has(option.value);
const isFocused = optionIndex === this.focusedIndex;
html += `
<div class="dropdown-option ${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''} ${option.disabled ? 'disabled' : ''}"
role="option"
aria-selected="${isSelected}"
data-value="${option.value}"
data-index="${optionIndex}">
${option.icon ? `
<span class="dropdown-option-icon">${option.icon}</span>
` : ''}
<div class="dropdown-option-content">
<div class="dropdown-option-text">${this.highlightMatch(option.label, this.searchQuery)}</div>
${option.description ? `
<div class="dropdown-option-description">${this.highlightMatch(option.description, this.searchQuery)}</div>
` : ''}
</div>
${this.isMultiple ? `
<span class="dropdown-option-checkbox">
<svg fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
` : ''}
</div>
`;
optionIndex++;
}
html += `</div>`;
}
this.optionsContainer.innerHTML = html;
// Bind option click events
this.optionsContainer.querySelectorAll('.dropdown-option:not(.disabled)').forEach(option => {
option.addEventListener('click', () => {
const value = option.dataset.value;
const optionData = this.options.find(opt => opt.value === value);
this.selectOption(optionData);
});
option.addEventListener('mouseenter', () => {
this.focusedIndex = parseInt(option.dataset.index);
this.updateFocus();
});
});
}
groupOptions(options) {
const groups = {};
for (const option of options) {
const group = option.group || 'undefined';
if (!groups[group]) {
groups[group] = [];
}
groups[group].push(option);
}
return groups;
}
updateDemoValues() {
const event = new CustomEvent('selectionChange', {
detail: {
dropdown: this.element.id,
values: Array.from(this.selectedValues)
}
});
document.dispatchEvent(event);
}
}
// Framework options with icons
const frameworkOptions = [
{
value: 'react',
label: 'React',
description: 'A JavaScript library for building user interfaces',
icon: '⚛️',
group: 'Frontend Frameworks'
},
{
value: 'vue',
label: 'Vue.js',
description: 'The progressive JavaScript framework',
icon: '🟢',
group: 'Frontend Frameworks'
},
{
value: 'angular',
label: 'Angular',
description: 'Platform for building mobile and desktop apps',
icon: '🔺',
group: 'Frontend Frameworks'
},
{
value: 'svelte',
label: 'Svelte',
description: 'Cybernetically enhanced web apps',
icon: '🧡',
group: 'Frontend Frameworks'
},
{
value: 'express',
label: 'Express.js',
description: 'Fast, unopinionated, minimalist web framework',
icon: '🚂',
group: 'Backend Frameworks'
},
{
value: 'nextjs',
label: 'Next.js',
description: 'The React framework for production',
icon: '▲',
group: 'Full-Stack Frameworks'
},
{
value: 'nuxt',
label: 'Nuxt.js',
description: 'The intuitive Vue framework',
icon: '🟩',
group: 'Full-Stack Frameworks'
},
{
value: 'gatsby',
label: 'Gatsby',
description: 'Fast static site generator for React',
icon: '🟣',
group: 'Static Site Generators'
}
];
// Team member options
const teamOptions = [
{
value: 'john',
label: 'John Smith',
description: 'Frontend Developer',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
},
{
value: 'sarah',
label: 'Sarah Johnson',
description: 'UX Designer',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
},
{
value: 'mike',
label: 'Mike Chen',
description: 'Backend Engineer',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
},
{
value: 'emma',
label: 'Emma Wilson',
description: 'Product Manager',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
},
{
value: 'alex',
label: 'Alex Brown',
description: 'DevOps Engineer',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
},
{
value: 'lisa',
label: 'Lisa Davis',
description: 'QA Engineer',
icon: `<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>`
}
];
// Country loader simulation
const loadCountries = async (search) => {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 500));
const countries = [
{ value: 'us', label: 'United States', description: 'North America' },
{ value: 'ca', label: 'Canada', description: 'North America' },
{ value: 'mx', label: 'Mexico', description: 'North America' },
{ value: 'uk', label: 'United Kingdom', description: 'Europe' },
{ value: 'de', label: 'Germany', description: 'Europe' },
{ value: 'fr', label: 'France', description: 'Europe' },
{ value: 'it', label: 'Italy', description: 'Europe' },
{ value: 'es', label: 'Spain', description: 'Europe' },
{ value: 'jp', label: 'Japan', description: 'Asia' },
{ value: 'cn', label: 'China', description: 'Asia' },
{ value: 'in', label: 'India', description: 'Asia' },
{ value: 'au', label: 'Australia', description: 'Oceania' },
{ value: 'nz', label: 'New Zealand', description: 'Oceania' },
{ value: 'br', label: 'Brazil', description: 'South America' },
{ value: 'ar', label: 'Argentina', description: 'South America' },
{ value: 'za', label: 'South Africa', description: 'Africa' },
{ value: 'eg', label: 'Egypt', description: 'Africa' }
];
if (search) {
return countries.filter(country =>
country.label.toLowerCase().includes(search.toLowerCase()) ||
country.description.toLowerCase().includes(search.toLowerCase())
);
}
return countries;
};
// Initialize dropdowns
const singleSelect = new EnhancedDropdown(
document.getElementById('single-select'),
{
options: frameworkOptions,
multiple: false
}
);
const multiSelect = new EnhancedDropdown(
document.getElementById('multi-select'),
{
options: teamOptions,
multiple: true
}
);
const asyncSelect = new EnhancedDropdown(
document.getElementById('async-select'),
{
loadData: loadCountries,
multiple: false
}
);
// Update demo values display
const selectedValuesDisplay = document.getElementById('selected-values');
const allSelections = {
'single-select': [],
'multi-select': [],
'async-select': []
};
document.addEventListener('selectionChange', (e) => {
allSelections[e.detail.dropdown] = e.detail.values;
const display = Object.entries(allSelections)
.filter(([_, values]) => values.length > 0)
.map(([dropdown, values]) => `${dropdown}: [${values.join(', ')}]`)
.join('\n');
selectedValuesDisplay.textContent = display || 'No selections yet';
});
</script>
</body>
</html>