Skip to content

Commit

Permalink
feat: preserve library sidebar tab while switching items (#1535)
Browse files Browse the repository at this point in the history
In the library home page, makes the selected tab on the sidebar persist while selecting Components or Collections Info Sidebar.
  • Loading branch information
rpenido authored Dec 3, 2024
1 parent 6e53e37 commit 0771923
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 19 deletions.
33 changes: 31 additions & 2 deletions src/library-authoring/LibraryAuthoringPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ describe('<LibraryAuthoringPage />', () => {
it('should open and close the collection sidebar', async () => {
await renderLibraryPage();

// Click on the first component. It could appear twice, in both "Recently Modified" and "Collections"
fireEvent.click((await screen.findAllByText('Collection 1'))[0]);
// Click on the first collection
fireEvent.click((await screen.findByText('Collection 1')));

const sidebar = screen.getByTestId('library-sidebar');

Expand All @@ -425,6 +425,35 @@ describe('<LibraryAuthoringPage />', () => {
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});

it('should preserve the tab while switching from a component to a collection', async () => {
await renderLibraryPage();

// Click on the first collection
fireEvent.click((await screen.findByText('Collection 1')));

const sidebar = screen.getByTestId('library-sidebar');

const { getByRole } = within(sidebar);

// Click on the Details tab
fireEvent.click(getByRole('tab', { name: 'Details' }));

// Change to a component
fireEvent.click((await screen.findAllByText('Introduction to Testing'))[0]);

// Check that the Details tab is still selected
expect(getByRole('tab', { name: 'Details' })).toHaveAttribute('aria-selected', 'true');

// Click on the Previews tab
fireEvent.click(getByRole('tab', { name: 'Preview' }));

// Switch back to the collection
fireEvent.click((await screen.findByText('Collection 1')));

// The Manage (default) tab should be selected because the collection does not have a Preview tab
expect(getByRole('tab', { name: 'Manage' })).toHaveAttribute('aria-selected', 'true');
});

it('can filter by capa problem type', async () => {
const problemTypes = {
'Multiple Choice': 'choiceresponse',
Expand Down
21 changes: 17 additions & 4 deletions src/library-authoring/collections/CollectionInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import {
import { useCallback } from 'react';
import { useNavigate, useMatch } from 'react-router-dom';

import { useLibraryContext } from '../common/context';
import {
useLibraryContext,
type CollectionInfoTab,
COLLECTION_INFO_TABS,
isCollectionInfoTab,
COMPONENT_INFO_TABS,
} from '../common/context';
import CollectionDetails from './CollectionDetails';
import messages from './messages';
import { ContentTagsDrawer } from '../../content-tags-drawer';
Expand All @@ -24,8 +30,13 @@ const CollectionInfo = () => {
setCollectionId,
sidebarComponentInfo,
componentPickerMode,
setSidebarCurrentTab,
} = useLibraryContext();

const tab: CollectionInfoTab = (
sidebarComponentInfo?.currentTab && isCollectionInfoTab(sidebarComponentInfo.currentTab)
) ? sidebarComponentInfo?.currentTab : COLLECTION_INFO_TABS.Manage;

const sidebarCollectionId = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!sidebarCollectionId) {
Expand Down Expand Up @@ -63,15 +74,17 @@ const CollectionInfo = () => {
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
defaultActiveKey="manage"
defaultActiveKey={COMPONENT_INFO_TABS.Manage}
activeKey={tab}
onSelect={setSidebarCurrentTab}
>
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
<Tab eventKey={COMPONENT_INFO_TABS.Manage} title={intl.formatMessage(messages.manageTabTitle)}>
<ContentTagsDrawer
id={collectionUsageKey}
variant="component"
/>
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
<Tab eventKey={COMPONENT_INFO_TABS.Details} title={intl.formatMessage(messages.detailsTabTitle)}>
<CollectionDetails />
</Tab>
</Tabs>
Expand Down
39 changes: 35 additions & 4 deletions src/library-authoring/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,32 @@ export enum SidebarBodyComponentId {
CollectionInfo = 'collection-info',
}

export const COLLECTION_INFO_TABS = {
Manage: 'manage',
Details: 'details',
} as const;
export type CollectionInfoTab = typeof COLLECTION_INFO_TABS[keyof typeof COLLECTION_INFO_TABS];
export const isCollectionInfoTab = (tab: string): tab is CollectionInfoTab => (
Object.values<string>(COLLECTION_INFO_TABS).includes(tab)
);

export const COMPONENT_INFO_TABS = {
Preview: 'preview',
Manage: 'manage',
Details: 'details',
} as const;
export type ComponentInfoTab = typeof COMPONENT_INFO_TABS[keyof typeof COMPONENT_INFO_TABS];
export const isComponentInfoTab = (tab: string): tab is ComponentInfoTab => (
Object.values<string>(COMPONENT_INFO_TABS).includes(tab)
);

export interface SidebarComponentInfo {
type: SidebarBodyComponentId;
id: string;
/** Additional action on Sidebar display */
additionalAction?: SidebarAdditionalActions;
/** Current tab in the sidebar */
currentTab?: CollectionInfoTab | ComponentInfoTab;
}

export interface ComponentEditorInfo {
Expand Down Expand Up @@ -110,6 +131,7 @@ export type LibraryContextData = {
openComponentEditor: (usageKey: string, onClose?: () => void) => void;
closeComponentEditor: () => void;
resetSidebarAdditionalActions: () => void;
setSidebarCurrentTab: (tab: CollectionInfoTab | ComponentInfoTab) => void;
} & ComponentPickerType;

/**
Expand Down Expand Up @@ -209,22 +231,26 @@ export const LibraryProvider = ({
const openInfoSidebar = useCallback(() => {
setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.Info });
}, []);

const openComponentInfoSidebar = useCallback((usageKey: string, additionalAction?: SidebarAdditionalActions) => {
setSidebarComponentInfo({
setSidebarComponentInfo((prev) => ({
...prev,
id: usageKey,
type: SidebarBodyComponentId.ComponentInfo,
additionalAction,
});
}));
}, []);

const openCollectionInfoSidebar = useCallback((
newCollectionId: string,
additionalAction?: SidebarAdditionalActions,
) => {
setSidebarComponentInfo({
setSidebarComponentInfo((prev) => ({
...prev,
id: newCollectionId,
type: SidebarBodyComponentId.CollectionInfo,
additionalAction,
});
}));
}, []);

const addComponentToSelectedComponents = useCallback<ComponentSelectedEvent>((
Expand Down Expand Up @@ -257,6 +283,10 @@ export const LibraryProvider = ({
});
}, []);

const setSidebarCurrentTab = useCallback((tab: CollectionInfoTab | ComponentInfoTab) => {
setSidebarComponentInfo((prev) => (prev && { ...prev, currentTab: tab }));
}, []);

const { data: libraryData, isLoading: isLoadingLibraryData } = useContentLibrary(libraryId);

const readOnly = !!componentPickerMode || !libraryData?.canEditLibrary;
Expand Down Expand Up @@ -286,6 +316,7 @@ export const LibraryProvider = ({
openComponentEditor,
closeComponentEditor,
resetSidebarAdditionalActions,
setSidebarCurrentTab,
};
if (!componentPickerMode) {
return {
Expand Down
30 changes: 21 additions & 9 deletions src/library-authoring/component-info/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import {
CheckBoxOutlineBlank,
} from '@openedx/paragon/icons';

import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
import {
SidebarAdditionalActions,
useLibraryContext,
COMPONENT_INFO_TABS,
ComponentInfoTab,
isComponentInfoTab,
} from '../common/context';
import ComponentMenu from '../components';
import { canEditComponent } from '../components/ComponentEditorModal';
import ComponentDetails from './ComponentDetails';
Expand Down Expand Up @@ -96,20 +102,25 @@ const ComponentInfo = () => {
readOnly,
openComponentEditor,
resetSidebarAdditionalActions,
setSidebarCurrentTab,
} = useLibraryContext();

const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
// Show Manage tab if JumpToAddCollections action is set in sidebarComponentInfo
const [tab, setTab] = React.useState(jumpToCollections ? 'manage' : 'preview');

const tab: ComponentInfoTab = (
sidebarComponentInfo?.currentTab && isComponentInfoTab(sidebarComponentInfo.currentTab)
) ? sidebarComponentInfo?.currentTab : COMPONENT_INFO_TABS.Preview;

useEffect(() => {
// Show Manage tab if JumpToAddCollections action is set in sidebarComponentInfo
if (jumpToCollections) {
setTab('manage');
setSidebarCurrentTab(COMPONENT_INFO_TABS.Manage);
}
}, [jumpToCollections]);

useEffect(() => {
// This is required to redo actions.
if (tab !== 'manage') {
if (tab !== COMPONENT_INFO_TABS.Manage) {
resetSidebarAdditionalActions();
}
}, [tab]);
Expand Down Expand Up @@ -158,16 +169,17 @@ const ComponentInfo = () => {
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
defaultActiveKey={COMPONENT_INFO_TABS.Preview}
activeKey={tab}
onSelect={(k: string) => setTab(k)}
onSelect={setSidebarCurrentTab}
>
<Tab eventKey="preview" title={intl.formatMessage(messages.previewTabTitle)}>
<Tab eventKey={COMPONENT_INFO_TABS.Preview} title={intl.formatMessage(messages.previewTabTitle)}>
<ComponentPreview />
</Tab>
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
<Tab eventKey={COMPONENT_INFO_TABS.Manage} title={intl.formatMessage(messages.manageTabTitle)}>
<ComponentManagement />
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
<Tab eventKey={COMPONENT_INFO_TABS.Details} title={intl.formatMessage(messages.detailsTabTitle)}>
<ComponentDetails />
</Tab>
</Tabs>
Expand Down

0 comments on commit 0771923

Please sign in to comment.