diff --git a/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx b/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx index 904b0cf6e..13376d83e 100644 --- a/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx +++ b/src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx @@ -1,4 +1,4 @@ -import { initializeMocks, render, screen } from '@src/testUtils'; +import { render, screen, initializeMocks } from '@src/testUtils'; import * as CourseAuthoringContext from '@src/CourseAuthoringContext'; import * as CourseDetailsApi from '@src/data/apiHooks'; diff --git a/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx b/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx index acf10b88f..486b9963f 100644 --- a/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx +++ b/src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx @@ -1,10 +1,11 @@ -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 { 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, @@ -24,20 +25,14 @@ export const OutlineAlignSidebar = () => { }; return ( -
- - -
+ ); }; diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx index 97a602a65..dda89e58a 100644 --- a/src/course-unit/CourseUnit.test.jsx +++ b/src/course-unit/CourseUnit.test.jsx @@ -2928,6 +2928,22 @@ describe('', () => { 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'); + }); + describe('Add sidebar', () => { let user; 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 000000000..95333ab2d --- /dev/null +++ b/src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx @@ -0,0 +1,39 @@ +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' }), +})); + +const renderComponent = () => render( + + + , +); + +describe('OutlineAlignSidebar', () => { + beforeEach(() => { + initializeMocks(); + }); + + it('renders ContentTagsDrawer with the correct id and variant', () => { + renderComponent(); + + 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 000000000..8e095a87e --- /dev/null +++ b/src/course-unit/unit-sidebar/UnitAlignSidebar.tsx @@ -0,0 +1,36 @@ +import { useParams } from 'react-router-dom'; +import { useContentData } from '@src/content-tags-drawer/data/apiHooks'; +import { AlignSidebar } from '@src/generic/sidebar/AlignSidebar'; +import { useCallback } from 'react'; +import { useUnitSidebarContext } from './UnitSidebarContext'; + +/** + * Align sidebar for unit or selected components. + */ +export const UnitAlignSidebar = () => { + const { blockId } = useParams(); + const { currentComponentId, setCurrentPageKey } = useUnitSidebarContext(); + + const sidebarContentId = currentComponentId || blockId; + + const { + data: contentData, + } = useContentData(sidebarContentId); + + const handleBack = useCallback(() => { + // Set the align sidebar without current component to back + // to unit align sidebar. + setCurrentPageKey('align'); + }, [setCurrentPageKey]); + + return ( + + ); +}; diff --git a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx index 13c197544..d76ff17f8 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx @@ -3,15 +3,20 @@ import { } from 'react'; import { SidebarPage } from '@src/generic/sidebar'; import { useToggle } from '@openedx/paragon'; +import { useStateWithUrlSearchParam } from '@src/hooks'; -export type UnitSidebarPageKeys = 'info' | 'add'; +export type UnitSidebarPageKeys = 'info' | 'add' | 'align'; export type UnitSidebarPages = Record; interface UnitSidebarContextData { currentPageKey: UnitSidebarPageKeys; - setCurrentPageKey: (pageKey: UnitSidebarPageKeys) => void; + setCurrentPageKey: (pageKey: UnitSidebarPageKeys, componentId?: string) => void; currentTabKey?: string; setCurrentTabKey: (tabKey: string | undefined) => 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; @@ -27,14 +32,23 @@ export const UnitSidebarProvider = ({ children?: React.ReactNode, readOnly: boolean, }) => { - 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) => { - // Reset tab + const setCurrentPageKey = useCallback(/* istanbul ignore next */ ( + pageKey: UnitSidebarPageKeys, + componentId?: string, + ) => { setCurrentTabKey(undefined); setCurrentPageKeyState(pageKey); + setCurrentComponentId(componentId); open(); }, [open]); @@ -44,6 +58,7 @@ export const UnitSidebarProvider = ({ setCurrentPageKey, currentTabKey, setCurrentTabKey, + currentComponentId, isOpen, open, toggle, @@ -54,6 +69,7 @@ export const UnitSidebarProvider = ({ setCurrentPageKey, currentTabKey, setCurrentTabKey, + currentComponentId, isOpen, open, toggle, diff --git a/src/course-unit/unit-sidebar/messages.ts b/src/course-unit/unit-sidebar/messages.ts index b5e372b79..fe4f04d22 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', + }, sidebarButtonAdd: { id: 'course-authoring.unit-page.sidebar.add.sidebar-button-add', defaultMessage: 'Add', diff --git a/src/course-unit/unit-sidebar/sidebarPages.ts b/src/course-unit/unit-sidebar/sidebarPages.ts index 1d09d85c7..47b6ef95a 100644 --- a/src/course-unit/unit-sidebar/sidebarPages.ts +++ b/src/course-unit/unit-sidebar/sidebarPages.ts @@ -1,7 +1,9 @@ -import { Info, Plus } from '@openedx/paragon/icons'; +import { getConfig } from '@edx/frontend-platform'; +import { Info, Tag, Plus } from '@openedx/paragon/icons'; import { SidebarPage } from '@src/generic/sidebar'; import messages from './messages'; import { UnitInfoSidebar } from './unit-info/UnitInfoSidebar'; +import { UnitAlignSidebar } from './UnitAlignSidebar'; import { AddSidebar } from './AddSidebar'; import { useUnitSidebarContext } from './UnitSidebarContext'; @@ -18,6 +20,7 @@ export type UnitSidebarPages = { * if you want to use the context in the sidebar pages. */ export const useUnitSidebarPages = (): UnitSidebarPages => { + const showAlignSidebar = getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true'; const { readOnly } = useUnitSidebarContext(); return { info: { @@ -32,5 +35,12 @@ export const useUnitSidebarPages = (): UnitSidebarPages => { title: messages.sidebarButtonAdd, }, }), + ...(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 2d449b0d0..bacf48cfe 100644 --- a/src/course-unit/xblock-container-iframe/index.tsx +++ b/src/course-unit/xblock-container-iframe/index.tsx @@ -39,6 +39,8 @@ import { AccessManagedXBlockDataTypes, } from './types'; import { formatAccessManagedXBlockData, getIframeUrl, getLegacyEditModalUrl } from './utils'; +import { useUnitSidebarContext } from '../unit-sidebar/UnitSidebarContext'; +import { isUnitPageNewDesignEnabled } from '../utils'; const XBlockContainerIframe: FC = ({ courseId, @@ -51,6 +53,7 @@ const XBlockContainerIframe: FC = ({ }) => { const intl = useIntl(); const dispatch = useDispatch(); + const { setCurrentPageKey } = useUnitSidebarContext(); // Useful to reload iframe const [iframeKey, setIframeKey] = useState(0); @@ -175,8 +178,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/block-type-utils/index.scss b/src/generic/block-type-utils/index.scss index b6fb80608..b69b041ad 100644 --- a/src/generic/block-type-utils/index.scss +++ b/src/generic/block-type-utils/index.scss @@ -285,7 +285,7 @@ } } -.icon-with-border-chapter { +.icon-with-border-section { border: 1px solid var(--content-library-container-section-color); .pgn__icon { @@ -293,7 +293,7 @@ } } -.icon-with-border-sequential { +.icon-with-border-subsection { border: 1px solid var(--content-library-container-subsection-color); .pgn__icon { @@ -301,7 +301,7 @@ } } -.icon-with-border-vertical { +.icon-with-border-unit { border: 1px solid var(--content-library-container-unit-color); .pgn__icon { diff --git a/src/generic/sidebar/AlignSidebar.tsx b/src/generic/sidebar/AlignSidebar.tsx new file mode 100644 index 000000000..0a38f07c8 --- /dev/null +++ b/src/generic/sidebar/AlignSidebar.tsx @@ -0,0 +1,27 @@ +import { ContentTagsDrawer } from '@src/content-tags-drawer'; +import { SchoolOutline } from '@openedx/paragon/icons'; +import { SidebarTitle } from './SidebarTitle'; + +export interface AlignSidebarProps { + contentId: string; + title: string; + onBackBtnClick?: () => void; +} + +/** + * Sidebar that renders Align Sidebar (manage tags sidebar) + * for the given content. + */ +export const AlignSidebar = ({ contentId, title, onBackBtnClick }: AlignSidebarProps) => ( +
+ + +
+);