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 #526

Closed
Closed
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
1,826 changes: 265 additions & 1,561 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.2.3",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure if it makes more sense to have this as a direct dependency here or to make it a peer dependency. I went with direct but I'd like to hear what others think.

"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
31 changes: 9 additions & 22 deletions src/Logo.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
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,
};
Comment on lines -4 to -11
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I combed through the codebase and couldn't find any time where we are using a logo without a link. I have some previous commits on a WIP branch that add slots and don't remove this, but I figured I might as well take the opportunity to clean up unused code.


const LinkedLogo = ({
href,
src,
alt,
...attributes
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could only find 2 attributes being added to the logo throughout the codebase. One was className="logo", which was being done everywhere the logo was being used. I figured just being able to write <Logo instead of <Logo className="logo" made more sense, so I moved that down into the component itself.

The other attribute was less straightforward. In MobileHeader only the LinkedLogo had itemType="http://schema.org/Organization". It wasn't clear to me why that would only be in the mobile header. For now I just removed it, but I'd like to revisit this and come up with a better solution.

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

LinkedLogo.propTypes = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
Logo.propTypes = {
content: PropTypes.shape({
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
}).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} />
</div>
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
<div className="w-100 d-flex justify-content-end align-items-center">
Expand Down
2 changes: 0 additions & 2 deletions src/__snapshots__/Header.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
Expand Down Expand Up @@ -381,7 +380,6 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
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 = ( logo ) => {
logo.RenderWidget.props.content.href = "https://openedx.org/";
return logo;
};

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

export default config;
```
18 changes: 18 additions & 0 deletions src/plugin-slots/LogoSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 }) => (
<PluginSlot id="logo_slot">
<Logo content={{ href, src, alt }} />
</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/)