Compare commits

...

50 Commits

Author SHA1 Message Date
David Joy
20649f06d9 Merge pull request #107 from bitmakerla/chris/order_history_in_user_menu
feat: Order history item added in user menu
2021-06-11 17:27:40 -04:00
ChrisChV
f2632fb449 feat: Order history item added in user menu
In Header.jsx, the Order history item was added in the user menu using the ORDER_HISTORY_URL environment variable as href.
Environment Variables section in README.rst updated with the description of ORDER_HISTORY_URL.
2021-06-05 10:43:06 -05:00
stvn
d4bf756993 merge(#86): renovate/actions-setup-node-2.x
commits
=======
- chore(deps): update actions/setup-node action to v2
2021-06-04 22:04:32 -07:00
Renovate Bot
076a7c8832 chore(deps): update actions/setup-node action to v2 2021-06-05 05:01:03 +00:00
stvn
ee53d4dfb8 merge(#106): build/renovate
commits
=======
- build(renovate): fix json syntax
2021-06-04 22:00:32 -07:00
stvn
42668c6d5a build(renovate): fix json syntax 2021-06-04 21:53:57 -07:00
stvn
5645dd4491 merge(#104): build/renovate-stricter
commits
=======
- build(renovate): be more selective about automerging devDependencies
2021-06-04 13:42:15 -07:00
stvn
2bb3febc39 build(renovate): be more selective about automerging devDependencies 2021-06-04 13:21:37 -07:00
Renovate Bot
2fb9804b91 chore(deps): update dependency husky to v6 2021-06-04 00:50:59 +00:00
Renovate Bot
a09317aaae chore(deps): update commitlint monorepo to v12 2021-06-04 00:43:30 +00:00
Renovate Bot
080621bcea fix(deps): update dependency react-transition-group to v4.4.2 2021-06-04 00:34:45 +00:00
Renovate Bot
37e9ef0434 fix(deps): update dependency react-responsive to v8.2.0 2021-06-04 00:24:39 +00:00
Renovate Bot
df1b6ff941 chore(deps): update react monorepo to v16.14.0 2021-06-04 00:13:10 +00:00
Renovate Bot
52b8b7ca11 chore(deps): update dependency redux to v4.1.0 2021-06-04 00:00:12 +00:00
Renovate Bot
9ed0af96ca chore(deps): update dependency react-router-dom to v5.2.0 2021-06-03 23:46:01 +00:00
Renovate Bot
8d0bd0ca05 chore(deps): update dependency react-redux to v7.2.4 2021-06-03 23:30:28 +00:00
Renovate Bot
de83d5f20f chore(deps): update dependency husky to v3.1.0 2021-06-03 23:12:56 +00:00
Renovate Bot
54ff71b0ec chore(deps): update dependency enzyme-adapter-react-16 to v1.15.6 2021-06-03 22:54:19 +00:00
Renovate Bot
a8347625ae chore(deps): update dependency enzyme to v3.11.0 2021-06-03 22:34:36 +00:00
Renovate Bot
305ae120c6 chore(deps): update dependency codecov to v3.8.2 2021-06-03 22:14:04 +00:00
Renovate Bot
e1468b5396 chore(deps): update dependency @edx/paragon to v12.8.0 2021-06-03 21:52:33 +00:00
Renovate Bot
10767bdaac chore(deps): update dependency @edx/frontend-platform to v1.11.0 2021-06-03 21:27:30 +00:00
Renovate Bot
484d6af4d9 chore(deps): update dependency @edx/frontend-build to v5.6.14 2021-06-03 21:01:01 +00:00
Renovate Bot
8ba7153806 chore(deps): update commitlint monorepo 2021-06-03 20:31:27 +00:00
stvn
334bdb34f3 merge(#103): build/renovate
commits
=======
- build(renovate): be more liberal about what automerges
2021-06-03 11:18:13 -07:00
stvn
12b63c5583 build(renovate): be more liberal about what automerges
based on https://github.com/edx/frontend-app-account .
2021-06-03 11:00:58 -07:00
stvn
3726792df2 merge: stvn/own/code 2021-05-26 13:42:44 -07:00
stvn
cc252e32e6 build: add CODEOWNERS; edx/community-engineering
Background
==========
As part of our Squad-based ownership, we should stay on top of what
happens in these repositories. However, due to the number of
repositories (and subsequently pull requests) across the edX ecosystem,
it is challenging to stay on top of notifications, separating the
'signal' from the 'noise'. Email filters can go a long way to taming
Inbox notifications, but this is manual and requires maintenance as
Squad ownership changes. It also fails to account for Github-specific behavior.

Proposal
========
By leveraging Github support for `CODEOWNERS` files [1],
we can ensure that our team is at least CCed explicitly, here,
in the form a requested review. This request is just that, a request,
not a requirement; we are not enacting any new merge requirements
at this time.

- [1] https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
2021-05-26 12:16:05 -07:00
David Joy
0c58362b49 docs: Updating README with repository details. (#101)
* docs: fleshing out the readme

Making it consistent with frontend-component-footer and much more detailed.

* fix: Removing unused variable, siteName

Also fixing logout URL in development.  (We fixed this in a number of other repositories already, but somehow this one snuck through)
2021-05-13 14:57:25 -04:00
Uzair Rasheed
b00e018105 Merge pull request #97 from edx/van-379/add-skipnav-link
fix: add skipnav link in header
2021-03-19 21:12:14 +05:00
uzairr
02464f9c09 fix: add skipnav link in header 2021-03-19 21:05:18 +05:00
Uzair Rasheed
67c1fb2cda Merge pull request #95 from edx/van-379/add-skipnav-link
add skipnav link in header
2021-03-19 18:23:28 +05:00
uzairr
e84843d83a add skipnav link in header 2021-03-19 14:57:42 +05:00
Renovate Bot
20606c2880 chore(deps): update dependency redux-saga to v1.1.3 2021-02-11 15:34:50 +00:00
Renovate Bot
3ba52a586e chore(deps): update dependency redux to v4.0.5 2021-02-11 10:36:34 +00:00
Renovate Bot
9eafcc9ca0 chore(deps): update dependency @edx/frontend-platform to v1.8.4 2021-02-11 01:55:14 +00:00
Uzair Rasheed
61ecf93785 Merge pull request #92 from edx/align-logo-to-left
fix: align logo to left
2021-02-08 14:57:53 +05:00
uzairr
917e748fc5 fix: align logo to left on authn mfe 2021-02-08 13:18:42 +05:00
Adeel Ehsan
0d64b19ac4 Merge pull request #89 from edx/aehsan/logistration-renmaed-to-authn
fix: logistration renamed to authn
2021-01-28 14:06:36 +05:00
adeelehsan
9415709b81 fix: logistration renamed to authn 2021-01-28 13:08:25 +05:00
Adeel Ehsan
003d8ee1a7 Merge pull request #88 from edx/revert-87-aehsan/logistration_renmaed_to_authn
Revert "logistration renamed to auth n"
2021-01-28 12:58:14 +05:00
Adeel Ehsan
18dd01d3d2 Revert "logistration renamed to auth n" 2021-01-28 12:52:31 +05:00
Adeel Ehsan
bdb1e03e4e Merge pull request #87 from edx/aehsan/logistration_renmaed_to_authn
logistration renamed to auth n
2021-01-28 12:34:54 +05:00
adeelehsan
5662e5daa3 logistration renamed to auth n 2021-01-28 12:27:17 +05:00
Renovate Bot
9306ce0783 chore(deps): update dependency react-redux to v7.1.3 2021-01-24 19:58:38 +00:00
Renovate Bot
f58ef0ace6 chore(deps): update dependency @edx/frontend-platform to v1.8.1 2021-01-10 13:50:47 +00:00
Waheed Ahmed
7de6ba4381 fix: logistration hide user menu icon on mobile screens (#84)
VAN-227
2020-12-22 13:02:55 +05:00
Adam Stankiewicz
14fe2d9bc6 fix: use LOGO_URL and move to GH actions (#81)
* fix: use LOGO_URL and move to GH actions

* fix: duplicate i18n message id
2020-12-01 09:48:38 -05:00
Waheed Ahmed
78573e30f1 feat: add LOGISTRATION_MINIMAL_HEADER env variable (#75)
The LOGISTRATION_MINIMAL_HEADER environment variable removes the main menu links
from the header. It also removes login/register links from the menu if user
is unauthenticated.
2020-11-23 15:04:20 +05:00
Adam Stankiewicz
ae014d2f24 feat: use logo from config settings, retheme with @edx/brand 2020-11-20 11:20:50 -05:00
25 changed files with 11512 additions and 7355 deletions

View File

@@ -6,10 +6,14 @@ ECOMMERCE_BASE_URL=http://localhost:18130
LANGUAGE_PREFERENCE_COOKIE_NAME=openedx-language-preference
LMS_BASE_URL=http://localhost:18000
LOGIN_URL=http://localhost:18000/login
LOGOUT_URL=http://localhost:18000/login
LOGOUT_URL=http://localhost:18000/logout
MARKETING_SITE_BASE_URL=http://localhost:18000
ORDER_HISTORY_URL=localhost:1996/orders
REFRESH_ACCESS_TOKEN_ENDPOINT=http://localhost:18000/login_refresh
SEGMENT_KEY=null
SITE_NAME=Open edX
USER_INFO_COOKIE_NAME=edx-user-info
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @edx/community-engineering

26
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Default CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Nodejs
uses: actions/setup-node@v2
with:
node-version: 12
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes
run: make validate-no-uncommitted-package-lock-changes
- name: Lint
run: npm run lint
- name: Test
run: npm run test
- name: i18n_extract
run: npm run i18n_extract
- name: Coverage
uses: codecov/codecov-action@v1

37
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Release CI
on:
push:
branches:
- master
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 12
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes
run: make validate-no-uncommitted-package-lock-changes
- name: Lint
run: npm run lint
- name: Test
run: npm run test
- name: i18n_extract
run: npm run i18n_extract
- name: Coverage
uses: codecov/codecov-action@v1
- name: Build
run: npm run build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
run: npx semantic-release

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ dist
node_modules
temp
src/i18n/transifex_input.json
module.config.js
.idea/

View File

@@ -1,17 +0,0 @@
language: node_js
node_js: 12
install:
- npm install
script:
- npm run lint
- npm run test
- npm run build
after_success:
- npx semantic-release
- codecov
env:
global:
# GH_TOKEN
- secure: CFN/uOByWC+7S+AAXECLQ0mNgoiyCDl1ZB5AT5+/qP+xEVs0ysFKDWrD9W8KeQqHqCMPUKNt23nrgxwvFCkSx+n1MAnxZmOdVJWbzGbUB9TsqrwVUALkqof1MrRB8UFVEzIRaO60iRN/L3zVXML+4GsycYX6rHyptbAypxplpljDyKrY8tc/mM7AGZ9eVFGSq+7CXXmdvkhP9kLkH1tIYvR7wjTKvZHbHf6YRjIVCiyzxM4S/E9l8JRnbERp02XosRD62PUJXXk6EJVn6Qoub6CaPnpew5crW0iRF1UJs54U29zWd/S+LuW66WkLfJu7rDq6AFJNMtMNusJxwVkOv4X+p0oJDWZEhojW+/Wm10UAu8/g5oAqeePZEGiSbT3Hp1VqOc4FY/kmOLiM+L6oq/AA2XX6iiE8lA64IH5R8ApQamF4GTUYTvKHeLPgXnGJH7A95Xy9/+jmX9I9wJREMrHrkyPjoX/NTRdG+RrebB1+An9Bt9vAbG862gbOZfgTcuWHDOlG5gcA964Fr7RqR5a62yr45Gw+Q0lTrLj5mGAjjSpMRIAQzi9e7oXmoMZnvenu/WJAe5M+u+/gv82HeXcMwLvNNGSvz+0i1xNUOoX1zHG046oGKiX0Zu+l0JfNwihJTO7vJlaITmjhfOyufwpk74xEyrhf8nLF9e5Frec=
# NPM_TOKEN
- secure: fuV04Ctf0mgbw6nTJhsTzGZ6dyafZtGVj30ZkvSWsB9hUU8KDtl7wWVW9EayCQsSyyFgPY9RVav5olgC/zljAjDGg0nfF7n8uKIABA0TXdP683WZd06bVOmDXfL26B3yM83aW2xgHZN6VCCvCE8bLP1V6eV8nsr38gDgxVbHa0YasDMmvtYrog+IjxwjcJx7fD0RbYyi7iJC++pdw9kcFqOad28Us7L/jCn+rC3CmUT4kOwPjP5g1v5sB2FA7ouN5s1hUUTKuttV32VJgRP7wbZzoHeHX5BRGSqijdXNSaK5UwzqRnM1sGZkuNDZhJbSB3q90SQrPRgV+fRizwN8zs8Htb+Kk8+wGY6zNhmi9C+lUIv7UpDYbstMWYIf39+P/24Oj+vJBjMY30M9NWB8gt1OQ0dJUoK53v1+BMVmDB0doL6I53xwzUjQetvqOF0Wm3E3OrqJP00OJdzIcAeh2DzcIRW1SrBhI4HAsl7QJZNpRw11QzJ3K2iQSiWNd8qIuX+XJjzQdn1v09gCstvbP33Vn8tP1x0XTSi8wIhTDqE0bII/Sc80Jh76nu0ItQ8pmX4lGsER4C0N3Dp7Zz51yW7E70AWWLsUrMNdF0oHoP437ZRGPhYs5OI6x5AM2jlU8fJ5aUroEsYFCwkH0OO37THohpAQpApe9zL10miTEbw=

View File

@@ -1,11 +1,76 @@
#########################
frontend-component-header
=========================
#########################
|Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
This is the standard Open edX header for use in React applications. It has two exports:
- **default**: The Header Component
- **messages**: for i18n in the form of ``{ locale: { key: translatedString } }``
********
Overview
********
A generic header for Open edX micro-frontend applications.
************
Requirements
************
This component uses ``@edx/frontend-platform`` services such as i18n, analytics, configuration, and the ``AppContext`` React component, and expects that it has been loaded into a micro-frontend that has been properly initialized via ``@edx/frontend-platform``'s ``initialize`` function. `Please visit the frontend template application to see an example. <https://github.com/edx/frontend-template-application/blob/master/src/index.jsx>`_
Environment Variables
=====================
* ``LMS_BASE_URL`` - The URL of the LMS of your Open edX instance.
* ``LOGOUT_URL`` - The URL of the API endpoint which performs a user logout.
* ``LOGIN_URL`` - The URL of the login page where a user can sign into their account.
* ``SITE_NAME`` - The user-facing name of the site, used as `alt` text on the logo in the header.
Defaults to "localhost" in development.
* ``LOGO_URL`` - The URL of the site's logo. This logo is displayed in the header.
* ``ORDER_HISTORY_URL`` - The URL of the order history page.
* ``AUTHN_MINIMAL_HEADER`` - A boolean flag which hides the main menu, user menu, and logged-out
menu items when truthy. This is intended to be used in micro-frontends like
frontend-app-authentication in which these menus are considered distractions from the user's task.
************
Installation
************
To install this header into your Open edX micro-frontend, run the following command in your MFE:
``npm i --save @edx/frontend-component-header``
This will make the component available to be imported into your application.
*****
Usage
*****
This library has the following exports:
* ``(default)``: The header as a React component.
* ``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.
Examples
========
* `An example of component and messages usage. <https://github.com/edx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L21>`_
* `An example of SCSS file usage. <https://github.com/edx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L8>`_
***********
Development
***********
Install dependencies::
npm i
Start the development server::
npm start
Build a production distribution::
npm run build
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-component-header.svg?branch=master
:target: https://travis-ci.com/edx/frontend-component-header

View File

@@ -4,8 +4,9 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { initialize, getConfig, subscribe, APP_READY } from '@edx/frontend-platform';
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
import Header from '@edx/frontend-component-header';
import './index.scss';
import Header from '../src/';
subscribe(APP_READY, () => {
ReactDOM.render(

View File

@@ -1,2 +1,6 @@
@import "~@edx/paragon/scss/core/core.scss";
@import '../src/index.scss';
@import "@edx/brand/paragon/fonts";
@import "@edx/brand/paragon/variables";
@import "@edx/paragon/scss/core/core";
@import "@edx/brand/paragon/overrides";
@import "@edx/frontend-component-header/index";

18302
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,34 +34,35 @@
},
"homepage": "https://github.com/edx/frontend-component-header#readme",
"devDependencies": {
"@commitlint/cli": "8.2.0",
"@commitlint/config-angular": "8.2.0",
"@commitlint/prompt": "8.2.0",
"@commitlint/prompt-cli": "8.2.0",
"@edx/frontend-build": "^2.0.4",
"@edx/frontend-platform": "1.1.14",
"@edx/paragon": "7.1.5",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"husky": "3.0.9",
"@commitlint/cli": "12.1.4",
"@commitlint/config-angular": "12.1.4",
"@commitlint/prompt": "12.1.4",
"@commitlint/prompt-cli": "12.1.4",
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-build": "5.6.14",
"@edx/frontend-platform": "1.11.0",
"@edx/paragon": "12.8.0",
"codecov": "3.8.2",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"husky": "6.0.0",
"prop-types": "15.7.2",
"react": "16.9.0",
"react-dom": "16.9.0",
"react-redux": "^7.1.1",
"react-router-dom": "^5.1.2",
"react-test-renderer": "16.9.0",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.4",
"react-router-dom": "5.2.0",
"react-test-renderer": "16.14.0",
"reactifex": "1.1.1",
"redux": "^4.0.4",
"redux-saga": "^1.1.1"
"redux": "4.1.0",
"redux-saga": "1.1.3"
},
"dependencies": {
"babel-polyfill": "6.26.0",
"react-responsive": "8.0.3",
"react-transition-group": "4.3.0"
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2"
},
"peerDependencies": {
"@edx/frontend-platform": "^1.1.4",
"@edx/frontend-platform": "^1.8.0",
"@edx/paragon": "^7.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0",

View File

@@ -1,9 +1,28 @@
{
"extends": [
"config:base"
"config:base",
"schedule:weekly",
":automergeLinters",
":automergeMinor",
":automergeTesters",
":enableVulnerabilityAlerts",
":rebaseStalePrs",
":semanticCommits",
":updateNotScheduled"
],
"patch": {
"automerge": true
},
"rebaseStalePrs": true
"packageRules": [
{
"matchDepTypes": [
"devDependencies"
],
"matchUpdateTypes": [
"lockFileMaintenance",
"minor",
"patch",
"pin"
],
"automerge": true
}
],
"timezone": "America/New_York"
}

View File

@@ -12,7 +12,7 @@ function Avatar({
const avatar = src ? (
<img className="d-block w-100 h-100" src={src} alt={alt} />
) : (
<AvatarIcon className="text-muted" style={{ width: size, height: size }} role="img" aria-hidden focusable="false" />
<AvatarIcon style={{ width: size, height: size }} role="img" aria-hidden focusable="false" />
);
return (

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
@@ -22,7 +23,9 @@ class DesktopHeader extends React.Component {
const { mainMenu } = this.props;
// Nodes are accepted as a prop
if (!Array.isArray(mainMenu)) return mainMenu;
if (!Array.isArray(mainMenu)) {
return mainMenu;
}
return mainMenu.map((menuItem) => {
const {
@@ -64,7 +67,7 @@ class DesktopHeader extends React.Component {
<MenuTrigger
tag="button"
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
className="btn btn-light d-inline-flex align-items-center pl-2 pr-3"
className="btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
>
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
{username} <CaretIcon role="img" aria-hidden focusable="false" />
@@ -101,12 +104,14 @@ class DesktopHeader extends React.Component {
intl,
} = this.props;
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'mw-100' : null;
return (
<header className="site-header-desktop">
<div className="container-fluid">
<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} />}
{logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} />}
<nav
aria-label={intl.formatMessage(messages['header.label.main.nav'])}
className="nav main-nav"

View File

@@ -2,13 +2,17 @@ import React, { useContext } from 'react';
import Responsive from 'react-responsive';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { ensureConfig } from '@edx/frontend-platform/config';
import {
APP_CONFIG_INITIALIZED,
ensureConfig,
mergeConfig,
getConfig,
subscribe,
} from '@edx/frontend-platform';
import DesktopHeader from './DesktopHeader';
import MobileHeader from './MobileHeader';
import LogoSVG from './logo.svg';
import messages from './Header.messages';
ensureConfig([
@@ -16,8 +20,16 @@ ensureConfig([
'LOGOUT_URL',
'LOGIN_URL',
'SITE_NAME',
'LOGO_URL',
'ORDER_HISTORY_URL',
], 'Header component');
subscribe(APP_CONFIG_INITIALIZED, () => {
mergeConfig({
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
}, 'Header additional config');
});
function Header({ intl }) {
const { authenticatedUser, config } = useContext(AppContext);
@@ -45,6 +57,11 @@ function Header({ intl }) {
href: `${config.LMS_BASE_URL}/account/settings`,
content: intl.formatMessage(messages['header.user.menu.account.settings']),
},
{
type: 'item',
href: config.ORDER_HISTORY_URL,
content: intl.formatMessage(messages['header.user.menu.order.history']),
},
{
type: 'item',
href: config.LOGOUT_URL,
@@ -66,27 +83,26 @@ function Header({ intl }) {
];
const props = {
logo: LogoSVG,
logo: config.LOGO_URL,
logoAltText: config.SITE_NAME,
siteName: config.SITE_NAME,
logoDestination: `${config.LMS_BASE_URL}/dashboard`,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu,
userMenu,
loggedOutItems,
mainMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,
loggedOutItems: getConfig().AUTHN_MINIMAL_HEADER ? [] : loggedOutItems,
};
return (
<React.Fragment>
<>
<Responsive maxWidth={768}>
<MobileHeader {...props} />
</Responsive>
<Responsive minWidth={769}>
<DesktopHeader {...props} />
</Responsive>
</React.Fragment>
</>
);
}

View File

@@ -67,7 +67,7 @@ const messages = defineMessages({
description: 'The aria label for the account menu trigger',
},
'header.label.account.menu.for': {
id: 'header.label.account.menu',
id: 'header.label.account.menu.for',
defaultMessage: 'Account menu for {username}',
description: 'The aria label for the account menu trigger when the username is displayed in it',
},
@@ -91,6 +91,11 @@ const messages = defineMessages({
defaultMessage: 'Secondary',
description: 'The aria label for the seconary nav',
},
'header.label.skip.nav': {
id: 'header.label.skip.nav',
defaultMessage: 'Skip to main content',
description: 'A link used by screen readers to allow users to skip to the main content of the page.',
},
});
export default messages;

View File

@@ -11,14 +11,16 @@ describe('<Header />', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
<AppContext.Provider
value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
@@ -36,19 +38,21 @@ describe('<Header />', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
@@ -66,14 +70,16 @@ describe('<Header />', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
<AppContext.Provider
value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
@@ -91,19 +97,21 @@ describe('<Header />', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
function Logo({ src, alt, ...attributes }) {
return (
<img src={src} alt={alt} {...attributes} />

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { CSSTransition } from 'react-transition-group';
import PropTypes from 'prop-types';
function MenuTrigger({ tag, className, ...attributes }) {
return React.createElement(tag, {
className: `menu-trigger ${className}`,
@@ -19,7 +18,6 @@ MenuTrigger.defaultProps = {
};
const MenuTriggerType = <MenuTrigger />.type;
function MenuContent({ tag, className, ...attributes }) {
return React.createElement(tag, {
className: ['menu-content', className].join(' '),
@@ -35,6 +33,17 @@ MenuContent.defaultProps = {
className: null,
};
const menuPropTypes = {
tag: PropTypes.string,
onClose: PropTypes.func,
onOpen: PropTypes.func,
closeOnDocumentClick: PropTypes.bool,
respondToPointerEvents: PropTypes.bool,
className: PropTypes.string,
transitionTimeout: PropTypes.number,
transitionClassName: PropTypes.string,
children: PropTypes.arrayOf(PropTypes.node).isRequired,
};
class Menu extends React.Component {
constructor(props) {
@@ -66,10 +75,14 @@ class Menu extends React.Component {
// Event handlers
onDocumentClick(e) {
if (!this.props.closeOnDocumentClick) return;
if (!this.props.closeOnDocumentClick) {
return;
}
const clickIsInMenu = this.menu.current === e.target || this.menu.current.contains(e.target);
if (clickIsInMenu) return;
if (clickIsInMenu) {
return;
}
this.close();
}
@@ -77,7 +90,9 @@ class Menu extends React.Component {
onTriggerClick(e) {
// Let the browser follow the link of the trigger if the menu
// is already expanded and the trigger has an href attribute
if (this.state.expanded && e.target.getAttribute('href')) return;
if (this.state.expanded && e.target.getAttribute('href')) {
return;
}
e.preventDefault();
this.toggle();
@@ -89,7 +104,9 @@ class Menu extends React.Component {
}
onKeyDown(e) {
if (!this.state.expanded) return;
if (!this.state.expanded) {
return;
}
switch (e.key) {
case 'Escape': {
e.preventDefault();
@@ -131,16 +148,19 @@ class Menu extends React.Component {
}
onMouseEnter() {
if (!this.props.respondToPointerEvents) return;
if (!this.props.respondToPointerEvents) {
return;
}
this.open();
}
onMouseLeave() {
if (!this.props.respondToPointerEvents) return;
if (!this.props.respondToPointerEvents) {
return;
}
this.close();
}
// Internal functions
getFocusableElements() {
@@ -151,7 +171,7 @@ class Menu extends React.Component {
// Any extra props are attributes for the menu
const attributes = {};
Object.keys(this.props)
.filter(property => Menu.propTypes[property] === undefined)
.filter(property => menuPropTypes[property] === undefined)
.forEach((property) => {
attributes[property] = this.props[property];
});
@@ -173,7 +193,9 @@ class Menu extends React.Component {
}
open() {
if (this.props.onOpen) this.props.onOpen();
if (this.props.onOpen) {
this.props.onOpen();
}
this.setState({ expanded: true });
// Listen to touchend and click events to ensure the menu
// can be closed on mobile, pointer, and mixed input devices
@@ -182,7 +204,9 @@ class Menu extends React.Component {
}
close() {
if (this.props.onClose) this.props.onClose();
if (this.props.onClose) {
this.props.onClose();
}
this.setState({ expanded: false });
document.removeEventListener('touchend', this.onDocumentClick, true);
document.removeEventListener('click', this.onDocumentClick, true);
@@ -240,18 +264,7 @@ class Menu extends React.Component {
}
}
Menu.propTypes = {
tag: PropTypes.string,
onClose: PropTypes.func,
onOpen: PropTypes.func,
closeOnDocumentClick: PropTypes.bool,
respondToPointerEvents: PropTypes.bool,
className: PropTypes.string,
transitionTimeout: PropTypes.number,
transitionClassName: PropTypes.string,
children: PropTypes.arrayOf(PropTypes.node).isRequired,
};
Menu.propTypes = menuPropTypes;
Menu.defaultProps = {
tag: 'div',
className: null,
@@ -263,5 +276,4 @@ Menu.defaultProps = {
transitionClassName: 'menu-content',
};
export { Menu, MenuTrigger, MenuContent };

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
@@ -22,7 +23,9 @@ class MobileHeader extends React.Component {
const { mainMenu } = this.props;
// Nodes are accepted as a prop
if (!Array.isArray(mainMenu)) return mainMenu;
if (!Array.isArray(mainMenu)) {
return mainMenu;
}
return mainMenu.map((menuItem) => {
const {
@@ -89,17 +92,22 @@ class MobileHeader extends React.Component {
stickyOnMobile,
intl,
mainMenu,
userMenu,
loggedOutItems,
} = this.props;
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
const stickyClassName = stickyOnMobile ? 'sticky-top' : '';
const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'justify-content-left pl-3' : 'justify-content-center';
return (
<header
aria-label={intl.formatMessage(messages['header.label.main.header'])}
className={`site-header-mobile d-flex justify-content-between align-items-center shadow ${stickyClassName}`}
>
<div className="w-100 d-flex justify-content-start">
{mainMenu.length > 0 ?
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
{mainMenu.length > 0 ? (
<div className="w-100 d-flex justify-content-start">
<Menu className="position-static">
<MenuTrigger
tag="button"
@@ -116,26 +124,29 @@ class MobileHeader extends React.Component {
>
{this.renderMainMenu()}
</MenuContent>
</Menu> : null }
</div>
<div className="w-100 d-flex justify-content-center">
</Menu>
</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" />}
</div>
<div className="w-100 d-flex justify-content-end align-items-center">
<Menu tag="nav" aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="position-static">
<MenuTrigger
tag="button"
className="icon-button"
aria-label={intl.formatMessage(messages['header.label.account.menu'])}
title={intl.formatMessage(messages['header.label.account.menu'])}
>
<Avatar size="1.5rem" src={avatar} alt={username} />
</MenuTrigger>
<MenuContent tag="ul" className="nav flex-column pin-left pin-right border-top shadow py-2">
{loggedIn ? this.renderUserMenuItems() : this.renderLoggedOutItems()}
</MenuContent>
</Menu>
</div>
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
<div className="w-100 d-flex justify-content-end align-items-center">
<Menu tag="nav" aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="position-static">
<MenuTrigger
tag="button"
className="icon-button"
aria-label={intl.formatMessage(messages['header.label.account.menu'])}
title={intl.formatMessage(messages['header.label.account.menu'])}
>
<Avatar size="1.5rem" src={avatar} alt={username} />
</MenuTrigger>
<MenuContent tag="ul" className="nav flex-column pin-left pin-right border-top shadow py-2">
{loggedIn ? this.renderUserMenuItems() : this.renderLoggedOutItems()}
</MenuContent>
</Menu>
</div>
) : null}
</header>
);
}

View File

@@ -4,8 +4,14 @@ exports[`<Header /> renders correctly for anonymous desktop 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid"
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
@@ -17,7 +23,7 @@ exports[`<Header /> renders correctly for anonymous desktop 1`] = `
<img
alt="edX"
className="d-block"
src="icon/mock/path"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
<nav
@@ -58,6 +64,12 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="w-100 d-flex justify-content-start"
>
@@ -126,7 +138,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
<img
alt="edX"
className="d-block"
src="icon/mock/path"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
</div>
@@ -159,7 +171,6 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
@@ -189,8 +200,14 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid"
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
@@ -202,7 +219,7 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<img
alt="edX"
className="d-block"
src="icon/mock/path"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
<nav
@@ -230,7 +247,7 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for edX"
className="menu-trigger btn btn-light d-inline-flex align-items-center pl-2 pr-3"
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
@@ -244,7 +261,6 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
@@ -294,6 +310,12 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="w-100 d-flex justify-content-start"
>
@@ -362,7 +384,7 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
<img
alt="edX"
className="d-block"
src="icon/mock/path"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
</div>
@@ -395,7 +417,6 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"

View File

@@ -32,6 +32,8 @@ $white: #fff;
}
.site-header-mobile {
height: 3rem;
.nav-link {
text-decoration: none;
cursor: pointer;
@@ -43,7 +45,6 @@ $white: #fff;
.site-header-desktop {
height: 3.75rem;
box-shadow: 0 1px 0 0 rgba(0,0,0,.1);
background: $white;
.nav-link {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -23,3 +23,7 @@ process.env.REFRESH_ACCESS_TOKEN_ENDPOINT = 'http://localhost:18000/login_refres
process.env.SEGMENT_KEY = 'segment_whoa';
process.env.SITE_NAME = 'edX';
process.env.USER_INFO_COOKIE_NAME = 'edx-user-info';
process.env.LOGO_URL = 'https://edx-cdn.org/v3/default/logo.svg';
process.env.LOGO_TRADEMARK_URL = 'https://edx-cdn.org/v3/default/logo-trademark.svg';
process.env.LOGO_WHITE_URL = 'https://edx-cdn.org/v3/default/logo-white.svg';
process.env.FAVICON_URL = 'https://edx-cdn.org/v3/default/favicon.ico';

View File

@@ -7,4 +7,9 @@ module.exports = createConfig('webpack-dev', {
path: path.resolve(__dirname, 'example/dist'),
publicPath: '/',
},
resolve: {
alias: {
'@edx/frontend-component-header': path.resolve(__dirname, 'src'),
},
},
});