Fix load filter dropdown positioning on bookshelf page
The load filter dropdown was being cut off when the button was positioned on the left side of the screen due to static right-0 alignment. This became more problematic as the button position changes with window resize. Changes: - Added dynamic dropdown alignment calculation based on button position and available viewport space - Implemented smart positioning logic that checks available space on both left and right sides before deciding alignment - Added window resize listener using requestAnimationFrame to dynamically update dropdown position while open - Added data-load-filter-btn attribute for reliable DOM querying - Changed from static right-0 to dynamic :class binding for left/right alignment Technical details: - Alpine.js state: dropdownAlign tracks current alignment (left/right) - calculateAlignment() method computes button position and available space - Uses getBoundingClientRect() to measure button position relative to viewport - Prefers side with >=320px space, otherwise chooses larger side - requestAnimationFrame ensures smooth updates during resize without performance degradation Fixes issue where dropdown extends beyond viewport edge when button is near left or right edge of screen.
This commit is contained in:
1 parent
0c1a55d6e3
commit
9dccdfbde0
3 files changed
+54
-7
No files matched your search
@@ -247,6 +247,7 @@ templ BookShelf(
|
||||
<div class="flex items-end relative">
|
||||
<button
|
||||
@click="toggleFiltersDropdown()"
|
||||
data-load-filter-btn
|
||||
class="px-4 py-2 rounded-lg font-medium border"
|
||||
style="border-color: var(--border); color: var(--text-primary);"
|
||||
>
|
||||
@@ -262,7 +263,8 @@ templ BookShelf(
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute top-full mt-2 right-0 w-80 rounded-lg shadow-lg z-50"
|
||||
class="absolute top-full mt-2 w-80 rounded-lg shadow-lg z-50"
|
||||
:class="dropdownAlign === 'left' ? 'left-0' : 'right-0'"
|
||||
style="background-color: var(--bg-secondary); border: 1px solid var(--border); display: none;"
|
||||
>
|
||||
<div class="p-4">
|
||||
|
||||
@@ -128,7 +128,7 @@ func BookShelf(
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</select></div><!-- Search Input --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Search</label> <input name=\"q\" type=\"text\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Search all fields...\"></div><!-- Author Filter with Autocomplete --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Author</label> <input type=\"text\" name=\"author_filter\" placeholder=\"Filter by author\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"author-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchAuthorValues($el)\"> <datalist id=\"author-datalist\"></datalist></div><!-- Tags Filter with Autocomplete --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Tags</label> <input type=\"text\" name=\"tags_filter\" placeholder=\"Filter by tags\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"tags-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchTagValues($el)\"> <datalist id=\"tags-datalist\"></datalist></div><!-- Series Filter with Autocomplete (NEW) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Series</label> <input type=\"text\" name=\"series_filter\" placeholder=\"Filter by series\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"series-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchSeriesValues($el)\"> <datalist id=\"series-datalist\"></datalist></div><!-- Language Filter with Autocomplete (NEW) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Language</label> <input type=\"text\" name=\"language_filter\" placeholder=\"Filter by language\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"language-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchLanguageValues($el)\"> <datalist id=\"language-datalist\"></datalist></div><!-- Year Range --><div class=\"flex-1 min-w-[200px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Year Range</label><div class=\"flex gap-2\"><input type=\"number\" name=\"year_min\" placeholder=\"From\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"> <input type=\"number\" name=\"year_max\" placeholder=\"To\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div></div><!-- Has Cover Filter - TriState Button --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Cover Filter</label> <input type=\"button\" id=\"has-cover-tristate\" class=\"tristate-btn w-full transition-all duration-300 cursor-pointer flex items-center justify-center gap-2 px-4 py-2 rounded-lg border text-sm\" :class=\"{\n 'state-null': hasCoverState === null,\n 'state-true': hasCoverState === true,\n 'state-false': hasCoverState === false\n }\" :value=\"hasCoverState === null ? '○ Cover: Any' : hasCoverState === true ? '✓ Has Cover' : '✗ No Cover'\" @click=\"cycleHasCover()\"><!-- Hidden input for HTMX form submission - conditionally rendered --><template x-if=\"hasCoverState !== null\"><input type=\"hidden\" name=\"has_cover\" :value=\"hasCoverState ? 'true' : 'false'\"></template></div><!-- Sort By Dropdown (PRESERVED) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sort By</label> <select name=\"sort\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" hx-get=\"/api/media-items/search\" hx-target=\"#books-grid\" hx-trigger=\"change\" hx-include=\"#filter-form\"><option value=\"title ASC\">Title (A-Z)</option> <option value=\"title DESC\">Title (Z-A)</option> <option value=\"author ASC\">Author (A-Z)</option> <option value=\"created_at DESC\">Date Added</option> <option value=\"page_count DESC\">Page Count</option></selectLine truncated
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</select></div><!-- Search Input --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Search</label> <input name=\"q\" type=\"text\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Search all fields...\"></div><!-- Author Filter with Autocomplete --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Author</label> <input type=\"text\" name=\"author_filter\" placeholder=\"Filter by author\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"author-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchAuthorValues($el)\"> <datalist id=\"author-datalist\"></datalist></div><!-- Tags Filter with Autocomplete --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Tags</label> <input type=\"text\" name=\"tags_filter\" placeholder=\"Filter by tags\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"tags-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchTagValues($el)\"> <datalist id=\"tags-datalist\"></datalist></div><!-- Series Filter with Autocomplete (NEW) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Series</label> <input type=\"text\" name=\"series_filter\" placeholder=\"Filter by series\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"series-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchSeriesValues($el)\"> <datalist id=\"series-datalist\"></datalist></div><!-- Language Filter with Autocomplete (NEW) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Language</label> <input type=\"text\" name=\"language_filter\" placeholder=\"Filter by language\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" list=\"language-datalist\" @input.debounce.300ms=\"if($el.value.length >= 2) fetchLanguageValues($el)\"> <datalist id=\"language-datalist\"></datalist></div><!-- Year Range --><div class=\"flex-1 min-w-[200px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Year Range</label><div class=\"flex gap-2\"><input type=\"number\" name=\"year_min\" placeholder=\"From\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"> <input type=\"number\" name=\"year_max\" placeholder=\"To\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div></div><!-- Has Cover Filter - TriState Button --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Cover Filter</label> <input type=\"button\" id=\"has-cover-tristate\" class=\"tristate-btn w-full transition-all duration-300 cursor-pointer flex items-center justify-center gap-2 px-4 py-2 rounded-lg border text-sm\" :class=\"{\n 'state-null': hasCoverState === null,\n 'state-true': hasCoverState === true,\n 'state-false': hasCoverState === false\n }\" :value=\"hasCoverState === null ? '○ Cover: Any' : hasCoverState === true ? '✓ Has Cover' : '✗ No Cover'\" @click=\"cycleHasCover()\"><!-- Hidden input for HTMX form submission - conditionally rendered --><template x-if=\"hasCoverState !== null\"><input type=\"hidden\" name=\"has_cover\" :value=\"hasCoverState ? 'true' : 'false'\"></template></div><!-- Sort By Dropdown (PRESERVED) --><div class=\"flex-1 min-w-[150px]\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sort By</label> <select name=\"sort\" class=\"w-full px-3 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" hx-get=\"/api/media-items/search\" hx-target=\"#books-grid\" hx-trigger=\"change\" hx-include=\"#filter-form\"><option value=\"title ASC\">Title (A-Z)</option> <option value=\"title DESC\">Title (Z-A)</option> <option value=\"author ASC\">Author (A-Z)</option> <option value=\"created_at DESC\">Date Added</option> <option value=\"page_count DESC\">Page Count</option></selectLine truncated
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func BookShelf(
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(uuidToString(filter.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 275, Col: 173}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 277, Col: 173}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -153,7 +153,7 @@ func BookShelf(
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(filter.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 277, Col: 27}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 279, Col: 27}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -194,7 +194,7 @@ func BookShelf(
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 311, Col: 59}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 313, Col: 59}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -227,7 +227,7 @@ func BookShelf(
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(offset/limit + 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 330, Col: 32}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 332, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
Reference in new issue
Block a user