Skip to content

Commit 085087e

Browse files
committed
feat: added a search bar for tags in the dropdown
1 parent 5a08d3c commit 085087e

File tree

4 files changed

+191
-19
lines changed

4 files changed

+191
-19
lines changed

options/options.css

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,69 @@ a:hover {
177177
padding: 12px 16px;
178178
border-radius: 5px;
179179
}
180+
181+
/* Custom Tag Dropdown Styles */
182+
.tag-dropdown {
183+
position: relative;
184+
min-width: 160px;
185+
font-size: 1em;
186+
}
187+
.tag-dropdown-selected {
188+
background: #fff;
189+
border: 1px solid #d1d5db;
190+
border-radius: 5px;
191+
padding: 7px 12px;
192+
cursor: pointer;
193+
user-select: none;
194+
min-width: 120px;
195+
transition: border-color 0.15s;
196+
}
197+
.tag-dropdown-selected:focus {
198+
outline: none;
199+
border-color: #8868bd;
200+
}
201+
.tag-dropdown-list {
202+
position: absolute;
203+
top: 110%;
204+
left: 0;
205+
right: 0;
206+
background: #fff;
207+
border: 1px solid #d1d5db;
208+
border-radius: 5px;
209+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
210+
z-index: 10;
211+
padding: 0;
212+
}
213+
.tag-dropdown-search {
214+
width: 100%;
215+
margin: 0;
216+
border-radius: 5px 5px 0 0;
217+
border-bottom: 1px solid #e5e7eb;
218+
border-top: none;
219+
border-left: none;
220+
border-right: none;
221+
box-sizing: border-box;
222+
padding: 10px 12px;
223+
font-size: 1em;
224+
background: #fafbfc;
225+
}
226+
.tag-dropdown-options {
227+
max-height: 180px;
228+
overflow-y: auto;
229+
padding-top: 4px;
230+
}
231+
.tag-dropdown-list {
232+
border-radius: 5px;
233+
overflow: hidden;
234+
}
235+
.tag-dropdown-option {
236+
padding: 7px 16px;
237+
cursor: pointer;
238+
transition: background 0.13s;
239+
color: #374151;
240+
}
241+
.tag-dropdown-option.selected,
242+
.tag-dropdown-option:hover {
243+
background: #f3f4f6;
244+
color: #8868bd;
245+
}

options/options.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@
3030
<h2>All Solved Problems</h2>
3131
<div class="problems-toolbar">
3232
<input type="text" id="searchInput" placeholder="Search by title...">
33-
<select id="tagFilter">
34-
<option value="all">All Tags</option>
35-
</select>
33+
<div id="tagDropdownContainer"></div>
3634
</div>
3735
<div id="problemsList"></div>
3836
</section>
@@ -61,6 +59,7 @@ <h2>FAQ / About</h2>
6159
</section>
6260
</main>
6361
</div>
62+
<script src="tagDropdown.js"></script>
6463
<script src="options.js"></script>
6564
</body>
6665
</html>

options/options.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ document.addEventListener('DOMContentLoaded', () => {
1616
// All Problems: load and render problems
1717
const problemsList = document.getElementById('problemsList');
1818
const searchInput = document.getElementById('searchInput');
19-
const tagFilter = document.getElementById('tagFilter');
19+
const tagDropdownContainer = document.getElementById('tagDropdownContainer');
20+
let tagDropdownInstance = null;
2021

2122
function renderProblems(problems, filterTag, searchTerm) {
2223
problemsList.innerHTML = '';
@@ -75,25 +76,20 @@ document.addEventListener('DOMContentLoaded', () => {
7576
p.tags.forEach(tag => tagSet.add(tag));
7677
}
7778
});
78-
tagFilter.innerHTML = '<option value="all">All Tags</option>';
79-
Array.from(tagSet)
80-
.sort((a, b) => a.localeCompare(b))
81-
.forEach(tag => {
82-
const opt = document.createElement('option');
83-
opt.value = tag;
84-
opt.textContent = tag;
85-
tagFilter.appendChild(opt);
86-
});
79+
const allTags = Array.from(tagSet).sort((a, b) => a.localeCompare(b));
80+
if (tagDropdownInstance) {
81+
tagDropdownInstance.setTags(allTags);
82+
} else {
83+
tagDropdownInstance = new window.TagDropdown(tagDropdownContainer, allTags, (selectedTag) => {
84+
renderProblems(problems, selectedTag, searchInput.value);
85+
});
86+
}
8787
// Initial render
88-
renderProblems(problems, tagFilter.value, searchInput.value);
88+
renderProblems(problems, tagDropdownInstance ? tagDropdownInstance.selectedTag : 'all', searchInput.value);
8989
// Event listeners
90-
tagFilter.addEventListener('change', () => {
91-
renderProblems(problems, tagFilter.value, searchInput.value);
92-
});
9390
searchInput.addEventListener('input', () => {
94-
renderProblems(problems, tagFilter.value, searchInput.value);
91+
renderProblems(problems, tagDropdownInstance ? tagDropdownInstance.selectedTag : 'all', searchInput.value);
9592
});
96-
9793
// set extension version in About section
9894
const extVersionElem = document.getElementById('extVersion');
9995
if (extVersionElem && browser.runtime.getManifest) {

options/tagDropdown.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Custom Tag Dropdown Component
2+
// Usage: new TagDropdown(containerElement, tagsArray, onChange)
3+
4+
class TagDropdown {
5+
constructor(container, tags, onChange) {
6+
this.container = container;
7+
this.tags = tags;
8+
this.onChange = onChange;
9+
this.selectedTag = 'all';
10+
this.filteredTags = ['all', ...tags];
11+
this.createDropdown();
12+
}
13+
14+
createDropdown() {
15+
this.container.innerHTML = '';
16+
this.dropdown = document.createElement('div');
17+
this.dropdown.className = 'tag-dropdown';
18+
19+
// Selected display
20+
this.selected = document.createElement('div');
21+
this.selected.className = 'tag-dropdown-selected';
22+
this.selected.tabIndex = 0;
23+
// Add text and chevron icon
24+
const selectedText = document.createElement('span');
25+
selectedText.className = 'tag-dropdown-selected-text';
26+
selectedText.textContent = 'All Tags';
27+
this.selected.appendChild(selectedText);
28+
const chevron = document.createElement('i');
29+
chevron.className = 'ri-arrow-down-s-line tag-dropdown-chevron';
30+
this.selected.appendChild(chevron);
31+
this.selected.addEventListener('click', () => this.toggleList());
32+
this.selected.addEventListener('keydown', (e) => {
33+
if (e.key === 'Enter' || e.key === ' ') this.toggleList();
34+
});
35+
this.dropdown.appendChild(this.selected);
36+
37+
// Dropdown list
38+
this.list = document.createElement('div');
39+
this.list.className = 'tag-dropdown-list';
40+
this.list.style.display = 'none';
41+
42+
// Search input
43+
this.searchInput = document.createElement('input');
44+
this.searchInput.type = 'text';
45+
this.searchInput.className = 'tag-dropdown-search';
46+
this.searchInput.placeholder = 'Search tags...';
47+
this.searchInput.addEventListener('input', () => this.filterTags());
48+
this.list.appendChild(this.searchInput);
49+
50+
// Tag options
51+
this.optionsContainer = document.createElement('div');
52+
this.optionsContainer.className = 'tag-dropdown-options';
53+
this.list.appendChild(this.optionsContainer);
54+
55+
this.dropdown.appendChild(this.list);
56+
this.container.appendChild(this.dropdown);
57+
58+
this.renderOptions();
59+
document.addEventListener('click', (e) => {
60+
if (!this.dropdown.contains(e.target)) this.closeList();
61+
});
62+
}
63+
64+
renderOptions() {
65+
this.optionsContainer.innerHTML = '';
66+
this.filteredTags.forEach(tag => {
67+
const opt = document.createElement('div');
68+
opt.className = 'tag-dropdown-option';
69+
opt.textContent = tag === 'all' ? 'All Tags' : tag;
70+
if (tag === this.selectedTag) opt.classList.add('selected');
71+
opt.addEventListener('click', () => this.selectTag(tag));
72+
this.optionsContainer.appendChild(opt);
73+
});
74+
}
75+
76+
filterTags() {
77+
const val = this.searchInput.value.toLowerCase();
78+
this.filteredTags = ['all', ...this.tags.filter(tag => tag.toLowerCase().includes(val))];
79+
this.renderOptions();
80+
}
81+
82+
selectTag(tag) {
83+
this.selectedTag = tag;
84+
// Update only the text span
85+
const textSpan = this.selected.querySelector('.tag-dropdown-selected-text');
86+
if (textSpan) textSpan.textContent = tag === 'all' ? 'All Tags' : tag;
87+
this.closeList();
88+
if (this.onChange) this.onChange(tag);
89+
}
90+
91+
toggleList() {
92+
this.list.style.display = this.list.style.display === 'none' ? 'block' : 'none';
93+
if (this.list.style.display === 'block') {
94+
this.searchInput.value = '';
95+
this.filterTags();
96+
this.searchInput.focus();
97+
}
98+
}
99+
100+
closeList() {
101+
this.list.style.display = 'none';
102+
}
103+
104+
setTags(tags) {
105+
this.tags = tags;
106+
this.filteredTags = ['all', ...tags];
107+
this.renderOptions();
108+
}
109+
}
110+
111+
window.TagDropdown = TagDropdown;

0 commit comments

Comments
 (0)