mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Redesign link field dropdown with modern single-column layout
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>
This commit is contained in:
parent
b90a9a30e9
commit
1aaf563db6
@ -5,9 +5,23 @@ function alpineMultiSelectController() {
|
||||
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;
|
||||
@ -79,13 +93,16 @@ function alpineMultiSelectController() {
|
||||
|
||||
// Finally, add it as a valid optgroup with options
|
||||
if (optionsForThisOptGroup.length > 0) {
|
||||
this.optgroups.push({
|
||||
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
|
||||
});
|
||||
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++) {
|
||||
|
||||
@ -81,30 +81,52 @@
|
||||
|
||||
<%# Opened state %>
|
||||
<div class="w-full">
|
||||
<div x-show.transition.origin.top="isOpen()" class="absolute shadow-lg top-100 bg-white z-40 w-full left-0 rounded-lg" x-on:click.away="close">
|
||||
<div class="flex flex-row w-full overflow-y-auto max-h-64">
|
||||
<template x-for="(optgroup,groupIndex) in optgroups" :key="groupIndex" class="overflow-auto">
|
||||
<div class="w-full">
|
||||
<%# Optgroup styling %>
|
||||
<div class="w-full py-1 rounded-t-lg text-white inline-flex items-center" x-bind:class="optgroup.color">
|
||||
<span x-text="optgroup.icon" class="material-icons mx-1"></span>
|
||||
<span x-text="optgroup.plural" class="text-xs font-medium"></span>
|
||||
<div x-show.transition.origin.top="isOpen()" class="absolute shadow-xl top-100 bg-white z-40 w-full left-0 rounded-lg border border-gray-200 overflow-hidden" x-on:click.away="close">
|
||||
|
||||
<%# Search box %>
|
||||
<div class="p-3 border-b border-gray-200 bg-gray-50">
|
||||
<input type="text"
|
||||
x-model="searchQuery"
|
||||
@input="filterOptions()"
|
||||
placeholder="Search pages..."
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
|
||||
@click.stop>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto max-h-80">
|
||||
<template x-for="(optgroup,groupIndex) in optgroups" :key="groupIndex">
|
||||
<div x-show="optgroup.filteredOptions && optgroup.filteredOptions.length > 0">
|
||||
<%# Subtle section header %>
|
||||
<div class="sticky top-0 bg-gray-100 px-3 py-1.5 border-b border-gray-200 z-10">
|
||||
<div class="flex items-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
<span x-text="optgroup.icon" class="material-icons text-sm mr-1.5" x-bind:class="optgroup.iconColor"></span>
|
||||
<span x-text="optgroup.plural"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<template x-for="(option, optionIndex) in optgroup.options" :key="optionIndex" class="overflow-auto">
|
||||
<%# Option styling %>
|
||||
<div class="cursor-pointer w-full border-gray-100 rounded-t hover:bg-blue-50" @click="select(option.index,$event)">
|
||||
<div class="flex w-full px-2 pl-2 border-transparent border-l-2 relative">
|
||||
<div class="w-full items-center flex justify-between">
|
||||
<div class="w-4">
|
||||
<svg x-show="option.selected" class="svg-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" d="M7.197,16.963H7.195c-0.204,0-0.399-0.083-0.544-0.227l-6.039-6.082c-0.3-0.302-0.297-0.788,0.003-1.087 C0.919,9.266,1.404,9.269,1.702,9.57l5.495,5.536L18.221,4.083c0.301-0.301,0.787-0.301,1.087,0c0.301,0.3,0.301,0.787,0,1.087 L7.741,16.738C7.596,16.882,7.401,16.963,7.197,16.963z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mx-2 py-2 leading-6 flex-1" x-model="option" x-text="option.text"></div>
|
||||
<div>
|
||||
<img :src="`${option.imageUrl}`" class="h-8 w-8 rounded-full">
|
||||
</div>
|
||||
|
||||
<%# Options list %>
|
||||
<div class="py-1">
|
||||
<template x-for="(option, optionIndex) in optgroup.filteredOptions" :key="optionIndex">
|
||||
<div class="cursor-pointer hover:bg-blue-50 transition-colors"
|
||||
@click="select(option.index,$event)"
|
||||
:class="option.selected ? 'bg-blue-50' : ''">
|
||||
<div class="flex items-center px-3 py-2.5 gap-3">
|
||||
<%# Checkbox/checkmark %>
|
||||
<div class="flex-shrink-0 w-5 h-5 rounded border-2 flex items-center justify-center transition-colors"
|
||||
:class="option.selected ? 'border-blue-500 bg-blue-500' : 'border-gray-300'">
|
||||
<svg x-show="option.selected" class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<%# Page name %>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-900 truncate" x-text="option.text"></div>
|
||||
</div>
|
||||
|
||||
<%# Thumbnail %>
|
||||
<div class="flex-shrink-0">
|
||||
<img :src="option.imageUrl" class="h-8 w-8 rounded-full object-cover border border-gray-200" :alt="option.text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,14 +135,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<%# Placeholder the link dropdown when there are no linkable pages to show %>
|
||||
<div x-show="options.length == 0">
|
||||
<p class="p-4">
|
||||
<i class="material-icons float-left mr-2 text-notebook-blue">info</i>
|
||||
This field only accepts links to
|
||||
<%= linkable_types.to_sentence %> notebook pages — and you haven't created
|
||||
any of those yet!
|
||||
</p>
|
||||
<%# No results message %>
|
||||
<div x-show="searchQuery && !optgroups.some(g => g.filteredOptions && g.filteredOptions.length > 0)" class="px-4 py-8 text-center">
|
||||
<i class="material-icons text-gray-300 text-4xl mb-2">search_off</i>
|
||||
<p class="text-sm text-gray-500">No pages found matching "<span x-text="searchQuery"></span>"</p>
|
||||
</div>
|
||||
|
||||
<%# Empty state %>
|
||||
<div x-show="options.length == 0 && !searchQuery" class="px-4 py-8">
|
||||
<div class="text-center">
|
||||
<i class="material-icons text-gray-300 text-4xl mb-2">link_off</i>
|
||||
<p class="text-sm text-gray-600">
|
||||
This field only accepts links to <%= linkable_types.to_sentence %> pages
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">You haven't created any of those yet!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user