Skip to content

Commit f43f9dd

Browse files
committed
Add [] navigation to the Python lessons
1 parent e53df3c commit f43f9dd

1 file changed

Lines changed: 92 additions & 6 deletions

File tree

‎ui/src/components/lesson/LessonNav.astro‎

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ const { prevLesson, nextLesson, activeLang = 'en' } = Astro.props;
1717
const t = useTranslations(activeLang as any);
1818
---
1919

20-
<nav class="lesson-nav" aria-label="Lesson navigation">
20+
<nav class="lesson-nav" aria-label="Lesson">
2121
{prevLesson ? (
2222
<a
2323
href={`/ultimate-python/${activeLang}/${prevLesson.path}/`}
2424
class="lesson-nav-btn prev"
25+
aria-keyshortcuts="["
26+
data-lesson-nav="prev"
2527
>
2628
<span class="nav-direction">
27-
<ChevronLeft size={14} />
29+
<ChevronLeft size={14} aria-hidden="true" />
2830
<span>{t('nav.prev')}</span>
31+
<kbd class="nav-kbd" aria-hidden="true">
32+
[
33+
</kbd>
2934
</span>
3035
<span class="nav-title">{prevLesson.name}</span>
3136
</a>
@@ -37,10 +42,15 @@ const t = useTranslations(activeLang as any);
3742
<a
3843
href={`/ultimate-python/${activeLang}/${nextLesson.path}/`}
3944
class="lesson-nav-btn next"
45+
aria-keyshortcuts="]"
46+
data-lesson-nav="next"
4047
>
4148
<span class="nav-direction">
49+
<kbd class="nav-kbd" aria-hidden="true">
50+
]
51+
</kbd>
4252
<span>{t('nav.next')}</span>
43-
<ChevronRight size={14} />
53+
<ChevronRight size={14} aria-hidden="true" />
4454
</span>
4555
<span class="nav-title">{nextLesson.name}</span>
4656
</a>
@@ -73,13 +83,19 @@ const t = useTranslations(activeLang as any);
7383
text-decoration: none;
7484
}
7585

76-
.lesson-nav-btn:hover {
86+
.lesson-nav-btn:hover,
87+
.lesson-nav-btn:focus-visible {
7788
background-color: #2d2d30;
7889
border-color: var(--forest-green-light);
7990
color: #ffffff;
8091
transform: translateY(-1px);
8192
}
8293

94+
.lesson-nav-btn:focus-visible {
95+
outline: 2px solid #52b788;
96+
outline-offset: 2px;
97+
}
98+
8399
.lesson-nav-btn.next {
84100
margin-left: auto;
85101
align-items: flex-end;
@@ -92,14 +108,37 @@ const t = useTranslations(activeLang as any);
92108
text-transform: uppercase;
93109
letter-spacing: 0.05em;
94110
font-family: var(--font-sans);
95-
margin-bottom: 0.35rem;
111+
margin-bottom: 0.5rem;
96112
line-height: 1.2;
97113
display: inline-flex;
98114
align-items: center;
99115
gap: 0.35rem;
100116
}
101117

102-
.lesson-nav-btn:hover .nav-direction {
118+
.lesson-nav-btn:hover .nav-direction,
119+
.lesson-nav-btn:focus-visible .nav-direction {
120+
color: #52b788;
121+
}
122+
123+
.nav-kbd {
124+
font-family: var(--font-mono);
125+
font-size: 0.65rem;
126+
color: #9d9d9d;
127+
background-color: #1e1e1e;
128+
border: 1px solid #3c3c3c;
129+
border-radius: 3px;
130+
padding: 0.05rem 0.3rem;
131+
line-height: 1.1;
132+
text-transform: none;
133+
letter-spacing: 0;
134+
transition:
135+
border-color var(--transition-speed),
136+
color var(--transition-speed);
137+
}
138+
139+
.lesson-nav-btn:hover .nav-kbd,
140+
.lesson-nav-btn:focus-visible .nav-kbd {
141+
border-color: rgba(82, 183, 136, 0.5);
103142
color: #52b788;
104143
}
105144

@@ -130,8 +169,55 @@ const t = useTranslations(activeLang as any);
130169
min-width: 120px;
131170
}
132171

172+
.nav-kbd {
173+
display: none;
174+
}
175+
133176
.nav-title {
134177
font-size: 0.82rem;
135178
}
136179
}
137180
</style>
181+
182+
<script>
183+
import { navigate } from 'astro:transitions/client';
184+
import { mountOnPageLoad } from '../../utils/lifecycle';
185+
186+
mountOnPageLoad((signal) => {
187+
const prevLink = document.querySelector<HTMLAnchorElement>(
188+
'[data-lesson-nav="prev"]'
189+
);
190+
const nextLink = document.querySelector<HTMLAnchorElement>(
191+
'[data-lesson-nav="next"]'
192+
);
193+
if (!prevLink && !nextLink) return;
194+
195+
document.addEventListener(
196+
'keydown',
197+
(e) => {
198+
if (e.defaultPrevented || e.repeat) return;
199+
if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) return;
200+
if (e.key !== '[' && e.key !== ']') return;
201+
202+
const active = document.activeElement as HTMLElement | null;
203+
if (
204+
active &&
205+
(active.tagName === 'INPUT' ||
206+
active.tagName === 'TEXTAREA' ||
207+
active.tagName === 'SELECT' ||
208+
active.isContentEditable)
209+
) {
210+
return;
211+
}
212+
213+
const targetLink = e.key === '[' ? prevLink : nextLink;
214+
const href = targetLink?.getAttribute('href');
215+
if (href) {
216+
e.preventDefault();
217+
navigate(href);
218+
}
219+
},
220+
{ signal }
221+
);
222+
});
223+
</script>

0 commit comments

Comments
 (0)