@@ -17,15 +17,20 @@ const { prevLesson, nextLesson, activeLang = 'en' } = Astro.props;
1717const 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