Compare commits

..

41 Commits

Author SHA1 Message Date
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
Waheed Ahmed
726aff9f8d Revert "modify header for logistration mfe (#73)" (#74)
This reverts commit bb7c5cb39f.
2020-11-18 11:24:13 +05:00
Uzair Rasheed
bb7c5cb39f modify header for logistration mfe (#73)
* modify header for logistration mfe

* Update .gitignore

Co-authored-by: Waheed Ahmed <waheed.ahmed@arbisoft.com>
2020-11-17 20:04:07 +05:00
Renovate Bot
90df9feb87 chore(deps): update dependency codecov to v3.7.2 2020-07-22 03:43:44 +00:00
dependabot[bot]
a86cde04bd chore(deps-dev): bump codecov from 3.6.5 to 3.7.1 (#65)
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.6.5 to 3.7.1.
- [Release notes](https://github.com/codecov/codecov-node/releases)
- [Commits](https://github.com/codecov/codecov-node/compare/v3.6.5...v3.7.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-21 09:25:53 -04:00
Renovate Bot
afc03b2253 chore(deps): update dependency codecov to v3.6.5 [security] 2020-02-19 20:32:04 +00:00
Renovate Bot
01cee41441 chore(deps): update dependency codecov to v3.6.4 2020-02-01 03:13:14 +00:00
Renovate Bot
307669cf9f chore(deps): update dependency codecov to v3.6.3 2020-01-31 14:11:18 +00:00
Renovate Bot
265f3be635 chore(deps): update dependency @edx/frontend-platform to v1.1.14 2020-01-28 13:13:00 +00:00
Renovate Bot
7742de1609 chore(deps): update dependency codecov to v3.6.2 2020-01-23 20:22:41 +00:00
Renovate Bot
1778b78f0a fix(deps): update dependency react-responsive to v8.0.3 2020-01-22 17:24:40 +00:00
Renovate Bot
36ced5252f fix(deps): update dependency react-responsive to v8.0.2 2020-01-21 18:13:54 +00:00
Renovate Bot
00ca9197d5 chore(deps): update dependency @edx/frontend-platform to v1.1.13 2019-12-28 00:11:09 +00:00
Renovate Bot
adf6f71c05 chore(deps): update dependency @edx/frontend-platform to v1.1.12 2019-12-15 01:11:53 +00:00
Renovate Bot
fee5d11aec chore(deps): update dependency @edx/frontend-platform to v1.1.11 2019-12-10 22:13:47 +00:00
Renovate Bot
5772de187f chore(deps): update dependency @edx/frontend-platform to v1.1.10 2019-12-09 17:12:50 +00:00
Renovate Bot
4c033a655a chore(deps): update dependency @edx/frontend-platform to v1.1.9 2019-12-05 23:13:46 +00:00
Renovate Bot
e75043dd2b chore(deps): update dependency @edx/frontend-platform to v1.1.8 2019-12-04 22:15:31 +00:00
Renovate Bot
97a15f5059 chore(deps): update dependency husky to v3.0.9 2019-12-04 21:04:04 +00:00
Renovate Bot
0b882173eb chore(deps): update dependency @edx/paragon to v7.1.5 2019-12-04 20:39:34 +00:00
Renovate Bot
ebaa9ea32e chore(deps): update dependency @edx/frontend-platform to v1.1.6 2019-12-04 20:18:18 +00:00
David Joy
07faa1290a Update renovate.json 2019-12-04 14:25:31 -05:00
Adam Butterworth
61b68fdd80 fix: upgrade frontend-build for i18n extract fix (#36) 2019-12-03 16:00:24 -05:00
Adam Butterworth
2b244c71b9 fix: upgrade frontend-build to fix translation job (#34) 2019-12-03 14:13:08 -05:00
Adam Butterworth
490edabf11 fix: update frontend-platform usage (#33) 2019-12-03 10:25:10 -05:00
Adam Butterworth
b503643603 feat: upgrade to frontend-platform (#31)
BREAKING CHANGE: This package now depends on frontend-platform instead of individual runway packages
2019-11-21 17:19:16 -05:00
David Joy
227c754c48 fix: use npx for semantic-release 2019-11-04 10:07:17 -05:00
David Joy
57e05c4d88 Modernize build and fix the anonymous header (#26)
* refactor: rename SiteHeader to Header

* build: use frontend-build

* fix: ensure that we can handle anonymous users

* feat: anonymous header works now

Up until now we haven’t had any microfrontends that allowed anonymous usage.  The Header was encountering errors rendering in such situations, though it was close.  Now it properly shows the Login and Sign Up buttons.

* build: bump version of frontend-build
2019-11-04 09:58:36 -05:00
36 changed files with 16190 additions and 11152 deletions

33
.env
View File

@@ -1,33 +0,0 @@
BASE_URL=localhost:1995
SITE_NAME=Open edX
LMS_BASE_URL=http://localhost:18000
CREDENTIALS_BASE_URL=http://localhost:18150
ECOMMERCE_BASE_URL=http://localhost:18130
LOGIN_URL=http://localhost:18000/login
LOGOUT_URL=http://localhost:18000/login
CSRF_TOKEN_API_PATH=/csrf/api/v1/token
REFRESH_ACCESS_TOKEN_ENDPOINT=http://localhost:18000/login_refresh
SEGMENT_KEY=ul
ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
USER_INFO_COOKIE_NAME=edx-user-info
CSRF_COOKIE_NAME=csrftoken
LANGUAGE_PREFERENCE_COOKIE_NAME=openedx-language-preference
SITE_NAME=edX
MARKETING_SITE_BASE_URL=http://localhost:18000
ENTERPRISE_MARKETING_URL=http://example.com
ENTERPRISE_MARKETING_UTM_CAMPAIGN=my_campaign
ENTERPRISE_MARKETING_UTM_SOURCE=edX profile
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=Footer
SUPPORT_URL=http://localhost:18000/support
CONTACT_URL=http://localhost:18000/contact
OPEN_SOURCE_URL=http://localhost:18000/openedx
TERMS_OF_SERVICE_URL=http://localhost:18000/terms-of-service
PRIVACY_POLICY_URL=http://localhost:18000/privacy-policy
FACEBOOK_URL=https://www.facebook.com
TWITTER_URL=https://twitter.com
YOU_TUBE_URL=https://www.youtube.com
LINKED_IN_URL=https://www.linkedin.com
REDDIT_URL=https://www.reddit.com
APPLE_APP_STORE_URL=https://www.apple.com/ios/app-store/
GOOGLE_PLAY_URL=https://play.google.com/store
ORDER_HISTORY_URL=localhost:1996/orders

19
.env.development Normal file
View File

@@ -0,0 +1,19 @@
ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
BASE_URL=localhost:8080
CREDENTIALS_BASE_URL=http://localhost:18150
CSRF_TOKEN_API_PATH=/csrf/api/v1/token
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
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

3
.eslintrc.js Normal file
View File

@@ -0,0 +1,3 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');

View File

@@ -1,19 +0,0 @@
{
"extends": "eslint-config-edx",
"parser": "babel-eslint",
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"webpack.config.js",
"**/*.test.jsx",
"**/*.test.js"
]
}
]
},
"env": {
"jest": true
}
}

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@v1
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@v1
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:
- npm run 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

@@ -12,7 +12,7 @@ transifex_temp = ./temp/babel-plugin-react-intl
build:
rm -rf ./dist
./node_modules/.bin/babel src --out-dir dist --source-maps --ignore **/*.test.jsx,**/__mocks__,**/__snapshots__,**/setupTest.js --copy-files
./node_modules/.bin/fedx-scripts babel src --out-dir dist --source-maps --ignore **/*.test.jsx,**/__mocks__,**/__snapshots__,**/setupTest.js --copy-files
@# --copy-files will bring in everything else that wasn't processed by babel. Remove what we don't want.
@rm -rf dist/**/*.test.jsx
@rm -rf dist/**/__snapshots__

View File

@@ -1,33 +1,3 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
modules: false,
},
],
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-class-properties',
],
env: {
i18n: {
plugins: [
[
'react-intl',
{
messagesDir: './temp/babel-plugin-react-intl',
moduleSourceName: '@edx/frontend-i18n',
},
],
],
},
test: {
presets: [
'@babel/preset-env',
],
},
},
};
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('babel-preserve-modules');

View File

@@ -1,10 +0,0 @@
const path = require('path');
// Resolve ~tilda paths used in paragon to
// node_modules. This is used to reference
// bootstrap specifically.
module.exports = {
includePaths: [
path.resolve(__dirname, '../node_modules'),
],
};

View File

@@ -1,12 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>

View File

@@ -2,39 +2,35 @@ import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { App, AppContext, APP_READY, AppProvider } from '@edx/frontend-base';
import { NewRelicLoggingService } from '@edx/frontend-logging';
import './index.scss';
import SiteHeader from '../src/';
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';
App.subscribe(APP_READY, () => {
import './index.scss';
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider>
{/* We can fake out authentication by including another provider here with the data we want */}
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
username: null,
roles: [],
administrator: false,
},
config: App.config
authenticatedUser: null,
config: getConfig(),
}}>
<SiteHeader />
<Header />
</AppContext.Provider>
<h5 className="mt-2 mb-5">Logged out state</h5>
{/* We can fake out authentication by including another provider here with the data we want */}
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
userId: '123abc',
username: 'testuser',
roles: [],
administrator: false,
},
config: App.config
config: getConfig(),
}}>
<SiteHeader />
<Header />
</AppContext.Provider>
<h5 className="mt-2">Logged in state</h5>
</AppProvider>,
@@ -42,4 +38,6 @@ App.subscribe(APP_READY, () => {
);
});
App.initialize({ messages: [], loggingService: NewRelicLoggingService });
initialize({
messages: []
});

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";

7
jest.config.js Normal file
View File

@@ -0,0 +1,7 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('jest', {
setupFiles: [
'<rootDir>/src/setupTest.js',
],
});

25948
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,22 +2,27 @@
"name": "@edx/frontend-component-header",
"version": "1.0.0-semantically-released",
"description": "The standard header for Open edX",
"main": "dist/index.js",
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"scripts": {
"build": "make build",
"i18n_extract": "BABEL_ENV=i18n babel src --quiet > /dev/null",
"lint": "eslint --ext .js --ext .jsx .",
"semantic-release": "semantic-release",
"start": "parcel ./example/index.html --no-source-maps --out-dir example/dist",
"test": "jest --coverage --passWithNoTests"
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage"
},
"files": [
"/dist",
"/src"
"/dist"
],
"husky": {
"hooks": {
"pre-commit": "npm run lint",
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/edx/frontend-component-header.git"
@@ -29,69 +34,38 @@
},
"homepage": "https://github.com/edx/frontend-component-header#readme",
"devDependencies": {
"@babel/cli": "7.6.0",
"@babel/core": "7.6.0",
"@babel/plugin-proposal-class-properties": "7.5.5",
"@babel/plugin-proposal-object-rest-spread": "7.5.5",
"@babel/preset-env": "7.6.0",
"@babel/preset-react": "7.0.0",
"@edx/frontend-analytics": "3.0.0",
"@edx/frontend-auth": "7.0.1",
"@edx/frontend-base": "4.0.0",
"@edx/frontend-i18n": "3.0.3",
"@edx/frontend-logging": "3.0.1",
"@edx/paragon": "7.1.4",
"babel-eslint": "10.0.3",
"babel-plugin-react-intl": "4.1.18",
"dotenv": "8.1.0",
"@commitlint/cli": "8.2.0",
"@commitlint/config-angular": "8.2.0",
"@commitlint/prompt": "8.2.0",
"@commitlint/prompt-cli": "8.2.0",
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-build": "5.4.0",
"@edx/frontend-platform": "1.8.1",
"@edx/paragon": "12.0.5",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"eslint": "6.3.0",
"eslint-config-edx": "4.0.4",
"jest": "24.9.0",
"parcel-bundler": "1.12.3",
"husky": "3.0.9",
"prop-types": "15.7.2",
"react": "16.9.0",
"react-dom": "16.9.0",
"react-redux": "7.1.1",
"react-router-dom": "5.0.1",
"react-redux": "7.1.3",
"react-router-dom": "5.1.2",
"react-test-renderer": "16.9.0",
"reactifex": "1.1.1",
"redux": "4.0.4",
"redux-saga": "1.0.5",
"sass": "1.22.12",
"semantic-release": "15.13.24"
"redux-saga": "1.1.1"
},
"dependencies": {
"babel-polyfill": "6.26.0",
"react-responsive": "8.0.1",
"react-responsive": "8.0.3",
"react-transition-group": "4.3.0"
},
"peerDependencies": {
"@edx/frontend-analytics": "^3.0.0",
"@edx/frontend-base": "^4.0.0",
"@edx/frontend-i18n": "^3.0.3",
"prop-types": "^15.7.2",
"react": "^16.9.0"
},
"jest": {
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!**/node_modules/**",
"!**/dist/**"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/__mocks__/fileMock.js"
},
"transformIgnorePatterns": [
"/node_modules/(?!@edx)"
],
"setupFiles": [
"./src/setupTest.js",
"dotenv/config"
]
"@edx/frontend-platform": "^1.8.0",
"@edx/paragon": "^7.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}

11
public/index.html Executable file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en-us" dir="ltr">
<head>
<title>Header</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,5 +1,9 @@
{
"extends": [
"config:base"
]
],
"patch": {
"automerge": true
},
"rebaseStalePrs": true
}

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-i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
@@ -8,7 +9,7 @@ import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
// i18n
import messages from './SiteHeader.messages';
import messages from './Header.messages';
// Assets
import { CaretIcon } from './Icons';
@@ -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,13 @@ 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">
<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

@@ -1,23 +1,35 @@
import React, { useContext } from 'react';
import Responsive from 'react-responsive';
import { injectIntl, intlShape } from '@edx/frontend-i18n';
import { App, AppContext } from '@edx/frontend-base';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
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';
import messages from './SiteHeader.messages';
App.ensureConfig([
ensureConfig([
'LMS_BASE_URL',
'LOGOUT_URL',
'LOGIN_URL',
'SITE_NAME',
'LOGO_URL',
], 'Header component');
function SiteHeader({ intl }) {
subscribe(APP_CONFIG_INITIALIZED, () => {
mergeConfig({
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
}, 'Header additional config');
});
function Header({ intl }) {
const { authenticatedUser, config } = useContext(AppContext);
const mainMenu = [
@@ -28,7 +40,7 @@ function SiteHeader({ intl }) {
},
];
const userMenu = [
const userMenu = authenticatedUser === null ? [] : [
{
type: 'item',
href: `${config.LMS_BASE_URL}/dashboard`,
@@ -65,32 +77,32 @@ function SiteHeader({ 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.username,
username: authenticatedUser.username,
avatar: authenticatedUser.avatar,
mainMenu,
userMenu,
loggedOutItems,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
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>
</>
);
}
SiteHeader.propTypes = {
Header.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(SiteHeader);
export default injectIntl(Header);

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'header.links.courses': {
@@ -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',
},

127
src/Header.test.jsx Normal file
View File

@@ -0,0 +1,127 @@
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 { Context as ResponsiveContext } from 'react-responsive';
import Header from './index';
describe('<Header />', () => {
it('renders correctly for anonymous desktop', () => {
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,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for authenticated desktop', () => {
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,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for anonymous mobile', () => {
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,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for authenticated mobile', () => {
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,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

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-i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
@@ -8,7 +9,7 @@ import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
// i18n
import messages from './SiteHeader.messages';
import messages from './Header.messages';
// Assets
import { MenuIcon } from './Icons';
@@ -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,21 @@ 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 ?
{mainMenu.length > 0 ? (
<div className="w-100 d-flex justify-content-start">
<Menu className="position-static">
<MenuTrigger
tag="button"
@@ -116,26 +123,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

@@ -1,70 +0,0 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-i18n';
import TestRenderer from 'react-test-renderer';
import { AppContext } from '@edx/frontend-base';
import { Context as ResponsiveContext } from 'react-responsive';
import SiteHeader from './index';
describe('<SiteHeader />', () => {
it('renders correctly for desktop', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
username: null,
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,
},
}}
>
<SiteHeader />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for mobile', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
username: null,
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,
},
}}
>
<SiteHeader />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -1,3 +0,0 @@
// __mocks__/fileMock.js
module.exports = 'test-file-stub';

View File

@@ -0,0 +1,419 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> renders correctly for anonymous desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<a
className="btn mr-2 btn-link"
href="http://localhost:18000/login"
>
Login
</a>
<a
className="btn mr-2 btn-outline-primary"
href="http://localhost:18000/register"
>
Sign Up
</a>
</nav>
</div>
</div>
</header>
`;
exports[`<Header /> renders correctly for anonymous mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;
exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for edX"
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
edX
<svg
aria-hidden={true}
focusable="false"
height="16px"
role="img"
version="1.1"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
fill="currentColor"
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
/>
</svg>
</button>
</div>
</nav>
</div>
</div>
</header>
`;
exports[`<Header /> renders correctly for authenticated mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;

View File

@@ -1,186 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SiteHeader /> renders correctly for desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="test-file-stub"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<a
className="btn mr-2 btn-link"
href="http://localhost:18000/login"
>
Login
</a>
<a
className="btn mr-2 btn-outline-primary"
href="http://localhost:18000/register"
>
Sign Up
</a>
</nav>
</div>
</div>
</header>
`;
exports[`<SiteHeader /> renders correctly for mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="test-file-stub"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;

View File

@@ -1,6 +1,6 @@
import SiteHeader from './SiteHeader';
import Header from './Header';
import messages from './i18n/index';
export { messages };
export default SiteHeader;
export default Header;

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

@@ -11,7 +11,6 @@ Enzyme.configure({ adapter: new Adapter() });
process.env.ACCESS_TOKEN_COOKIE_NAME = 'edx-jwt-cookie-header-payload';
process.env.BASE_URL = 'localhost:1995';
process.env.CREDENTIALS_BASE_URL = 'http://localhost:18150';
process.env.CSRF_COOKIE_NAME = 'csrftoken';
process.env.CSRF_TOKEN_API_PATH = '/csrf/api/v1/token';
process.env.ECOMMERCE_BASE_URL = 'http://localhost:18130';
process.env.LANGUAGE_PREFERENCE_COOKIE_NAME = 'openedx-language-preference';
@@ -24,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';

15
webpack.dev.config.js Normal file
View File

@@ -0,0 +1,15 @@
const path = require('path');
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('webpack-dev', {
entry: path.resolve(__dirname, 'example'),
output: {
path: path.resolve(__dirname, 'example/dist'),
publicPath: '/',
},
resolve: {
alias: {
'@edx/frontend-component-header': path.resolve(__dirname, 'src'),
},
},
});