Skip to content

Commit 9fb8a88

Browse files
Chityalaakhilakhildevsargam
authored andcommitted
feature: auto scroll to the selected week in sidebar and closing sidebar on ESC (#1531)
* feature: auto scroll to the selected week in sidebar and closing sidebar on ESC * formatting --------- Co-authored-by: akhil <akhil49@duck.com> Co-authored-by: Sargam <sargampoudel100@gmail.com>
1 parent 454dc26 commit 9fb8a88

File tree

1 file changed

+49
-7
lines changed

1 file changed

+49
-7
lines changed

src/components/Sidebar.tsx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,19 @@ export function Sidebar({
8383
}
8484
};
8585

86+
// listen for ESC key and close the sidebar
87+
const handleEscape = (event: KeyboardEvent) => {
88+
if (event.key === 'Escape') {
89+
closeSidebar();
90+
}
91+
};
92+
8693
document.addEventListener('mousedown', handleClickOutside);
94+
document.addEventListener('keydown', handleEscape);
8795

8896
return () => {
8997
document.removeEventListener('mousedown', handleClickOutside);
98+
document.removeEventListener('keydown', handleEscape);
9099
};
91100
}, [sidebarRef]);
92101

@@ -129,6 +138,30 @@ export function Sidebar({
129138
[courseId, findPathToContent, fullCourseContent],
130139
);
131140

141+
const activeItemRef = useRef<HTMLDivElement | HTMLAnchorElement | null>(null);
142+
143+
useEffect(() => {
144+
if (sidebarOpen && activeItemRef.current) {
145+
activeItemRef.current.scrollIntoView({
146+
behavior: 'smooth',
147+
block: 'center',
148+
});
149+
// focus on the active item
150+
if (activeItemRef.current instanceof HTMLAnchorElement) {
151+
activeItemRef.current.focus();
152+
} else if (activeItemRef.current instanceof HTMLDivElement) {
153+
// check for the first focusable element and focus on it
154+
const firstFocusableElement =
155+
activeItemRef.current.querySelector('button, a');
156+
if (firstFocusableElement) {
157+
(
158+
firstFocusableElement as HTMLButtonElement | HTMLAnchorElement
159+
).focus();
160+
}
161+
}
162+
}
163+
}, [sidebarOpen]);
164+
132165
const renderContent = useCallback(
133166
(contents: FullCourseContent[]) => {
134167
return contents.map((content) => {
@@ -140,6 +173,11 @@ export function Sidebar({
140173
key={content.id}
141174
value={`item-${content.id}`}
142175
className={`rounded-md border-none ${isActiveContent ? 'bg-primary/5' : ''}`}
176+
ref={
177+
isActiveContent
178+
? (activeItemRef as React.RefObject<HTMLDivElement>)
179+
: null
180+
}
143181
>
144182
<AccordionTrigger className="rounded-md px-4 text-lg font-medium capitalize">
145183
{content.title}
@@ -156,6 +194,11 @@ export function Sidebar({
156194
key={content.id}
157195
href={navigateToContent(content.id) || '#'}
158196
className={`flex w-full cursor-pointer items-center rounded-md p-4 tracking-tight hover:bg-primary/10 ${isActiveContent ? 'bg-primary/10' : ''}`}
197+
ref={
198+
isActiveContent
199+
? (activeItemRef as React.RefObject<HTMLAnchorElement>)
200+
: null
201+
}
159202
>
160203
<div className="flex w-full items-center justify-between gap-2">
161204
<div className="flex items-center gap-2">
@@ -186,10 +229,7 @@ export function Sidebar({
186229
);
187230

188231
return (
189-
190-
<div className="sticky top-[55px] z-20 bg-background py-2">
191-
192-
232+
<div className="sticky top-[72px] z-20 bg-background py-2">
193233
<Button
194234
ref={buttonRef}
195235
onClick={() => setSidebarOpen((s) => !s)}
@@ -209,11 +249,9 @@ export function Sidebar({
209249
variants={sidebarVariants}
210250
className="fixed right-0 top-0 z-[99999] flex h-screen w-full flex-col gap-4 overflow-y-auto rounded-r-lg border-l border-primary/10 bg-neutral-50 dark:bg-neutral-900 md:max-w-[30vw]"
211251
>
212-
213252
<div className="sticky top-0 z-10 flex items-center justify-between border-b border-primary/10 bg-neutral-50 p-5 dark:bg-neutral-900">
214253
{' '}
215254
<h4 className="text-xl font-bold tracking-tighter text-primary lg:text-2xl">
216-
217255
Course Content
218256
</h4>
219257
<Button
@@ -224,7 +262,11 @@ export function Sidebar({
224262
<X className="size-5" />
225263
</Button>
226264
</div>
227-
<Accordion type="multiple" className="w-full px-4 pb-20 capitalize">
265+
<Accordion
266+
type="multiple"
267+
defaultValue={currentActiveContentIds.map((num) => `item-${num}`)}
268+
className="w-full px-4 capitalize"
269+
>
228270
{memoizedContent}
229271
</Accordion>
230272
</motion.div>

0 commit comments

Comments
 (0)