mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 16:29:40 +02:00
Add new reusable components to shared-landing-ui package: - AppScrollerSection, TimelineSection, MasonryGridSection, PrinciplesSection - LegalPageTemplate for privacy/terms/cookies/imprint pages - Navigation component with mobile menu and language switcher - GradientText and LanguageSwitcher atoms - i18n system with getLangFromUrl, useTranslations, localizePath - Theme files for picture (indigo), chat (blue), zitare (teal) Add legal pages to ManaDeck and Chat landing pages: - privacy, terms, cookies, imprint pages using shared template - Updated footers with cookies link
510 lines
10 KiB
Text
510 lines
10 KiB
Text
---
|
|
/**
|
|
* Navigation - Shared header navigation component
|
|
*
|
|
* Usage:
|
|
* ```astro
|
|
* <Navigation
|
|
* brand={{ name: 'MyApp', logo: '/logo.svg', href: '/' }}
|
|
* links={[
|
|
* { label: 'Features', href: '#features' },
|
|
* { label: 'Pricing', href: '/pricing' },
|
|
* { label: 'Docs', href: 'https://docs.example.com', external: true }
|
|
* ]}
|
|
* ctaButton={{ text: 'Get Started', href: '/signup' }}
|
|
* showLanguageSwitcher={true}
|
|
* currentLang="en"
|
|
* languages={{ de: 'Deutsch', en: 'English', fr: 'Français' }}
|
|
* />
|
|
* ```
|
|
*/
|
|
|
|
export interface NavLink {
|
|
label: string;
|
|
href: string;
|
|
external?: boolean;
|
|
}
|
|
|
|
export interface Brand {
|
|
name: string;
|
|
logo?: string;
|
|
href?: string;
|
|
}
|
|
|
|
export interface CtaButton {
|
|
text: string;
|
|
href: string;
|
|
}
|
|
|
|
interface Props {
|
|
brand: Brand;
|
|
links?: NavLink[];
|
|
ctaButton?: CtaButton;
|
|
showLanguageSwitcher?: boolean;
|
|
currentLang?: string;
|
|
languages?: Record<string, string>;
|
|
getLocalizedPath?: (lang: string) => string;
|
|
class?: string;
|
|
}
|
|
|
|
const {
|
|
brand,
|
|
links = [],
|
|
ctaButton,
|
|
showLanguageSwitcher = false,
|
|
currentLang = 'en',
|
|
languages = {},
|
|
getLocalizedPath,
|
|
class: className = '',
|
|
} = Astro.props;
|
|
---
|
|
|
|
<header class:list={['nav-header', className]}>
|
|
<div class="nav-container">
|
|
<!-- Brand -->
|
|
<a href={brand.href || '/'} class="nav-brand">
|
|
{
|
|
brand.logo ? (
|
|
<img src={brand.logo} alt={brand.name} class="nav-logo" />
|
|
) : (
|
|
<span class="nav-brand-text">{brand.name}</span>
|
|
)
|
|
}
|
|
</a>
|
|
|
|
<!-- Desktop Navigation -->
|
|
<nav class="nav-links">
|
|
{
|
|
links.map((link) => (
|
|
<a
|
|
href={link.href}
|
|
class="nav-link"
|
|
target={link.external ? '_blank' : undefined}
|
|
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
>
|
|
{link.label}
|
|
{link.external && (
|
|
<svg class="nav-external-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
/>
|
|
</svg>
|
|
)}
|
|
</a>
|
|
))
|
|
}
|
|
</nav>
|
|
|
|
<!-- Right side: Language switcher & CTA -->
|
|
<div class="nav-right">
|
|
{
|
|
showLanguageSwitcher && Object.keys(languages).length > 0 && (
|
|
<div class="nav-language">
|
|
<button class="language-trigger" aria-haspopup="true" aria-expanded="false">
|
|
<span>{languages[currentLang] || currentLang.toUpperCase()}</span>
|
|
<svg class="language-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M19 9l-7 7-7-7"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
<div class="language-dropdown" role="menu">
|
|
{Object.entries(languages).map(([code, label]) => (
|
|
<a
|
|
href={getLocalizedPath ? getLocalizedPath(code) : `/${code}`}
|
|
class:list={[
|
|
'language-option',
|
|
{ 'language-option-active': code === currentLang },
|
|
]}
|
|
role="menuitem"
|
|
>
|
|
{label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
{
|
|
ctaButton && (
|
|
<a href={ctaButton.href} class="nav-cta">
|
|
{ctaButton.text}
|
|
</a>
|
|
)
|
|
}
|
|
|
|
<!-- Mobile Menu Toggle -->
|
|
<button class="nav-mobile-toggle" aria-label="Toggle menu" aria-expanded="false">
|
|
<svg class="nav-hamburger" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
<svg class="nav-close" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
<div class="nav-mobile-menu">
|
|
<nav class="nav-mobile-links">
|
|
{
|
|
links.map((link) => (
|
|
<a
|
|
href={link.href}
|
|
class="nav-mobile-link"
|
|
target={link.external ? '_blank' : undefined}
|
|
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
))
|
|
}
|
|
</nav>
|
|
{
|
|
ctaButton && (
|
|
<a href={ctaButton.href} class="nav-mobile-cta">
|
|
{ctaButton.text}
|
|
</a>
|
|
)
|
|
}
|
|
</div>
|
|
</header>
|
|
|
|
<style>
|
|
.nav-header {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
background: var(--color-background-page);
|
|
border-bottom: 1px solid var(--color-border);
|
|
backdrop-filter: blur(10px);
|
|
background: color-mix(in srgb, var(--color-background-page) 80%, transparent);
|
|
}
|
|
|
|
.nav-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 0 24px;
|
|
height: 72px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 32px;
|
|
}
|
|
|
|
.nav-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
text-decoration: none;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nav-logo {
|
|
height: 36px;
|
|
width: auto;
|
|
}
|
|
|
|
.nav-brand-text {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--color-text-primary);
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.nav-links {
|
|
display: none;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.nav-links {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.nav-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 8px 16px;
|
|
color: var(--color-text-secondary);
|
|
text-decoration: none;
|
|
font-size: 0.9375rem;
|
|
font-weight: 500;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.nav-link:hover {
|
|
color: var(--color-text-primary);
|
|
background: var(--color-background-card);
|
|
}
|
|
|
|
.nav-external-icon {
|
|
width: 14px;
|
|
height: 14px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.nav-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nav-language {
|
|
position: relative;
|
|
display: none;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.nav-language {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
.language-trigger {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 8px 12px;
|
|
background: transparent;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.language-trigger:hover {
|
|
border-color: var(--color-border-hover);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.language-chevron {
|
|
width: 16px;
|
|
height: 16px;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.nav-language.open .language-chevron {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.language-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 4px);
|
|
right: 0;
|
|
min-width: 140px;
|
|
background: var(--color-background-card);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-8px);
|
|
transition: all 0.2s ease;
|
|
z-index: 100;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.nav-language.open .language-dropdown {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.language-option {
|
|
display: block;
|
|
padding: 10px 16px;
|
|
color: var(--color-text-secondary);
|
|
text-decoration: none;
|
|
font-size: 0.875rem;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.language-option:hover {
|
|
background: var(--color-background-card-hover);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.language-option-active {
|
|
color: var(--color-primary);
|
|
background: var(--color-primary-glow);
|
|
}
|
|
|
|
.nav-cta {
|
|
display: none;
|
|
padding: 10px 20px;
|
|
background: var(--color-primary);
|
|
color: white;
|
|
text-decoration: none;
|
|
font-size: 0.9375rem;
|
|
font-weight: 600;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.nav-cta {
|
|
display: inline-flex;
|
|
}
|
|
}
|
|
|
|
.nav-cta:hover {
|
|
background: var(--color-primary-hover);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px var(--color-primary-glow);
|
|
}
|
|
|
|
.nav-mobile-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 8px;
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--color-text-primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.nav-mobile-toggle {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.nav-hamburger,
|
|
.nav-close {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.nav-close {
|
|
display: none;
|
|
}
|
|
|
|
.nav-header.mobile-open .nav-hamburger {
|
|
display: none;
|
|
}
|
|
|
|
.nav-header.mobile-open .nav-close {
|
|
display: block;
|
|
}
|
|
|
|
.nav-mobile-menu {
|
|
display: none;
|
|
padding: 16px 24px 24px;
|
|
background: var(--color-background-page);
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
|
|
.nav-header.mobile-open .nav-mobile-menu {
|
|
display: block;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.nav-mobile-menu {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
.nav-mobile-links {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.nav-mobile-link {
|
|
display: block;
|
|
padding: 12px 16px;
|
|
color: var(--color-text-secondary);
|
|
text-decoration: none;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.nav-mobile-link:hover {
|
|
color: var(--color-text-primary);
|
|
background: var(--color-background-card);
|
|
}
|
|
|
|
.nav-mobile-cta {
|
|
display: block;
|
|
margin-top: 16px;
|
|
padding: 14px 24px;
|
|
background: var(--color-primary);
|
|
color: white;
|
|
text-decoration: none;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.nav-mobile-cta:hover {
|
|
background: var(--color-primary-hover);
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Mobile menu toggle
|
|
document.querySelectorAll('.nav-mobile-toggle').forEach((toggle) => {
|
|
toggle.addEventListener('click', () => {
|
|
const header = toggle.closest('.nav-header');
|
|
header?.classList.toggle('mobile-open');
|
|
});
|
|
});
|
|
|
|
// Language dropdown toggle
|
|
document.querySelectorAll('.nav-language .language-trigger').forEach((trigger) => {
|
|
trigger.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const dropdown = trigger.closest('.nav-language');
|
|
dropdown?.classList.toggle('open');
|
|
});
|
|
});
|
|
|
|
// Close dropdowns when clicking outside
|
|
document.addEventListener('click', () => {
|
|
document.querySelectorAll('.nav-language.open').forEach((dropdown) => {
|
|
dropdown.classList.remove('open');
|
|
});
|
|
});
|
|
|
|
// Close mobile menu on escape
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
document.querySelectorAll('.nav-header.mobile-open').forEach((header) => {
|
|
header.classList.remove('mobile-open');
|
|
});
|
|
document.querySelectorAll('.nav-language.open').forEach((dropdown) => {
|
|
dropdown.classList.remove('open');
|
|
});
|
|
}
|
|
});
|
|
</script>
|