Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added implementation for new sidebar #1260

Merged
merged 11 commits into from
Jan 8, 2024
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 @@ -86,7 +91,7 @@ const Course = ({
courseId={courseId}
contentToolsEnabled={course.showCalculator || course.notes.enabled}
/>
<SidebarTriggers />
{enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers /> }
</>
)}
</div>
Expand All @@ -112,7 +117,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
44 changes: 44 additions & 0 deletions src/courseware/course/new-sidebar/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useContext } from 'react';
import { ArrowBackIos } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import classNames from 'classnames';
import { SIDEBARS } from './sidebars';
import SidebarContext from './SidebarContext';
import messages from './messages';

const Sidebar = () => {
const intl = useIntl();

Check warning on line 11 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L11

Added line #L11 was not covered by tests

const {
toggleSidebar,
shouldDisplayFullScreen,
currentSidebar,
} = useContext(SidebarContext);

Check warning on line 17 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L17

Added line #L17 was not covered by tests

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

Check warning on line 20 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L20

Added line #L20 was not covered by tests

return (

Check warning on line 22 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L22

Added line #L22 was not covered by tests
<div className={classNames('d-flex flex-column', { 'bg-white fixed-top': shouldDisplayFullScreen })}>
{shouldDisplayFullScreen && (
<div

Check warning on line 25 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L25

Added line #L25 was not covered by tests
className="pt-2 pb-2.5 border-bottom border-light-400 d-flex align-items-center ml-2"
onClick={() => toggleSidebar(null)}
onKeyDown={() => toggleSidebar(null)}

Check warning on line 28 in src/courseware/course/new-sidebar/Sidebar.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/Sidebar.jsx#L27-L28

Added lines #L27 - L28 were not covered by tests
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>
)}
<SidebarToRender />
</div>
);
};

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;
105 changes: 105 additions & 0 deletions src/courseware/course/new-sidebar/SidebarContextProvider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { breakpoints, useWindowSize } from '@edx/paragon';
import PropTypes from 'prop-types';
import React, {
useEffect, useState, useMemo, useCallback,
} from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import isEmpty from 'lodash/isEmpty';
import { SIDEBARS } from './sidebars';
import { getLocalStorage, setLocalStorage } from '../../../data/localStorage';
import SidebarContext from './SidebarContext';
import { useModel } from '../../../generic/model-store';
import messages from './messages';

const SidebarProvider = ({
courseId,
unitId,
children,
}) => {
const intl = useIntl();
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.large.minWidth;
const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.medium.minWidth;
const query = new URLSearchParams(window.location.search);

Check warning on line 22 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L18-L22

Added lines #L18 - L22 were not covered by tests
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(

Check warning on line 29 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L24-L29

Added lines #L24 - L29 were not covered by tests
getLocalStorage(`upgradeNotificationCurrentState.${courseId}`),
);
const topic = useModel('discussionTopics', unitId);
const { verifiedMode } = useModel('courseHomeMeta', courseId);

Check warning on line 33 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L32-L33

Added lines #L32 - L33 were not covered by tests
const isDiscussionbarAvailable = !(!topic?.id || !topic?.enabledInContext);
sundasnoreen12 marked this conversation as resolved.
Show resolved Hide resolved
const isNotificationbarAvailable = !isEmpty(verifiedMode);

Check warning on line 35 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L35

Added line #L35 was not covered by tests

useEffect(() => {
setHideDiscussionbar(!isDiscussionbarAvailable);
setHideNotificationbar(!isNotificationbarAvailable);
setCurrentSidebar(SIDEBARS.DISCUSSIONS_NOTIFICATIONS.ID);

Check warning on line 40 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L37-L40

Added lines #L37 - L40 were not covered by tests
}, [unitId, topic]);

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

Check warning on line 45 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L43-L45

Added lines #L43 - L45 were not covered by tests
}, [courseId]);

useEffect(() => {

Check warning on line 48 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L48

Added line #L48 was not covered by tests
if (hideDiscussionbar && hideNotificationbar) {
setCurrentSidebar(null);

Check warning on line 50 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L50

Added line #L50 was not covered by tests
}
}, [hideDiscussionbar, hideNotificationbar]);

const toggleSidebar = useCallback((sidebarId, widgetId) => {

Check warning on line 54 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L54

Added line #L54 was not covered by tests
if (widgetId) {
if (widgetId === intl.formatMessage(messages.discussionsTitle)) {
setHideDiscussionbar(true);

Check warning on line 57 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L57

Added line #L57 was not covered by tests
} else if (widgetId === intl.formatMessage(messages.notificationTitle)) {
setHideNotificationbar(true);

Check warning on line 59 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L59

Added line #L59 was not covered by tests
}
} else {

Check warning on line 61 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L61

Added line #L61 was not covered by tests
setCurrentSidebar(prevSidebar => (sidebarId === prevSidebar ? null : sidebarId));
setHideDiscussionbar(!isDiscussionbarAvailable);
setHideNotificationbar(!isNotificationbarAvailable);

Check warning on line 64 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L63-L64

Added lines #L63 - L64 were not covered by tests
}
}, [isDiscussionbarAvailable, isNotificationbarAvailable]);

const contextValue = useMemo(() => ({

Check warning on line 68 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L68

Added line #L68 was not covered by tests
toggleSidebar,
onNotificationSeen,
setNotificationStatus,
currentSidebar,
notificationStatus,
upgradeNotificationCurrentState,
setUpgradeNotificationCurrentState,
shouldDisplaySidebarOpen,
shouldDisplayFullScreen,
courseId,
unitId,
hideDiscussionbar,
hideNotificationbar,
isNotificationbarAvailable,
isDiscussionbarAvailable,
awais-ansari marked this conversation as resolved.
Show resolved Hide resolved
}), [courseId, currentSidebar, notificationStatus, onNotificationSeen, shouldDisplayFullScreen,
shouldDisplaySidebarOpen, toggleSidebar, unitId, upgradeNotificationCurrentState, hideDiscussionbar,
hideNotificationbar, isNotificationbarAvailable, isDiscussionbarAvailable]);

return (

Check warning on line 88 in src/courseware/course/new-sidebar/SidebarContextProvider.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarContextProvider.jsx#L88

Added line #L88 was not covered by tests
<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;
20 changes: 20 additions & 0 deletions src/courseware/course/new-sidebar/SidebarTriggers.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useContext } from 'react';
import SidebarContext from './SidebarContext';
import { SIDEBAR_ORDER, SIDEBARS } from './sidebars';

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

Check warning on line 6 in src/courseware/course/new-sidebar/SidebarTriggers.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarTriggers.jsx#L6

Added line #L6 was not covered by tests

return (

Check warning on line 8 in src/courseware/course/new-sidebar/SidebarTriggers.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarTriggers.jsx#L8

Added line #L8 was not covered by tests
<div className="d-flex ml-auto">
{SIDEBAR_ORDER.map((sidebarId) => {
const { Trigger } = SIDEBARS[sidebarId];
return (
<Trigger onClick={() => toggleSidebar(sidebarId)} key={sidebarId} />

Check warning on line 13 in src/courseware/course/new-sidebar/SidebarTriggers.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/SidebarTriggers.jsx#L10-L13

Added lines #L10 - L13 were not covered by tests
);
})}
</div>
);
};

export default SidebarTriggers;
93 changes: 93 additions & 0 deletions src/courseware/course/new-sidebar/common/SidebarBase.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon, IconButton } from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useCallback, useContext } from 'react';
import { useEventListener } from '../../../../generic/hooks';
import messages from '../messages';
import SidebarContext from '../SidebarContext';

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

Check warning on line 22 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L21-L22

Added lines #L21 - L22 were not covered by tests
const {
toggleSidebar,
shouldDisplayFullScreen,
currentSidebar,
} = useContext(SidebarContext);

Check warning on line 27 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L27

Added line #L27 was not covered by tests

const receiveMessage = useCallback(({ data }) => {
const { type } = data;

Check warning on line 30 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L29-L30

Added lines #L29 - L30 were not covered by tests
if (type === 'learning.events.sidebar.close') {
toggleSidebar(sidebarId, intl.formatMessage(messages.discussionsTitle));

Check warning on line 32 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L32

Added line #L32 was not covered by tests
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toggleSidebar]);

useEventListener('message', receiveMessage);

Check warning on line 37 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L37

Added line #L37 was not covered by tests

return (

Check warning on line 39 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L39

Added line #L39 was not covered by tests
<section
className={classNames('ml-0 ml-lg-4 h-auto align-top', {
'min-vh-100': !shouldDisplayFullScreen && allowFullHeight,
'd-none': currentSidebar !== sidebarId,
'border border-light-400 rounded-sm': showBorder,
}, className)}
data-testid={`sidebar-${sidebarId}`}
style={{ width: shouldDisplayFullScreen ? '100%' : width }}
aria-label={ariaLabel}
>
{showTitleBar && (
<>

Check warning on line 51 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L51

Added line #L51 was not covered by tests
<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, title)}

Check warning on line 59 in src/courseware/course/new-sidebar/common/SidebarBase.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/common/SidebarBase.jsx#L59

Added line #L59 was not covered by tests
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: '31rem',
allowFullHeight: false,
showTitleBar: true,
className: '',
showBorder: true,
};

export default SidebarBase;
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

Check warning on line 4 in src/courseware/course/new-sidebar/icons/RightSidebarFilled.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/icons/RightSidebarFilled.jsx#L4

Added line #L4 was not covered by tests
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

Check warning on line 4 in src/courseware/course/new-sidebar/icons/RightSidebarOutlined.jsx

View check run for this annotation

Codecov / codecov/patch

src/courseware/course/new-sidebar/icons/RightSidebarOutlined.jsx#L4

Added line #L4 was not covered by tests
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