Skip to content

Commit 6975067

Browse files
authored
feat: User menu trigger plugin slots. (#618)
1 parent 813cbb3 commit 6975067

21 files changed

+377
-23
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"snapshot": "fedx-scripts jest --updateSnapshot",
1515
"start": "fedx-scripts webpack-dev-server --progress",
1616
"test": "fedx-scripts jest --coverage",
17+
"test:dev": "fedx-scripts jest --watchAll",
1718
"types": "tsc --noEmit"
1819
},
1920
"files": [
@@ -70,3 +71,4 @@
7071
"react-router-dom": "^6.14.2"
7172
}
7273
}
74+

src/desktop-header/DesktopHeader.jsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
44
import { getConfig } from '@edx/frontend-platform';
55

66
// Local Components
7+
import DesktopUserMenuToggleSlot
8+
from '../plugin-slots/DesktopUserMenuToggleSlot';
79
import { Menu, MenuTrigger, MenuContent } from '../Menu';
8-
import Avatar from '../Avatar';
910
import LogoSlot from '../plugin-slots/LogoSlot';
1011
import DesktopLoggedOutItemsSlot from '../plugin-slots/DesktopLoggedOutItemsSlot';
1112
import { desktopLoggedOutItemsDataShape } from './DesktopLoggedOutItems';
@@ -19,7 +20,6 @@ import { desktopUserMenuDataShape } from './DesktopHeaderUserMenu';
1920
import messages from '../Header.messages';
2021

2122
// Assets
22-
import { CaretIcon } from '../Icons';
2323

2424
class DesktopHeader extends React.Component {
2525
constructor(props) { // eslint-disable-line @typescript-eslint/no-useless-constructor
@@ -51,8 +51,7 @@ class DesktopHeader extends React.Component {
5151
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
5252
className="btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
5353
>
54-
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
55-
{username} <CaretIcon role="img" aria-hidden focusable="false" />
54+
<DesktopUserMenuToggleSlot avatar={avatar} label={username} />
5655
</MenuTrigger>
5756
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
5857
<DesktopUserMenuSlot menu={userMenu} />
@@ -123,15 +122,15 @@ export const desktopHeaderDataShape = {
123122

124123
DesktopHeader.propTypes = {
125124
mainMenu: desktopHeaderDataShape.mainMenu,
126-
secondaryMenu: desktopHeaderDataShape.secondaryMenumainMenu,
127-
userMenu: desktopHeaderDataShape.userMenumainMenu,
128-
loggedOutItems: desktopHeaderDataShape.loggedOutItemsmainMenu,
129-
logo: desktopHeaderDataShape.logomainMenu,
130-
logoAltText: desktopHeaderDataShape.logoAltTextmainMenu,
131-
logoDestination: desktopHeaderDataShape.logoDestinationmainMenu,
132-
avatar: desktopHeaderDataShape.avatarmainMenu,
133-
username: desktopHeaderDataShape.usernamemainMenu,
134-
loggedIn: desktopHeaderDataShape.loggedInmainMenu,
125+
secondaryMenu: desktopHeaderDataShape.secondaryMenu,
126+
userMenu: desktopHeaderDataShape.userMenu,
127+
loggedOutItems: desktopHeaderDataShape.loggedOutItems,
128+
logo: desktopHeaderDataShape.logo,
129+
logoAltText: desktopHeaderDataShape.logoAltText,
130+
logoDestination: desktopHeaderDataShape.logoDestination,
131+
avatar: desktopHeaderDataShape.avatar,
132+
username: desktopHeaderDataShape.username,
133+
loggedIn: desktopHeaderDataShape.loggedIn,
135134

136135
// i18n
137136
intl: intlShape.isRequired,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { CaretIcon } from '../Icons';
4+
import Avatar from '../Avatar';
5+
6+
const DesktopUserMenuToggle = ({ avatar, label }) => (
7+
<>
8+
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
9+
{label} <CaretIcon role="img" aria-hidden focusable="false" />
10+
</>
11+
);
12+
13+
export const DesktopUserMenuTogglePropTypes = {
14+
avatar: PropTypes.string,
15+
label: PropTypes.string,
16+
};
17+
18+
DesktopUserMenuToggle.propTypes = DesktopUserMenuTogglePropTypes;
19+
20+
export default DesktopUserMenuToggle;

src/learning-header/AuthenticatedUserDropdown.jsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
54
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
65
import { getConfig } from '@edx/frontend-platform';
76
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
87
import { Dropdown } from '@openedx/paragon';
98

9+
import LearningUserMenuToggleSlot from '../plugin-slots/LearningUserMenuToggleSlot';
1010
import LearningUserMenuSlot from '../plugin-slots/LearningUserMenuSlot';
1111

1212
import messages from './messages';
@@ -38,10 +38,7 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
3838
return (
3939
<Dropdown className="user-dropdown ml-3">
4040
<Dropdown.Toggle variant="outline-primary" aria-label={intl.formatMessage(messages.userOptionsDropdownLabel)}>
41-
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
42-
<span data-hj-suppress className="d-none d-md-inline">
43-
{username}
44-
</span>
41+
<LearningUserMenuToggleSlot label={username} icon={faUserCircle} />
4542
</Dropdown.Toggle>
4643
<Dropdown.Menu className="dropdown-menu-right">
4744
<LearningUserMenuSlot items={dropdownItems} />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3+
import PropTypes from 'prop-types';
4+
5+
const LearningUserMenuToggle = ({
6+
label,
7+
icon,
8+
}) => (
9+
<>
10+
<FontAwesomeIcon icon={icon} className="d-md-none" size="lg" />
11+
<span data-hj-suppress className="d-none d-md-inline">
12+
{label}
13+
</span>
14+
</>
15+
);
16+
17+
export const LearningUserMenuTogglePropTypes = {
18+
label: PropTypes.string.isRequired,
19+
// Full shape available by examining @fortawesome/fontawesome-common-types/index.d.ts.
20+
icon: PropTypes.shape({
21+
prefix: PropTypes.string.isRequired,
22+
iconName: PropTypes.string.isRequired,
23+
}).isRequired,
24+
};
25+
26+
LearningUserMenuToggle.propTypes = LearningUserMenuTogglePropTypes;
27+
28+
export default LearningUserMenuToggle;

src/mobile-header/MobileHeader.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
44
import { getConfig } from '@edx/frontend-platform';
55

66
// Local Components
7+
import MobileUserMenuToggleSlot from '../plugin-slots/MobileUserMenuToggleSlot';
78
import { Menu, MenuTrigger, MenuContent } from '../Menu';
8-
import Avatar from '../Avatar';
99
import LogoSlot from '../plugin-slots/LogoSlot';
1010
import MobileLoggedOutItemsSlot from '../plugin-slots/MobileLoggedOutItemsSlot';
1111
import { mobileHeaderLoggedOutItemsDataShape } from './MobileLoggedOutItems';
@@ -40,14 +40,17 @@ class MobileHeader extends React.Component {
4040
return <MobileLoggedOutItemsSlot items={loggedOutItems} />;
4141
}
4242

43+
renderUserMenuToggle() {
44+
const { avatar, username } = this.props;
45+
return <MobileUserMenuToggleSlot avatar={avatar} label={username} />;
46+
}
47+
4348
render() {
4449
const {
4550
logo,
4651
logoAltText,
4752
logoDestination,
4853
loggedIn,
49-
avatar,
50-
username,
5154
stickyOnMobile,
5255
intl,
5356
mainMenu,
@@ -98,7 +101,7 @@ class MobileHeader extends React.Component {
98101
aria-label={intl.formatMessage(messages['header.label.account.menu'])}
99102
title={intl.formatMessage(messages['header.label.account.menu'])}
100103
>
101-
<Avatar size="1.5rem" src={avatar} alt={username} />
104+
{this.renderUserMenuToggle()}
102105
</MenuTrigger>
103106
<MenuContent tag="ul" className="nav flex-column pin-left pin-right border-top shadow py-2">
104107
{loggedIn ? this.renderUserMenuItems() : this.renderLoggedOutItems()}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import Avatar from '../Avatar';
4+
5+
const MobileUserMenuToggle = ({ avatar, username }) => <Avatar size="1.5rem" src={avatar} alt={username} />;
6+
7+
export const MobileUserMenuTogglePropTypes = {
8+
avatar: PropTypes.string,
9+
username: PropTypes.string,
10+
};
11+
12+
MobileUserMenuToggle.propTypes = MobileUserMenuTogglePropTypes;
13+
14+
export default MobileUserMenuToggle;

src/plugin-slots/DesktopHeaderSlot/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ const config = {
4141
}
4242

4343
export default config;
44-
```
44+
```
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Desktop User Menu Toggle Slot
2+
3+
### Slot ID: `org.openedx.frontend.layout.header_desktop_user_menu_toggle.v1`
4+
5+
## Description
6+
7+
This slot is used to replace/modify/hide the contents of the user menu toggle button on desktop sized screens.
8+
9+
## Examples
10+
11+
### Modify Label Text
12+
13+
The following `env.config.jsx` will modify the label text to be something more generic:
14+
15+
![Screenshot of modified label](./images/desktop_user_menu_modified_toggle.png)
16+
17+
```jsx
18+
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
19+
import { faHouse } from '@fortawesome/free-solid-svg-icons';
20+
21+
const modifyUserMenuToggle = ( widget ) => {
22+
widget.content.label = "My Profile";
23+
return widget;
24+
};
25+
26+
const config = {
27+
pluginSlots: {
28+
'org.openedx.frontend.layout.header_desktop_user_menu_toggle.v1': {
29+
keepDefault: true,
30+
plugins: [
31+
{
32+
op: PLUGIN_OPERATIONS.Modify,
33+
widgetId: 'default_contents',
34+
fn: modifyUserMenuToggle,
35+
},
36+
]
37+
},
38+
},
39+
}
40+
41+
export default config;
42+
```
43+
44+
### Replace Menu toggle contents with Custom Component
45+
46+
The following `env.config.jsx` will replace the contents of the learning user menu toggle button entirely (in this case with an emoji)
47+
48+
![Screenshot of replaced with custom component](./images/desktop_user_menu_custom_component.png)
49+
50+
```jsx
51+
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
52+
53+
const config = {
54+
pluginSlots: {
55+
'org.openedx.frontend.layout.header_desktop_user_menu_toggle.v1': {
56+
keepDefault: false,
57+
plugins: [
58+
{
59+
op: PLUGIN_OPERATIONS.Insert,
60+
widget: {
61+
id: 'custom_desktop_user_menu_toggle',
62+
type: DIRECT_PLUGIN,
63+
RenderWidget: () => (
64+
<span>🦊</span>
65+
),
66+
},
67+
},
68+
]
69+
},
70+
},
71+
}
72+
73+
export default config;
74+
```
3.23 KB
Loading

0 commit comments

Comments
 (0)