From cdd52b7382aa3e2e0cb90238b35ade8a28ef4a22 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 22 Dec 2025 20:55:22 +0530 Subject: [PATCH 01/35] feat: add sidebar in course outline --- src/authz/data/apiHooks.ts | 5 +- src/course-outline/CourseOutline.tsx | 4 +- .../outline-sidebar/AddSidebar.tsx | 43 +++++++++++++ .../outline-sidebar/OutlineSidebarContext.tsx | 10 ++- .../outline-sidebar/messages.ts | 5 ++ src/generic/sidebar/SidebarContent.tsx | 2 +- src/generic/sidebar/index.scss | 8 ++- .../LibraryAuthoringPage.tsx | 61 +++++++++++-------- src/library-authoring/LibraryContent.tsx | 6 +- .../common/context/LibraryContext.tsx | 17 ++++-- .../component-picker/ComponentPicker.tsx | 14 +++-- .../component-picker/SelectLibrary.tsx | 2 +- src/library-authoring/data/apiHooks.ts | 3 +- src/types.ts | 1 + 14 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 src/course-outline/outline-sidebar/AddSidebar.tsx create mode 100644 src/types.ts diff --git a/src/authz/data/apiHooks.ts b/src/authz/data/apiHooks.ts index b91d582f57..6beea71092 100644 --- a/src/authz/data/apiHooks.ts +++ b/src/authz/data/apiHooks.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query'; +import { skipToken, useQuery } from '@tanstack/react-query'; import { PermissionValidationAnswer, PermissionValidationQuery } from '@src/authz/types'; import { validateUserPermissions } from './api'; @@ -29,8 +29,9 @@ const adminConsoleQueryKeys = { */ export const useUserPermissions = ( permissions: PermissionValidationQuery, + enabled: boolean = true, ) => useQuery({ queryKey: adminConsoleQueryKeys.permissions(permissions), - queryFn: () => validateUserPermissions(permissions), + queryFn: enabled ? () => validateUserPermissions(permissions): skipToken, retry: false, }); diff --git a/src/course-outline/CourseOutline.tsx b/src/course-outline/CourseOutline.tsx index 813ab24f88..1895f62e15 100644 --- a/src/course-outline/CourseOutline.tsx +++ b/src/course-outline/CourseOutline.tsx @@ -269,7 +269,7 @@ const CourseOutline = () => { if (isLoadingDenied) { return ( - + { {getPageHeadTitle(courseName, intl.formatMessage(messages.headingTitle))} - +
{ + const intl = useIntl(); + const { courseDetails } = useCourseAuthoringContext(); + + return ( +
+ + + + + + +
+ ); +}; + +const ShowLibraryContent = () => { + return ( + + ); +} diff --git a/src/course-outline/outline-sidebar/OutlineSidebarContext.tsx b/src/course-outline/outline-sidebar/OutlineSidebarContext.tsx index fbe03d99b3..390392b24f 100644 --- a/src/course-outline/outline-sidebar/OutlineSidebarContext.tsx +++ b/src/course-outline/outline-sidebar/OutlineSidebarContext.tsx @@ -7,15 +7,16 @@ import { } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; import { useToggle } from '@openedx/paragon'; -import { HelpOutline, Info } from '@openedx/paragon/icons'; +import { HelpOutline, Info, Plus } from '@openedx/paragon/icons'; import type { SidebarPage } from '@src/generic/sidebar'; import OutlineHelpSidebar from './OutlineHelpSidebar'; import { OutlineInfoSidebar } from './OutlineInfoSidebar'; import messages from './messages'; +import { AddSidebar } from './AddSidebar'; -export type OutlineSidebarPageKeys = 'help' | 'info'; +export type OutlineSidebarPageKeys = 'help' | 'info' | 'add'; export type OutlineSidebarPages = Record; interface OutlineSidebarContextData { @@ -51,6 +52,11 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod icon: HelpOutline, title: intl.formatMessage(messages.sidebarButtonHelp), }, + add: { + component: AddSidebar, + icon: Plus, + title: intl.formatMessage(messages.sidebarButtonAdd), + }, } satisfies OutlineSidebarPages; const context = useMemo( diff --git a/src/course-outline/outline-sidebar/messages.ts b/src/course-outline/outline-sidebar/messages.ts index c1514648f6..6098188bdb 100644 --- a/src/course-outline/outline-sidebar/messages.ts +++ b/src/course-outline/outline-sidebar/messages.ts @@ -70,6 +70,11 @@ const messages = defineMessages({ defaultMessage: 'Help', description: 'Button label for the help sidebar', }, + sidebarButtonAdd: { + id: 'course-authoring.course-outline.sidebar.sidebar-button-add', + defaultMessage: 'Add', + description: 'Button text for add button in sidebar', + }, sidebarButtonInfo: { id: 'course-authoring.course-outline.sidebar.sidebar-button-info', defaultMessage: 'Info', diff --git a/src/generic/sidebar/SidebarContent.tsx b/src/generic/sidebar/SidebarContent.tsx index 718275bd39..fcd28a5523 100644 --- a/src/generic/sidebar/SidebarContent.tsx +++ b/src/generic/sidebar/SidebarContent.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Stack } from '@openedx/paragon'; interface SidebarContentProps { - children: React.ReactNode | React.ReactNode[], + children: React.ReactNode | React.ReactNode[]; } /** diff --git a/src/generic/sidebar/index.scss b/src/generic/sidebar/index.scss index 084f8b2572..1717e7ad1f 100644 --- a/src/generic/sidebar/index.scss +++ b/src/generic/sidebar/index.scss @@ -1,6 +1,7 @@ .sidebar { .sidebar-content { - width: 400px; + flex: 0 1 auto; + max-width: 750px; } .dropdown-toggle { @@ -38,8 +39,9 @@ &:hover { border-width: 2px; } - - &.btn-icon-primary-active::before { + + // Add a triangle to the active button + &.btn-icon-primary-active:before { content: ""; border-right: 5px solid var(--pgn-color-primary-base); border-top: 5px solid transparent; diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 5557fdd73f..534eb55af4 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -163,6 +163,7 @@ const LibraryAuthoringPage = ({ const { componentPickerMode, restrictToLibrary } = useComponentPickerContext(); const { libraryId, + libraryIds, libraryData, isLoadingLibraryData, showOnlyPublished, @@ -223,7 +224,7 @@ const LibraryAuthoringPage = ({ }, [navigateTo]); // Verify the migration task status - if (migrationId) { + if (migrationId && libraryId) { let deleteMigrationIdParam = false; if (migrationStatusData?.state === 'Succeeded') { // Check if any library migrations failed. @@ -273,7 +274,7 @@ const LibraryAuthoringPage = ({ ); } - if (!libraryData) { + if (libraryId && !libraryData) { return ; } @@ -289,7 +290,13 @@ const LibraryAuthoringPage = ({ /> ) : undefined; - const extraFilter = [`context_key = "${libraryId}"`]; + let extraFilter: string[] = []; + if (libraryId) { + extraFilter.push(`context_key = "${libraryId}"`); + } + if (libraryIds && libraryIds.length > 0) { + extraFilter.push(`context_key IN ["${libraryIds.join('","')}"]`); + } if (showOnlyPublished) { extraFilter.push('last_published IS NOT NULL'); } @@ -336,32 +343,38 @@ const LibraryAuthoringPage = ({ return (
- {libraryData.title} | {process.env.SITE_NAME} - {!componentPickerMode && ( -
- )} + {libraryData && + <> + {libraryData.title} | {process.env.SITE_NAME} + {!componentPickerMode && ( +
+ )} + + } - } - subtitle={!componentPickerMode ? intl.formatMessage(messages.headingSubtitle) : undefined} - breadcrumbs={breadcumbs} - headerActions={} - hideBorder - /> + {libraryData && + } + subtitle={!componentPickerMode ? intl.formatMessage(messages.headingSubtitle) : undefined} + breadcrumbs={breadcumbs} + headerActions={} + hideBorder + /> + } {visibleTabs.length > 1 && ( void; } -export type LibraryContextData = { - /** The ID of the current library */ +export type LibraryIdOneOrMore = { libraryId: string; + libraryIds: string[]; +} + +export type LibraryContextData = AtLeastOne & { + /** The ID of the current library */ libraryData?: ContentLibrary; readOnly: boolean; canPublish: boolean; @@ -65,9 +70,8 @@ export type LibraryContextData = { */ const LibraryContext = createContext(undefined); -type LibraryProviderProps = { +type LibraryProviderProps = AtLeastOne & { children?: React.ReactNode; - libraryId: string; showOnlyPublished?: boolean; extraFilter?: string[] // If set, will initialize the current collection and/or component from the current URL @@ -86,6 +90,7 @@ type LibraryProviderProps = { export const LibraryProvider = ({ children, libraryId, + libraryIds, showOnlyPublished = false, extraFilter = [], skipUrlUpdate = false, @@ -115,7 +120,7 @@ export const LibraryProvider = ({ action: CONTENT_LIBRARY_PERMISSIONS.PUBLISH_LIBRARY_CONTENT, scope: libraryId, }, - }); + }, typeof libraryId !== "undefined"); const canPublish = userPermissions?.canPublish || false; const readOnly = !!componentPickerMode || !libraryData?.canEditLibrary; @@ -135,6 +140,7 @@ export const LibraryProvider = ({ const context = useMemo(() => { const contextValue = { libraryId, + libraryIds: libraryIds || [], libraryData, collectionId, setCollectionId, @@ -159,6 +165,7 @@ export const LibraryProvider = ({ return contextValue; }, [ libraryId, + libraryIds, libraryData, collectionId, setCollectionId, diff --git a/src/library-authoring/component-picker/ComponentPicker.tsx b/src/library-authoring/component-picker/ComponentPicker.tsx index 170de643d4..144d64e630 100644 --- a/src/library-authoring/component-picker/ComponentPicker.tsx +++ b/src/library-authoring/component-picker/ComponentPicker.tsx @@ -8,13 +8,14 @@ import { type ComponentSelectionChangedEvent, ComponentPickerProvider, } from '../common/context/ComponentPickerContext'; -import { LibraryProvider, useLibraryContext } from '../common/context/LibraryContext'; +import { LibraryIdOneOrMore, LibraryProvider, useLibraryContext } from '../common/context/LibraryContext'; import { SidebarProvider } from '../common/context/SidebarContext'; import LibraryAuthoringPage from '../LibraryAuthoringPage'; import LibraryCollectionPage from '../collections/LibraryCollectionPage'; import SelectLibrary from './SelectLibrary'; import messages from './messages'; import { ContentType, allLibraryPageTabs } from '../routes'; +import { AtLeastOne } from '../../types'; interface LibraryComponentPickerProps { returnToLibrarySelection: () => void; @@ -48,19 +49,20 @@ const defaultSelectionChangedCallback: ComponentSelectionChangedEvent = (selecti window.parent.postMessage({ type: 'pickerSelectionChanged', selections }, '*'); }; -type ComponentPickerProps = { - libraryId?: string, +type ComponentPickerProps = AtLeastOne & { showOnlyPublished?: boolean, extraFilter?: string[], visibleTabs?: ContentType[], componentPickerMode?: 'single' | 'multiple', onComponentSelected?: ComponentSelectedEvent, onChangeComponentSelection?: ComponentSelectionChangedEvent, + selectLibrary?: boolean; }; export const ComponentPicker: React.FC = ({ /** Restrict the component picker to a specific library */ libraryId, + libraryIds, showOnlyPublished, extraFilter, componentPickerMode = 'single', @@ -70,9 +72,10 @@ export const ComponentPicker: React.FC = ({ */ onComponentSelected = defaultComponentSelectedCallback, onChangeComponentSelection = defaultSelectionChangedCallback, + selectLibrary = true, }) => { - const [currentStep, setCurrentStep] = useState(!libraryId ? 'select-library' : 'pick-components'); - const [selectedLibrary, setSelectedLibrary] = useState(libraryId || ''); + const [currentStep, setCurrentStep] = useState(!libraryId && selectLibrary ? 'select-library' : 'pick-components'); + const [selectedLibrary, setSelectedLibrary] = useState(libraryId); const location = useLocation(); @@ -118,6 +121,7 @@ export const ComponentPicker: React.FC = ({ ( ); interface SelectLibraryProps { - selectedLibrary: string; + selectedLibrary: string | undefined; setSelectedLibrary: (libraryKey: string) => void; itemType: ContentType; } diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 9f516f82a4..0dbc2fbe69 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -165,8 +165,7 @@ export function invalidateComponentData(queryClient: QueryClient, contentLibrary export const useContentLibrary = (libraryId: string | undefined) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId), - queryFn: () => api.getContentLibrary(libraryId!), - enabled: libraryId !== undefined, + queryFn: libraryId ? () => api.getContentLibrary(libraryId!): skipToken, }) ); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000000..9361410197 --- /dev/null +++ b/src/types.ts @@ -0,0 +1 @@ +export type AtLeastOne }> = Partial & U[keyof U]; From b2144700ff77cb790129638c53aaaabaff36a6ab Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Thu, 25 Dec 2025 11:11:27 +0530 Subject: [PATCH 02/35] feat: sidebar filter --- src/CourseAuthoringPage.tsx | 3 ++ .../outline-sidebar/AddSidebar.tsx | 2 + src/generic/sidebar/index.scss | 2 +- .../LibraryAuthoringPage.tsx | 32 ++++---------- .../component-picker/ComponentPicker.tsx | 7 ++++ .../library-filters/MainFilters.tsx | 32 ++++++++++++++ .../library-filters/SidebarFilters.tsx | 42 +++++++++++++++++++ .../library-filters/index.tsx | 4 ++ 8 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 src/library-authoring/library-filters/MainFilters.tsx create mode 100644 src/library-authoring/library-filters/SidebarFilters.tsx create mode 100644 src/library-authoring/library-filters/index.tsx diff --git a/src/CourseAuthoringPage.tsx b/src/CourseAuthoringPage.tsx index c8668ad90a..614efbc5a9 100644 --- a/src/CourseAuthoringPage.tsx +++ b/src/CourseAuthoringPage.tsx @@ -57,6 +57,9 @@ const CourseAuthoringPage = ({ children }: Props) => { org={courseOrg} title={courseTitle} contextId={courseId} + containerProps={{ + size: "fluid", + }} /> ) )} diff --git a/src/course-outline/outline-sidebar/AddSidebar.tsx b/src/course-outline/outline-sidebar/AddSidebar.tsx index 2a1d6c657f..6437172f61 100644 --- a/src/course-outline/outline-sidebar/AddSidebar.tsx +++ b/src/course-outline/outline-sidebar/AddSidebar.tsx @@ -7,6 +7,7 @@ import messages from './messages'; import { useCourseAuthoringContext } from '@src/CourseAuthoringContext'; import { ComponentPicker } from '../../library-authoring'; import { ContentType } from '../../library-authoring/routes'; +import { SidebarFilters } from '@src/library-authoring/library-filters/SidebarFilters'; export const AddSidebar = ({}: { courseId: string }) => { const intl = useIntl(); @@ -38,6 +39,7 @@ const ShowLibraryContent = () => { libraryIds={[]} selectLibrary={false} visibleTabs={[ContentType.home]} + FiltersComponent={SidebarFilters} /> ); } diff --git a/src/generic/sidebar/index.scss b/src/generic/sidebar/index.scss index 1717e7ad1f..f0a7705cf8 100644 --- a/src/generic/sidebar/index.scss +++ b/src/generic/sidebar/index.scss @@ -1,7 +1,7 @@ .sidebar { .sidebar-content { flex: 0 1 auto; - max-width: 750px; + max-width: 650px; } .dropdown-toggle { diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 534eb55af4..1a256ce379 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -4,13 +4,13 @@ import { useContext, useEffect, useState, + type ReactElement, } from 'react'; import { Helmet } from 'react-helmet'; import classNames from 'classnames'; import { StudioFooterSlot } from '@edx/frontend-component-footer'; import { useIntl } from '@edx/frontend-platform/i18n'; import { - ActionRow, Alert, Badge, Breadcrumb, @@ -31,12 +31,7 @@ import Header from '@src/header'; import NotFoundAlert from '@src/generic/NotFoundAlert'; import { useStudioHome } from '@src/studio-home/hooks'; import { - ClearFiltersButton, - FilterByBlockType, - FilterByTags, SearchContextProvider, - SearchKeywordsField, - SearchSortWidget, TypesFilterData, } from '@src/search-manager'; import { ToastContext } from '@src/generic/toast-context'; @@ -49,8 +44,8 @@ import { useLibraryContext } from './common/context/LibraryContext'; import { SidebarBodyItemId, useSidebarContext } from './common/context/SidebarContext'; import { allLibraryPageTabs, ContentType, useLibraryRoutes } from './routes'; import messages from './messages'; -import LibraryFilterByPublished from './generic/filter-by-published'; import { libraryQueryPredicate } from './data/apiHooks'; +import { FiltersProps, MainFilters } from '@src/library-authoring/library-filters/MainFilters'; const HeaderActions = () => { const intl = useIntl(); @@ -133,13 +128,15 @@ export const SubHeaderTitle = ({ title }: { title: ReactNode }) => { }; interface LibraryAuthoringPageProps { - returnToLibrarySelection?: () => void, - visibleTabs?: ContentType[], + returnToLibrarySelection?: () => void; + visibleTabs?: ContentType[]; + FiltersComponent?: React.ComponentType; } const LibraryAuthoringPage = ({ returnToLibrarySelection, visibleTabs = allLibraryPageTabs, + FiltersComponent = MainFilters, }: LibraryAuthoringPageProps) => { const intl = useIntl(); const location = useLocation(); @@ -385,22 +382,7 @@ const LibraryAuthoringPage = ({ {visibleTabsToRender} )} - - - - {!(onlyOneType) && } - - - - - + diff --git a/src/library-authoring/component-picker/ComponentPicker.tsx b/src/library-authoring/component-picker/ComponentPicker.tsx index 144d64e630..168dd6745d 100644 --- a/src/library-authoring/component-picker/ComponentPicker.tsx +++ b/src/library-authoring/component-picker/ComponentPicker.tsx @@ -16,15 +16,18 @@ import SelectLibrary from './SelectLibrary'; import messages from './messages'; import { ContentType, allLibraryPageTabs } from '../routes'; import { AtLeastOne } from '../../types'; +import { FiltersProps } from '@src/library-authoring/library-filters'; interface LibraryComponentPickerProps { returnToLibrarySelection: () => void; visibleTabs: ContentType[], + FiltersComponent?: React.ComponentType; } const InnerComponentPicker: React.FC = ({ returnToLibrarySelection, visibleTabs, + FiltersComponent, }) => { const { collectionId } = useLibraryContext(); @@ -35,6 +38,7 @@ const InnerComponentPicker: React.FC = ({ ); }; @@ -57,6 +61,7 @@ type ComponentPickerProps = AtLeastOne & { onComponentSelected?: ComponentSelectedEvent, onChangeComponentSelection?: ComponentSelectionChangedEvent, selectLibrary?: boolean; + FiltersComponent?: React.ComponentType; }; export const ComponentPicker: React.FC = ({ @@ -73,6 +78,7 @@ export const ComponentPicker: React.FC = ({ onComponentSelected = defaultComponentSelectedCallback, onChangeComponentSelection = defaultSelectionChangedCallback, selectLibrary = true, + FiltersComponent, }) => { const [currentStep, setCurrentStep] = useState(!libraryId && selectLibrary ? 'select-library' : 'pick-components'); const [selectedLibrary, setSelectedLibrary] = useState(libraryId); @@ -136,6 +142,7 @@ export const ComponentPicker: React.FC = ({ diff --git a/src/library-authoring/library-filters/MainFilters.tsx b/src/library-authoring/library-filters/MainFilters.tsx new file mode 100644 index 0000000000..181d562799 --- /dev/null +++ b/src/library-authoring/library-filters/MainFilters.tsx @@ -0,0 +1,32 @@ +import { ActionRow } from "@openedx/paragon" +import LibraryFilterByPublished from "@src/library-authoring/generic/filter-by-published" +import { FiltersProps } from "." +import { useLibraryRoutes } from "@src/library-authoring/routes" +import { ClearFiltersButton, FilterByBlockType, FilterByTags, SearchKeywordsField, SearchSortWidget } from "@src/search-manager" + +export const MainFilters = ({onlyOneType}: FiltersProps) => { + const { + insideCollections, + insideUnits, + } = useLibraryRoutes(); + + return ( + + + + {!(onlyOneType) && } + + + + + + ) +} + diff --git a/src/library-authoring/library-filters/SidebarFilters.tsx b/src/library-authoring/library-filters/SidebarFilters.tsx new file mode 100644 index 0000000000..e676d1ce5e --- /dev/null +++ b/src/library-authoring/library-filters/SidebarFilters.tsx @@ -0,0 +1,42 @@ +import { Form, Menu, SearchField, Stack } from "@openedx/paragon" +import { FiltersProps } from "." +import { ClearFiltersButton, FilterByBlockType, FilterByTags, SearchKeywordsField, SearchSortWidget } from "@src/search-manager" +import SearchFilterWidget from "@src/search-manager/SearchFilterWidget" +import { Newsstand } from "@openedx/paragon/icons" + +export const SidebarFilters = ({onlyOneType}: FiltersProps) => { + + return ( + + + {}} + icon={Newsstand} + > + + {}} + onChange={() => {}} + onClear={() => {}} + value={''} + placeholder={''} + className="mx-3 mb-1" + /> + + + + + + + + + {!(onlyOneType) && } + + + + + ) +} + diff --git a/src/library-authoring/library-filters/index.tsx b/src/library-authoring/library-filters/index.tsx new file mode 100644 index 0000000000..8d0282323b --- /dev/null +++ b/src/library-authoring/library-filters/index.tsx @@ -0,0 +1,4 @@ +export interface FiltersProps { + onlyOneType: boolean +} + From a1af0d08ad47bad24a65b410cc5f2edf762d6dbf Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 26 Dec 2025 16:27:54 +0530 Subject: [PATCH 03/35] feat: add new tab section in sidebar --- .../header-navigations/HeaderActions.tsx | 5 +- .../outline-sidebar/AddSidebar.tsx | 115 +++++++++++++++--- .../outline-sidebar/messages.ts | 10 ++ src/generic/sidebar/index.scss | 3 +- .../component-picker/ComponentPicker.tsx | 3 +- .../library-filters/SidebarFilters.tsx | 24 +++- .../library-filters/messages.ts | 16 +++ src/search-manager/SearchFilterWidget.tsx | 3 +- 8 files changed, 151 insertions(+), 28 deletions(-) create mode 100644 src/library-authoring/library-filters/messages.ts diff --git a/src/course-outline/header-navigations/HeaderActions.tsx b/src/course-outline/header-navigations/HeaderActions.tsx index 71367b248d..66152d735b 100644 --- a/src/course-outline/header-navigations/HeaderActions.tsx +++ b/src/course-outline/header-navigations/HeaderActions.tsx @@ -15,7 +15,6 @@ import messages from './messages'; export interface HeaderActionsProps { actions: { - handleNewSection: () => void, lmsLink: string, }, courseActions: XBlockActions, @@ -28,7 +27,7 @@ const HeaderActions = ({ errors, }: HeaderActionsProps) => { const intl = useIntl(); - const { handleNewSection, lmsLink } = actions; + const { lmsLink } = actions; const { setCurrentPageKey, sidebarPages } = useOutlineSidebarContext(); @@ -45,7 +44,7 @@ const HeaderActions = ({ > + ); +}; + +/** Add New Content Tab Section */ +const AddNewContent = () => { + const intl = useIntl(); + return ( + + {}} + /> + {}} + /> + {}} + /> + + ); +} + +/** Add Existing Content Tab Section */ +const ShowLibraryContent = () => { + return ( + + ); +} + +/** Tabs Component */ +const AddTabs = () => { + const intl = useIntl(); + + return ( + + + + + + + + + ); +} + +/** Main Sidebar Component */ +export const AddSidebar = () => { const intl = useIntl(); const { courseDetails } = useCourseAuthoringContext(); @@ -24,22 +122,9 @@ export const AddSidebar = ({}: { courseId: string }) => { title={intl.formatMessage(messages.sidebarSectionSummary)} icon={SchoolOutline} > - +
); }; - -const ShowLibraryContent = () => { - return ( - - ); -} diff --git a/src/course-outline/outline-sidebar/messages.ts b/src/course-outline/outline-sidebar/messages.ts index 6098188bdb..3fb7aa9193 100644 --- a/src/course-outline/outline-sidebar/messages.ts +++ b/src/course-outline/outline-sidebar/messages.ts @@ -95,6 +95,16 @@ const messages = defineMessages({ defaultMessage: 'Manage tags', description: 'Action to open the tags drawer', }, + sidebarTabsAddNew: { + id: 'course-authoring.course-outline.sidebar.sidebar-section-add.add-new-tab', + defaultMessage: 'Add New', + description: 'Tab title for adding new components in outline using sidebar', + }, + sidebarTabsAddExisiting: { + id: 'course-authoring.course-outline.sidebar.sidebar-section-add.add-existing-tab', + defaultMessage: 'Add Existing', + description: 'Tab title for adding existing library components in outline using sidebar', + } }); export default messages; diff --git a/src/generic/sidebar/index.scss b/src/generic/sidebar/index.scss index f0a7705cf8..e0bcb37e75 100644 --- a/src/generic/sidebar/index.scss +++ b/src/generic/sidebar/index.scss @@ -1,7 +1,8 @@ .sidebar { .sidebar-content { flex: 0 1 auto; - max-width: 650px; + max-width: 660px; + min-width: 500px; } .dropdown-toggle { diff --git a/src/library-authoring/component-picker/ComponentPicker.tsx b/src/library-authoring/component-picker/ComponentPicker.tsx index 168dd6745d..965c67b9ad 100644 --- a/src/library-authoring/component-picker/ComponentPicker.tsx +++ b/src/library-authoring/component-picker/ComponentPicker.tsx @@ -15,7 +15,6 @@ import LibraryCollectionPage from '../collections/LibraryCollectionPage'; import SelectLibrary from './SelectLibrary'; import messages from './messages'; import { ContentType, allLibraryPageTabs } from '../routes'; -import { AtLeastOne } from '../../types'; import { FiltersProps } from '@src/library-authoring/library-filters'; interface LibraryComponentPickerProps { @@ -53,7 +52,7 @@ const defaultSelectionChangedCallback: ComponentSelectionChangedEvent = (selecti window.parent.postMessage({ type: 'pickerSelectionChanged', selections }, '*'); }; -type ComponentPickerProps = AtLeastOne & { +type ComponentPickerProps = Partial & { showOnlyPublished?: boolean, extraFilter?: string[], visibleTabs?: ContentType[], diff --git a/src/library-authoring/library-filters/SidebarFilters.tsx b/src/library-authoring/library-filters/SidebarFilters.tsx index e676d1ce5e..2e7f257d51 100644 --- a/src/library-authoring/library-filters/SidebarFilters.tsx +++ b/src/library-authoring/library-filters/SidebarFilters.tsx @@ -1,19 +1,24 @@ -import { Form, Menu, SearchField, Stack } from "@openedx/paragon" +import { Form, IconButton, Menu, SearchField, Stack, useToggle } from "@openedx/paragon" import { FiltersProps } from "." import { ClearFiltersButton, FilterByBlockType, FilterByTags, SearchKeywordsField, SearchSortWidget } from "@src/search-manager" import SearchFilterWidget from "@src/search-manager/SearchFilterWidget" -import { Newsstand } from "@openedx/paragon/icons" +import { FilterList, Newsstand } from "@openedx/paragon/icons" +import messages from './messages'; +import { useIntl } from "@edx/frontend-platform/i18n" export const SidebarFilters = ({onlyOneType}: FiltersProps) => { + const intl = useIntl(); + const [isOn,,, toggle] = useToggle(false); return ( - + {}} icon={Newsstand} + btnSize="md" > { + - + {isOn && {!(onlyOneType) && } - + } ) } diff --git a/src/library-authoring/library-filters/messages.ts b/src/library-authoring/library-filters/messages.ts new file mode 100644 index 0000000000..f55c5cef93 --- /dev/null +++ b/src/library-authoring/library-filters/messages.ts @@ -0,0 +1,16 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + librariesFilterBtnText: { + id: 'course-authoring.library-authoring.library-filters.libraries.filter.btn', + defaultMessage: 'All libraries', + description: 'Button text for libraries filter', + }, + additionalFilterBtnAltText: { + id: 'course-authoring.library-authoring.library-filters.additional.filter.alt-btn', + defaultMessage: 'See more', + description: 'Alt text for more fitlers', + }, +}); + +export default messages; diff --git a/src/search-manager/SearchFilterWidget.tsx b/src/search-manager/SearchFilterWidget.tsx index c2a6a3536d..5e1e1598cb 100644 --- a/src/search-manager/SearchFilterWidget.tsx +++ b/src/search-manager/SearchFilterWidget.tsx @@ -28,6 +28,7 @@ const SearchFilterWidget: React.FC<{ clearFilter: () => void, icon: React.ComponentType; skipLabelUpdate?: boolean; + btnSize?: 'sm' | 'md' | 'lg'; }> = ({ appliedFilters, ...props }) => { const intl = useIntl(); const [isOpen, open, close] = useToggle(false); @@ -44,7 +45,7 @@ const SearchFilterWidget: React.FC<{