mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 20:09:41 +02:00
- apps/bauntown: Developer community website (Astro landing) - apps/presi: Presentation project - games/voxel-lava: Voxel lava game (SvelteKit) - games/whopixels: Whopixels game 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
356 lines
No EOL
9.4 KiB
Text
356 lines
No EOL
9.4 KiB
Text
---
|
|
import { languages } from '../i18n/ui';
|
|
import { getLangFromUrl } from '../utils/i18n';
|
|
|
|
const lang = getLangFromUrl(Astro.url);
|
|
const currentPath = Astro.url.pathname;
|
|
|
|
const getPathInLang = (targetLang: string) => {
|
|
// Split URL path into segments
|
|
const segments = currentPath.split('/').filter(Boolean);
|
|
|
|
// First segment is always the language code
|
|
if (segments.length === 0) {
|
|
// Homepage case
|
|
return `/${targetLang}`;
|
|
} else if (Object.keys(languages).includes(segments[0])) {
|
|
// Normal case: first segment is a language code
|
|
segments[0] = targetLang;
|
|
} else {
|
|
// Fallback for any non-language URLs
|
|
segments.unshift(targetLang);
|
|
}
|
|
|
|
return `/${segments.join('/')}`;
|
|
};
|
|
|
|
// Props for the component
|
|
interface Props {
|
|
dropdownStyle?: boolean;
|
|
}
|
|
|
|
const { dropdownStyle = false } = Astro.props;
|
|
---
|
|
|
|
{
|
|
dropdownStyle ? (
|
|
<div class="language-dropdown">
|
|
<button class="dropdown-button">
|
|
<span class="current-lang">{languages[lang]}</span>
|
|
<svg class="dropdown-arrow" xmlns="http://www.w3.org/2000/svg" width="12" height="6" viewBox="0 0 12 6" fill="none">
|
|
<path d="M1 1L6 5L11 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
</svg>
|
|
</button>
|
|
<div class="dropdown-menu">
|
|
{Object.entries(languages).map(([code, name]) => (
|
|
<a
|
|
href={getPathInLang(code)}
|
|
class={lang === code ? 'active' : ''}
|
|
hreflang={code}
|
|
lang={code}
|
|
>
|
|
{name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div class="language-selector">
|
|
{Object.entries(languages).map(([code, name]) => (
|
|
<a
|
|
href={getPathInLang(code)}
|
|
class={lang === code ? 'active' : ''}
|
|
hreflang={code}
|
|
lang={code}
|
|
>
|
|
{name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
<style>
|
|
/* Shared styles */
|
|
.language-selector,
|
|
.language-dropdown {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.language-selector a,
|
|
.dropdown-menu a {
|
|
padding: 0.35rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.875rem;
|
|
color: var(--text-color);
|
|
text-decoration: none;
|
|
transition: background-color 0.2s, color 0.2s;
|
|
}
|
|
|
|
.language-selector a:hover,
|
|
.dropdown-menu a:hover {
|
|
background-color: var(--hover-bg);
|
|
}
|
|
|
|
.language-selector a.active,
|
|
.dropdown-menu a.active {
|
|
font-weight: bold;
|
|
background-color: var(--hover-bg);
|
|
}
|
|
|
|
/* Horizontal selector styles */
|
|
.language-selector {
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
/* Dropdown styles */
|
|
.language-dropdown {
|
|
position: relative;
|
|
z-index: 150;
|
|
}
|
|
|
|
.dropdown-button {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 0.75rem;
|
|
background: none;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 0.375rem;
|
|
font-size: 0.875rem;
|
|
color: var(--text-color);
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
/* Fix text color in dark mode */
|
|
:global(:root.dark) .dropdown-button {
|
|
color: #f9fafb;
|
|
}
|
|
|
|
.dropdown-button:hover {
|
|
background-color: var(--hover-bg);
|
|
}
|
|
|
|
.dropdown-arrow {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.dropdown-menu {
|
|
position: absolute;
|
|
top: calc(100% + 0.25rem);
|
|
right: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 120px;
|
|
background-color: var(--card-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 0.375rem;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
visibility: hidden;
|
|
transition: opacity 0.2s, transform 0.2s, visibility 0s 0.2s;
|
|
z-index: 200; /* Higher z-index to ensure it appears above other elements */
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Initial state for dropdowns */
|
|
.dropdown-menu {
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.dropdown-menu.show {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
visibility: visible;
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
}
|
|
|
|
.dropdown-menu a {
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0;
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
|
|
/* Fix link color in dark mode */
|
|
:global(:root.dark) .dropdown-menu a {
|
|
color: #f9fafb;
|
|
}
|
|
|
|
/* Only show dropdown on desktop hover, not mobile */
|
|
@media (min-width: 769px) {
|
|
.language-dropdown:hover .dropdown-menu,
|
|
.dropdown-button:focus-within .dropdown-menu {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
visibility: visible;
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
}
|
|
|
|
.language-dropdown:hover .dropdown-arrow,
|
|
.dropdown-button:focus-within .dropdown-arrow,
|
|
.dropdown-button[aria-expanded="true"] .dropdown-arrow {
|
|
transform: rotate(180deg);
|
|
}
|
|
}
|
|
|
|
/* Show dropdown in mobile when active */
|
|
.dropdown-menu.show {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
visibility: visible;
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
}
|
|
|
|
/* Standard animation for all dropdowns */
|
|
.mobile-menu-controls .dropdown-menu.show {
|
|
transform: none !important;
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
/* Mobile styles */
|
|
@media (max-width: 768px) {
|
|
.language-selector {
|
|
flex-direction: row;
|
|
width: 100%;
|
|
justify-content: flex-start;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.language-selector a {
|
|
font-size: 1rem;
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
|
|
.language-dropdown {
|
|
width: 100%;
|
|
position: relative; /* Keep relative positioning for proper dropdown placement */
|
|
}
|
|
|
|
.dropdown-button {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
padding: 1rem 1.25rem;
|
|
font-size: 1.1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Important: absolute positioning for mobile dropdown */
|
|
.dropdown-menu {
|
|
position: absolute;
|
|
width: 100%;
|
|
left: 0;
|
|
top: 100%; /* Show below the button */
|
|
z-index: 2000; /* Extra high z-index */
|
|
max-height: calc(100vh - 200px);
|
|
overflow-y: auto;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* Style for mobile menu dropdown - directly attached to button */
|
|
.mobile-menu-controls .dropdown-menu {
|
|
position: absolute;
|
|
width: 100%; /* Match button width */
|
|
left: 0;
|
|
top: 100%; /* Position below the button */
|
|
bottom: auto;
|
|
margin-top: 2px; /* Small gap */
|
|
max-height: 180px;
|
|
border-radius: 8px;
|
|
z-index: 2000;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
background-color: var(--card-bg);
|
|
}
|
|
|
|
.dropdown-menu a {
|
|
padding: 1rem;
|
|
font-size: 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// For accessibility - allow keyboard navigation
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Handle all dropdown buttons in the document, not just the first one
|
|
const dropdownButtons = document.querySelectorAll('.dropdown-button');
|
|
|
|
dropdownButtons.forEach(dropdownButton => {
|
|
if (!dropdownButton) return;
|
|
|
|
// Open/close dropdown on click
|
|
dropdownButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const isExpanded = dropdownButton.getAttribute('aria-expanded') === 'true';
|
|
dropdownButton.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
|
|
|
|
const menu = dropdownButton.nextElementSibling;
|
|
if (menu && menu.classList.contains('dropdown-menu')) {
|
|
menu.classList.toggle('show');
|
|
|
|
if (!isExpanded) {
|
|
// Focus the first menu item
|
|
const firstItem = menu.querySelector('a');
|
|
if (firstItem) firstItem.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
dropdownButton.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
dropdownButton.click();
|
|
}
|
|
|
|
// Close when escape is pressed
|
|
if (e.key === 'Escape') {
|
|
const menu = dropdownButton.nextElementSibling;
|
|
if (menu && menu.classList.contains('show')) {
|
|
menu.classList.remove('show');
|
|
dropdownButton.setAttribute('aria-expanded', 'false');
|
|
dropdownButton.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize ARIA attributes
|
|
dropdownButton.setAttribute('aria-haspopup', 'true');
|
|
dropdownButton.setAttribute('aria-expanded', 'false');
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
dropdownButtons.forEach(dropdownButton => {
|
|
if (!dropdownButton.contains(e.target)) {
|
|
const menu = dropdownButton.nextElementSibling;
|
|
if (menu && menu.classList.contains('show')) {
|
|
menu.classList.remove('show');
|
|
dropdownButton.setAttribute('aria-expanded', 'false');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add click listener to all dropdown menu items
|
|
const dropdownMenuItems = document.querySelectorAll('.dropdown-menu a');
|
|
dropdownMenuItems.forEach(item => {
|
|
item.addEventListener('click', (e) => {
|
|
const menu = item.closest('.dropdown-menu');
|
|
if (menu) {
|
|
menu.classList.remove('show');
|
|
const button = menu.previousElementSibling;
|
|
if (button) {
|
|
button.setAttribute('aria-expanded', 'false');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script> |