JSFiddle - React, Tailwind, and code Playground
by Patrick Ludewig
HTML
<div class="country-language-selector">
<!-- Trigger button for the selector -->
<button id="selector-trigger" class="selector-trigger" aria-label="Select country and language">
<span class="flag">
{% if localization.country.iso_code == 'AT' %}
🇦🇹
{% elsif localization.country.iso_code == 'DE' %}
🇩🇪
{% elsif localization.country.iso_code == 'CH' %}
🇨🇭
{% elsif localization.country.iso_code == 'FR' %}
🇫🇷
{% else %}
🌐
{% endif %}
</span>
<span class="country-name">{{ localization.country.name }}</span>
<span class="separator">|</span>
<span class="language-name">{{ localization.language.endonym_name | capitalize }}</span>
<svg class="chevron-icon" width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L5 5L9 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<!-- Desktop Modal -->
<div id="desktop-modal" class="selector-modal desktop-modal" role="dialog" aria-modal="true" aria-labelledby="desktop-title">
<div class="modal-content">
<div class="modal-header">
<button class="close-button" aria-label="Close">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="country-section">
<h2 id="desktop-country-title" class="section-title">Lieferland</h2>
<p class="section-description">Wähle das Land aus, in dem deine Lieferung gehen soll.</p>
<form id="desktop-country-form" class="country-form">
{% for country in localization.available_countries %}
<div class="option-item">
...
CSS
/* Base styles */
.country-language-selector {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: #000;
position: relative;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Trigger button */
.selector-trigger {
display: flex;
align-items: center;
background: none;
border: none;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
}
.selector-trigger .flag {
margin-right: 8px;
font-size: 18px;
}
.selector-trigger .separator {
margin: 0 8px;
color: #666;
}
.selector-trigger .chevron-icon {
margin-left: 8px;
}
/* Modal base styles */
.selector-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow: auto;
}
.selector-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background-color: #fff;
max-width: 800px;
width: 100%;
margin: 0 auto;
position: relative;
padding: 32px;
}
.modal-header {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
}
.close-button {
background: none;
border: none;
cursor: pointer;
padding: 4px;
}
.section-title {
font-size: 24px;
font-weight: bold;
margin: 0 0 8px 0;
}
.section-description {
font-size: 14px;
color: #333;
margin: 0 0 16px 0;
}
/* Option items */
.option-item {
margin-bottom: 8px;
}
.option-label {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
border-radius: 2px;
transition: background-color 0.2s;
}
.country-form .option-label {
background-color: #fff;
}
.language-form .option-label {
background-color: #f5f5f5;
}
.option-label .flag {
font-size: 24px;
margin-right: 12px;
}
.option-text {
flex-grow: 1;
}
.check-icon {
display:...
JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Elements
const selectorTrigger = document.getElementById('selector-trigger');
const desktopModal = document.getElementById('desktop-modal');
const mobileCountryModal = document.getElementById('mobile-country-modal');
const mobileLanguageModal = document.getElementById('mobile-language-modal');
const closeButtons = document.querySelectorAll('.close-button');
const desktopSubmitButton = document.getElementById('desktop-submit');
const mobileSubmitButton = document.getElementById('mobile-submit');
// Country and language forms
const desktopCountryForm = document.getElementById('desktop-country-form');
const desktopLanguageForm = document.getElementById('desktop-language-form');
const mobileCountryForm = document.getElementById('mobile-country-form');
const mobileLanguageForm = document.getElementById('mobile-language-form');
// Check if we're on mobile
const isMobile = () => window.innerWidth < 768;
// Open the appropriate modal
selectorTrigger.addEventListener('click', function() {
if (isMobile()) {
mobileCountryModal.classList.add('active');
} else {
desktopModal.classList.add('active');
}
});
// Close all modals
function closeAllModals() {
desktopModal.classList.remove('active');
mobileCountryModal.classList.remove('active');
mobileLanguageModal.classList.remove('active');
}
// Close button event listeners
closeButtons.forEach(button => {
button.addEventListener('click', closeAllModals);
});
// Close modal when clicking outside
window.addEventListener('click', function(event) {
if (event.target === desktopModal ||
event.target === mobileCountryModal ||
event.target === mobileLanguageModal) {
closeAllModals();
}
});
// Handle country selection in mobile view
const mobileCountryInputs = mobileCountryForm.querySelectorAll('input[type="radio"]');
...