Skip to content
Open
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
444 changes: 391 additions & 53 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"ts-jest": "^29.4.4"
},
"dependencies": {
"@edx/frontend-plugin-notifications": "^2.0.5",
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2",
"@fortawesome/free-regular-svg-icons": "6.7.2",
Expand Down
5 changes: 4 additions & 1 deletion src/Header.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import TestRenderer from 'react-test-renderer';
import { AppContext } from '@edx/frontend-platform/react';
import { MemoryRouter } from 'react-router-dom';
import { Context as ResponsiveContext } from 'react-responsive';

import Header from './index';
Expand All @@ -13,7 +14,9 @@ const HeaderComponent = ({ width, contextValue }) => (
<AppContext.Provider
value={contextValue}
>
<Header />
<MemoryRouter>
<Header />
</MemoryRouter>
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
Expand Down
2 changes: 2 additions & 0 deletions src/desktop-header/DesktopHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DesktopMainMenuSlot from '../plugin-slots/DesktopMainMenuSlot';
import { desktopHeaderMainOrSecondaryMenuDataShape } from './DesktopHeaderMainOrSecondaryMenu';
import DesktopSecondaryMenuSlot from '../plugin-slots/DesktopSecondaryMenuSlot';
import DesktopUserMenuSlot from '../plugin-slots/DesktopUserMenuSlot';
import HeaderNotificationsSlot from '../plugin-slots/HeaderNotificationsSlot';
import { desktopUserMenuDataShape } from './DesktopHeaderUserMenu';

// i18n
Expand Down Expand Up @@ -78,6 +79,7 @@ const DesktopHeader = ({
{loggedIn
? (
<>
<HeaderNotificationsSlot />
{renderSecondaryMenu()}
{renderUserMenu()}
</>
Expand Down
2 changes: 2 additions & 0 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CourseInfoSlot from '../plugin-slots/CourseInfoSlot';
import { courseInfoDataShape } from './LearningHeaderCourseInfo';
import messages from './messages';
import LearningHelpSlot from '../plugin-slots/LearningHelpSlot';
import HeaderNotificationsSlot from '../plugin-slots/HeaderNotificationsSlot';

const LearningHeader = ({
courseOrg,
Expand Down Expand Up @@ -39,6 +40,7 @@ const LearningHeader = ({
</div>
{showUserDropdown && authenticatedUser && (
<>
<HeaderNotificationsSlot />
<LearningHelpSlot />
<AuthenticatedUserDropdown
username={authenticatedUser.username}
Expand Down
2 changes: 2 additions & 0 deletions src/mobile-header/MobileHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import MobileMainMenuSlot from '../plugin-slots/MobileMainMenuSlot';
import { mobileHeaderMainMenuDataShape } from './MobileHeaderMainMenu';
import MobileUserMenuSlot from '../plugin-slots/MobileUserMenuSlot';
import { mobileHeaderUserMenuDataShape } from './MobileHeaderUserMenu';
import HeaderNotificationsSlot from '../plugin-slots/HeaderNotificationsSlot';

// i18n
import messages from '../Header.messages';
Expand Down Expand Up @@ -80,6 +81,7 @@ const MobileHeader = ({
</div>
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
<div className="w-100 d-flex justify-content-end align-items-center">
{loggedIn && <HeaderNotificationsSlot />}
<Menu tag="nav" aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="position-static">
<MenuTrigger
tag="button"
Expand Down
114 changes: 114 additions & 0 deletions src/plugin-slots/HeaderNotificationsSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Header Notifications Slot

### Slot ID: `org.openedx.frontend.layout.header_notifications_tray.v1`

### Slot ID Aliases
* `header_notifications_tray`

## Description

This slot renders the notifications tray (bell icon + notification popover) from `@edx/frontend-plugin-notifications` by default. It is present in **all header types** for authenticated users:

- **Desktop Header** — before the secondary menu, to the left of menu items and user dropdown
- **Mobile Header** — before the user menu trigger (avatar)
- **Learning Header** — before the help link, to the left of help and user dropdown
- **Studio Header** — before the search button, to the left of search and user menu (both desktop and mobile)

Notifications are **enabled by default** for all community instances. The `NotificationsTray` component is self-gating: it renders nothing when the backend waffle flag is disabled.

## Examples

### Disable Notifications

The following `env.config.jsx` will hide the notifications tray entirely.

```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.header_notifications_tray.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
]
},
},
}

export default config;
```

### Replace Notifications with Custom Component

The following `env.config.jsx` will replace the default notifications tray with a custom component.

```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import CustomNotifications from './CustomNotifications';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.header_notifications_tray.v1': {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_notifications_component',
type: DIRECT_PLUGIN,
RenderWidget: CustomNotifications,
},
},
]
},
},
}

export default config;
```

### Add Custom Components before and after Notifications

The following `env.config.jsx` will place custom components before and after the notifications tray.

```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.header_notifications_tray.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_notifications_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<span>📢</span>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_notifications_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<span>💬</span>
),
},
},
]
},
},
}

export default config;
```
14 changes: 14 additions & 0 deletions src/plugin-slots/HeaderNotificationsSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import NotificationsTray from '@edx/frontend-plugin-notifications';

const HeaderNotificationsSlot = () => (
<PluginSlot
id="org.openedx.frontend.layout.header_notifications_tray.v1"
idAliases={['header_notifications_tray']}
>
<NotificationsTray />
</PluginSlot>
);

export default HeaderNotificationsSlot;
1 change: 1 addition & 0 deletions src/plugin-slots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Shared
* [`org.openedx.frontend.layout.header_logo.v1`](./LogoSlot/)
* [`org.openedx.frontend.layout.header_notifications_tray.v1`](./HeaderNotificationsSlot/)

### Desktop Header
* [`org.openedx.frontend.layout.header_desktop.v1`](./DesktopHeaderSlot/)
Expand Down
2 changes: 2 additions & 0 deletions src/studio-header/HeaderBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import UserMenu from './UserMenu';
import BrandNav from './BrandNav';
import NavDropdownMenu from './NavDropdownMenu';
import StudioHeaderSearchButtonSlot from '../plugin-slots/StudioHeaderSearchButtonSlot';
import HeaderNotificationsSlot from '../plugin-slots/HeaderNotificationsSlot';

export interface HeaderBodyProps {
studioBaseUrl: string;
Expand Down Expand Up @@ -137,6 +138,7 @@ const HeaderBody = ({
</>
)}
<ActionRow.Spacer />
<HeaderNotificationsSlot />
<StudioHeaderSearchButtonSlot
searchButtonAction={searchButtonAction}
/>
Expand Down