Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add frontend-plugin-framework LogoSlot #528

Merged
merged 1 commit into from
Sep 26, 2024
Merged
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
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ This library has the following exports:
* ``messages``: Internationalization messages suitable for use with `@edx/frontend-platform/i18n <https://edx.github.io/frontend-platform/module-Internationalization.html>`_
* ``dist/index.scss``: A SASS file which contains style information for the component. It should be imported into the micro-frontend's own SCSS file.

Plugins
-------
This can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.

The parts of this that can be customized in that manner are documented `here </src/plugin-slots>`_.

Examples
========

Expand Down
1,959 changes: 397 additions & 1,562 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 @@ -60,6 +60,7 @@
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/frontend-plugin-framework": "^1.3.0",
"axios-mock-adapter": "1.22.0",
"babel-polyfill": "6.26.0",
"jest-environment-jsdom": "^29.7.0",
Expand Down
4 changes: 2 additions & 2 deletions src/DesktopHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
import LogoSlot from './plugin-slots/LogoSlot';

// i18n
import messages from './Header.messages';
Expand Down Expand Up @@ -145,7 +145,7 @@ class DesktopHeader extends React.Component {
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
<div className={`container-fluid ${logoClasses}`}>
<div className="nav-container position-relative d-flex align-items-center">
{logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} />}
<LogoSlot {...logoProps} />
<nav
aria-label={intl.formatMessage(messages['header.label.main.nav'])}
className="nav main-nav"
Expand Down
16 changes: 3 additions & 13 deletions src/Logo.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';

const Logo = ({ src, alt, ...attributes }) => (
<img src={src} alt={alt} {...attributes} />
);

Logo.propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};

const LinkedLogo = ({
const Logo = ({
href,
src,
alt,
...attributes
}) => (
<a href={href} {...attributes}>
<a href={href} className="logo" {...attributes}>
<img className="d-block" src={src} alt={alt} />
</a>
);

LinkedLogo.propTypes = {
Logo.propTypes = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};

export { LinkedLogo, Logo };
export default Logo;
4 changes: 2 additions & 2 deletions src/MobileHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
import LogoSlot from './plugin-slots/LogoSlot';

// i18n
import messages from './Header.messages';
Expand Down Expand Up @@ -155,7 +155,7 @@ class MobileHeader extends React.Component {
</div>
) : null}
<div className={`w-100 d-flex ${logoClasses}`}>
{ logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} itemType="http://schema.org/Organization" />}
<LogoSlot {...logoProps} itemType="http://schema.org/Organization" />
</div>
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
<div className="w-100 d-flex justify-content-end align-items-center">
Expand Down
21 changes: 2 additions & 19 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,16 @@ import { AppContext } from '@edx/frontend-platform/react';

import AnonymousUserMenu from './AnonymousUserMenu';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import LogoSlot from '../plugin-slots/LogoSlot';
import messages from './messages';

const LinkedLogo = ({
href,
src,
alt,
...attributes
}) => (
<a href={href} {...attributes}>
<img className="d-block" src={src} alt={alt} />
</a>
);

LinkedLogo.propTypes = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};

const LearningHeader = ({
courseOrg, courseNumber, courseTitle, intl, showUserDropdown,
}) => {
const { authenticatedUser } = useContext(AppContext);

const headerLogo = (
<LinkedLogo
className="logo"
<LogoSlot
href={`${getConfig().LMS_BASE_URL}/dashboard`}
src={getConfig().LOGO_URL}
alt={getConfig().SITE_NAME}
Expand Down
69 changes: 69 additions & 0 deletions src/plugin-slots/LogoSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Logo Slot

### Slot ID: `logo_slot`

## Description

This slot is used to replace/modify/hide the logo.

## Examples

### Modify URL

The following `env.config.jsx` will modify the link href for the logo.

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

const modifyLogoHref = ( widget ) => {
widget.content.href = "https://openedx.org/";
return widget;
};

const config = {
pluginSlots: {
logo_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifyLogoHref,
},
]
},
},
}

export default config;
```

### Custom Component

The following `env.config.jsx` will replace the logo entirely (in this case with a centered 🗺️ `h1`)

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

const config = {
pluginSlots: {
logo_slot: {
keepDefault: false,
plugins: [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as an aside while we're all talking about the syntax for plugins, I have to say... that's a whole lot of code to replace a logo. 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think people generally agree that FPF config feels a bit too verbose (hence the thread https://discuss.openedx.org/t/proposal-simplified-frontend-plugin-api-config/13312)

That being said, just changing the logo image itself is still very much possible via existing config methods - wrapping the Logo component in a PluginSlot doesn't change that

{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_logo_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺️</h1>
),
},
},
]
}
},
}

export default config;
```
25 changes: 25 additions & 0 deletions src/plugin-slots/LogoSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import Logo from '../../Logo';

const LogoSlot = ({
href, src, alt, ...attributes
}) => (
<PluginSlot
id="logo_slot"
slotOptions={{
mergeProps: true,
}}
>
<Logo href={href} src={src} alt={alt} {...attributes} />
</PluginSlot>
);

LogoSlot.propTypes = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};

export default LogoSlot;
3 changes: 3 additions & 0 deletions src/plugin-slots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `frontend-component-header` Plugin Slots

* [`logo_slot`](./LogoSlot/)