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: preserve library sidebar tab while switching items #1535

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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