diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx index e8bf9e2882..99ccd48adf 100644 --- a/src/courseware/course/sequence/Sequence.jsx +++ b/src/courseware/course/sequence/Sequence.jsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ +import SequenceBottomNavigationSlot from '@src/plugin-slots/SequenceBottomNavigationSlot'; import { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; @@ -91,27 +92,27 @@ const Sequence = ({ }; /* istanbul ignore next */ - const nextHandler = () => { - logEvent('edx.ui.lms.sequence.next_selected', 'top'); + const nextHandler = (placement) => () => { + logEvent('edx.ui.lms.sequence.next_selected', placement); handleNext(); }; /* istanbul ignore next */ - const previousHandler = () => { - logEvent('edx.ui.lms.sequence.previous_selected', 'top'); + const previousHandler = (placement) => () => { + logEvent('edx.ui.lms.sequence.previous_selected', placement); handlePrevious(); }; /* istanbul ignore next */ - const onNavigate = (destinationUnitId) => { - logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId); + const onNavigate = (placement) => (destinationUnitId) => { + logEvent('edx.ui.lms.sequence.tab_selected', placement, destinationUnitId); handleNavigate(destinationUnitId); }; const sequenceNavProps = { - nextHandler, - previousHandler, - onNavigate, + nextHandler: nextHandler('top'), + previousHandler: previousHandler('top'), + onNavigate: onNavigate('top'), }; useSequenceBannerTextAlert(sequenceId); @@ -166,20 +167,18 @@ const Sequence = ({ const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated; + const unitNavigationProps = { + courseId, + sequenceId, + unitId, + onClickPrevious: previousHandler('bottom'), + onClickNext: nextHandler('bottom'), + }; + const renderUnitNavigation = (isAtTop) => ( { - logEvent('edx.ui.lms.sequence.previous_selected', 'bottom'); - handlePrevious(); - }} - onClickNext={() => { - logEvent('edx.ui.lms.sequence.next_selected', 'bottom'); - handleNext(); - }} + {...unitNavigationProps} /> ); @@ -224,7 +223,12 @@ const Sequence = ({ isOriginalUserStaff={originalUserIsStaff} renderUnitNavigation={renderUnitNavigation} /> - {unitHasLoaded && renderUnitNavigation(false)} + {unitHasLoaded && ( + + )} diff --git a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.jsx b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.jsx index ce8903dd4f..e0c9ad0188 100644 --- a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.jsx +++ b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.jsx @@ -2,13 +2,13 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; +import { NextUnitTopNavTriggerSlot } from '@src/plugin-slots/NextUnitTopNavTriggerSlot'; import { GetCourseExitNavigation } from '../../course-exit'; import { useSequenceNavigationMetadata } from './hooks'; import messages from './messages'; import PreviousButton from './generic/PreviousButton'; import NextButton from './generic/NextButton'; -import { NextUnitTopNavTriggerSlot } from '../../../../plugin-slots/NextUnitTopNavTriggerSlot'; const UnitNavigation = ({ sequenceId, diff --git a/src/plugin-slots/SequenceBottomNavigationSlot/README.md b/src/plugin-slots/SequenceBottomNavigationSlot/README.md new file mode 100644 index 0000000000..4ca651ec5a --- /dev/null +++ b/src/plugin-slots/SequenceBottomNavigationSlot/README.md @@ -0,0 +1,66 @@ +# Sequence Bottom Navigation Slot + +### Slot ID: `org.openedx.frontend.learning.sequence_bottom_navigation.v1` + +### Props: +* `sequenceId` (string) — Current sequence identifier +* `unitId` (string) — Current unit identifier +* `nextHandler` (function) — Handler for next navigation action +* `onNavigate` (function) — Handler for direct unit navigation +* `previousHandler` (function) — Handler for previous navigation action + +## Description + +This slot is used to replace/modify/hide the sequence navigation component that controls navigation between units within a course sequence at the bottom of the page. + +## Example + +### Default Navigation +![Default Navigation](./default-navigation.png) + +### Replaced with top naviation arrows +![Top navigation at bottom](./arrow-navigation.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.learning.sequence_bottom_navigation.v1': { + keepDefault: false, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_sequence_navigation', + type: DIRECT_PLUGIN, + RenderWidget: ({ + sequenceId, + unitId, + nextHandler, + onNavigate, + previousHandler + }) => { + const { courseId } = useParams(); + return ( +
+ +
+ ); + }, + }, + }, + ], + }, + }, +}; + +export default config; +``` diff --git a/src/plugin-slots/SequenceBottomNavigationSlot/arrow-naviation.png b/src/plugin-slots/SequenceBottomNavigationSlot/arrow-naviation.png new file mode 100644 index 0000000000..0cfd615189 Binary files /dev/null and b/src/plugin-slots/SequenceBottomNavigationSlot/arrow-naviation.png differ diff --git a/src/plugin-slots/SequenceBottomNavigationSlot/default-navigation.png b/src/plugin-slots/SequenceBottomNavigationSlot/default-navigation.png new file mode 100644 index 0000000000..d07c1be223 Binary files /dev/null and b/src/plugin-slots/SequenceBottomNavigationSlot/default-navigation.png differ diff --git a/src/plugin-slots/SequenceBottomNavigationSlot/index.tsx b/src/plugin-slots/SequenceBottomNavigationSlot/index.tsx new file mode 100644 index 0000000000..ea9ff0cb0d --- /dev/null +++ b/src/plugin-slots/SequenceBottomNavigationSlot/index.tsx @@ -0,0 +1,47 @@ +import { UnitNavigation } from '@src/courseware/course/sequence/sequence-navigation'; +import React from 'react'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; + +export interface SequenceBottomNavigationSlotProps { + courseId: string; + sequenceId: string; + unitId: string; + nextHandler: () => void; + onNavigate: (unitId: string) => void; + previousHandler: () => void; +} + +const SequenceBottomNavigationSlot = ({ + courseId, + sequenceId, + unitId, + nextHandler, + onNavigate, + previousHandler, +}: SequenceBottomNavigationSlotProps) => ( + + + +); + +export default SequenceBottomNavigationSlot;