mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Replace overwhelming multi-column layout with clean, scannable single-column design: - Add search functionality to filter pages - Convert to single-column layout for better readability - Use subtle section headers instead of bright color blocks - Improve item styling with checkboxes, better spacing, and hover states - Add proper empty states and no-results messaging - Increase max height and make more compact This transforms the dropdown from a janky color explosion into a modern, usable selector. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
122 lines
4.6 KiB
JavaScript
122 lines
4.6 KiB
JavaScript
function alpineMultiSelectController() {
|
|
return {
|
|
optgroups: [],
|
|
options: [],
|
|
selected: [],
|
|
show: false,
|
|
sourceFieldId: '',
|
|
searchQuery: '',
|
|
open() { this.show = true },
|
|
close() { this.show = false },
|
|
isOpen() { return this.show === true },
|
|
filterOptions() {
|
|
// Update filteredOptions for each optgroup based on search query
|
|
this.optgroups.forEach(optgroup => {
|
|
if (!this.searchQuery || this.searchQuery.trim() === '') {
|
|
optgroup.filteredOptions = optgroup.options;
|
|
} else {
|
|
const query = this.searchQuery.toLowerCase();
|
|
optgroup.filteredOptions = optgroup.options.filter(option =>
|
|
option.text.toLowerCase().includes(query)
|
|
);
|
|
}
|
|
});
|
|
},
|
|
select(index, event) {
|
|
if (!this.options[index].selected) {
|
|
this.options[index].selected = true;
|
|
this.options[index].element = event.target;
|
|
this.selected.push(index);
|
|
|
|
} else {
|
|
this.selected.splice(this.selected.lastIndexOf(index), 1);
|
|
this.options[index].selected = false;
|
|
}
|
|
|
|
// Update the original select element's selected options
|
|
const originalSelect = document.getElementById(this.sourceFieldId);
|
|
for (let i = 0; i < originalSelect.options.length; i++) {
|
|
originalSelect.options[i].selected = this.options[i].selected;
|
|
}
|
|
|
|
// Finally, trigger a manual on-change event on the original select
|
|
// to make sure our autosave fires on it.
|
|
originalSelect.dispatchEvent(new Event('change'));
|
|
},
|
|
remove(index, option) {
|
|
this.options[option].selected = false;
|
|
this.selected.splice(index, 1);
|
|
|
|
// We also want to manually remove the `selected` attribute from the
|
|
// original select element's option.
|
|
const originalSelect = document.getElementById(this.sourceFieldId);
|
|
originalSelect.options[option].selected = false;
|
|
|
|
// After removing the option, we want to emit a change event to trigger
|
|
// a field autosave.
|
|
originalSelect.dispatchEvent(new Event('change'));
|
|
},
|
|
loadOptions(fieldId) {
|
|
this.sourceFieldId = fieldId;
|
|
const select = document.getElementById(fieldId);
|
|
const optgroups = select.getElementsByTagName('optgroup');
|
|
|
|
// Since we're effectively resetting indices per optgroup, we need to track
|
|
// a single running index throughout all optgroups to use as an index for
|
|
// each option for events, etc.
|
|
let runningOptionIndex = 0;
|
|
|
|
// For each optgroup (page type)...
|
|
for (let i = 0; i < optgroups.length; i++) {
|
|
const groupOptions = optgroups[i].getElementsByTagName('option');
|
|
|
|
// Prepare the `options` array for this optgroup
|
|
const optionsForThisOptGroup = [];
|
|
for (let j = 0; j < groupOptions.length; j++) {
|
|
const option = groupOptions[j];
|
|
const imageUrl = option.getAttribute('data-image-url');
|
|
|
|
optionsForThisOptGroup.push({
|
|
index: runningOptionIndex++,
|
|
value: option.value,
|
|
text: option.textContent.trim(),
|
|
imageUrl: imageUrl,
|
|
icon: option.getAttribute('data-icon'),
|
|
icon_color: option.getAttribute('data-icon-color'),
|
|
selected: !!option.selected
|
|
});
|
|
|
|
if (!!option.selected) {
|
|
this.selected.push(runningOptionIndex - 1);
|
|
}
|
|
}
|
|
|
|
// Finally, add it as a valid optgroup with options
|
|
if (optionsForThisOptGroup.length > 0) {
|
|
const optgroupData = {
|
|
label: optgroups[i].label,
|
|
icon: window.ContentTypeData[optgroups[i].label].icon,
|
|
color: window.ContentTypeData[optgroups[i].label].color,
|
|
iconColor: window.ContentTypeData[optgroups[i].label].text_color || 'text-gray-600',
|
|
plural: window.ContentTypeData[optgroups[i].label].plural,
|
|
options: optionsForThisOptGroup,
|
|
filteredOptions: optionsForThisOptGroup // Initialize with all options
|
|
};
|
|
this.optgroups.push(optgroupData);
|
|
|
|
// And also track all the options in a flat array so we can reference them with a single index
|
|
for (let j = 0; j < optionsForThisOptGroup.length; j++) {
|
|
this.options.push(optionsForThisOptGroup[j]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
selectedValues(){
|
|
// Return all this.options where selected=true
|
|
return this.options.filter(op => op.selected === true); // .map(el => el.text)
|
|
// return this.selected.map((option)=>{
|
|
// return this.options[option].value;
|
|
// });
|
|
}
|
|
}
|
|
} |