1+ export const basicAutocompleteExample = `
2+ <style>
3+ * {
4+ margin: 0;
5+ padding: 0;
6+ box-sizing: border-box;
7+ }
8+
9+ body {
10+ font-family: system-ui, -apple-system, sans-serif;
11+ padding: 2rem;
12+ background: #f8fafc;
13+ line-height: 1.6;
14+ }
15+
16+ .autocomplete {
17+ position: relative;
18+ width: 100%;
19+ max-width: 400px;
20+ margin: 0 auto;
21+ }
22+
23+ label {
24+ display: block;
25+ margin-bottom: 0.5rem;
26+ font-size: 0.875rem;
27+ font-weight: 500;
28+ color: #374151;
29+ }
30+
31+ .input-wrapper {
32+ position: relative;
33+ }
34+
35+ input[type="text"] {
36+ width: 100%;
37+ padding: 0.75rem 2.5rem 0.75rem 1rem;
38+ border: 1px solid #d1d5db;
39+ border-radius: 0.375rem;
40+ font-size: 1rem;
41+ background: white;
42+ transition: border-color 0.2s, box-shadow 0.2s;
43+ }
44+
45+ input[type="text"]:focus {
46+ outline: none;
47+ border-color: #3b82f6;
48+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
49+ }
50+
51+ .clear-button {
52+ position: absolute;
53+ right: 0.5rem;
54+ top: 50%;
55+ transform: translateY(-50%);
56+ padding: 0.25rem;
57+ border: none;
58+ background: none;
59+ color: #6b7280;
60+ cursor: pointer;
61+ opacity: 0;
62+ visibility: hidden;
63+ transition: opacity 0.2s;
64+ }
65+
66+ .clear-button.visible {
67+ opacity: 1;
68+ visibility: visible;
69+ }
70+
71+ .clear-button:hover {
72+ color: #374151;
73+ }
74+
75+ .suggestions-list {
76+ position: absolute;
77+ top: calc(100% + 0.25rem);
78+ left: 0;
79+ right: 0;
80+ max-height: 200px;
81+ overflow-y: auto;
82+ background: white;
83+ border: 1px solid #d1d5db;
84+ border-radius: 0.375rem;
85+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
86+ z-index: 10;
87+ display: none;
88+ }
89+
90+ .suggestions-list.open {
91+ display: block;
92+ }
93+
94+ .suggestion-item {
95+ padding: 0.75rem 1rem;
96+ cursor: pointer;
97+ transition: background-color 0.15s;
98+ border-bottom: 1px solid #f3f4f6;
99+ }
100+
101+ .suggestion-item:last-child {
102+ border-bottom: none;
103+ }
104+
105+ .suggestion-item:hover,
106+ .suggestion-item.highlighted {
107+ background-color: #f3f4f6;
108+ }
109+
110+ .suggestion-item mark {
111+ background-color: #fef3c7;
112+ color: inherit;
113+ font-weight: 600;
114+ }
115+
116+ .no-results {
117+ padding: 1rem;
118+ text-align: center;
119+ color: #6b7280;
120+ font-size: 0.875rem;
121+ }
122+
123+ .loading {
124+ padding: 1rem;
125+ text-align: center;
126+ color: #6b7280;
127+ font-size: 0.875rem;
128+ }
129+
130+ .sr-only {
131+ position: absolute;
132+ width: 1px;
133+ height: 1px;
134+ padding: 0;
135+ margin: -1px;
136+ overflow: hidden;
137+ clip: rect(0, 0, 0, 0);
138+ white-space: nowrap;
139+ border-width: 0;
140+ }
141+ </style>
142+
143+ <div class="autocomplete">
144+ <label for="country-input">Select Country</label>
145+ <div class="input-wrapper">
146+ <input
147+ type="text"
148+ id="country-input"
149+ name="country"
150+ placeholder="Start typing a country name..."
151+ aria-autocomplete="list"
152+ aria-controls="suggestions"
153+ aria-expanded="false"
154+ autocomplete="off"
155+ />
156+ <button
157+ type="button"
158+ class="clear-button"
159+ aria-label="Clear input"
160+ >
161+ ✕
162+ </button>
163+ </div>
164+ <div
165+ id="suggestions"
166+ class="suggestions-list"
167+ role="listbox"
168+ aria-label="Country suggestions"
169+ >
170+ <!-- Suggestions will be populated here -->
171+ </div>
172+ <div class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>
173+ </div>
174+
175+ <script>
176+ const countries = [
177+ 'Afghanistan', 'Albania', 'Algeria', 'Argentina', 'Australia',
178+ 'Austria', 'Bangladesh', 'Belgium', 'Brazil', 'Bulgaria',
179+ 'Canada', 'Chile', 'China', 'Colombia', 'Croatia',
180+ 'Czech Republic', 'Denmark', 'Egypt', 'Finland', 'France',
181+ 'Germany', 'Greece', 'Hungary', 'India', 'Indonesia',
182+ 'Ireland', 'Israel', 'Italy', 'Japan', 'Kenya',
183+ 'Malaysia', 'Mexico', 'Netherlands', 'New Zealand', 'Nigeria',
184+ 'Norway', 'Pakistan', 'Peru', 'Philippines', 'Poland',
185+ 'Portugal', 'Romania', 'Russia', 'Saudi Arabia', 'Singapore',
186+ 'South Africa', 'South Korea', 'Spain', 'Sweden', 'Switzerland',
187+ 'Thailand', 'Turkey', 'Ukraine', 'United Kingdom', 'United States'
188+ ];
189+
190+ const input = document.getElementById('country-input');
191+ const suggestionsList = document.getElementById('suggestions');
192+ const clearButton = document.querySelector('.clear-button');
193+ const statusElement = document.querySelector('[role="status"]');
194+
195+ let currentHighlightedIndex = -1;
196+ let filteredCountries = [];
197+
198+ // Input event handler
199+ input.addEventListener('input', function(e) {
200+ const value = e.target.value.trim();
201+
202+ // Toggle clear button visibility
203+ clearButton.classList.toggle('visible', value.length > 0);
204+
205+ if (value.length === 0) {
206+ closeSuggestions();
207+ return;
208+ }
209+
210+ // Filter countries
211+ filteredCountries = countries.filter(country =>
212+ country.toLowerCase().includes(value.toLowerCase())
213+ );
214+
215+ displaySuggestions(filteredCountries, value);
216+ });
217+
218+ // Clear button handler
219+ clearButton.addEventListener('click', function() {
220+ input.value = '';
221+ clearButton.classList.remove('visible');
222+ closeSuggestions();
223+ input.focus();
224+ });
225+
226+ // Keyboard navigation
227+ input.addEventListener('keydown', function(e) {
228+ const isOpen = suggestionsList.classList.contains('open');
229+
230+ if (!isOpen && e.key !== 'ArrowDown') return;
231+
232+ switch(e.key) {
233+ case 'ArrowDown':
234+ e.preventDefault();
235+ if (!isOpen && filteredCountries.length > 0) {
236+ displaySuggestions(filteredCountries, input.value);
237+ }
238+ highlightNext();
239+ break;
240+ case 'ArrowUp':
241+ e.preventDefault();
242+ highlightPrevious();
243+ break;
244+ case 'Enter':
245+ e.preventDefault();
246+ if (currentHighlightedIndex >= 0) {
247+ selectCountry(filteredCountries[currentHighlightedIndex]);
248+ }
249+ break;
250+ case 'Escape':
251+ closeSuggestions();
252+ break;
253+ }
254+ });
255+
256+ // Display suggestions
257+ function displaySuggestions(countries, searchTerm) {
258+ if (countries.length === 0) {
259+ suggestionsList.innerHTML = '<div class="no-results">No countries found</div>';
260+ suggestionsList.classList.add('open');
261+ input.setAttribute('aria-expanded', 'true');
262+ statusElement.textContent = 'No suggestions available';
263+ return;
264+ }
265+
266+ suggestionsList.innerHTML = countries
267+ .map((country, index) => {
268+ const highlighted = searchTerm
269+ ? country.replace(
270+ new RegExp('(' + searchTerm + ')', 'gi'),
271+ '<mark>$1</mark>'
272+ )
273+ : country;
274+
275+ return \`
276+ <div
277+ class="suggestion-item"
278+ role="option"
279+ id="suggestion-\${index}"
280+ aria-selected="false"
281+ data-value="\${country}"
282+ >
283+ \${highlighted}
284+ </div>
285+ \`;
286+ })
287+ .join('');
288+
289+ suggestionsList.classList.add('open');
290+ input.setAttribute('aria-expanded', 'true');
291+ statusElement.textContent = \`\${countries.length} suggestions available\`;
292+ currentHighlightedIndex = -1;
293+
294+ // Add click handlers to suggestions
295+ document.querySelectorAll('.suggestion-item').forEach((item, index) => {
296+ item.addEventListener('click', function() {
297+ selectCountry(this.dataset.value);
298+ });
299+ });
300+ }
301+
302+ // Close suggestions
303+ function closeSuggestions() {
304+ suggestionsList.classList.remove('open');
305+ input.setAttribute('aria-expanded', 'false');
306+ currentHighlightedIndex = -1;
307+ removeHighlights();
308+ }
309+
310+ // Highlight next suggestion
311+ function highlightNext() {
312+ const items = document.querySelectorAll('.suggestion-item');
313+ if (items.length === 0) return;
314+
315+ removeHighlights();
316+ currentHighlightedIndex = Math.min(currentHighlightedIndex + 1, items.length - 1);
317+ highlightItem(currentHighlightedIndex);
318+ }
319+
320+ // Highlight previous suggestion
321+ function highlightPrevious() {
322+ const items = document.querySelectorAll('.suggestion-item');
323+ if (items.length === 0) return;
324+
325+ removeHighlights();
326+ currentHighlightedIndex = Math.max(currentHighlightedIndex - 1, -1);
327+
328+ if (currentHighlightedIndex >= 0) {
329+ highlightItem(currentHighlightedIndex);
330+ }
331+ }
332+
333+ // Highlight specific item
334+ function highlightItem(index) {
335+ const items = document.querySelectorAll('.suggestion-item');
336+ if (items[index]) {
337+ items[index].classList.add('highlighted');
338+ items[index].setAttribute('aria-selected', 'true');
339+ input.setAttribute('aria-activedescendant', \`suggestion-\${index}\`);
340+ items[index].scrollIntoView({ block: 'nearest' });
341+ }
342+ }
343+
344+ // Remove all highlights
345+ function removeHighlights() {
346+ document.querySelectorAll('.suggestion-item').forEach(item => {
347+ item.classList.remove('highlighted');
348+ item.setAttribute('aria-selected', 'false');
349+ });
350+ input.removeAttribute('aria-activedescendant');
351+ }
352+
353+ // Select a country
354+ function selectCountry(country) {
355+ input.value = country;
356+ clearButton.classList.add('visible');
357+ closeSuggestions();
358+ statusElement.textContent = \`Selected: \${country}\`;
359+ }
360+
361+ // Close suggestions when clicking outside
362+ document.addEventListener('click', function(e) {
363+ if (!e.target.closest('.autocomplete')) {
364+ closeSuggestions();
365+ }
366+ });
367+ </script>
368+ ` ;
0 commit comments