* Add flow in course outline sidebar. Allows author to add new section/subsection/unit or any container from existing libraries via sidebar. * Adds library dropdown filter and collections dropdown filter in add sidebar. Allows authors to filter containers by selected libraries and collections.
150 lines
4.2 KiB
TypeScript
150 lines
4.2 KiB
TypeScript
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
import {
|
|
Button,
|
|
Dropdown,
|
|
Icon,
|
|
IconButton,
|
|
IconButtonToggle,
|
|
Stack,
|
|
} from '@openedx/paragon';
|
|
import {
|
|
FormatIndentDecrease,
|
|
FormatIndentIncrease,
|
|
} from '@openedx/paragon/icons';
|
|
|
|
import messages from './messages';
|
|
|
|
export interface SidebarPage {
|
|
component: React.ComponentType;
|
|
icon: React.ComponentType;
|
|
title: string;
|
|
hideFromActionMenu?: boolean;
|
|
}
|
|
|
|
type SidebarPages = Record<string, SidebarPage>;
|
|
|
|
/**
|
|
* Sidebar component
|
|
*/
|
|
interface SidebarProps<T extends SidebarPages> {
|
|
/** Object containing the pages that are rendered in the sidebar.
|
|
* Must satisfy the SidebarPages interface */
|
|
pages: T;
|
|
/** The page that is initially rendered in the sidebar.
|
|
* Must be a key of the pages object */
|
|
currentPageKey: keyof T;
|
|
/** Function that is called when the page is changed.
|
|
* Must be a key of the pages object */
|
|
setCurrentPageKey: (pageKey: keyof T) => void;
|
|
/** Whether the sidebar is open or not */
|
|
isOpen: boolean;
|
|
/** Function that toggles the sidebar */
|
|
toggle: () => void;
|
|
}
|
|
|
|
/**
|
|
* Sidebar component
|
|
*
|
|
* This component is used to render a sidebar that can be toggled open and closed.
|
|
* The generic type T is used to define the pages that are rendered in the sidebar.
|
|
*
|
|
* Example usage:
|
|
*
|
|
* ```tsx
|
|
* const sidebarPages = {
|
|
* help: {
|
|
* component: OutlineHelpSidebar,
|
|
* icon: HelpOutline,
|
|
* title: intl.formatMessage(messages.sidebarButtonHelp),
|
|
* },
|
|
* info: {
|
|
* component: OutlineInfoSidebar,
|
|
* icon: Info,
|
|
* title: intl.formatMessage(messages.sidebarButtonInfo),
|
|
* },
|
|
* } satisfies SidebarPages;
|
|
*
|
|
* const [isOpen, open, , toggle] = useToggle(true);
|
|
*
|
|
* return (
|
|
* <Sidebar
|
|
* pages={sidebarPages}
|
|
* currentPageKey="help"
|
|
* isOpen={isOpen}
|
|
* toggle={toggle}
|
|
* />
|
|
*);
|
|
* ```
|
|
*/
|
|
// eslint-disable-next-line react/function-component-definition
|
|
export function Sidebar<T extends SidebarPages>({
|
|
pages,
|
|
currentPageKey,
|
|
setCurrentPageKey,
|
|
isOpen,
|
|
toggle,
|
|
}: SidebarProps<T>) {
|
|
const intl = useIntl();
|
|
|
|
const SidebarComponent = pages[currentPageKey].component;
|
|
|
|
return (
|
|
<Stack direction="horizontal" className="sidebar align-items-baseline ml-3" gap={2}>
|
|
{isOpen && !!currentPageKey && (
|
|
<div className="sidebar-content p-2 bg-white border-right">
|
|
<Dropdown data-testid="sidebar-dropdown">
|
|
<Dropdown.Toggle
|
|
id="dropdown-toggle-with-iconbutton"
|
|
as={Button}
|
|
variant="tertiary"
|
|
className="x-small text-primary font-weight-bold pl-0"
|
|
>
|
|
{pages[currentPageKey].title}
|
|
<Icon src={pages[currentPageKey].icon} size="xs" className="ml-2" />
|
|
</Dropdown.Toggle>
|
|
<Dropdown.Menu className="mt-1">
|
|
{Object.entries(pages).map(([key, page]) => (
|
|
<Dropdown.Item
|
|
key={key}
|
|
onClick={() => setCurrentPageKey(key)}
|
|
>
|
|
<Stack direction="horizontal" gap={2}>
|
|
<Icon src={page.icon} />
|
|
{page.title}
|
|
</Stack>
|
|
</Dropdown.Item>
|
|
))}
|
|
</Dropdown.Menu>
|
|
</Dropdown>
|
|
<SidebarComponent />
|
|
</div>
|
|
)}
|
|
<div className="sidebar-toggle" data-testid="sidebar-toggle">
|
|
<IconButton
|
|
src={isOpen ? FormatIndentIncrease : FormatIndentDecrease}
|
|
alt={intl.formatMessage(messages.toggle)}
|
|
onClick={toggle}
|
|
variant="primary"
|
|
/>
|
|
<IconButtonToggle
|
|
activeValue={currentPageKey}
|
|
onChange={setCurrentPageKey}
|
|
>
|
|
{Object.entries(pages).map(([key, page]) => (
|
|
<IconButton
|
|
key={key}
|
|
// FIXME: The following ts-ignore can be removed when the type fix is released in paragon
|
|
// https://github.com/openedx/paragon/pull/4031
|
|
// @ts-ignore
|
|
value={key}
|
|
src={page.icon}
|
|
alt={page.title}
|
|
className="rounded-iconbutton"
|
|
/>
|
|
))}
|
|
</IconButtonToggle>
|
|
</div>
|
|
</Stack>
|
|
);
|
|
}
|