diff --git a/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx b/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx index 1c1b133206..a0df36ad10 100644 --- a/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx +++ b/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, initializeMocks } from '@src/testUtils'; import * as CourseAuthoringContext from '@src/CourseAuthoringContext'; import * as CourseDetailsApi from '@src/data/apiHooks'; @@ -17,6 +16,7 @@ jest.mock('@src/content-tags-drawer', () => ({ describe('OutlineAlignSidebar', () => { beforeEach(() => { + initializeMocks(); jest .spyOn(CourseAuthoringContext, 'useCourseAuthoringContext') .mockReturnValue({ diff --git a/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx b/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx index 73593d0572..d8ee533a86 100644 --- a/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx +++ b/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx @@ -1,11 +1,12 @@ -import { SchoolOutline } from '@openedx/paragon/icons'; -import { ContentTagsDrawer } from '@src/content-tags-drawer'; import { useContentData } from '@src/content-tags-drawer/data/apiHooks'; import { useCourseAuthoringContext } from '@src/CourseAuthoringContext'; import { useCourseDetails } from '@src/data/apiHooks'; -import { SidebarTitle } from '@src/generic/sidebar'; +import { AlignSidebar } from '@src/generic/sidebar/AlignSidebar'; import { useOutlineSidebarContext } from './OutlineSidebarContext'; +/** + * Align sidebar for course or selected containers. + */ export const OutlineAlignSidebar = () => { const { courseId } = useCourseAuthoringContext(); const { currentContainerId } = useOutlineSidebarContext(); @@ -21,19 +22,13 @@ export const OutlineAlignSidebar = () => { } = useContentData(currentContainerId); return ( -
- - -
+ ); }; diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx index 2d58f1d8b6..ca564288bf 100644 --- a/src/course-unit/CourseUnit.test.jsx +++ b/src/course-unit/CourseUnit.test.jsx @@ -2917,4 +2917,20 @@ describe('', () => { render(); expect(await screen.findByText('Access: 3 Groups')).toBeInTheDocument(); }); + + it('opens the align sidebar on postMessage event', async () => { + setConfig({ + ...getConfig(), + ENABLE_TAGGING_TAXONOMY_PAGES: 'true', + ENABLE_UNIT_PAGE_NEW_DESIGN: 'true', + }); + + render(); + + await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + + simulatePostMessageEvent(messageTypes.openManageTags, { contentId: blockId }); + + await screen.findByText('Align'); + }); }); diff --git a/src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx b/src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx new file mode 100644 index 0000000000..83f63b6af0 --- /dev/null +++ b/src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx @@ -0,0 +1,33 @@ +import { render, screen, initializeMocks } from '@src/testUtils'; +import { UnitAlignSidebar } from './UnitAlignSidebar'; +import { UnitSidebarProvider } from './UnitSidebarContext'; + +jest.mock('@src/content-tags-drawer', () => ({ + ContentTagsDrawer: jest.fn(({ id, variant }) => ( +
+ drawer-mock-{id}-{variant} +
+ )), +})); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ blockId: 'unit-id-1' }), +})); + +describe('OutlineAlignSidebar', () => { + beforeEach(() => { + initializeMocks(); + }); + + it('renders ContentTagsDrawer with the correct id and variant', () => { + render(); + + const drawer = screen.getByTestId('content-tags-drawer'); + + expect(drawer).toBeInTheDocument(); + expect(drawer).toHaveTextContent( + 'drawer-mock-unit-id-1-component', + ); + }); +}); diff --git a/src/course-unit/unit-sidebar/UnitAlignSidebar.tsx b/src/course-unit/unit-sidebar/UnitAlignSidebar.tsx new file mode 100644 index 0000000000..6d4448e7fd --- /dev/null +++ b/src/course-unit/unit-sidebar/UnitAlignSidebar.tsx @@ -0,0 +1,28 @@ +import { useParams } from 'react-router-dom'; +import { useContentData } from '@src/content-tags-drawer/data/apiHooks'; +import { AlignSidebar } from '@src/generic/sidebar/AlignSidebar'; +import { useUnitSidebarContext } from './UnitSidebarContext'; + +/** + * Align sidebar for unit or selected components. + */ +export const UnitAlignSidebar = () => { + const { blockId } = useParams(); + const { currentComponentId } = useUnitSidebarContext(); + + const sidebarContentId = currentComponentId || blockId; + + const { + data: contentData, + } = useContentData(sidebarContentId); + + return ( + + ); +}; diff --git a/src/course-unit/unit-sidebar/UnitSidebar.tsx b/src/course-unit/unit-sidebar/UnitSidebar.tsx index 19a3668718..a866b0bec9 100644 --- a/src/course-unit/unit-sidebar/UnitSidebar.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebar.tsx @@ -2,7 +2,7 @@ import { Sidebar } from '@src/generic/sidebar'; import LegacySidebar, { LegacySidebarProps } from '../legacy-sidebar'; import { useUnitSidebarContext } from './UnitSidebarContext'; import { isUnitPageNewDesignEnabled } from '../utils'; -import { UNIT_SIDEBAR_PAGES } from './constants'; +import { getUnitSidebarPages } from './sidebarPages'; export type UnitSidebarProps = { legacySidebarProps: LegacySidebarProps, @@ -26,7 +26,7 @@ export const UnitSidebar = ({ return ( ; interface UnitSidebarContextData { currentPageKey: UnitSidebarPageKeys; - setCurrentPageKey: (pageKey: UnitSidebarPageKeys) => void; + setCurrentPageKey: (pageKey: UnitSidebarPageKeys, componentId?: string) => void; currentTabKey?: string; setCurrentTabKey: (tabKey: string) => void; + // The Id of the component used in the current sidebar page + // The component is not necessarily selected to open a selected sidebar. + // Example: Align sidebar + currentComponentId?: string; isOpen: boolean; open: () => void; toggle: () => void; @@ -20,12 +25,22 @@ interface UnitSidebarContextData { const UnitSidebarContext = createContext(undefined); export const UnitSidebarProvider = ({ children }: { children?: React.ReactNode }) => { - const [currentPageKey, setCurrentPageKeyState] = useState('info'); + const [currentPageKey, setCurrentPageKeyState] = useStateWithUrlSearchParam( + 'info', + 'sidebar', + (value: string) => value as UnitSidebarPageKeys, + (value: UnitSidebarPageKeys) => value, + ); const [currentTabKey, setCurrentTabKey] = useState(); + const [currentComponentId, setCurrentComponentId] = useState(); const [isOpen, open,, toggle] = useToggle(true); - const setCurrentPageKey = useCallback(/* istanbul ignore next */ (pageKey: UnitSidebarPageKeys) => { + const setCurrentPageKey = useCallback(/* istanbul ignore next */ ( + pageKey: UnitSidebarPageKeys, + componentId?: string, + ) => { setCurrentPageKeyState(pageKey); + setCurrentComponentId(componentId); open(); }, [open]); @@ -35,6 +50,7 @@ export const UnitSidebarProvider = ({ children }: { children?: React.ReactNode } setCurrentPageKey, currentTabKey, setCurrentTabKey, + currentComponentId, isOpen, open, toggle, @@ -44,6 +60,7 @@ export const UnitSidebarProvider = ({ children }: { children?: React.ReactNode } setCurrentPageKey, currentTabKey, setCurrentTabKey, + currentComponentId, isOpen, open, toggle, diff --git a/src/course-unit/unit-sidebar/constants.ts b/src/course-unit/unit-sidebar/constants.ts deleted file mode 100644 index 8bfbe7e003..0000000000 --- a/src/course-unit/unit-sidebar/constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Info } from '@openedx/paragon/icons'; -import { SidebarPage } from '@src/generic/sidebar'; -import messages from './messages'; -import { UnitInfoSidebar } from './unit-info/UnitInfoSidebar'; - -export type UnitSidebarPageKeys = 'info'; - -/** - * Sidebar pages for the unit sidebar - * - * This has been separated from the context to avoid a cyclical import - * if you want to use the context in the sidebar pages. - */ -export const UNIT_SIDEBAR_PAGES: Record = { - info: { - component: UnitInfoSidebar, - icon: Info, - title: messages.sidebarButtonInfo, - }, -}; diff --git a/src/course-unit/unit-sidebar/messages.ts b/src/course-unit/unit-sidebar/messages.ts index 96c3326ce1..0abbcfe275 100644 --- a/src/course-unit/unit-sidebar/messages.ts +++ b/src/course-unit/unit-sidebar/messages.ts @@ -6,6 +6,11 @@ const messages = defineMessages({ defaultMessage: 'Info', description: 'Label of the button for the Info sidebar', }, + sidebarButtonAlign: { + id: 'course-authoring.unit-page.sidebar.info.sidebar-button-align', + defaultMessage: 'Align', + description: 'Label of the button for the Align sidebar', + }, }); export default messages; diff --git a/src/course-unit/unit-sidebar/sidebarPages.ts b/src/course-unit/unit-sidebar/sidebarPages.ts new file mode 100644 index 0000000000..ee5a431f2c --- /dev/null +++ b/src/course-unit/unit-sidebar/sidebarPages.ts @@ -0,0 +1,35 @@ +import { getConfig } from '@edx/frontend-platform'; +import { Info, Tag } from '@openedx/paragon/icons'; +import { SidebarPage } from '@src/generic/sidebar'; +import messages from './messages'; +import { UnitInfoSidebar } from './unit-info/UnitInfoSidebar'; +import { UnitAlignSidebar } from './UnitAlignSidebar'; + +export type UnitSidebarPages = { + info: SidebarPage; + align?: SidebarPage; +}; + +/** + * Sidebar pages for the unit sidebar + * + * This has been separated from the context to avoid a cyclical import + * if you want to use the context in the sidebar pages. + */ +export const getUnitSidebarPages = (): UnitSidebarPages => { + const showAlignSidebar = getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true'; + return { + info: { + component: UnitInfoSidebar, + icon: Info, + title: messages.sidebarButtonInfo, + }, + ...(showAlignSidebar && { + align: { + component: UnitAlignSidebar, + icon: Tag, + title: messages.sidebarButtonAlign, + }, + }), + }; +}; diff --git a/src/course-unit/xblock-container-iframe/index.tsx b/src/course-unit/xblock-container-iframe/index.tsx index 5b67b340af..309ea686b1 100644 --- a/src/course-unit/xblock-container-iframe/index.tsx +++ b/src/course-unit/xblock-container-iframe/index.tsx @@ -39,12 +39,15 @@ import { AccessManagedXBlockDataTypes, } from './types'; import { formatAccessManagedXBlockData, getIframeUrl, getLegacyEditModalUrl } from './utils'; +import { useUnitSidebarContext } from '../unit-sidebar/UnitSidebarContext'; +import { isUnitPageNewDesignEnabled } from '../utils'; const XBlockContainerIframe: FC = ({ courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType, }) => { const intl = useIntl(); const dispatch = useDispatch(); + const { setCurrentPageKey } = useUnitSidebarContext(); // Useful to reload iframe const [iframeKey, setIframeKey] = useState(0); @@ -169,8 +172,13 @@ const XBlockContainerIframe: FC = ({ }; const handleOpenManageTagsModal = (id: string) => { - setConfigureXBlockId(id); - openManageTagsModal(); + if (isUnitPageNewDesignEnabled()) { + setCurrentPageKey('align', id); + } else { + // Legacy manage tags modal + setConfigureXBlockId(id); + openManageTagsModal(); + } }; const handleShowProcessingNotification = (variant: string) => { diff --git a/src/generic/sidebar/AlignSidebar.tsx b/src/generic/sidebar/AlignSidebar.tsx new file mode 100644 index 0000000000..030c9319b7 --- /dev/null +++ b/src/generic/sidebar/AlignSidebar.tsx @@ -0,0 +1,25 @@ +import { ContentTagsDrawer } from '@src/content-tags-drawer'; +import { SchoolOutline } from '@openedx/paragon/icons'; +import { SidebarTitle } from './SidebarTitle'; + +export interface AlignSidebarProps { + contentId: string; + title: string; +} + +/** + * Sidebar that renders Align Sidebar (manage tags sidebar) + * for the given content. + */ +export const AlignSidebar = ({ contentId, title }: AlignSidebarProps) => ( +
+ + +
+);