diff --git a/src/learning-header/LearningHeader.jsx b/src/learning-header/LearningHeader.jsx index 9d70ee28f..a1801bb28 100644 --- a/src/learning-header/LearningHeader.jsx +++ b/src/learning-header/LearningHeader.jsx @@ -37,13 +37,11 @@ const LearningHeader = ({
- {showUserDropdown && authenticatedUser && ( - <> - + + {authenticatedUser && showUserDropdown && ( - )} {showUserDropdown && !authenticatedUser && ( diff --git a/src/learning-header/LearningHeader.test.jsx b/src/learning-header/LearningHeader.test.jsx index 3d80888ef..cffdcd9e7 100644 --- a/src/learning-header/LearningHeader.test.jsx +++ b/src/learning-header/LearningHeader.test.jsx @@ -1,9 +1,36 @@ import React from 'react'; +import { mergeConfig } from '@edx/frontend-platform'; +import { AppContext } from '@edx/frontend-platform/react'; +import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework'; import { authenticatedUser, initializeMockApp, render, screen, } from '../setupTest'; import { LearningHeader as Header } from '../index'; +const V2_SLOT_ID = 'org.openedx.frontend.layout.learning_header_actions.v2'; +const V2_WIDGET_TEXT = 'Test V2 Widget'; + +const configureV2Widget = () => { + mergeConfig({ + pluginSlots: { + [V2_SLOT_ID]: { + keepDefault: true, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'test_v2_widget', + type: DIRECT_PLUGIN, + priority: 10, + RenderWidget: () => {V2_WIDGET_TEXT}, + }, + }, + ], + }, + }, + }); +}; + describe('Header', () => { beforeAll(async () => { // We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`. @@ -26,4 +53,32 @@ describe('Header', () => { expect(screen.getByText(`${courseData.courseOrg} ${courseData.courseNumber}`)).toBeInTheDocument(); expect(screen.getByText(courseData.courseTitle)).toBeInTheDocument(); }); + + it('hides the user dropdown and the header actions when showUserDropdown is false', () => { + render(
); + + expect(screen.queryByText(authenticatedUser.username)).not.toBeInTheDocument(); + expect(screen.queryByText('Help')).not.toBeInTheDocument(); + }); + + it('renders v2 slot content for an authenticated user even when showUserDropdown is false', () => { + configureV2Widget(); + render(
); + + expect(screen.getByText(V2_WIDGET_TEXT)).toBeInTheDocument(); + expect(screen.queryByText(authenticatedUser.username)).not.toBeInTheDocument(); + expect(screen.queryByText('Help')).not.toBeInTheDocument(); + }); + + it('renders v2 slot content for an anonymous user', () => { + configureV2Widget(); + render( + +
+ , + ); + + expect(screen.getByText(V2_WIDGET_TEXT)).toBeInTheDocument(); + expect(screen.queryByText('Help')).not.toBeInTheDocument(); + }); }); diff --git a/src/plugin-slots/HeaderNotificationsSlot/README.md b/src/plugin-slots/HeaderNotificationsSlot/README.md index de32da015..ed95d9f72 100644 --- a/src/plugin-slots/HeaderNotificationsSlot/README.md +++ b/src/plugin-slots/HeaderNotificationsSlot/README.md @@ -12,7 +12,7 @@ This slot renders the notifications tray (bell icon + notification popover) from 1. **Desktop Header** — via `org.openedx.frontend.layout.header_desktop_secondary_menu.v2` Notifications appear before secondary menu items (e.g., "New", "Help") -2. **Learning Header** — via `org.openedx.frontend.layout.learning_header_actions.v1` +2. **Learning Header** — via `org.openedx.frontend.layout.learning_header_actions.v1` (nested within [`.v2`](../LearningHeaderActionsSlot/v2/)) Notifications appear before the help link 3. **Studio Header** — via `org.openedx.frontend.layout.studio_header_actions.v1` @@ -27,9 +27,10 @@ Desktop Header └── org.openedx.frontend.layout.header_desktop_secondary_menu.v1 (menu items only) Learning Header -└── org.openedx.frontend.layout.learning_header_actions.v1 - ├── org.openedx.frontend.layout.header_notifications_tray.v1 ← This slot - └── org.openedx.frontend.layout.header_learning_help.v1 +└── org.openedx.frontend.layout.learning_header_actions.v2 + └── org.openedx.frontend.layout.learning_header_actions.v1 (only when showUserDropdown is true and a user is authenticated) + ├── org.openedx.frontend.layout.header_notifications_tray.v1 ← This slot + └── org.openedx.frontend.layout.header_learning_help.v1 Studio Header └── org.openedx.frontend.layout.studio_header_actions.v1 diff --git a/src/plugin-slots/LearningHeaderActionsSlot/README.md b/src/plugin-slots/LearningHeaderActionsSlot/README.md index 449ea9440..c49a6d40e 100644 --- a/src/plugin-slots/LearningHeaderActionsSlot/README.md +++ b/src/plugin-slots/LearningHeaderActionsSlot/README.md @@ -1,115 +1,6 @@ # Learning Header Actions Slot -### Slot ID: `org.openedx.frontend.layout.learning_header_actions.v1` - -**Default Content:** -- **Notification Tray** (via `HeaderNotificationsSlot`) — Rendered before the help link -- **Help Link** (via `LearningHelpSlot`) - ---- - -### Add Custom Components before and after Learning Header Actions - -The following `env.config.jsx` inserts a custom component before the notification tray (`priority: 10`) and another after the help link (`priority: 90`). - -![Screenshot of custom components before and after learning header actions](./images/custom_components_before_and_after_learning_actions.png) - -```jsx -import React from 'react'; -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.layout.learning_header_actions.v1': { - keepDefault: true, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_before_learning_actions', - type: DIRECT_PLUGIN, - priority: 10, - RenderWidget: () => ( -

🌜

- ), - }, - }, - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_after_learning_actions', - type: DIRECT_PLUGIN, - priority: 90, - RenderWidget: () => ( -

🌛

- ), - }, - }, - ], - }, - }, -}; - -export default config; -``` - -### Hide the Entire Learning Header Actions Area - -The following `env.config.jsx` removes both the notification tray and the help link from the learning header. - -![Screenshot of hiding learning header actions area](./images/hide_learning_actions.png) - -```jsx -import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.layout.learning_header_actions.v1': { - keepDefault: true, - plugins: [ - { - op: PLUGIN_OPERATIONS.Hide, - widgetId: 'default_contents', - }, - ], - }, - }, -}; - -export default config; -``` - -### Replace the Entire Learning Header Actions Area with a Custom Component - -The following `env.config.jsx` replaces the notification tray and help link with a single custom component. - -![Screenshot of replacing learning header actions area with custom component](./images/replace_learning_actions_with_custom_component.png) - -```jsx -import React from 'react'; -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.layout.learning_header_actions.v1': { - keepDefault: false, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_learning_actions', - type: DIRECT_PLUGIN, - priority: 50, - RenderWidget: () => ( - My Custom Learning Actions - ), - }, - }, - ], - }, - }, -}; - -export default config; -``` - +| Slot ID | Description | Docs | +|---------|-------------|------| +| `org.openedx.frontend.layout.learning_header_actions.v2` | Always rendered, regardless of `showUserDropdown` (Default slot) | [v2 docs](./v2/) | +| `org.openedx.frontend.layout.learning_header_actions.v1` **(Deprecated)** | Notification tray + help link, only rendered when `showUserDropdown` is `true` and a user is authenticated | [v1 docs](./v1/) | diff --git a/src/plugin-slots/LearningHeaderActionsSlot/index.jsx b/src/plugin-slots/LearningHeaderActionsSlot/index.jsx index 6055190a5..169204719 100644 --- a/src/plugin-slots/LearningHeaderActionsSlot/index.jsx +++ b/src/plugin-slots/LearningHeaderActionsSlot/index.jsx @@ -1,15 +1,4 @@ -import React from 'react'; -import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import HeaderNotificationsSlot from '../HeaderNotificationsSlot'; -import LearningHelpSlot from '../LearningHelpSlot'; - -const LearningHeaderActionsSlot = () => ( - - - - -); +import LearningHeaderActionsSlot from './v2'; +export { default as LearningHeaderActionsSlotV1 } from './v1'; export default LearningHeaderActionsSlot; diff --git a/src/plugin-slots/LearningHeaderActionsSlot/v1/README.md b/src/plugin-slots/LearningHeaderActionsSlot/v1/README.md new file mode 100644 index 000000000..5967a0044 --- /dev/null +++ b/src/plugin-slots/LearningHeaderActionsSlot/v1/README.md @@ -0,0 +1,123 @@ +# Learning Header Actions Slot — v1 (Notification Tray + Help Link) + +> **⚠️ Deprecated:** This slot is deprecated and will be removed. Use the +> [`v2` slot](../v2/) instead, which renders unconditionally regardless of +> `showUserDropdown`. See [DEPR ticket #681](https://github.com/openedx/frontend-component-header/issues/681) +> for the removal plan and timeline. + +### Slot ID: `org.openedx.frontend.layout.learning_header_actions.v1` + +**Default Content:** +- **Notification Tray** (via `HeaderNotificationsSlot`) — Rendered before the help link +- **Help Link** (via `LearningHelpSlot`) + +> **Note:** This slot is only rendered when `showUserDropdown` is `true` and a user is authenticated. To render content regardless of `showUserDropdown` or authentication, use the parent [`v2` slot](../v2/). + +--- + +## Examples + +### Add Custom Components before and after Learning Header Actions + +The following `env.config.jsx` inserts a custom component before the notification tray (`priority: 10`) and another after the help link (`priority: 90`). + +![Screenshot of custom components before and after learning header actions](../images/custom_components_before_and_after_learning_actions.png) + +```jsx +import React from 'react'; +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v1': { + keepDefault: true, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_before_learning_actions', + type: DIRECT_PLUGIN, + priority: 10, + RenderWidget: () => ( +

🌜

+ ), + }, + }, + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_after_learning_actions', + type: DIRECT_PLUGIN, + priority: 90, + RenderWidget: () => ( +

🌛

+ ), + }, + }, + ], + }, + }, +}; + +export default config; +``` + +### Hide the Entire Learning Header Actions Area + +The following `env.config.jsx` removes both the notification tray and the help link from the learning header. + +![Screenshot of hiding learning header actions area](../images/hide_learning_actions.png) + +```jsx +import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v1': { + keepDefault: true, + plugins: [ + { + op: PLUGIN_OPERATIONS.Hide, + widgetId: 'default_contents', + }, + ], + }, + }, +}; + +export default config; +``` + +### Replace the Entire Learning Header Actions Area with a Custom Component + +The following `env.config.jsx` replaces the notification tray and help link with a single custom component. + +![Screenshot of replacing learning header actions area with custom component](../images/replace_learning_actions_with_custom_component.png) + +```jsx +import React from 'react'; +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v1': { + keepDefault: false, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_learning_actions', + type: DIRECT_PLUGIN, + priority: 50, + RenderWidget: () => ( + My Custom Learning Actions + ), + }, + }, + ], + }, + }, +}; + +export default config; +``` diff --git a/src/plugin-slots/LearningHeaderActionsSlot/v1/index.jsx b/src/plugin-slots/LearningHeaderActionsSlot/v1/index.jsx new file mode 100644 index 000000000..b01e6a074 --- /dev/null +++ b/src/plugin-slots/LearningHeaderActionsSlot/v1/index.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import HeaderNotificationsSlot from '../../HeaderNotificationsSlot'; +import LearningHelpSlot from '../../LearningHelpSlot'; + +const SLOT_ID = 'org.openedx.frontend.layout.learning_header_actions.v1'; + +let hasWarned = false; + +/** @deprecated Use `LearningHeaderActionsSlotV2` instead. See DEPR ticket openedx/frontend-component-header#681. */ +const LearningHeaderActionsSlotV1 = () => { + if (!hasWarned && getConfig()?.pluginSlots?.[SLOT_ID]) { + hasWarned = true; + // eslint-disable-next-line no-console + console.warn( + `[Deprecated] The "${SLOT_ID}" plugin slot ` + + 'is deprecated and will be removed. Migrate your pluginSlots config to ' + + '"org.openedx.frontend.layout.learning_header_actions.v2". ' + + 'See https://github.com/openedx/frontend-component-header/issues/681 for details.', + ); + } + + return ( + + + + + ); +}; + +export default LearningHeaderActionsSlotV1; diff --git a/src/plugin-slots/LearningHeaderActionsSlot/v2/README.md b/src/plugin-slots/LearningHeaderActionsSlot/v2/README.md new file mode 100644 index 000000000..9d923e4a0 --- /dev/null +++ b/src/plugin-slots/LearningHeaderActionsSlot/v2/README.md @@ -0,0 +1,117 @@ +# Learning Header Actions Slot — v2 (Full Learning Header Actions Area) + +### Slot ID: `org.openedx.frontend.layout.learning_header_actions.v2` + +**Default Content:** +- **Learning Header Actions v1** (via [`LearningHeaderActionsSlotV1`](../v1/)) — Notification tray + help link, only rendered when `showUserDropdown` is `true` + +This slot always renders, regardless of the `showUserDropdown` prop passed to `LearningHeader`. Use it to add, hide, or replace the whole actions area independently of `showUserDropdown`. + +--- + +## Examples + +### Add Custom Components before and after the Learning Header Actions Area + +The following `env.config.jsx` inserts a custom component before the notification tray/help link (`priority: 10`) and another after (`priority: 90`). These render even when `showUserDropdown` is `false`. + +![Screenshot of custom components before and after learning header actions](../images/custom_components_before_and_after_learning_actions.png) + +```jsx +import React from 'react'; +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v2': { + keepDefault: true, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_before_learning_actions', + type: DIRECT_PLUGIN, + priority: 10, + RenderWidget: () => ( +

🌜

+ ), + }, + }, + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_after_learning_actions', + type: DIRECT_PLUGIN, + priority: 90, + RenderWidget: () => ( +

🌛

+ ), + }, + }, + ], + }, + }, +}; + +export default config; +``` + +### Hide the Entire Learning Header Actions Area + +The following `env.config.jsx` removes the actions area (notification tray + help link) from the learning header, regardless of `showUserDropdown`. + +![Screenshot of hiding learning header actions area](../images/hide_learning_actions.png) + +```jsx +import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v2': { + keepDefault: true, + plugins: [ + { + op: PLUGIN_OPERATIONS.Hide, + widgetId: 'default_contents', + }, + ], + }, + }, +}; + +export default config; +``` + +### Replace the Entire Learning Header Actions Area with a Custom Component + +The following `env.config.jsx` replaces the actions area with a single custom component that renders regardless of `showUserDropdown`. + +![Screenshot of replacing learning header actions area with custom component](../images/replace_learning_actions_with_custom_component.png) + +```jsx +import React from 'react'; +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.layout.learning_header_actions.v2': { + keepDefault: false, + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_learning_actions', + type: DIRECT_PLUGIN, + priority: 50, + RenderWidget: () => ( + My Custom Learning Actions + ), + }, + }, + ], + }, + }, +}; + +export default config; +``` diff --git a/src/plugin-slots/LearningHeaderActionsSlot/v2/index.jsx b/src/plugin-slots/LearningHeaderActionsSlot/v2/index.jsx new file mode 100644 index 000000000..2e8483737 --- /dev/null +++ b/src/plugin-slots/LearningHeaderActionsSlot/v2/index.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import LearningHeaderActionsSlotV1 from '../v1'; + +const LearningHeaderActionsSlot = ({ showDefaultActions = true }) => ( + + {showDefaultActions && } + +); + +LearningHeaderActionsSlot.propTypes = { + showDefaultActions: PropTypes.bool, +}; + +export default LearningHeaderActionsSlot; diff --git a/src/plugin-slots/README.md b/src/plugin-slots/README.md index 3a5d4aa7a..fadd225e5 100644 --- a/src/plugin-slots/README.md +++ b/src/plugin-slots/README.md @@ -15,7 +15,8 @@ ### Learning Header * [`org.openedx.frontend.layout.header_learning_course_info.v1`](./CourseInfoSlot/) -* [`org.openedx.frontend.layout.learning_header_actions.v1`](./LearningHeaderActionsSlot/) +* [`org.openedx.frontend.layout.learning_header_actions.v1`](./LearningHeaderActionsSlot/v1/) **(Deprecated, use `.v2`)** +* [`org.openedx.frontend.layout.learning_header_actions.v2`](./LearningHeaderActionsSlot/v2/) * [`org.openedx.frontend.layout.header_learning_help.v1`](./LearningHelpSlot/) * [`org.openedx.frontend.layout.header_learning_logged_out_items.v1`](./LearningLoggedOutItemsSlot/) * [`org.openedx.frontend.layout.header_learning_user_menu.v1`](./LearningUserMenuSlot/)