Skip to content

Commit 359aef1

Browse files
committed
chore: upgrade user interactions
1 parent cb9f761 commit 359aef1

File tree

11 files changed

+551
-76
lines changed

11 files changed

+551
-76
lines changed

messages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"nav_community": "Community",
2323
"nav_faq": "FAQ",
2424
"nav_contribute": "Contribute on GitHub",
25+
"language_switcher_label": "Change language",
2526
"landing_courses_badge": "Featured Courses",
2627
"landing_courses_title": "Start Learning Today",
2728
"landing_courses_description": "Explore our most popular courses and begin your journey",

messages/tr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"nav_community": "Topluluk",
2323
"nav_faq": "SSS",
2424
"nav_contribute": "GitHub'da Katkı Ver",
25+
"language_switcher_label": "Dili değiştir",
2526
"landing_courses_badge": "Öne Çıkan Kurslar",
2627
"landing_courses_title": "Bugün Öğrenmeye Başla",
2728
"landing_courses_description": "En popüler kurslarımızı keşfedin ve yolculuğunuza başlayın",
Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,120 @@
11
<script lang="ts">
2+
import { browser } from '$app/environment';
3+
import { createSelect, melt } from '@melt-ui/svelte';
4+
import { onDestroy } from 'svelte';
25
import { getLocale, setLocale, locales } from '$lib/paraglide/runtime.js';
36
import * as m from '$lib/paraglide/messages';
47
5-
let currentLocale = $state(getLocale());
6-
const languageLabels = $derived({
7-
en: m.language_name_english(),
8-
tr: m.language_name_turkish()
9-
});
8+
type Locale = (typeof locales)[number];
9+
10+
const availableLocales = locales as readonly Locale[];
11+
12+
const localeLabelGetters: Partial<Record<Locale, () => string>> = {
13+
en: m.language_name_english,
14+
tr: m.language_name_turkish
15+
};
16+
17+
const initialLocale = getLocale() as Locale;
18+
let currentLocale = $state(initialLocale);
19+
20+
function getLanguageLabel(lang: Locale) {
21+
const getter = localeLabelGetters[lang];
22+
if (getter) {
23+
return getter();
24+
}
25+
26+
if (typeof Intl !== 'undefined' && typeof Intl.DisplayNames !== 'undefined') {
27+
try {
28+
const displayNames = new Intl.DisplayNames([currentLocale, 'en'], { type: 'language' });
29+
const label = displayNames.of(lang);
30+
if (label) {
31+
return label;
32+
}
33+
} catch {
34+
// Ignore browsers without Intl.DisplayNames support for the given locale.
35+
}
36+
}
37+
38+
return lang.toUpperCase();
39+
}
40+
41+
function switchLanguage(lang: Locale) {
42+
if (!browser) {
43+
return;
44+
}
45+
46+
if (lang === getLocale()) {
47+
return;
48+
}
1049
11-
function switchLanguage(lang: 'en' | 'tr') {
1250
setLocale(lang, { reload: false });
13-
currentLocale = lang;
14-
// Force a re-render by updating the state
1551
setTimeout(() => window.location.reload(), 100);
1652
}
53+
54+
const {
55+
elements: {
56+
trigger: selectTrigger,
57+
menu: selectMenu,
58+
option: selectOption,
59+
hiddenInput: selectHiddenInput
60+
},
61+
states: { selected }
62+
} = createSelect<Locale>({
63+
defaultSelected: { value: initialLocale, label: getLanguageLabel(initialLocale) }
64+
});
65+
66+
const unsubscribe = selected.subscribe(($selected) => {
67+
const next = $selected?.value;
68+
if (!next || next === currentLocale) {
69+
return;
70+
}
71+
72+
currentLocale = next;
73+
switchLanguage(next);
74+
});
75+
76+
onDestroy(unsubscribe);
1777
</script>
1878

19-
<div class="flex gap-2 items-center">
20-
{#each locales as lang}
21-
<button
22-
onclick={() => switchLanguage(lang as 'en' | 'tr')}
23-
class="px-3 py-1 rounded-full text-sm font-medium transition {currentLocale === lang
24-
? 'bg-rose-400 text-white'
25-
: 'bg-white border-2 border-rose-300 text-rose-700 hover:bg-rose-50'}"
26-
>
27-
{languageLabels[lang as keyof typeof languageLabels]}
28-
</button>
29-
{/each}
79+
<div class="relative inline-flex items-center">
80+
<button
81+
type="button"
82+
aria-label={m.language_switcher_label()}
83+
class="flex items-center gap-2 rounded-full border-2 border-rose-300 bg-white px-4 py-2 text-sm font-medium text-rose-700 shadow-sm transition data-[state=open]:border-rose-400 data-[state=open]:ring-rose-400 hover:border-rose-400 hover:bg-rose-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-400"
84+
use:melt={$selectTrigger}
85+
>
86+
<span>{getLanguageLabel(currentLocale)}</span>
87+
<svg class="h-3 w-3 text-rose-500" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
88+
<path d="M1 1.5 5 4.5 9 1.5" stroke-linecap="round" stroke-linejoin="round" />
89+
</svg>
90+
</button>
91+
<input class="hidden" use:melt={$selectHiddenInput} />
92+
<div
93+
class="z-50 mt-2 min-w-[10rem] overflow-hidden rounded-2xl border border-rose-200 bg-white p-1 text-sm text-rose-700 shadow-xl ring-1 ring-black/5 focus:outline-none"
94+
use:melt={$selectMenu}
95+
>
96+
{#each availableLocales as lang (lang)}
97+
<button
98+
type="button"
99+
class="flex w-full items-center justify-between rounded-xl px-3 py-2 text-left transition data-[highlighted]:bg-rose-50 data-[highlighted]:text-rose-900"
100+
use:melt={$selectOption({ value: lang, label: getLanguageLabel(lang) })}
101+
>
102+
<span>{getLanguageLabel(lang)}</span>
103+
{#if lang === currentLocale}
104+
<svg
105+
class="h-4 w-4 text-rose-500"
106+
viewBox="0 0 16 16"
107+
fill="none"
108+
stroke="currentColor"
109+
stroke-width="2"
110+
stroke-linecap="round"
111+
stroke-linejoin="round"
112+
aria-hidden="true"
113+
>
114+
<path d="M4 8l3 3 5-6" />
115+
</svg>
116+
{/if}
117+
</button>
118+
{/each}
119+
</div>
30120
</div>
Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,98 @@
11
<script lang="ts">
22
import * as m from '$lib/paraglide/messages';
33
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
4+
import { onMount } from 'svelte';
5+
6+
let isScrolled = $state(false);
7+
let mobileMenuOpen = $state(false);
8+
9+
onMount(() => {
10+
const handleScroll = () => {
11+
isScrolled = window.scrollY > 50;
12+
};
13+
14+
window.addEventListener('scroll', handleScroll);
15+
return () => window.removeEventListener('scroll', handleScroll);
16+
});
17+
18+
function toggleMobileMenu() {
19+
mobileMenuOpen = !mobileMenuOpen;
20+
}
21+
22+
function closeMobileMenu() {
23+
mobileMenuOpen = false;
24+
}
425
</script>
526

6-
<nav class="relative z-10 container mx-auto px-6 py-8">
7-
<div class="flex items-center justify-between">
8-
<a href="/" class="text-2xl font-bold text-rose-800 hover:text-rose-900 transition">
9-
{m.site_title()}
10-
</a>
11-
<div class="hidden md:flex gap-8">
12-
<a href="/#why" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_why()}</a>
13-
<a href="/#paths" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_paths()}</a>
14-
<a href="/browse-courses" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_courses()}</a>
15-
<a href="/#community" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_community()}</a>
16-
<a href="/#faq" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_faq()}</a>
17-
</div>
18-
<div class="flex items-center gap-4">
19-
<LanguageSwitcher />
20-
<a
21-
href="https://github.com/k61b/devsteps"
22-
target="_blank"
23-
rel="noreferrer"
24-
class="px-6 py-2 border-2 border-rose-300 text-rose-700 rounded-full hover:bg-rose-100 transition font-medium"
25-
>
26-
{m.nav_contribute()}
27+
<nav class={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled ? 'bg-white/95 backdrop-blur-md shadow-lg py-3' : 'py-5'}`}>
28+
<div class="container mx-auto px-6 w-full">
29+
<div class="flex items-center justify-between">
30+
<a href="/" class="text-2xl font-bold text-rose-800 hover:text-rose-900 transition">
31+
{m.site_title()}
2732
</a>
33+
34+
<!-- Desktop Navigation -->
35+
<div class="hidden md:flex gap-8">
36+
<a href="/#why" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_why()}</a>
37+
<a href="/#paths" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_paths()}</a>
38+
<a href="/browse-courses" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_courses()}</a>
39+
<a href="/#community" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_community()}</a>
40+
<a href="/#faq" class="text-rose-700 hover:text-rose-900 transition font-medium">{m.nav_faq()}</a>
41+
</div>
42+
43+
<!-- Desktop Actions -->
44+
<div class="hidden md:flex items-center gap-4">
45+
<LanguageSwitcher />
46+
<a
47+
href="https://github.com/k61b/devsteps"
48+
target="_blank"
49+
rel="noreferrer"
50+
class="px-6 py-2 border-2 border-rose-300 text-rose-700 rounded-full hover:bg-rose-100 transition font-medium"
51+
>
52+
{m.nav_contribute()}
53+
</a>
54+
</div>
55+
56+
<!-- Mobile Menu Button -->
57+
<div class="md:hidden flex items-center gap-3">
58+
<LanguageSwitcher />
59+
<button
60+
onclick={toggleMobileMenu}
61+
class="p-2 text-rose-700 hover:text-rose-900 transition"
62+
aria-label="Toggle menu"
63+
>
64+
{#if mobileMenuOpen}
65+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
66+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
67+
</svg>
68+
{:else}
69+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
70+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
71+
</svg>
72+
{/if}
73+
</button>
74+
</div>
2875
</div>
76+
77+
<!-- Mobile Menu -->
78+
{#if mobileMenuOpen}
79+
<div class="md:hidden mt-4 pb-4 border-t-2 border-rose-200 pt-4 bg-white/95 backdrop-blur-md rounded-2xl shadow-xl">
80+
<div class="flex flex-col gap-4">
81+
<a href="/#why" onclick={closeMobileMenu} class="text-rose-700 hover:text-rose-900 transition font-medium px-4 py-2 hover:bg-rose-50 rounded-lg">{m.nav_why()}</a>
82+
<a href="/#paths" onclick={closeMobileMenu} class="text-rose-700 hover:text-rose-900 transition font-medium px-4 py-2 hover:bg-rose-50 rounded-lg">{m.nav_paths()}</a>
83+
<a href="/browse-courses" onclick={closeMobileMenu} class="text-rose-700 hover:text-rose-900 transition font-medium px-4 py-2 hover:bg-rose-50 rounded-lg">{m.nav_courses()}</a>
84+
<a href="/#community" onclick={closeMobileMenu} class="text-rose-700 hover:text-rose-900 transition font-medium px-4 py-2 hover:bg-rose-50 rounded-lg">{m.nav_community()}</a>
85+
<a href="/#faq" onclick={closeMobileMenu} class="text-rose-700 hover:text-rose-900 transition font-medium px-4 py-2 hover:bg-rose-50 rounded-lg">{m.nav_faq()}</a>
86+
<a
87+
href="https://github.com/k61b/devsteps"
88+
target="_blank"
89+
rel="noreferrer"
90+
class="mx-4 mt-2 px-6 py-2 border-2 border-rose-300 text-rose-700 rounded-full hover:bg-rose-100 transition font-medium text-center"
91+
>
92+
{m.nav_contribute()}
93+
</a>
94+
</div>
95+
</div>
96+
{/if}
2997
</div>
3098
</nav>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
4+
let isVisible = $state(false);
5+
6+
onMount(() => {
7+
const handleScroll = () => {
8+
isVisible = window.scrollY > 500;
9+
};
10+
11+
window.addEventListener('scroll', handleScroll);
12+
return () => window.removeEventListener('scroll', handleScroll);
13+
});
14+
15+
function scrollToTop() {
16+
window.scrollTo({
17+
top: 0,
18+
behavior: 'smooth'
19+
});
20+
}
21+
</script>
22+
23+
{#if isVisible}
24+
<button
25+
onclick={scrollToTop}
26+
class="fixed bottom-8 right-8 z-40 p-4 bg-rose-400 hover:bg-rose-500 text-white rounded-full shadow-2xl transition-all duration-300 transform hover:scale-110 hover:-translate-y-1"
27+
aria-label="Scroll to top"
28+
>
29+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
30+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
31+
</svg>
32+
</button>
33+
{/if}

0 commit comments

Comments
 (0)