Skip to content

Commit

Permalink
feat: added implementation for new sidebar (#1260)
Browse files Browse the repository at this point in the history
* feat: added implementation for new sidebar

* test: fixed test cases

* refactor: fixed naming convention and combine useeffects

* refactor: improved sidebar UI and renamed sidebar flag

* refactor: remove additional states

* refactor: fixed UI and logic related issue

* refactor: simplified condition

* refactor: toggle sidebar action

* refactor: fixed toggle issues

* refactor: back arrow component

* refactor: changed useeffect position

---------

Co-authored-by: Awais Ansari <[email protected]>
  • Loading branch information
sundasnoreen12 and awais-ansari authored Jan 8, 2024
1 parent 4dc4725 commit 023f5ac
Show file tree
Hide file tree
Showing 28 changed files with 834 additions and 15 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL=''
ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
EXAMS_BASE_URL=''
Expand Down
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL=''
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL='http://localhost:18740'
Expand Down
13 changes: 9 additions & 4 deletions src/courseware/course/Course.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import ContentTools from './content-tools';
import CourseBreadcrumbs from './CourseBreadcrumbs';
import SidebarProvider from './sidebar/SidebarContextProvider';
import SidebarTriggers from './sidebar/SidebarTriggers';
import NewSidebarProvider from './new-sidebar/SidebarContextProvider';
import NewSidebarTriggers from './new-sidebar/SidebarTriggers';

import { useModel } from '../../generic/model-store';

Expand All @@ -34,6 +36,7 @@ const Course = ({
} = useModel('courseHomeMeta', courseId);
const sequence = useModel('sequences', sequenceId);
const section = useModel('sections', sequence ? sequence.sectionId : null);
const enableNewSidebar = getConfig().ENABLE_NEW_SIDEBAR;

const pageTitleBreadCrumbs = [
sequence,
Expand Down Expand Up @@ -64,12 +67,14 @@ const Course = ({
));
}, [sequenceId]);

const SidebarProviderComponent = enableNewSidebar === 'true' ? NewSidebarProvider : SidebarProvider;

return (
<SidebarProvider courseId={courseId} unitId={unitId}>
<SidebarProviderComponent courseId={courseId} unitId={unitId}>
<Helmet>
<title>{`${pageTitleBreadCrumbs.join(' | ')} | ${getConfig().SITE_NAME}`}</title>
</Helmet>
<div className="position-relative d-flex align-items-start">
<div className="position-relative d-flex align-items-center mb-4 mt-1">
<CourseBreadcrumbs
courseId={courseId}
sectionId={section ? section.id : null}
Expand All @@ -87,7 +92,7 @@ const Course = ({
contentToolsEnabled={course.showCalculator || course.notes.enabled}
unitId={unitId}
/>
<SidebarTriggers />
{enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers /> }
</>
)}
</div>
Expand All @@ -113,7 +118,7 @@ const Course = ({
onClose={() => setWeeklyGoalCelebrationOpen(false)}
/>
<ContentTools course={course} />
</SidebarProvider>
</SidebarProviderComponent>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/courseware/course/CourseBreadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const CourseBreadcrumbs = ({
}, [courseStatus, sequenceStatus, allSequencesInSections]);

return (
<nav aria-label="breadcrumb" className="my-4 d-inline-block col-sm-10">
<nav aria-label="breadcrumb" className="d-inline-block col-sm-10">
<ol className="list-unstyled d-flex flex-nowrap align-items-center m-0">
<li className="list-unstyled col-auto m-0 p-0">
<Link
Expand Down
17 changes: 17 additions & 0 deletions src/courseware/course/new-sidebar/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { useContext } from 'react';

import SidebarContext from './SidebarContext';
import { SIDEBARS } from './sidebars';

const Sidebar = () => {
const { currentSidebar } = useContext(SidebarContext);

if (currentSidebar === null) { return null; }
const SidebarToRender = SIDEBARS[currentSidebar].Sidebar;

return (
<SidebarToRender />
);
};

export default Sidebar;
5 changes: 5 additions & 0 deletions src/courseware/course/new-sidebar/SidebarContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const SidebarContext = React.createContext({});

export default SidebarContext;
103 changes: 103 additions & 0 deletions src/courseware/course/new-sidebar/SidebarContextProvider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, {
useCallback, useEffect, useMemo, useState,
} from 'react';
import PropTypes from 'prop-types';

import isEmpty from 'lodash/isEmpty';

import { breakpoints, useWindowSize } from '@edx/paragon';

import { getLocalStorage, setLocalStorage } from '../../../data/localStorage';
import { useModel } from '../../../generic/model-store';
import WIDGETS from './constants';
import SidebarContext from './SidebarContext';
import { SIDEBARS } from './sidebars';

const SidebarProvider = ({
courseId,
unitId,
children,
}) => {
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.large.minWidth;
const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.medium.minWidth;
const query = new URLSearchParams(window.location.search);
const initialSidebar = (shouldDisplaySidebarOpen || query.get('sidebar') === 'true')
? SIDEBARS.DISCUSSIONS_NOTIFICATIONS.ID : null;
const [currentSidebar, setCurrentSidebar] = useState(initialSidebar);
const [notificationStatus, setNotificationStatus] = useState(getLocalStorage(`notificationStatus.${courseId}`));
const [hideDiscussionbar, setHideDiscussionbar] = useState(false);
const [hideNotificationbar, setHideNotificationbar] = useState(false);
const [upgradeNotificationCurrentState, setUpgradeNotificationCurrentState] = useState(
getLocalStorage(`upgradeNotificationCurrentState.${courseId}`),
);
const topic = useModel('discussionTopics', unitId);
const { verifiedMode } = useModel('courseHomeMeta', courseId);
const isDiscussionbarAvailable = topic?.id && topic?.enabledInContext;
const isNotificationbarAvailable = !isEmpty(verifiedMode);

const onNotificationSeen = useCallback(() => {
setNotificationStatus('inactive');
setLocalStorage(`notificationStatus.${courseId}`, 'inactive');
}, [courseId]);

useEffect(() => {
setHideDiscussionbar(!isDiscussionbarAvailable);
setHideNotificationbar(!isNotificationbarAvailable);
setCurrentSidebar(SIDEBARS.DISCUSSIONS_NOTIFICATIONS.ID);
}, [unitId, topic]);

useEffect(() => {
if (hideDiscussionbar && hideNotificationbar) {
setCurrentSidebar(null);
}
}, [hideDiscussionbar, hideNotificationbar]);

const toggleSidebar = useCallback((sidebarId = null, widgetId = null) => {
if (widgetId) {
setHideDiscussionbar(prevWidgetId => (widgetId === WIDGETS.DISCUSSIONS ? true : prevWidgetId));
setHideNotificationbar(prevWidgetId => (widgetId === WIDGETS.NOTIFICATIONS ? true : prevWidgetId));
} else {
setCurrentSidebar(prevSidebar => (sidebarId === prevSidebar ? null : sidebarId));
setHideDiscussionbar(!isDiscussionbarAvailable);
setHideNotificationbar(!isNotificationbarAvailable);
}
}, [isDiscussionbarAvailable, isNotificationbarAvailable]);

const contextValue = useMemo(() => ({
toggleSidebar,
onNotificationSeen,
setNotificationStatus,
currentSidebar,
notificationStatus,
upgradeNotificationCurrentState,
setUpgradeNotificationCurrentState,
shouldDisplaySidebarOpen,
shouldDisplayFullScreen,
courseId,
unitId,
hideDiscussionbar,
hideNotificationbar,
isNotificationbarAvailable,
isDiscussionbarAvailable,
}), [courseId, currentSidebar, notificationStatus, onNotificationSeen, shouldDisplayFullScreen,
shouldDisplaySidebarOpen, toggleSidebar, unitId, upgradeNotificationCurrentState, hideDiscussionbar,
hideNotificationbar, isNotificationbarAvailable, isDiscussionbarAvailable]);

return (
<SidebarContext.Provider value={contextValue}>
{children}
</SidebarContext.Provider>
);
};

SidebarProvider.propTypes = {
courseId: PropTypes.string.isRequired,
unitId: PropTypes.string.isRequired,
children: PropTypes.node,
};

SidebarProvider.defaultProps = {
children: null,
};

export default SidebarProvider;
21 changes: 21 additions & 0 deletions src/courseware/course/new-sidebar/SidebarTriggers.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useContext } from 'react';

import SidebarContext from './SidebarContext';
import { SIDEBAR_ORDER, SIDEBARS } from './sidebars';

const SidebarTriggers = () => {
const { toggleSidebar } = useContext(SidebarContext);

return (
<div className="d-flex ml-auto">
{SIDEBAR_ORDER.map((sidebarId) => {
const { Trigger } = SIDEBARS[sidebarId];
return (
<Trigger onClick={() => toggleSidebar(sidebarId)} key={sidebarId} />
);
})}
</div>
);
};

export default SidebarTriggers;
113 changes: 113 additions & 0 deletions src/courseware/course/new-sidebar/common/SidebarBase.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useCallback, useContext } from 'react';
import PropTypes from 'prop-types';

import classNames from 'classnames';

import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon, IconButton } from '@edx/paragon';
import { ArrowBackIos, Close } from '@edx/paragon/icons';

import { useEventListener } from '../../../../generic/hooks';
import WIDGETS from '../constants';
import messages from '../messages';
import SidebarContext from '../SidebarContext';

const SidebarBase = ({
title,
ariaLabel,
sidebarId,
className,
children,
showTitleBar,
width,
allowFullHeight,
showBorder,
}) => {
const intl = useIntl();
const {
toggleSidebar,
shouldDisplayFullScreen,
currentSidebar,
} = useContext(SidebarContext);

const receiveMessage = useCallback(({ data }) => {
const { type } = data;
if (type === 'learning.events.sidebar.close') {
toggleSidebar(currentSidebar, WIDGETS.DISCUSSIONS);
}
}, [toggleSidebar]);

useEventListener('message', receiveMessage);

return (
<section
className={classNames('ml-0 ml-lg-4 h-auto align-top', {
'min-vh-100': !shouldDisplayFullScreen && allowFullHeight,
'bg-white m-0 border-0 fixed-top vh-100 rounded-0': shouldDisplayFullScreen,
'd-none': currentSidebar !== sidebarId,
'border border-light-400 rounded-sm': showBorder,
}, className)}
data-testid={`sidebar-${sidebarId}`}
style={{ width: shouldDisplayFullScreen ? '100%' : width }}
aria-label={ariaLabel}
>
{shouldDisplayFullScreen
&& (
<div
className="pt-2 pb-2.5 border-bottom border-light-400 d-flex align-items-center ml-2"
onClick={() => toggleSidebar(null)}
onKeyDown={() => toggleSidebar(null)}
role="button"
tabIndex="0"
alt={intl.formatMessage(messages.responsiveCloseSidebarTray)}
>
<Icon src={ArrowBackIos} />
<span className="font-weight-bold m-2 d-inline-block">
{intl.formatMessage(messages.responsiveCloseSidebarTray)}
</span>
</div>
)}
{showTitleBar && (
<>
<div className="d-flex align-items-center">
<span className="p-2.5 d-inline-block">{title}</span>
<div className="d-inline-flex mr-2 mt-1.5 ml-auto">
<IconButton
src={Close}
size="sm"
iconAs={Icon}
onClick={() => toggleSidebar(sidebarId)}
alt={intl.formatMessage(messages.closeTrigger)}
className="icon-hover"
/>
</div>
</div>
<div className="py-1 bg-gray-100 border-top border-bottom border-light-400" />
</>
)}
{children}
</section>
);
};

SidebarBase.propTypes = {
title: PropTypes.string.isRequired,
ariaLabel: PropTypes.string.isRequired,
sidebarId: PropTypes.string.isRequired,
className: PropTypes.string,
children: PropTypes.element.isRequired,
showTitleBar: PropTypes.bool,
width: PropTypes.string,
allowFullHeight: PropTypes.bool,
showBorder: PropTypes.bool,
};

SidebarBase.defaultProps = {
width: '50rem',
allowFullHeight: false,
showTitleBar: true,
className: '',
showBorder: true,
};

export default SidebarBase;
6 changes: 6 additions & 0 deletions src/courseware/course/new-sidebar/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const WIDGETS = {
DISCUSSIONS: 'DISCUSSIONS',
NOTIFICATIONS: 'NOTIFICATIONS',
};

export default WIDGETS;
20 changes: 20 additions & 0 deletions src/courseware/course/new-sidebar/icons/RightSidebarFilled.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

const RightSidebarFilled = (props) => (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 22V2h20v20H2ZM14 4H4v16h10V4Z"
fill="currentColor"
/>
</svg>
);
export default RightSidebarFilled;
20 changes: 20 additions & 0 deletions src/courseware/course/new-sidebar/icons/RightSidebarOutlined.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

const RightSidebarOutlined = (props) => (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 2v20h20V2H2Zm18 2h-4v16h4V4ZM4 4h10v16H4V4Z"
fill="currentColor"
/>
</svg>
);
export default RightSidebarOutlined;
Loading

0 comments on commit 023f5ac

Please sign in to comment.