Skip to content

Commit 06ae806

Browse files
committed
feat: Make selectable Section, Subsection, unit cards in Course Outline
1 parent 2209242 commit 06ae806

6 files changed

Lines changed: 119 additions & 3 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { createContext, useCallback, useContext, useMemo, useState } from "react";
2+
3+
export type SidebarContextData = {
4+
selectedContainerId?: string;
5+
openContainerInfoSidebar: (containerId: string) => void;
6+
};
7+
8+
/**
9+
* Course Outline Sidebar Context.
10+
*
11+
* Get this using `useSidebarContext()`
12+
*
13+
*/
14+
const SidebarContext = createContext<SidebarContextData | undefined>(undefined);
15+
16+
type SidebarProviderProps = {
17+
children?: React.ReactNode;
18+
};
19+
20+
export const SidebarProvider = ({ children }: SidebarProviderProps) => {
21+
const [ selectedContainerId, setSelectedContainerId ] = useState<string | undefined>();
22+
23+
const openContainerInfoSidebar = useCallback((containerId: string) => {
24+
setSelectedContainerId(containerId);
25+
}, [setSelectedContainerId]);
26+
27+
const context = useMemo<SidebarContextData>(() => {
28+
const contextValue = {
29+
selectedContainerId,
30+
openContainerInfoSidebar,
31+
};
32+
33+
return contextValue;
34+
}, [
35+
selectedContainerId,
36+
openContainerInfoSidebar,
37+
]);
38+
39+
return (
40+
<SidebarContext.Provider value={context}>
41+
{children}
42+
</SidebarContext.Provider>
43+
);
44+
};
45+
46+
export function useSidebarContext(): SidebarContextData {
47+
const ctx = useContext(SidebarContext);
48+
if (ctx === undefined) {
49+
/* istanbul ignore next */
50+
return {
51+
selectedContainerId: undefined,
52+
openContainerInfoSidebar: () => {},
53+
};
54+
}
55+
return ctx;
56+
}

src/course-outline/drag-helper/SortableItem.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface SortableItemProps {
2121
isDraggable?: boolean;
2222
children: React.ReactNode;
2323
componentStyle?: object;
24+
onClick?: (e: React.MouseEvent) => void;
2425
}
2526

2627
const SortableItem = ({
@@ -30,6 +31,7 @@ const SortableItem = ({
3031
componentStyle,
3132
data,
3233
children,
34+
onClick,
3335
}: SortableItemProps) => {
3436
const intl = useIntl();
3537
const {
@@ -66,8 +68,18 @@ const SortableItem = ({
6668
return (
6769
<Row
6870
ref={setNodeRef}
71+
tabIndex={onClick ? 0 : -1}
6972
style={style}
7073
className="mx-0"
74+
onClick={onClick}
75+
onKeyDown={(e) => {
76+
if (!onClick) return;
77+
78+
if (e.key === "Enter" || e.key === " ") {
79+
e.preventDefault();
80+
onClick(e);
81+
}
82+
}}
7183
>
7284
<Col className="extend-margin px-0">
7385
{children}

src/course-outline/section-card/SectionCard.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { UpstreamInfoIcon } from '@src/generic/upstream-info-icon';
2929
import type { XBlock } from '@src/data/types';
3030
import { invalidateLinksQuery } from '@src/course-libraries/data/apiHooks';
3131
import messages from './messages';
32+
import { useSidebarContext } from '../common/context/SidebarContext';
3233

3334
interface SectionCardProps {
3435
section: XBlock,
@@ -77,6 +78,7 @@ const SectionCard = ({
7778
const intl = useIntl();
7879
const dispatch = useDispatch();
7980
const { activeId, overId } = useContext(DragContext);
81+
const { selectedContainerId, openContainerInfoSidebar } = useSidebarContext();
8082
const [searchParams] = useSearchParams();
8183
const locatorId = searchParams.get('show');
8284
const isScrolledToElement = locatorId === section.id;
@@ -269,6 +271,12 @@ const SectionCard = ({
269271

270272
const isDraggable = actions.draggable && (actions.allowMoveUp || actions.allowMoveDown);
271273

274+
const onClickCard = useCallback((e: React.MouseEvent) => {
275+
if (e.target === e.currentTarget) {
276+
openContainerInfoSidebar(section.id);
277+
}
278+
}, [openContainerInfoSidebar]);
279+
272280
return (
273281
<>
274282
<SortableItem
@@ -284,9 +292,15 @@ const SectionCard = ({
284292
padding: '1.75rem',
285293
...borderStyle,
286294
}}
295+
onClick={onClickCard}
287296
>
288297
<div
289-
className={`section-card ${isScrolledToElement ? 'highlight' : ''}`}
298+
className={classNames('section-card',
299+
{
300+
'highlight': isScrolledToElement,
301+
'outline-card-selected': section.id === selectedContainerId,
302+
}
303+
)}
290304
data-testid="section-card"
291305
ref={currentRef}
292306
>

src/course-outline/subsection-card/SubsectionCard.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { PreviewLibraryXBlockChanges } from '@src/course-unit/preview-changes';
3030
import type { XBlock } from '@src/data/types';
3131
import { invalidateLinksQuery } from '@src/course-libraries/data/apiHooks';
3232
import messages from './messages';
33+
import { useSidebarContext } from '../common/context/SidebarContext';
3334

3435
interface SubsectionCardProps {
3536
section: XBlock,
@@ -88,6 +89,7 @@ const SubsectionCard = ({
8889
const intl = useIntl();
8990
const dispatch = useDispatch();
9091
const { activeId, overId } = useContext(DragContext);
92+
const { selectedContainerId, openContainerInfoSidebar } = useSidebarContext();
9193
const [searchParams] = useSearchParams();
9294
const locatorId = searchParams.get('show');
9395
const isScrolledToElement = locatorId === subsection.id;
@@ -269,6 +271,12 @@ const SubsectionCard = ({
269271
closeAddLibraryUnitModal();
270272
}, [id, onAddUnitFromLibrary, closeAddLibraryUnitModal]);
271273

274+
const onClickCard = useCallback((e: React.MouseEvent) => {
275+
if (e.target === e.currentTarget) {
276+
openContainerInfoSidebar(subsection.id);
277+
}
278+
}, [openContainerInfoSidebar]);
279+
272280
return (
273281
<>
274282
<SortableItem
@@ -286,9 +294,15 @@ const SubsectionCard = ({
286294
background: '#f8f7f6',
287295
...borderStyle,
288296
}}
297+
onClick={onClickCard}
289298
>
290299
<div
291-
className={`subsection-card ${isScrolledToElement ? 'highlight' : ''}`}
300+
className={classNames('subsection-card',
301+
{
302+
'highlight': isScrolledToElement,
303+
'outline-card-selected': subsection.id === selectedContainerId,
304+
}
305+
)}
292306
data-testid="subsection-card"
293307
ref={currentRef}
294308
>

src/course-outline/unit-card/UnitCard.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
useMemo,
55
useRef,
66
} from 'react';
7+
import classNames from 'classnames';
78
import { useDispatch } from 'react-redux';
89
import { useToggle } from '@openedx/paragon';
910
import { isEmpty } from 'lodash';
@@ -24,6 +25,7 @@ import { UpstreamInfoIcon } from '@src/generic/upstream-info-icon';
2425
import { PreviewLibraryXBlockChanges } from '@src/course-unit/preview-changes';
2526
import { invalidateLinksQuery } from '@src/course-libraries/data/apiHooks';
2627
import type { XBlock } from '@src/data/types';
28+
import { useSidebarContext } from '../common/context/SidebarContext';
2729

2830
interface UnitCardProps {
2931
unit: XBlock;
@@ -70,6 +72,7 @@ const UnitCard = ({
7072
const currentRef = useRef(null);
7173
const dispatch = useDispatch();
7274
const [searchParams] = useSearchParams();
75+
const { selectedContainerId, openContainerInfoSidebar } = useSidebarContext();
7376
const locatorId = searchParams.get('show');
7477
const isScrolledToElement = locatorId === unit.id;
7578
const [isFormOpen, openForm, closeForm] = useToggle(false);
@@ -213,6 +216,12 @@ const UnitCard = ({
213216
&& !subsection.upstreamInfo?.upstreamRef
214217
);
215218

219+
const onClickCard = useCallback((e: React.MouseEvent) => {
220+
if (e.target === e.currentTarget) {
221+
openContainerInfoSidebar(unit.id);
222+
}
223+
}, [openContainerInfoSidebar]);
224+
216225
return (
217226
<>
218227
<SortableItem
@@ -229,9 +238,15 @@ const UnitCard = ({
229238
background: '#fdfdfd',
230239
...borderStyle,
231240
}}
241+
onClick={onClickCard}
232242
>
233243
<div
234-
className={`unit-card ${isScrolledToElement ? 'highlight' : ''}`}
244+
className={classNames('unit-card',
245+
{
246+
'highlight': isScrolledToElement,
247+
'outline-card-selected': unit.id === selectedContainerId,
248+
}
249+
)}
235250
data-testid="unit-card"
236251
ref={currentRef}
237252
>

src/index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ div.row:has(> div > div.highlight) {
3939
animation-timing-function: cubic-bezier(1, 0, .72, .04);
4040
}
4141

42+
// To apply selection style to selected Section/Subsecion/Units, in the Course Outline
43+
div.row:has(> div > div.outline-card-selected) {
44+
box-shadow: 0 0 3px 3px var(--pgn-color-primary-500) !important;
45+
}
46+
4247
// To apply the glow effect to the selected xblock, in the Unit Outline
4348
div.xblock-highlight {
4449
animation: 5s glow;

0 commit comments

Comments
 (0)