Compare commits

...

74 Commits

Author SHA1 Message Date
Renovate Bot
457bd843cf fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.9 2020-03-05 18:10:56 +00:00
Renovate Bot
777c697c41 fix(deps): update dependency @fortawesome/fontawesome-svg-core to v1.2.27 2020-02-07 16:09:34 +00:00
Renovate Bot
b3b32613c9 chore(deps): update dependency codecov to v3.6.5 2020-02-07 15:11:57 +00:00
Renovate Bot
a6f70e895a chore(deps): update dependency codecov to v3.6.4 2020-02-01 03:12:36 +00:00
Renovate Bot
1112b426f0 chore(deps): update dependency codecov to v3.6.3 2020-01-31 14:13:51 +00:00
Renovate Bot
17278cc355 chore(deps): update dependency codecov to v3.6.2 2020-01-23 20:22:47 +00:00
Renovate Bot
65c25bfc95 fix(deps): update dependency @fortawesome/fontawesome-svg-core to v1.2.26 2019-12-11 00:12:05 +00:00
Renovate Bot
75e8962563 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.8 2019-12-06 17:14:43 +00:00
Renovate Bot
65aba84491 fix(deps): update font awesome 2019-12-04 23:19:08 +00:00
Renovate Bot
093dfa6ac2 chore(deps): update react monorepo to v16.10.2 2019-12-04 22:15:57 +00:00
Renovate Bot
4637834750 chore(deps): update dependency husky to v3.0.9 2019-12-04 21:04:01 +00:00
Renovate Bot
a021e72359 chore(deps): update dependency @edx/paragon to v7.1.5 2019-12-04 20:39:24 +00:00
Renovate Bot
925ddb7adf chore(deps): update dependency @edx/frontend-platform to v1.1.6 2019-12-04 20:21:15 +00:00
David Joy
9ebcd15623 Update renovate.json 2019-12-04 14:25:58 -05:00
Adam Butterworth
3a2e553feb fix: upgrade frontend-build for i18n extract fix (#67) 2019-12-03 16:00:43 -05:00
Adam Butterworth
eb6c253e35 fix: upgrade frontend-build to fix translation job (#65) 2019-12-03 14:13:11 -05:00
David Joy
d2315c2c21 build: dev tweaks (#64)
* build: dev tweaks

need an eslintrc file and to gitignore .vscode directories

* Update .eslintrc.js
2019-12-03 10:46:32 -05:00
Adam Butterworth
f33a972933 fix: upgrade frontend-platform (#63) 2019-12-03 10:33:28 -05:00
Adam Butterworth
d540aaa390 feat: upgrade to frontend-platform (#60)
BREAKING CHANGE: This package now relies upon frontend-platform instead of individual runway packages.
2019-11-21 17:18:26 -05:00
Adam Butterworth
c64496dd08 Update README.rst 2019-10-08 12:54:29 -04:00
edX Transifex Bot
e94cf3ac2d fix(i18n): update translations 2019-10-06 16:33:20 -04:00
David Joy
d4259ef1f6 feat: handleAllTrackEvents is no longer used (#48)
* feat: use frontend-analytics directly

BREAKING CHANGE: handleAllTrackEvents is no longer part of the Footer props API.  The footer uses frontend-analytics’s sendTrackEvent directly.

Also updating other dependencies to keep them up to date.  This part should probably be a separate PR, technically.

* fix: remove unused dependency of frontend-logging

* fix: a little PR cleanup

* fix: putting frontend logging back

It’s needed by frontend-i18n and fails if it’s not there for tests.

* fix: pinning npm at 6
2019-10-02 14:59:12 -04:00
renovate[bot]
b4f35d37c8 chore(deps): add renovate.json (#47) 2019-10-02 12:34:44 -04:00
Adam Butterworth
83bad3d47b feat: repurpose as open edx footer (#46)
* feat: simplify prop api and bake in many values

* feat: refactor build and dev server

BREAKING CHANGE: The footer is now  transpiled from es6 preserving modules. To use this package in a project it must now be transpiled by the requiring project via webpack or other configuration

* feat: make this the open edx footer

BREAKING CHANGE: This footer now serves as the Open edX footer. For the edX footer see edx/frontend-component-footer-edx on github
2019-09-30 14:33:06 -04:00
AlasdairSwan
31e255ebe9 Merge pull request #44 from edx/alasdair/remove-paragon
perf(paragon): remove paragon to reduce bundle size
2019-06-21 11:14:29 -04:00
AlasdairSwan
349f83f686 perf(paragon): remove paragon to reduce bundle size 2019-06-21 11:11:01 -04:00
David Joy
e0c205ce14 fix: non deprecated use of hyperlink (#43) 2019-06-21 09:56:05 -04:00
albemarle
81398c8cfd Merge pull request #42 from edx/albemarle/different-diff
fix(i18n): make detect_changed_source_translations work for both push and pull jobs
2019-06-11 13:51:08 -04:00
albemarle
3fcd76b455 fix(i18n): make detect_changed_source_translations work for both push and pull jobs 2019-06-11 13:34:27 -04:00
edX Transifex Bot
a8f57aa553 fix(i18n): update translations 2019-06-09 16:32:55 -04:00
David Joy
f32b0c8dcf Merge pull request #40 from edx/djoy/upgrade_i18n
fix: upgrade frontend-i18n library
2019-06-07 11:12:41 -04:00
David Joy
fb1f5c2d50 fix: delete old i18n concat file 2019-06-07 10:53:50 -04:00
David Joy
1a51d992cd Update .gitignore 2019-06-07 10:07:30 -04:00
David Joy
e9c28163d9 fix: upgrade frontend-i18n library 2019-06-07 08:58:38 -04:00
AlasdairSwan
6db2d70e97 Merge pull request #37 from edx/alasdair/update-link-sections-to-accept-arrays
fix(linkprops): update link props
2019-06-04 09:03:27 -04:00
AlasdairSwan
3ab4adf10b fix(linkprops): update link props
BREAKING CHANGE move to link arrays and remove strings from component (and thus the need for i18n)
2019-06-03 15:57:30 -04:00
edX Transifex Bot
a482325eac fix(i18n): update translations 2019-06-02 17:33:12 -04:00
Douglas Hall
d919d1a577 Merge pull request #38 from edx/douglashall/finish_i18n_refactoring
fix: modify babelrc for i18n
2019-05-31 14:17:28 -04:00
Douglas Hall
4b3a91138d fix: modify babelrc for i18n 2019-05-31 14:11:48 -04:00
David Joy
7680ad3393 Merge pull request #36 from edx/djoy/is-es5
fix: adding es check
2019-05-28 13:33:09 -04:00
David Joy
061b41bee8 fix: adding es check 2019-05-28 09:42:04 -04:00
Douglas Hall
cacae4789f fix: move to @edx/frontend-i18n dependency (#35)
fix: move to @edx/frontend-i18n dependency
2019-05-24 14:20:22 -04:00
Douglas Hall
adceca6b53 fix: move to @edx/frontend-i18n dependency
BREAKING CHANGE: consumers will now be required to supply @edx/frontend-i18n instead of react-intl
2019-05-24 12:06:17 -04:00
Douglas Hall
ab61fd35eb Merge pull request #34 from edx/douglashall/reduce_bundle_size
fix: remove paragon from bundle
2019-05-23 14:34:38 -04:00
Douglas Hall
4d739f2962 fix: remove paragon from bundle
BREAKING CHANGE: Now requires containing app to provide `@edx/paragon`.
2019-05-23 14:28:28 -04:00
albemarle
28a6c85bc0 Merge pull request #30 from edx/aehsan/prod-277/update_the_edx_icp_license
Updated ICP license in footer
2019-05-22 14:01:36 -04:00
adeelehsan
6125988bbd Updated ICP license in footer 2019-05-22 00:33:19 +05:00
edX Transifex Bot
9eeccc63cb fix(i18n): update translations 2019-05-19 17:33:41 -04:00
Robert Raposa
2f33c6718b Merge pull request #31 from edx/robrap/ARCH-748-fix-i18n-jobs
fix(i18n): fix i18n make targets
2019-05-16 13:20:46 -04:00
Adam Stankiewicz
89397fcfdb Merge pull request #32 from edx/astankiewicz/downgrade-query-string
fix(query-string): downgrade query-string to version 5 for es5
2019-05-16 12:57:21 -04:00
Adam Stankiewicz
278b4b1473 fix(query-string): downgrade query-string to version 5 for es5 2019-05-16 09:44:44 -07:00
Robert Raposa
7ff9039bb7 fix(i18n): fix i18n make targets
ARCH-748
2019-05-16 12:27:27 -04:00
Chris Pappas
0cf3164901 Merge pull request #28 from edx/cpappas/ENT-1888
fix: updating enterprise marketing link logic
2019-05-10 16:55:50 -04:00
Christopher Pappas
bf8020dfac fix: updating business marketing footer
fix: adding https:// to business marketing link

fix: fixing marketing link for business

fix: fixing marketing link for business
2019-05-10 15:49:40 -04:00
Adam Butterworth
f2a13b8c8e fix: move dependencies out of footer 2019-05-10 08:27:18 -06:00
Adam Butterworth
3dd9b25d11 fix: move peerDependencies to dependencies 2019-05-10 10:24:23 -04:00
Adam Butterworth
21d7fe04e3 fix: move dependencies out of footer 2019-05-10 10:14:37 -04:00
AlasdairSwan
46d464d515 Merge pull request #27 from edx/alasdair/add-language-selector
Alasdair/add language selector
2019-05-08 13:42:52 -04:00
AlasdairSwan
85ed8fbff4 feat(langselect): add optional language selector 2019-05-08 13:38:01 -04:00
AlasdairSwan
d6adfec189 Merge pull request #25 from edx/alasdair/update-social-media-link-approach
fix(sociallinks): update link props
2019-05-07 12:53:21 -04:00
AlasdairSwan
8b1943356e fix(sociallinks): update link props
BREAKING CHANGE will not render social links unless array is passed in
2019-05-07 12:17:42 -04:00
AlasdairSwan
5aae6e8336 Merge pull request #26 from edx/alasdair/version-bump
fix(version): trivial update to minor version bump
2019-05-07 12:12:33 -04:00
AlasdairSwan
ae18ad45b9 fix(version): trivial update to minor version bump 2019-05-07 11:56:32 -04:00
AlasdairSwan
0e15bb7e55 Merge pull request #24 from edx/alasdair/revert-social-media-links
Revert social media links PR
2019-05-07 11:30:02 -04:00
AlasdairSwan
ff664b9d3b Revert social media links PR 2019-05-07 11:24:04 -04:00
AlasdairSwan
da91fb6392 Merge pull request #23 from edx/alasdair/fontawesome-svg-and-social-link-array-prop
fix(sociallinks): array for social props BREAKING CHANGE: props change
2019-05-07 10:29:25 -04:00
AlasdairSwan
f265c7daa6 fix(sociallinks): array for social props BREAKING CHANGE: props change 2019-05-06 15:08:50 -04:00
albemarle
3037e4d8a6 Merge pull request #22 from edx/albemarle/i18n-travis
build: validate i18n
2019-05-06 10:20:20 -04:00
albemarle
b2a7696c0e build: validate i18n 2019-05-03 15:32:30 -04:00
albemarle
6dc8f64ccb Merge pull request #19 from edx/albemarle/more-i18n
fix(i18n): use up-to-date i18n-concat.js
2019-04-09 15:46:31 -04:00
albemarle
0453d7fbaa fix(i18n): use up-to-date i18n-concat.js 2019-04-09 15:38:46 -04:00
Robert Raposa
fa6d38f422 Merge pull request #18 from edx/albemarle/check-changes
fix(i18n): add detect_changed_source_translations target to stop jobs erroring
2019-04-05 16:52:44 -04:00
albemarle
9f3e945d4e fix(i18n): add detect_changed_source_translations target to stop jobs erroring 2019-04-05 16:42:50 -04:00
edX Transifex Bot
429d19291d fix(i18n): update translations 2019-04-05 16:21:24 -04:00
54 changed files with 8323 additions and 17311 deletions

View File

@@ -1,27 +0,0 @@
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "ie 11"]
}
}],
"babel-preset-react"
],
"plugins": [
"transform-object-rest-spread"
],
"env": {
"test": {
"plugins": [
"rewire"
]
},
"i18n": {
"plugins": [
["react-intl", {
"messagesDir": "./temp"
}]
]
}
}
}

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",
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"webpack.config.js",
"src/tests/setupTest.js",
"**/*.test.jsx",
"**/*.test.js"
]
}
]
},
"env": {
"jest": true
}
}

5
.gitignore vendored
View File

@@ -1,6 +1,11 @@
.DS_Store
coverage
dist
node_modules
temp
.idea/
src/i18n/transifex_input.json
temp/babel-plugin-react-intl
/.vscode

View File

@@ -1,18 +1,18 @@
language: node_js
node_js:
- 8
cache:
directories:
- "~/.npm"
node_js: 12
before_install:
- npm install -g npm@6
install:
- npm install
- npm install
script:
- npm run lint
- npm run test
- npm run build
- make validate-no-uncommitted-package-lock-changes
- npm run lint
- npm run i18n_extract
- npm run test
- npm run build
after_success:
- npm run travis-deploy-once "npm run semantic-release"
- npm run coveralls
- npx semantic-release
- codecov
env:
global:
- secure: dxQadqP6tsoJzHcqs/Hs5AjE42z45q8ZeWKP5HcjbXoJURB4gc1uIxLky0FA6ZpulaTgRVTLcWQbx9yOODc9PQuFnFEDWlCg5EP8tONzeu7BVlJvV5eakgGUhl9w2pekBKsTGhK5dDg2y2D8bGfIL55UX81uiWeytp8s/y8QNs/FNXx9ScJnfhnC+2RfW52fB7iW12F1VYdQfVe43o5PsHze+YhB3FU/ztGe3iMaQiq9QplZWpvqQMpI7pTjyUAX8ITiiPS6UvLFObgpXpfjZdgd+yveFoi3z8o8F0NkmzBphFeSYFjFZE0qJ8bnGNIZldanMeuUgHmDeTwVmKQFhH2LqqnfcdGgW6UsKcHkSN1G51zzad2dEwAHrgxj1NkMp3JfEed2C7Kvntl6KRjVDmYZqHJvt+e+AHNbpjzblOW8tYMIrdz0TeJdk4D9pP3B3tRCtP6fvQ3GLzAMnaCrSsN6hZ9YVxWku8sg8WNEDHl14sZsdgk312MlHIdiUw97FHGrqx/NCix4IkUlCBDbKYbKzbZp20FfzZcwNRNH74+k6xpOnMGSfq8gByEhm9y02MBL76HiAI2VGct2La1ExaUfoikYGoNaZpFcZyOZKo6PYTYHpiUJmqrEnDyVQEOOXUaVsxWXwnYq/mU4nOEPKCRbNpPoksZdNxf6jlmMi8s=

View File

@@ -1,36 +1,58 @@
extract_translations: ## no prerequisites so we can control order of operations
echo "We have to define this target due to tooling assumptions"
echo "Also we have to npm install using this hook b/c there's no other place for it in the current setup"
transifex_resource = frontend-component-footer
transifex_langs = "ar,fr,es_419,zh_CN"
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
build:
rm -rf ./dist
./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__
@rm -rf dist/__mocks__
requirements:
npm install
npm run-script i18n_extract
i18n.extract:
# Pulling display strings from .jsx files into .json files...
# Pulling display strings from .jsx files into .json files...
rm -rf $(transifex_temp)
npm run-script i18n_extract
i18n.concat:
# Gathering JSON messages into one file...
./src/i18n/i18n-concat.js ./temp/src ./src/i18n/transifex_input.json
# Gathering JSON messages into one file...
$(transifex_utils) $(transifex_temp) $(transifex_input)
i18n.pre_validate: | i18n.extract i18n.concat
git diff --exit-code ./src/i18n/transifex_input.json
extract_translations: | requirements i18n.extract i18n.concat
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/frontend-component-footer/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/frontend-component-footer/source/
# Despite the name, we actually need this target to detect changes in the incoming translated message files as well.
detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# push translations to Transifex, doing magic so we can include the translator comments
push_translations: | i18n.extract
# Adding translator comments...
# Fetching strings from Transifex...
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
# Writing out comments to file...
./src/i18n/i18n-concat.js ./temp/src --comments
# Adding comments to Transifex...
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
# pull translations from Transifex
pull_translations: ## must be exactly this name for edx tooling support, see ecommerce-scripts/transifex/pull.py
tx pull -f --mode reviewed --language="ar,fr,es_419,zh_CN"
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --language=$(transifex_langs)
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json

View File

@@ -5,9 +5,7 @@ frontend-component-footer
|semantic-release|
frontend-component-footer is a library containing a site footer
component for use when building edX frontend applications.
At this time, this component is hard-coded to match the legacy LMS site footer, including all of its links. As implemented, this component should probably be called the ``frontend-component-lms-footer``.
component for use when building Open edX frontend applications.
Usage
-----
@@ -16,14 +14,32 @@ To install frontend-component-footer into your project::
npm i --save @edx/frontend-component-footer
The component expects properties specifying the various URLs that are
linked in the footer. See the sample app in `src/index.jsx <src/index.jsx>`__ for an example
of how the SiteFooter component can be specified.
Component Usage::
import Footer, { messages } from '@edx/frontend-component-footer';
...
<Footer
onLanguageSelected={(languageCode) => {/* set language */}}
supportedLanguages={[
{ label: 'English', value: 'en'},
{ label: 'Español', value: 'es' },
]}
/>
- onLanguageSelected (optional)
- supportedLanguages (optional)
Styles (project.scss)::
@import '@edx/frontend-component-footer/src/footer.scss';
Requirements
------------
This component uses ``react-intl``. Any containing app must provide ``react-intl`` as a peer dependency, and be wrapped inside an ``IntlProvider`` element, whether or not your consuming application is actually localized. For a basic default locale (English) version, follow the ``IntlProvider`` example in the sample application in `src/index.jsx <src/index.jsx>`__.
This component uses ``@edx/frontend-i18n``. Any containing app must provide ``@edx/frontend-i18n`` as a peer dependency, and be wrapped inside an ``IntlProvider`` element, whether or not your consuming application is actually localized. For a basic default locale (English) version, follow the ``IntlProvider`` example in the sample application in `src/index.jsx <src/index.jsx>`__.
Development
-----------

3
babel.config.js Normal file
View File

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

View File

@@ -1,15 +0,0 @@
// This is the common Webpack config. The dev and prod Webpack configs both
// inherit config defined here.
const path = require('path');
module.exports = {
entry: {
app: path.resolve(__dirname, '../src/index.jsx'),
},
output: {
path: path.resolve(__dirname, '../dist'),
},
resolve: {
extensions: ['.js', '.jsx'],
},
};

View File

@@ -1,115 +0,0 @@
// This is the dev Webpack config. All settings here should prefer a fast build
// time at the expense of creating larger, unoptimized bundles.
const Merge = require('webpack-merge');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const commonConfig = require('./webpack.common.config.js');
module.exports = Merge.smart(commonConfig, {
mode: 'development',
entry: [
// enable react's custom hot dev client so we get errors reported in the browser
require.resolve('react-dev-utils/webpackHotDevClient'),
path.resolve(__dirname, '../src/index.jsx'),
],
module: {
// Specify file-by-file rules to Webpack. Some file-types need a particular kind of loader.
rules: [
// The babel-loader transforms newer ES2015+ syntax to older ES5 for older browsers.
// Babel is configured with the .babelrc file at the root of the project.
{
test: /\.(js|jsx)$/,
include: [
path.resolve(__dirname, '../src'),
],
loader: 'babel-loader',
options: {
// Caches result of loader to the filesystem. Future builds will attempt to read from the
// cache to avoid needing to run the expensive recompilation process on each run.
cacheDirectory: true,
},
},
// We are not extracting CSS from the javascript bundles in development because extracting
// prevents hot-reloading from working, it increases build time, and we don't care about
// flash-of-unstyled-content issues in development.
{
test: /(.scss|.css)$/,
use: [
'style-loader', // creates style nodes from JS strings
{
loader: 'css-loader', // translates CSS into CommonJS
options: {
sourceMap: true,
},
},
{
loader: 'sass-loader', // compiles Sass to CSS
options: {
sourceMap: true,
includePaths: [
path.join(__dirname, '../node_modules'),
path.join(__dirname, '../src'),
],
},
},
],
},
// Webpack, by default, uses the url-loader for images and fonts that are required/included by
// files it processes, which just base64 encodes them and inlines them in the javascript
// bundles. This makes the javascript bundles ginormous and defeats caching so we will use the
// file-loader instead to copy the files directly to the output directory.
{
test: /\.(woff2?|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
},
{
test: /\.(jpe?g|png|gif|ico)(\?v=\d+\.\d+\.\d+)?$/,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
optimizationlevel: 7,
mozjpeg: {
progressive: true,
},
gifsicle: {
interlaced: false,
},
pngquant: {
quality: '65-90',
speed: 4,
},
},
},
],
},
],
},
// Specify additional processing or side-effects done on the Webpack output bundles as a whole.
plugins: [
// Generates an HTML file in the output directory.
new HtmlWebpackPlugin({
inject: true, // Appends script tags linking to the webpack bundles at the end of the body
template: path.resolve(__dirname, '../public/index.html'),
}),
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
// when the --hot option is not passed in as part of the command
// the HotModuleReplacementPlugin has to be specified in the Webpack configuration
// https://webpack.js.org/configuration/dev-server/#devserver-hot
new webpack.HotModuleReplacementPlugin(),
],
// This configures webpack-dev-server which serves bundles from memory and provides live
// reloading.
devServer: {
host: '0.0.0.0',
port: 3000,
historyApiFallback: true,
hot: true,
inline: true,
},
});

View File

@@ -1,59 +0,0 @@
// This is the prod Webpack config. All settings here should prefer smaller,
// optimized bundles at the expense of a longer build time.
const Merge = require('webpack-merge');
const commonConfig = require('./webpack.common.config.js');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = Merge.smart(commonConfig, {
mode: 'production',
devtool: 'source-map',
entry: './src/lib/index.js',
output: {
path: path.resolve(__dirname, '../dist'),
library: 'frontend-component-footer',
libraryTarget: 'umd',
globalObject: 'typeof self !== \'undefined\' ? self : this',
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
react: path.resolve(__dirname, './node_modules/react'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom'),
},
},
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'React',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'ReactDOM',
root: 'ReactDOM',
},
},
plugins: [
// Cleans the dist directory before each build
new CleanWebpackPlugin(['dist'], {
root: path.join(__dirname, '../'),
}),
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
include: [
path.resolve(__dirname, '../src/lib'),
],
exclude: /(node_modules)/,
use: [
{ loader: 'babel-loader' },
],
},
],
},
});

22
example/index.jsx Normal file
View File

@@ -0,0 +1,22 @@
import React from 'react';
import { render } from 'react-dom';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import './index.scss';
import Footer from '../src';
const App = () => (
<div>
<IntlProvider locale="en">
<Footer
onLanguageSelected={() => {}}
supportedLanguages={[
{ label: 'English', value: 'en' },
{ label: 'Español', value: 'es' },
]}
/>
</IntlProvider>
</div>
);
render(<App />, document.getElementById('root'));

2
example/index.scss Normal file
View File

@@ -0,0 +1,2 @@
@import "~@edx/paragon/scss/core/core.scss";
@import "../src/footer";

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',
],
});

8
openedx.yaml Normal file
View File

@@ -0,0 +1,8 @@
# openedx.yaml
---
owner: edx/fedx-team
tags:
- library
- component
- react

22404
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,27 @@
{
"name": "@edx/frontend-component-footer",
"version": "1.0.0-semantically-released",
"description": "Site footer component for use when building edX frontend applications",
"main": "dist/main.js",
"module": "dist/main.js",
"description": "Footer component for use when building Open edX frontend applications",
"main": "dist/index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "NODE_ENV=production BABEL_ENV=production webpack --config=config/webpack.prod.config.js",
"gc": "commit",
"commitmsg": "commitlint -e $GIT_PARAMS",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"i18n_extract": "BABEL_ENV=i18n babel src --quiet > /dev/null",
"lint": "eslint --ext .js --ext .jsx .",
"precommit": "npm run lint",
"prepublishOnly": "npm run build",
"semantic-release": "semantic-release",
"start": "NODE_ENV=development BABEL_ENV=development node_modules/.bin/webpack-dev-server --config=config/webpack.dev.config.js --progress",
"test": "jest --coverage",
"snapshot": "jest --updateSnapshot",
"travis-deploy-once": "travis-deploy-once",
"watch": "watch 'npm run build' ./src"
"build": "make build",
"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"
],
"husky": {
"hooks": {
"pre-commit": "npm run lint",
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
"repository": {
"type": "git",
@@ -34,77 +34,35 @@
},
"homepage": "https://github.com/edx/frontend-component-footer#readme",
"devDependencies": {
"@commitlint/cli": "^7.1.2",
"@commitlint/config-angular": "^6.0.2",
"@commitlint/prompt": "^6.0.2",
"@commitlint/prompt-cli": "^6.0.2",
"@edx/paragon": "^3.8.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-react-intl": "^3.0.1",
"babel-plugin-rewire": "^1.2.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"coveralls": "^3.0.0",
"css-loader": "^0.28.9",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^5.2.0",
"eslint-config-edx": "^4.0.4",
"eslint-plugin-jsx-a11y": "^6.1.2",
"file-loader": "^1.1.9",
"glob": "^7.1.3",
"html-webpack-plugin": "^3.2.0",
"husky": "^0.14.3",
"image-webpack-loader": "^4.2.0",
"jest": "23.6.0",
"node-sass": "^4.7.2",
"prop-types": "^15.5.10",
"react": "^16.4.2",
"react-dev-utils": "^5.0.0",
"react-dom": "^16.2.0",
"react-intl": "^2.8.0",
"react-test-renderer": "^16.6.0",
"reactifex": "^1.1.1",
"sass-loader": "^6.0.6",
"semantic-release": "^15.1.7",
"source-map-loader": "^0.2.1",
"style-loader": "^0.20.2",
"travis-deploy-once": "^5.0.0",
"watch": "^1.0.2",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.9",
"webpack-merge": "^4.2.1"
"@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.6",
"@edx/paragon": "7.1.5",
"codecov": "3.6.5",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"husky": "3.0.9",
"prop-types": "15.7.2",
"react": "16.10.2",
"react-dom": "16.10.2",
"react-test-renderer": "16.10.2",
"reactifex": "1.1.1"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.27",
"@fortawesome/free-brands-svg-icons": "5.8.2",
"@fortawesome/free-regular-svg-icons": "5.8.2",
"@fortawesome/free-solid-svg-icons": "5.8.2",
"@fortawesome/react-fontawesome": "0.1.9"
},
"peerDependencies": {
"@edx/paragon": "^3.8.0",
"clean-webpack-plugin": "^0.1.19",
"html-webpack-plugin": "^3.2.0",
"@edx/frontend-platform": "^1.1.4",
"@edx/paragon": "^7.0.0",
"prop-types": "^15.5.10",
"react": "^16.4.2",
"react-dom": "^16.2.0",
"react-intl": "2.x",
"webpack": "^4.19.1",
"webpack-merge": "^4.2.1"
},
"jest": {
"setupFiles": [
"./src/tests/setupTest.js"
],
"collectCoverageFrom": [
"src/lib/**/*.{js,jsx}",
"!src/tests/setupTest.js",
"!src/index.js",
"!**/node_modules/**",
"!**/tests/**"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|scss)$": "identity-obj-proxy"
}
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}

View File

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

9
renovate.json Normal file
View File

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

5
src/_footer.scss Normal file
View File

@@ -0,0 +1,5 @@
$gray-footer: #fcfcfc !default;
.footer {
background-color: $gray-footer;
}

95
src/components/Footer.jsx Normal file
View File

@@ -0,0 +1,95 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import messages from './Footer.messages';
import FooterLogo from '../edx-openedx-logo-tag.png';
import LanguageSelector from './LanguageSelector';
const EVENT_NAMES = {
FOOTER_LINK: 'edx.bi.footer.link',
};
class SiteFooter extends React.Component {
constructor(props) {
super(props);
this.externalLinkClickHandler = this.externalLinkClickHandler.bind(this);
}
getLocalePrefix(locale) {
const twoLetterPrefix = locale.substring(0, 2).toLowerCase();
if (twoLetterPrefix === 'en') {
return '';
}
return `/${twoLetterPrefix}`;
}
externalLinkClickHandler(event) {
const label = event.currentTarget.getAttribute('href');
const eventName = EVENT_NAMES.FOOTER_LINK;
const properties = {
category: 'outbound_link',
label,
};
sendTrackEvent(eventName, properties);
}
render() {
const {
supportedLanguages,
onLanguageSelected,
logo,
intl,
} = this.props;
const showLanguageSelector = supportedLanguages.length > 0 && onLanguageSelected;
return (
<footer
role="contentinfo"
aria-label={intl.formatMessage(messages['footer.logo.ariaLabel'])}
className="footer d-flex border-top py-3 px-4"
>
<div className="container d-flex">
<a
className="d-block mb-3"
href="https://open.edx.org"
aria-label={intl.formatMessage(messages['footer.logo.ariaLabel'])}
>
<img
style={{ maxWidth: 150 }}
src={logo || FooterLogo}
alt={intl.formatMessage(messages['footer.logo.altText'])}
/>
</a>
<div className="flex-grow-1" />
{showLanguageSelector &&
<LanguageSelector
options={supportedLanguages}
onSubmit={onLanguageSelected}
/>
}
</div>
</footer>
);
}
}
SiteFooter.propTypes = {
intl: intlShape.isRequired,
logo: PropTypes.string,
onLanguageSelected: PropTypes.func,
supportedLanguages: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
})),
};
SiteFooter.defaultProps = {
logo: undefined,
onLanguageSelected: undefined,
supportedLanguages: [],
};
export default injectIntl(SiteFooter);
export { EVENT_NAMES };

View File

@@ -0,0 +1,156 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'footer.socialLinks.srText.facebook': {
id: 'footer.socialLinks.srText.facebook',
defaultMessage: 'Like edX on Facebook',
description: 'This is screenreader text for the edX Facebook social media link in the footer.',
},
'footer.socialLinks.srText.twitter': {
id: 'footer.socialLinks.srText.twitter',
defaultMessage: 'Follow edX on Twitter',
description: 'This is screenreader text for the edX Twitter social media link in the footer.',
},
'footer.socialLinks.srText.youtube': {
id: 'footer.socialLinks.srText.youtube',
defaultMessage: 'Subscribe to the edX YouTube channel',
description: 'This is screenreader text for the edX YouTube social media link in the footer.',
},
'footer.socialLinks.srText.linkedin': {
id: 'footer.socialLinks.srText.linkedin',
defaultMessage: 'Follow edX on LinkedIn',
description: 'This is screenreader text for the edX LinkedIn social media link in the footer.',
},
'footer.socialLinks.srText.reddit': {
id: 'footer.socialLinks.srText.reddit',
defaultMessage: 'Subscribe to the edX subreddit',
description: 'This is screenreader text for the edX reddit social media link in the footer.',
},
'footer.languageForm.select.label': {
id: 'footer.languageForm.select.label',
defaultMessage: 'Choose Language',
description: 'The label for the laguage select part of the language selection form.',
},
'footer.languageForm.submit.label': {
id: 'footer.languageForm.submit.label',
defaultMessage: 'Apply',
description: 'The label for button to submit the language selection form.',
},
'footer.edxLinks.about': {
id: 'footer.edxLinks.about',
defaultMessage: 'About',
description: 'The label for the link to the about edX page.',
},
'footer.edxLinks.business': {
id: 'footer.edxLinks.business',
defaultMessage: 'edX for Business',
description: 'The label for the link to the edX for business page.',
},
'footer.edxLinks.affiliates': {
id: 'footer.edxLinks.affiliates',
defaultMessage: 'Affiliates',
description: 'The label for the link to the edX affiliates page.',
},
'footer.edxLinks.openEdx': {
id: 'footer.edxLinks.openEdx',
defaultMessage: 'Open edX',
description: 'The label for the link to the open edX site.',
},
'footer.edxLinks.careers': {
id: 'footer.edxLinks.careers',
defaultMessage: 'Careers',
description: 'The label for the link to the edX Careers page.',
},
'footer.edxLinks.news': {
id: 'footer.edxLinks.news',
defaultMessage: 'News',
description: 'The label for the link to the edX news page.',
},
'footer.legalLinks.heading': {
id: 'footer.legalLinks.heading',
defaultMessage: 'Legal',
description: 'Heading for the legal links section of the footer.',
},
'footer.legalLinks.termsOfService': {
id: 'footer.legalLinks.termsOfService',
defaultMessage: 'Terms of Service & Honor Code',
description: 'The label for the link to the edX terms of service page.',
},
'footer.legalLinks.privacyPolicy': {
id: 'footer.legalLinks.privacyPolicy',
defaultMessage: 'Privacy Policy',
description: 'The label for the link to the edX privacy policy page.',
},
'footer.legalLinks.a11yPolicy': {
id: 'footer.legalLinks.a11yPolicy',
defaultMessage: 'Accessibility Policy',
description: 'The label for the link to the edX accessibility policy page.',
},
'footer.legalLinks.trademarkPolicy': {
id: 'footer.legalLinks.trademarkPolicy',
defaultMessage: 'Trademark Policy',
description: 'The label for the link to the edX trademark policy page.',
},
'footer.legalLinks.sitemap': {
id: 'footer.legalLinks.sitemap',
defaultMessage: 'Sitemap',
description: 'The label for the link to the edX sitemap page.',
},
'footer.connectLinks.heading': {
id: 'footer.connectLinks.heading',
defaultMessage: 'Connect',
description: 'Heading for the connect links section of the footer.',
},
'footer.connectLinks.blog': {
id: 'footer.connectLinks.blog',
defaultMessage: 'Blog',
description: 'The label for the link to the edX blog.',
},
'footer.connectLinks.contact': {
id: 'footer.connectLinks.contact',
defaultMessage: 'Contact Us',
description: 'The label for the link to the contact edX page.',
},
'footer.connectLinks.help': {
id: 'footer.connectLinks.help',
defaultMessage: 'Help Center',
description: 'The label for the link to the edX help center.',
},
'footer.connectLinks.mediaKit': {
id: 'footer.connectLinks.mediaKit',
defaultMessage: 'Media Kit',
description: 'The label for the link to the edX media kit page.',
},
'footer.connectLinks.donate': {
id: 'footer.connectLinks.donate',
defaultMessage: 'Donate',
description: 'The label for the link to the edX donation page.',
},
'footer.mobileApp.apple': {
id: 'footer.mobileApp.apple',
defaultMessage: 'Download the edX mobile app from the Apple App Store',
description: 'The label for the link to download the apple version of the edX app.',
},
'footer.mobileApp.google': {
id: 'footer.mobileApp.google',
defaultMessage: 'Download the edX mobile app from Google Play',
description: 'The label for the link to download the google version of the edX app.',
},
'footer.logo.altText': {
id: 'footer.logo.altText',
defaultMessage: 'Powered by Open edX',
description: 'alt text for the footer logo.',
},
'footer.logo.ariaLabel': {
id: 'footer.logo.ariaLabel',
defaultMessage: 'edX Home',
description: 'aria-label for the footer logo.',
},
'footer.ariaLabel': {
id: 'footer.ariaLabel',
defaultMessage: 'Page Footer',
description: 'aria-label for the footer component',
},
});
export default messages;

View File

@@ -0,0 +1,76 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import Footer from './Footer';
describe('<Footer />', () => {
describe('renders correctly', () => {
it('renders without a language selector', () => {
const tree = renderer
.create((
<IntlProvider locale="en">
<Footer />
</IntlProvider>
))
.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders without a language selector in es', () => {
const tree = renderer
.create((
<IntlProvider locale="es">
<Footer />
</IntlProvider>
))
.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders with a language selector', () => {
const tree = renderer
.create((
<IntlProvider locale="en">
<Footer
onLanguageSelected={() => {}}
supportedLanguages={[
{ label: 'English', value: 'en' },
{ label: 'Español', value: 'es' },
]}
/>
</IntlProvider>
))
.toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('handles language switching', () => {
it('calls onLanguageSelected prop when a language is changed', () => {
const mockHandleLanguageSelected = jest.fn();
const wrapper = mount((
<IntlProvider locale="en">
<Footer
onLanguageSelected={mockHandleLanguageSelected}
supportedLanguages={[
{ label: 'English', value: 'en' },
{ label: 'Español', value: 'es' },
]}
/>
</IntlProvider>
));
wrapper.find('form').simulate('submit', {
target: {
elements: {
'site-footer-language-select': {
value: 'es',
},
},
},
});
expect(mockHandleLanguageSelected).toHaveBeenCalledWith('es');
});
});
});

View File

@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
const LanguageSelector = ({
intl, options, onSubmit, ...props
}) => {
const handleSubmit = (e) => {
e.preventDefault();
const languageCode = e.target.elements['site-footer-language-select'].value;
onSubmit(languageCode);
};
return (
<form
className="form-inline"
onSubmit={handleSubmit}
{...props}
>
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
<div className="form-group">
<label htmlFor="site-footer-language-select" className="d-inline-block m-0">
<FormattedMessage
id="footer.languageForm.select.label"
defaultMessage="Choose Language"
description="The label for the laguage select part of the language selection form."
/>
</label>
<select
id="site-footer-language-select"
className="form-control-sm mx-2"
name="site-footer-language-select"
defaultValue={intl.locale}
>
{options.map(({ value, label }) =>
<option key={value} value={value}>{label}</option>)}
</select>
<button className="btn btn-outline-primary btn-sm" type="submit">
<FormattedMessage
id="footer.languageForm.submit.label"
defaultMessage="Apply"
description="The label for button to submit the language selection form."
/>
</button>
</div>
</form>
);
};
LanguageSelector.propTypes = {
intl: intlShape.isRequired,
onSubmit: PropTypes.func.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string,
label: PropTypes.string,
})).isRequired,
};
export default injectIntl(LanguageSelector);

View File

@@ -0,0 +1,136 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> renders correctly renders with a language selector 1`] = `
<footer
aria-label="edX Home"
className="footer d-flex border-top py-3 px-4"
role="contentinfo"
>
<div
className="container d-flex"
>
<a
aria-label="edX Home"
className="d-block mb-3"
href="https://open.edx.org"
>
<img
alt="Powered by Open edX"
src="test-file-stub"
style={
Object {
"maxWidth": 150,
}
}
/>
</a>
<div
className="flex-grow-1"
/>
<form
className="form-inline"
onSubmit={[Function]}
>
<div
className="form-group"
>
<label
className="d-inline-block m-0"
htmlFor="site-footer-language-select"
>
<span>
Choose Language
</span>
</label>
<select
className="form-control-sm mx-2"
defaultValue="en"
id="site-footer-language-select"
name="site-footer-language-select"
>
<option
value="en"
>
English
</option>
<option
value="es"
>
Español
</option>
</select>
<button
className="btn btn-outline-primary btn-sm"
type="submit"
>
<span>
Apply
</span>
</button>
</div>
</form>
</div>
</footer>
`;
exports[`<Footer /> renders correctly renders without a language selector 1`] = `
<footer
aria-label="edX Home"
className="footer d-flex border-top py-3 px-4"
role="contentinfo"
>
<div
className="container d-flex"
>
<a
aria-label="edX Home"
className="d-block mb-3"
href="https://open.edx.org"
>
<img
alt="Powered by Open edX"
src="test-file-stub"
style={
Object {
"maxWidth": 150,
}
}
/>
</a>
<div
className="flex-grow-1"
/>
</div>
</footer>
`;
exports[`<Footer /> renders correctly renders without a language selector in es 1`] = `
<footer
aria-label="edX Home"
className="footer d-flex border-top py-3 px-4"
role="contentinfo"
>
<div
className="container d-flex"
>
<a
aria-label="edX Home"
className="d-block mb-3"
href="https://open.edx.org"
>
<img
alt="Powered by Open edX"
src="test-file-stub"
style={
Object {
"maxWidth": 150,
}
}
/>
</a>
<div
className="flex-grow-1"
/>
</div>
</footer>
`;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,57 +0,0 @@
#!/usr/bin/env node
/**
* This code originally came from https://github.com/efischer19/reactifex/blob/master/main.js,
* which should be edx/reactifex. It is temporarily being copied here until we find it a new home.
*/
// NOTE: This script is called from Jenkins using devDependencies, so eslint is being
// disabled so it doesn't force you to make these real dependencies.
const fs = require('fs'); // eslint-disable-line import/no-extraneous-dependencies
const glob = require('glob'); // eslint-disable-line import/no-extraneous-dependencies
const path = require('path'); // eslint-disable-line import/no-extraneous-dependencies
// Expected input: a directory, possibly containing subdirectories, with .json files. Each .json
// file is an array of translation triplets (id, description, defaultMessage).
function gatherJson(dir) {
const ret = [];
const files = glob.sync(`${dir}/**/*.json`);
files.forEach((filename) => {
const messages = JSON.parse(fs.readFileSync(filename));
ret.push(...messages);
});
return ret;
}
const jsonDir = process.argv[2];
const messageObjects = gatherJson(jsonDir);
if (process.argv[3] === '--comments') { // prepare to handle the translator notes
const thisFile = path.basename(`${__filename}`);
const bashScriptsPath = './node_modules/reactifex/bash_scripts';
process.stdout.write(`${thisFile}: generating bash scripts...\n`);
process.stdout.write(`${thisFile}: info file at ${bashScriptsPath}/hashmap.json\n`);
const messageInfo = JSON.parse(fs.readFileSync(`${bashScriptsPath}/hashmap.json`));
const dataPath = `${bashScriptsPath}/hashed_data.txt`;
process.stdout.write(`${thisFile}: data path is ${dataPath}\n`);
fs.writeFileSync(dataPath, '');
messageObjects.forEach((message) => {
const info = messageInfo.find(mi => mi.key === message.id);
if (info) {
fs.appendFileSync(dataPath, `${info.string_hash}|${message.description}\n`);
} else {
process.stdout.write(`${thisFile}: string ${message.id} does not yet exist on transifex!\n`);
}
});
} else {
const output = {};
messageObjects.forEach((message) => {
output[message.id] = message.defaultMessage;
});
fs.writeFileSync(process.argv[3], JSON.stringify(output, null, 2));
}

32
src/i18n/index.js Normal file
View File

@@ -0,0 +1,32 @@
import arMessages from './messages/ar.json';
import caMessages from './messages/ca.json';
// no need to import en messages-- they are in the defaultMessage field
import es419Messages from './messages/es_419.json';
import frMessages from './messages/fr.json';
import zhcnMessages from './messages/zh_CN.json';
import heMessages from './messages/he.json';
import idMessages from './messages/id.json';
import kokrMessages from './messages/ko_kr.json';
import plMessages from './messages/pl.json';
import ptbrMessages from './messages/pt_br.json';
import ruMessages from './messages/ru.json';
import thMessages from './messages/th.json';
import ukMessages from './messages/uk.json';
const messages = {
ar: arMessages,
'es-419': es419Messages,
fr: frMessages,
'zh-cn': zhcnMessages,
ca: caMessages,
he: heMessages,
id: idMessages,
'ko-kr': kokrMessages,
pl: plMessages,
'pt-br': ptbrMessages,
ru: ruMessages,
th: thMessages,
uk: ukMessages,
};
export default messages;

View File

@@ -1,2 +1,32 @@
{
}
"footer.socialLinks.srText.facebook": "Like edX on Facebook",
"footer.socialLinks.srText.twitter": "Follow edX on Twitter",
"footer.socialLinks.srText.youtube": "Subscribe to the edX YouTube channel",
"footer.socialLinks.srText.linkedin": "Follow edX on LinkedIn",
"footer.socialLinks.srText.reddit": "Subscribe to the edX subreddit",
"footer.languageForm.select.label": "Choose Language",
"footer.languageForm.submit.label": "Apply",
"footer.edxLinks.about": "About",
"footer.edxLinks.business": "edX for Business",
"footer.edxLinks.affiliates": "Affiliates",
"footer.edxLinks.openEdx": "Open edX",
"footer.edxLinks.careers": "Careers",
"footer.edxLinks.news": "News",
"footer.legalLinks.heading": "Legal",
"footer.legalLinks.termsOfService": "Terms of Service & Honor Code",
"footer.legalLinks.privacyPolicy": "Privacy Policy",
"footer.legalLinks.a11yPolicy": "Accessibility Policy",
"footer.legalLinks.trademarkPolicy": "Trademark Policy",
"footer.legalLinks.sitemap": "Sitemap",
"footer.connectLinks.heading": "Connect",
"footer.connectLinks.blog": "Blog",
"footer.connectLinks.contact": "Contact Us",
"footer.connectLinks.help": "Help Center",
"footer.connectLinks.mediaKit": "Media Kit",
"footer.connectLinks.donate": "Donate",
"footer.mobileApp.apple": "Download the edX mobile app from the Apple App Store",
"footer.mobileApp.google": "Download the edX mobile app from Google Play",
"footer.logo.altText": "Powered by Open edX",
"footer.logo.ariaLabel": "edX Home",
"footer.ariaLabel": "Page Footer"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -1,2 +1,32 @@
{
}
"footer.socialLinks.srText.facebook": "Like edX on Facebook",
"footer.socialLinks.srText.twitter": "Follow edX on Twitter",
"footer.socialLinks.srText.youtube": "Subscribe to the edX YouTube channel",
"footer.socialLinks.srText.linkedin": "Follow edX on LinkedIn",
"footer.socialLinks.srText.reddit": "Subscribe to the edX subreddit",
"footer.languageForm.select.label": "Choose Language",
"footer.languageForm.submit.label": "Apply",
"footer.edxLinks.about": "About",
"footer.edxLinks.business": "edX for Business",
"footer.edxLinks.affiliates": "Affiliates",
"footer.edxLinks.openEdx": "Open edX",
"footer.edxLinks.careers": "Careers",
"footer.edxLinks.news": "News",
"footer.legalLinks.heading": "Legal",
"footer.legalLinks.termsOfService": "Terms of Service & Honor Code",
"footer.legalLinks.privacyPolicy": "Privacy Policy",
"footer.legalLinks.a11yPolicy": "Accessibility Policy",
"footer.legalLinks.trademarkPolicy": "Trademark Policy",
"footer.legalLinks.sitemap": "Sitemap",
"footer.connectLinks.heading": "Connect",
"footer.connectLinks.blog": "Blog",
"footer.connectLinks.contact": "Contact Us",
"footer.connectLinks.help": "Help Center",
"footer.connectLinks.mediaKit": "Media Kit",
"footer.connectLinks.donate": "Donate",
"footer.mobileApp.apple": "Download the edX mobile app from the Apple App Store",
"footer.mobileApp.google": "Download the edX mobile app from Google Play",
"footer.logo.altText": "Powered by Open edX",
"footer.logo.ariaLabel": "edX Home",
"footer.ariaLabel": "Page Footer"
}

View File

@@ -1,2 +1,32 @@
{
}
"footer.socialLinks.srText.facebook": "Like edX on Facebook",
"footer.socialLinks.srText.twitter": "Follow edX on Twitter",
"footer.socialLinks.srText.youtube": "Subscribe to the edX YouTube channel",
"footer.socialLinks.srText.linkedin": "Follow edX on LinkedIn",
"footer.socialLinks.srText.reddit": "Subscribe to the edX subreddit",
"footer.languageForm.select.label": "Choose Language",
"footer.languageForm.submit.label": "Apply",
"footer.edxLinks.about": "About",
"footer.edxLinks.business": "edX for Business",
"footer.edxLinks.affiliates": "Affiliates",
"footer.edxLinks.openEdx": "Open edX",
"footer.edxLinks.careers": "Careers",
"footer.edxLinks.news": "News",
"footer.legalLinks.heading": "Legal",
"footer.legalLinks.termsOfService": "Terms of Service & Honor Code",
"footer.legalLinks.privacyPolicy": "Privacy Policy",
"footer.legalLinks.a11yPolicy": "Accessibility Policy",
"footer.legalLinks.trademarkPolicy": "Trademark Policy",
"footer.legalLinks.sitemap": "Sitemap",
"footer.connectLinks.heading": "Connect",
"footer.connectLinks.blog": "Blog",
"footer.connectLinks.contact": "Contact Us",
"footer.connectLinks.help": "Help Center",
"footer.connectLinks.mediaKit": "Media Kit",
"footer.connectLinks.donate": "Donate",
"footer.mobileApp.apple": "Download the edX mobile app from the Apple App Store",
"footer.mobileApp.google": "Download the edX mobile app from Google Play",
"footer.logo.altText": "Powered by Open edX",
"footer.logo.ariaLabel": "edX Home",
"footer.ariaLabel": "Page Footer"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -1,2 +1,32 @@
{
}
"footer.socialLinks.srText.facebook": "Like edX on Facebook",
"footer.socialLinks.srText.twitter": "Follow edX on Twitter",
"footer.socialLinks.srText.youtube": "Subscribe to the edX YouTube channel",
"footer.socialLinks.srText.linkedin": "Follow edX on LinkedIn",
"footer.socialLinks.srText.reddit": "Subscribe to the edX subreddit",
"footer.languageForm.select.label": "Choose Language",
"footer.languageForm.submit.label": "Apply",
"footer.edxLinks.about": "About",
"footer.edxLinks.business": "edX for Business",
"footer.edxLinks.affiliates": "Affiliates",
"footer.edxLinks.openEdx": "Open edX",
"footer.edxLinks.careers": "Careers",
"footer.edxLinks.news": "News",
"footer.legalLinks.heading": "Legal",
"footer.legalLinks.termsOfService": "Terms of Service & Honor Code",
"footer.legalLinks.privacyPolicy": "Privacy Policy",
"footer.legalLinks.a11yPolicy": "Accessibility Policy",
"footer.legalLinks.trademarkPolicy": "Trademark Policy",
"footer.legalLinks.sitemap": "Sitemap",
"footer.connectLinks.heading": "Connect",
"footer.connectLinks.blog": "Blog",
"footer.connectLinks.contact": "Contact Us",
"footer.connectLinks.help": "Help Center",
"footer.connectLinks.mediaKit": "Media Kit",
"footer.connectLinks.donate": "Donate",
"footer.mobileApp.apple": "Download the edX mobile app from the Apple App Store",
"footer.mobileApp.google": "Download the edX mobile app from Google Play",
"footer.logo.altText": "Powered by Open edX",
"footer.logo.ariaLabel": "edX Home",
"footer.ariaLabel": "Page Footer"
}

View File

@@ -1,39 +0,0 @@
{
"footer.site-footer.link.about": "About",
"footer.site-footer.link.business": "{siteName} for Business",
"footer.site-footer.link.affiliates": "Affiliates",
"footer.site-footer.link.open-source": "Open {siteName}",
"footer.site-footer.link.careers": "Careers",
"footer.site-footer.link.news": "News",
"footer.site-footer.link.header.legal": "Legal",
"footer.site-footer.link.terms-of-service": "Terms of Service & Honor Code",
"footer.site-footer.link.privacy": "Privacy Policy",
"footer.site-footer.link.accessibility": "Accessibility Policy",
"footer.site-footer.link.trademark": "Trademark Policy",
"footer.site-footer.link.sitemap": "Sitemap",
"footer.site-footer.link.header.connect": "Connect",
"footer.site-footer.link.blog": "Blog",
"footer.site-footer.link.contact-us": "Contact Us",
"footer.site-footer.link.help-center": "Help Center",
"footer.site-footer.link.media-kit": "Media Kit",
"footer.site-footer.link.donate": "Donate",
"footer.site-footer.copyright-text": "{copyrightSymbol} {startDate}{endDate} {siteName} Inc.",
"footer.site-footer.trademark-text": "EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | {icpLicense}",
"footer.site-footer.site-logo.alt-text": "{siteName} logo",
"footer.site-footer.site-logo.aria-label": "{siteName} Home",
"footer.site-footer.facebook.title": "Facebook",
"footer.site-footer.facebook.screen-reader-text": "Like {siteName} on Facebook",
"footer.site-footer.twitter.title": "Twitter",
"footer.site-footer.twitter.screen-reader-text": "Follow {siteName} on Twitter",
"footer.site-footer.youtube.title": "Youtube",
"footer.site-footer.youtube.screen-reader-text": "Subscribe to the {siteName} YouTube channel",
"footer.site-footer.linkedin.title": "LinkedIn",
"footer.site-footer.linkedin.screen-reader-text": "Follow {siteName} on LinkedIn",
"footer.site-footer.google-plus.title": "Google+",
"footer.site-footer.google-plus.screen-reader-text": "Follow {siteName} on Google+",
"footer.site-footer.reddit.title": "Reddit",
"footer.site-footer.reddit.screen-reader-text": "Subscribe to the {siteName} subreddit",
"footer.site-footer.apple-app-store.alt-text": "Download the {siteName} mobile app from the Apple App Store",
"footer.site-footer.google-play.alt-text": "Download the {siteName} mobile app from Google Play",
"footer.site-footer.footer.aria-label": "Page Footer"
}

5
src/index.js Normal file
View File

@@ -0,0 +1,5 @@
import Footer, { EVENT_NAMES } from './components/Footer';
import messages from './i18n/index';
export default Footer;
export { messages, EVENT_NAMES };

View File

@@ -1,33 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import { IntlProvider } from 'react-intl';
import SiteFooter from './lib';
import './index.scss';
import FooterLogo from './edx-footer.png';
const App = () => (
<IntlProvider locale="en">
<SiteFooter
siteName="edX"
siteLogo={FooterLogo}
marketingSiteBaseUrl="https://www.example.com"
supportUrl="https://www.example.com/support"
contactUrl="https://www.example.com/contact"
openSourceUrl="https://www.example.com/open"
termsOfServiceUrl="https://www.example.com/terms-of-service"
privacyPolicyUrl="https://www.example.com/privacy-policy"
facebookUrl="https://www.facebook.com"
twitterUrl="https://www.twitter.com"
youTubeUrl="https://www.youtube.com"
linkedInUrl="https://www.linkedin.com"
googlePlusUrl="https://plus.google.com"
redditUrl="https://reddit.com"
appleAppStoreUrl="https://store.apple.com"
googlePlayUrl="https://play.google.com"
handleAllTrackEvents={() => {}}
/>
</IntlProvider>
);
render(<App />, document.getElementById('root'));

View File

@@ -1,4 +0,0 @@
@import "~@edx/edx-bootstrap/sass/edx/theme";
@import "~bootstrap/scss/bootstrap";
@import './lib/scss/_site-footer.scss';

View File

@@ -1,91 +0,0 @@
import { defineMessages } from 'react-intl';
const messages = defineMessages({
'footer.site-footer.site-logo.alt-text': {
id: 'footer.site-footer.site-logo.alt-text',
defaultMessage: '{siteName} logo',
description: 'The alt description of the site logo',
},
'footer.site-footer.site-logo.aria-label': {
id: 'footer.site-footer.site-logo.aria-label',
defaultMessage: '{siteName} Home',
description: 'Aria label for the site logo which goes to the marketing site',
},
'footer.site-footer.facebook.title': {
id: 'footer.site-footer.facebook.title',
defaultMessage: 'Facebook',
description: 'Facebook button title',
},
'footer.site-footer.facebook.screen-reader-text': {
id: 'footer.site-footer.facebook.screen-reader-text',
defaultMessage: 'Like {siteName} on Facebook',
description: 'Facebook button screen reader text',
},
'footer.site-footer.twitter.title': {
id: 'footer.site-footer.twitter.title',
defaultMessage: 'Twitter',
description: 'Twitter button title',
},
'footer.site-footer.twitter.screen-reader-text': {
id: 'footer.site-footer.twitter.screen-reader-text',
defaultMessage: 'Follow {siteName} on Twitter',
description: 'Twitter button screen reader text',
},
'footer.site-footer.youtube.title': {
id: 'footer.site-footer.youtube.title',
defaultMessage: 'Youtube',
description: 'Youtube button title',
},
'footer.site-footer.youtube.screen-reader-text': {
id: 'footer.site-footer.youtube.screen-reader-text',
defaultMessage: 'Subscribe to the {siteName} YouTube channel',
description: 'Youtube button screen reader text',
},
'footer.site-footer.linkedin.title': {
id: 'footer.site-footer.linkedin.title',
defaultMessage: 'LinkedIn',
description: 'LinkedIn button title',
},
'footer.site-footer.linkedin.screen-reader-text': {
id: 'footer.site-footer.linkedin.screen-reader-text',
defaultMessage: 'Follow {siteName} on LinkedIn',
description: 'LinkedIn button screen reader text',
},
'footer.site-footer.google-plus.title': {
id: 'footer.site-footer.google-plus.title',
defaultMessage: 'Google+',
description: 'Google+ button title',
},
'footer.site-footer.google-plus.screen-reader-text': {
id: 'footer.site-footer.google-plus.screen-reader-text',
defaultMessage: 'Follow {siteName} on Google+',
description: 'Google+ button screen reader text',
},
'footer.site-footer.reddit.title': {
id: 'footer.site-footer.reddit.title',
defaultMessage: 'Reddit',
description: 'Reddit button title',
},
'footer.site-footer.reddit.screen-reader-text': {
id: 'footer.site-footer.reddit.screen-reader-text',
defaultMessage: 'Subscribe to the {siteName} subreddit',
description: 'Reddit button screen reader text',
},
'footer.site-footer.apple-app-store.alt-text': {
id: 'footer.site-footer.apple-app-store.alt-text',
defaultMessage: 'Download the {siteName} mobile app from the Apple App Store',
description: 'Apple App Store button alt description',
},
'footer.site-footer.google-play.alt-text': {
id: 'footer.site-footer.google-play.alt-text',
defaultMessage: 'Download the {siteName} mobile app from Google Play',
description: 'Google Play button alt description',
},
'footer.site-footer.footer.aria-label': {
id: 'footer.site-footer.footer.aria-label',
defaultMessage: 'Page Footer',
description: 'Aria label for the footer',
},
});
export default messages;

View File

@@ -1,123 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';
import { IntlProvider } from 'react-intl';
import FooterLogo from '../../../edx-footer.png';
import SiteFooter, { EVENT_NAMES } from './index';
const completeSiteFooterComponent = mockHandleAllTrackEvents =>
(
<IntlProvider locale="en">
<SiteFooter
siteName="example"
siteLogo={FooterLogo}
marketingSiteBaseUrl="https://www.example.com"
supportUrl="https://www.example.com/support"
contactUrl="https://www.example.com/contact"
openSourceUrl="https://www.example.com/open"
termsOfServiceUrl="https://www.example.com/terms-of-service"
privacyPolicyUrl="https://www.example.com/privacy-policy"
facebookUrl="https://www.facebook.com"
twitterUrl="https://www.twitter.com"
youTubeUrl="https://www.youtube.com"
linkedInUrl="https://www.linkedin.com"
googlePlusUrl="https://plus.google.com"
redditUrl="https://reddit.com"
appleAppStoreUrl="https://store.apple.com"
googlePlayUrl="https://play.google.com"
handleAllTrackEvents={mockHandleAllTrackEvents}
/>
</IntlProvider>
);
describe('<SiteFooter />', () => {
describe('renders correctly', () => {
it('renders with social and mobile links', () => {
const mockHandleAllTrackEvents = jest.fn();
const tree = renderer
.create(completeSiteFooterComponent(mockHandleAllTrackEvents))
.toJSON();
expect(tree).toMatchSnapshot();
});
it('does not render social links', () => {
const tree = renderer
.create((
<IntlProvider locale="en">
<SiteFooter
siteName="example"
siteLogo={FooterLogo}
marketingSiteBaseUrl="https://www.example.com"
supportUrl="https://www.example.com/support"
contactUrl="https://www.example.com/contact"
openSourceUrl="https://www.example.com/open"
termsOfServiceUrl="https://www.example.com/terms-of-service"
privacyPolicyUrl="https://www.example.com/privacy-policy"
facebookUrl="https://www.facebook.com"
twitterUrl="https://www.twitter.com"
youTubeUrl="https://www.youtube.com"
linkedInUrl="https://www.linkedin.com"
googlePlusUrl="https://plus.google.com"
redditUrl="https://reddit.com"
appleAppStoreUrl="https://store.apple.com"
googlePlayUrl="https://play.google.com"
handleAllTrackEvents={jest.fn()}
showSocialLinks={false}
/>
</IntlProvider>
)).toJSON();
expect(tree).toMatchSnapshot();
});
it('does not render mobile links', () => {
const tree = renderer
.create((
<IntlProvider locale="en">
<SiteFooter
siteName="example"
siteLogo={FooterLogo}
marketingSiteBaseUrl="https://www.example.com"
supportUrl="https://www.example.com/support"
contactUrl="https://www.example.com/contact"
openSourceUrl="https://www.example.com/open"
termsOfServiceUrl="https://www.example.com/terms-of-service"
privacyPolicyUrl="https://www.example.com/privacy-policy"
facebookUrl="https://www.facebook.com"
twitterUrl="https://www.twitter.com"
youTubeUrl="https://www.youtube.com"
linkedInUrl="https://www.linkedin.com"
googlePlusUrl="https://plus.google.com"
redditUrl="https://reddit.com"
appleAppStoreUrl="https://store.apple.com"
googlePlayUrl="https://play.google.com"
handleAllTrackEvents={jest.fn()}
showMobileLinks={false}
/>
</IntlProvider>
)).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('handles analytics', () => {
it('calls handleAllTrackEvents prop when external links clicked', () => {
const mockHandleAllTrackEvents = jest.fn();
const footer = mount((completeSiteFooterComponent(mockHandleAllTrackEvents)));
const externalLinks = footer.find("a[target='_blank']");
expect(externalLinks).toHaveLength(8);
externalLinks.forEach((externalLink) => {
const callIndex = mockHandleAllTrackEvents.mock.calls.length;
externalLink.simulate('click');
expect(mockHandleAllTrackEvents.mock.calls[callIndex]).toEqual([
EVENT_NAMES.FOOTER_LINK,
{
category: 'outbound_link',
label: externalLink.prop('href'),
},
]);
});
});
});
});

View File

@@ -1,979 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SiteFooter /> renders correctly does not render mobile links 1`] = `
<footer
aria-label="Page Footer"
className="footer d-flex justify-content-center border-top py-3 px-4"
role="contentinfo"
>
<div
className="max-width-1180 d-grid"
>
<div
className="area-1"
>
<a
aria-label="example Home"
href="https://www.example.com/"
onClick={[Function]}
target="_self"
>
<img
alt="example logo"
src="test-file-stub"
/>
</a>
</div>
<div
className="area-2"
>
<h2>
example
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/about-us"
>
<span>
About
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/enterprise"
>
<span>
example for Business
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/affiliate-program"
>
<span>
Affiliates
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/open"
>
<span>
Open example
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/careers"
>
<span>
Careers
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/news-announcements"
>
<span>
News
</span>
</a>
</li>
</ul>
</div>
<div
className="area-3"
>
<h2>
<span>
Legal
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/terms-of-service"
>
<span>
Terms of Service & Honor Code
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/privacy-policy"
>
<span>
Privacy Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/accessibility"
>
<span>
Accessibility Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/trademarks"
>
<span>
Trademark Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/sitemap"
>
<span>
Sitemap
</span>
</a>
</li>
</ul>
</div>
<div
className="area-4"
>
<h2>
<span>
Connect
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/blog"
>
<span>
Blog
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/contact"
>
<span>
Contact Us
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/support"
>
<span>
Help Center
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/media-kit"
>
<span>
Media Kit
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/donate"
>
<span>
Donate
</span>
</a>
</li>
</ul>
</div>
<div
className="area-5"
>
<ul
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
>
<li>
<a
href="https://www.facebook.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Facebook"
>
<span
aria-hidden={true}
className="fa fa-facebook-square fa-2x"
id="edx-footer-icon-facebook"
/>
<span
className="sr-only"
>
Like example on Facebook
</span>
</a>
</li>
<li>
<a
href="https://www.twitter.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Twitter"
>
<span
aria-hidden={true}
className="fa fa-twitter-square fa-2x"
id="edx-footer-icon-twitter"
/>
<span
className="sr-only"
>
Follow example on Twitter
</span>
</a>
</li>
<li>
<a
href="https://www.youtube.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Youtube"
>
<span
aria-hidden={true}
className="fa fa-youtube-square fa-2x"
id="edx-footer-icon-youtube"
/>
<span
className="sr-only"
>
Subscribe to the example YouTube channel
</span>
</a>
</li>
<li>
<a
href="https://www.linkedin.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="LinkedIn"
>
<span
aria-hidden={true}
className="fa fa-linkedin-square fa-2x"
id="edx-footer-icon-linkedin"
/>
<span
className="sr-only"
>
Follow example on LinkedIn
</span>
</a>
</li>
<li>
<a
href="https://plus.google.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Google+"
>
<span
aria-hidden={true}
className="fa fa-google-plus-square fa-2x"
id="edx-footer-icon-google"
/>
<span
className="sr-only"
>
Follow example on Google+
</span>
</a>
</li>
<li>
<a
href="https://reddit.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Reddit"
>
<span
aria-hidden={true}
className="fa fa-reddit-square fa-2x"
id="edx-footer-icon-reddit"
/>
<span
className="sr-only"
>
Subscribe to the example subreddit
</span>
</a>
</li>
</ul>
<p>
<span>
© 20122019 example Inc.
</span>
<br />
<span>
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
</span>
</p>
</div>
</div>
</footer>
`;
exports[`<SiteFooter /> renders correctly does not render social links 1`] = `
<footer
aria-label="Page Footer"
className="footer d-flex justify-content-center border-top py-3 px-4"
role="contentinfo"
>
<div
className="max-width-1180 d-grid"
>
<div
className="area-1"
>
<a
aria-label="example Home"
href="https://www.example.com/"
onClick={[Function]}
target="_self"
>
<img
alt="example logo"
src="test-file-stub"
/>
</a>
</div>
<div
className="area-2"
>
<h2>
example
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/about-us"
>
<span>
About
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/enterprise"
>
<span>
example for Business
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/affiliate-program"
>
<span>
Affiliates
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/open"
>
<span>
Open example
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/careers"
>
<span>
Careers
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/news-announcements"
>
<span>
News
</span>
</a>
</li>
</ul>
</div>
<div
className="area-3"
>
<h2>
<span>
Legal
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/terms-of-service"
>
<span>
Terms of Service & Honor Code
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/privacy-policy"
>
<span>
Privacy Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/accessibility"
>
<span>
Accessibility Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/trademarks"
>
<span>
Trademark Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/sitemap"
>
<span>
Sitemap
</span>
</a>
</li>
</ul>
</div>
<div
className="area-4"
>
<h2>
<span>
Connect
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/blog"
>
<span>
Blog
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/contact"
>
<span>
Contact Us
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/support"
>
<span>
Help Center
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/media-kit"
>
<span>
Media Kit
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/donate"
>
<span>
Donate
</span>
</a>
</li>
</ul>
</div>
<div
className="area-5"
>
<ul
className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5"
>
<li>
<a
href="https://store.apple.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Download the example mobile app from the Apple App Store"
className="max-height-39"
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
/>
</a>
</li>
<li>
<a
href="https://play.google.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Download the example mobile app from Google Play"
className="max-height-39"
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
/>
</a>
</li>
</ul>
<p>
<span>
© 20122019 example Inc.
</span>
<br />
<span>
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
</span>
</p>
</div>
</div>
</footer>
`;
exports[`<SiteFooter /> renders correctly renders with social and mobile links 1`] = `
<footer
aria-label="Page Footer"
className="footer d-flex justify-content-center border-top py-3 px-4"
role="contentinfo"
>
<div
className="max-width-1180 d-grid"
>
<div
className="area-1"
>
<a
aria-label="example Home"
href="https://www.example.com/"
onClick={[Function]}
target="_self"
>
<img
alt="example logo"
src="test-file-stub"
/>
</a>
</div>
<div
className="area-2"
>
<h2>
example
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/about-us"
>
<span>
About
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/enterprise"
>
<span>
example for Business
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/affiliate-program"
>
<span>
Affiliates
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/open"
>
<span>
Open example
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/careers"
>
<span>
Careers
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/news-announcements"
>
<span>
News
</span>
</a>
</li>
</ul>
</div>
<div
className="area-3"
>
<h2>
<span>
Legal
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/terms-of-service"
>
<span>
Terms of Service & Honor Code
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/privacy-policy"
>
<span>
Privacy Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/accessibility"
>
<span>
Accessibility Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/trademarks"
>
<span>
Trademark Policy
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/sitemap"
>
<span>
Sitemap
</span>
</a>
</li>
</ul>
</div>
<div
className="area-4"
>
<h2>
<span>
Connect
</span>
</h2>
<ul
className="list-unstyled p-0 m-0"
>
<li>
<a
href="https://www.example.com/blog"
>
<span>
Blog
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/contact"
>
<span>
Contact Us
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/support"
>
<span>
Help Center
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/media-kit"
>
<span>
Media Kit
</span>
</a>
</li>
<li>
<a
href="https://www.example.com/donate"
>
<span>
Donate
</span>
</a>
</li>
</ul>
</div>
<div
className="area-5"
>
<ul
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
>
<li>
<a
href="https://www.facebook.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Facebook"
>
<span
aria-hidden={true}
className="fa fa-facebook-square fa-2x"
id="edx-footer-icon-facebook"
/>
<span
className="sr-only"
>
Like example on Facebook
</span>
</a>
</li>
<li>
<a
href="https://www.twitter.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Twitter"
>
<span
aria-hidden={true}
className="fa fa-twitter-square fa-2x"
id="edx-footer-icon-twitter"
/>
<span
className="sr-only"
>
Follow example on Twitter
</span>
</a>
</li>
<li>
<a
href="https://www.youtube.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Youtube"
>
<span
aria-hidden={true}
className="fa fa-youtube-square fa-2x"
id="edx-footer-icon-youtube"
/>
<span
className="sr-only"
>
Subscribe to the example YouTube channel
</span>
</a>
</li>
<li>
<a
href="https://www.linkedin.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="LinkedIn"
>
<span
aria-hidden={true}
className="fa fa-linkedin-square fa-2x"
id="edx-footer-icon-linkedin"
/>
<span
className="sr-only"
>
Follow example on LinkedIn
</span>
</a>
</li>
<li>
<a
href="https://plus.google.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Google+"
>
<span
aria-hidden={true}
className="fa fa-google-plus-square fa-2x"
id="edx-footer-icon-google"
/>
<span
className="sr-only"
>
Follow example on Google+
</span>
</a>
</li>
<li>
<a
href="https://reddit.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
title="Reddit"
>
<span
aria-hidden={true}
className="fa fa-reddit-square fa-2x"
id="edx-footer-icon-reddit"
/>
<span
className="sr-only"
>
Subscribe to the example subreddit
</span>
</a>
</li>
</ul>
<ul
className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5"
>
<li>
<a
href="https://store.apple.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Download the example mobile app from the Apple App Store"
className="max-height-39"
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
/>
</a>
</li>
<li>
<a
href="https://play.google.com"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Download the example mobile app from Google Play"
className="max-height-39"
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
/>
</a>
</li>
</ul>
<p>
<span>
© 20122019 example Inc.
</span>
<br />
<span>
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
</span>
</p>
</div>
</div>
</footer>
`;

View File

@@ -1,479 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Hyperlink, Icon } from '@edx/paragon';
import messages from './SiteFooter.messages';
const EVENT_NAMES = {
FOOTER_LINK: 'edx.bi.footer.link',
};
class SiteFooter extends React.Component {
constructor(props) {
super(props);
this.externalLinkClickHandler = this.externalLinkClickHandler.bind(this);
}
externalLinkClickHandler(event) {
const label = event.currentTarget.getAttribute('href');
const eventName = EVENT_NAMES.FOOTER_LINK;
const properties = {
category: 'outbound_link',
label,
};
this.props.handleAllTrackEvents(eventName, properties);
}
renderSiteLogo() {
return (
<img
src={this.props.siteLogo}
alt={this.props.intl.formatMessage(
messages['footer.site-footer.site-logo.alt-text'],
{ siteName: this.props.siteName },
)}
/>
);
}
renderMarketingSiteUrl(path) {
return `${this.props.marketingSiteBaseUrl}${path}`;
}
renderSocialLinks() {
const {
intl,
siteName,
showSocialLinks,
facebookUrl,
twitterUrl,
youTubeUrl,
linkedInUrl,
googlePlusUrl,
redditUrl,
} = this.props;
let socialLinks = null;
if (showSocialLinks) {
socialLinks = (
<ul
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
>
{/* TODO: Use Paragon HyperLink with Icon. */}
{/* Would need to add rel to paragon if we still need it. */}
<li>
<a
href={facebookUrl}
title={intl.formatMessage(messages['footer.site-footer.facebook.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-facebook"
className={['fa', 'fa-facebook-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.facebook.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
<li>
<a
href={twitterUrl}
title={intl.formatMessage(messages['footer.site-footer.twitter.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-twitter"
className={['fa', 'fa-twitter-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.twitter.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
<li>
<a
href={youTubeUrl}
title={intl.formatMessage(messages['footer.site-footer.youtube.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-youtube"
className={['fa', 'fa-youtube-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.youtube.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
<li>
<a
href={linkedInUrl}
title={intl.formatMessage(messages['footer.site-footer.linkedin.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-linkedin"
className={['fa', 'fa-linkedin-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.linkedin.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
<li>
<a
href={googlePlusUrl}
title={intl.formatMessage(messages['footer.site-footer.google-plus.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-google"
className={['fa', 'fa-google-plus-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.google-plus.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
<li>
<a
href={redditUrl}
title={intl.formatMessage(messages['footer.site-footer.reddit.title'])}
rel="noopener noreferrer"
target="_blank"
onClick={this.externalLinkClickHandler}
>
<Icon
id="edx-footer-icon-reddit"
className={['fa', 'fa-reddit-square', 'fa-2x']}
screenReaderText={intl.formatMessage(
messages['footer.site-footer.reddit.screen-reader-text'],
{ siteName },
)}
/>
</a>
</li>
</ul>
);
}
return socialLinks;
}
renderMobileLinks() {
const {
intl,
siteName,
showMobileLinks,
appleAppStoreUrl,
googlePlayUrl,
} = this.props;
let mobileLinks = null;
if (showMobileLinks) {
mobileLinks = (
<ul className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5">
<li>
<a href={appleAppStoreUrl} rel="noopener noreferrer" target="_blank" onClick={this.externalLinkClickHandler}>
<img
className="max-height-39"
alt={intl.formatMessage(
messages['footer.site-footer.apple-app-store.alt-text'],
{ siteName },
)}
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
/>
</a>
</li>
<li>
<a href={googlePlayUrl} rel="noopener noreferrer" target="_blank" onClick={this.externalLinkClickHandler}>
<img
className="max-height-39"
alt={intl.formatMessage(
messages['footer.site-footer.google-play.alt-text'],
{ siteName },
)}
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
/>
</a>
</li>
</ul>
);
}
return mobileLinks;
}
render() {
const {
intl,
siteName,
openSourceUrl,
termsOfServiceUrl,
privacyPolicyUrl,
contactUrl,
supportUrl,
} = this.props;
return (
<footer
role="contentinfo"
aria-label={intl.formatMessage(messages['footer.site-footer.footer.aria-label'])}
className="footer d-flex justify-content-center border-top py-3 px-4"
>
<div className="max-width-1180 d-grid">
<div className="area-1">
<Hyperlink
destination={this.renderMarketingSiteUrl('/')}
content={this.renderSiteLogo()}
aria-label={intl.formatMessage(
messages['footer.site-footer.site-logo.aria-label'],
{ siteName },
)}
/>
</div>
<div className="area-2">
<h2>{siteName}</h2>
<ul className="list-unstyled p-0 m-0">
<li>
<a href={this.renderMarketingSiteUrl('/about-us')}>
<FormattedMessage
id="footer.site-footer.link.about"
defaultMessage="About"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/enterprise')}>
<FormattedMessage
id="footer.site-footer.link.business"
defaultMessage="{siteName} for Business"
values={{ siteName }}
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/affiliate-program')}>
<FormattedMessage
id="footer.site-footer.link.affiliates"
defaultMessage="Affiliates"
/>
</a>
</li>
<li>
<a href={openSourceUrl}>
<FormattedMessage
id="footer.site-footer.link.open-source"
defaultMessage="Open {siteName}"
values={{ siteName }}
description="Open Source link text"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/careers')}>
<FormattedMessage
id="footer.site-footer.link.careers"
defaultMessage="Careers"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/news-announcements')}>
<FormattedMessage
id="footer.site-footer.link.news"
defaultMessage="News"
/>
</a>
</li>
</ul>
</div>
<div className="area-3">
<h2>
<FormattedMessage
id="footer.site-footer.link.header.legal"
defaultMessage="Legal"
description="Header for legal links"
/>
</h2>
<ul className="list-unstyled p-0 m-0">
<li>
<a href={termsOfServiceUrl}>
<FormattedMessage
id="footer.site-footer.link.terms-of-service"
defaultMessage="Terms of Service & Honor Code"
/>
</a>
</li>
<li>
<a href={privacyPolicyUrl}>
<FormattedMessage
id="footer.site-footer.link.privacy"
defaultMessage="Privacy Policy"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/accessibility')}>
<FormattedMessage
id="footer.site-footer.link.accessibility"
defaultMessage="Accessibility Policy"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/trademarks')}>
<FormattedMessage
id="footer.site-footer.link.trademark"
defaultMessage="Trademark Policy"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/sitemap')}>
<FormattedMessage
id="footer.site-footer.link.sitemap"
defaultMessage="Sitemap"
/>
</a>
</li>
</ul>
</div>
<div className="area-4">
<h2>
<FormattedMessage
id="footer.site-footer.link.header.connect"
defaultMessage="Connect"
description="Header for connect links"
/>
</h2>
<ul className="list-unstyled p-0 m-0">
<li>
<a href={this.renderMarketingSiteUrl('/blog')}>
<FormattedMessage
id="footer.site-footer.link.blog"
defaultMessage="Blog"
/>
</a>
</li>
<li>
<a href={contactUrl}>
<FormattedMessage
id="footer.site-footer.link.contact-us"
defaultMessage="Contact Us"
/>
</a>
</li>
<li>
<a href={supportUrl}>
<FormattedMessage
id="footer.site-footer.link.help-center"
defaultMessage="Help Center"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/media-kit')}>
<FormattedMessage
id="footer.site-footer.link.media-kit"
defaultMessage="Media Kit"
/>
</a>
</li>
<li>
<a href={this.renderMarketingSiteUrl('/donate')}>
<FormattedMessage
id="footer.site-footer.link.donate"
defaultMessage="Donate"
/>
</a>
</li>
</ul>
</div>
<div className="area-5">
{this.renderSocialLinks()}
{this.renderMobileLinks()}
<p>
<FormattedMessage
id="footer.site-footer.copyright-text"
defaultMessage="{copyrightSymbol} {startDate}{endDate} {siteName} Inc."
values={{
copyrightSymbol: '©',
startDate: '2012',
endDate: `${new Date().getFullYear()}`,
siteName,
}}
description="Footer copyright text with copyright symbol and dates"
/>
<br />
<FormattedMessage
id="footer.site-footer.trademark-text"
defaultMessage="EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | {icpLicense}"
values={{ icpLicense: '粤ICP备17044299号-2' }}
description="Footer trademark text"
/>
</p>
</div>
</div>
</footer>
);
}
}
SiteFooter.propTypes = {
siteName: PropTypes.string,
siteLogo: PropTypes.node,
marketingSiteBaseUrl: PropTypes.string,
supportUrl: PropTypes.string,
contactUrl: PropTypes.string,
openSourceUrl: PropTypes.string,
termsOfServiceUrl: PropTypes.string,
privacyPolicyUrl: PropTypes.string,
showSocialLinks: PropTypes.bool,
facebookUrl: PropTypes.string,
twitterUrl: PropTypes.string,
youTubeUrl: PropTypes.string,
linkedInUrl: PropTypes.string,
googlePlusUrl: PropTypes.string,
redditUrl: PropTypes.string,
showMobileLinks: PropTypes.bool,
appleAppStoreUrl: PropTypes.string,
googlePlayUrl: PropTypes.string,
handleAllTrackEvents: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};
SiteFooter.defaultProps = {
siteName: null,
siteLogo: null,
marketingSiteBaseUrl: null,
supportUrl: null,
contactUrl: null,
openSourceUrl: null,
termsOfServiceUrl: null,
privacyPolicyUrl: null,
showSocialLinks: true,
facebookUrl: null,
twitterUrl: null,
youTubeUrl: null,
linkedInUrl: null,
googlePlusUrl: null,
redditUrl: null,
showMobileLinks: true,
appleAppStoreUrl: null,
googlePlayUrl: null,
};
export default injectIntl(SiteFooter);
export { EVENT_NAMES };

View File

@@ -1,3 +0,0 @@
import SiteFooter from './components/SiteFooter';
export default SiteFooter;

View File

@@ -1,165 +0,0 @@
.max-width-222 {
max-width: 222px;
}
.max-width-264 {
max-width: 264px;
}
.max-width-1180 {
max-width: 1180px;
}
.max-height-39 {
max-height: 39px;
}
.d-grid {
display: grid;
}
$gray-footer: #fcfcfc;
$border-1: 1px solid $gray-200;
.footer {
background-color: $gray-footer;
.area-1 {
grid-column: 1;
grid-row: 1;
border-bottom: $border-1;
padding-bottom: 1rem;
}
.area-2 {
grid-column: 1;
grid-row: 2;
border-bottom: $border-1;
padding: 1rem 0;
}
.area-3 {
grid-column: 1;
grid-row: 3;
border-bottom: $border-1;
padding: 1rem 0;
}
.area-4 {
grid-column: 1;
grid-row: 4;
border-bottom: $border-1;
padding: 1rem 0;
}
.area-5 {
grid-column: 1;
grid-row: 5;
padding: 1rem 0;
}
@media only screen and (min-width: 717px) {
.area-1 {
grid-column: 1 / span 2;
grid-row: 1;
border-bottom: none;
padding: 1rem 0;
}
.area-2 {
grid-column: 1;
grid-row: 2;
}
.area-3 {
grid-column: 1;
grid-row: 3;
}
.area-4 {
grid-column: 1;
grid-row: 4;
border-bottom: none;
}
.area-5 {
grid-column: 2;
grid-row: 2 / span 3;
border-left: $border-1;
padding-left: 1rem;
margin-left: 1rem;
}
}
@media only screen and (min-width: 870px) {
.area-1 {
grid-column: 1;
grid-row: 1 / span 3;
border-right: $border-1;
padding-right: 1rem;
margin-right: 1rem;
}
.area-2 {
grid-column: 2;
grid-row: 1;
border-bottom: none;
border-right: $border-1;
padding-right: 1rem;
margin-right: 1rem;
}
.area-3 {
grid-column: 3;
grid-row: 1;
border-bottom: none;
border-right: $border-1;
padding-right: 1rem;
margin-right: 1rem;
}
.area-4 {
grid-column: 4;
grid-row: 1;
}
.area-5 {
grid-column: 2 / span 3;
grid-row: 2;
border: none;
margin-left: 0;
padding-left: 0;
}
}
@media only screen and (min-width: 1188px) {
.area-1 {
grid-column: 1 / span 1;
grid-row: 1;
}
.area-2 {
grid-column: 2;
grid-row: 1;
}
.area-3 {
grid-column: 3;
grid-row: 1;
}
.area-4 {
grid-column: 4;
grid-row: 1;
border-right: $border-1;
padding-right: 1rem;
margin-right: 1rem;
}
.area-5 {
grid-column: 5 / span 1;
grid-row: 1;
max-width: 372px;
}
}
}

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

@@ -0,0 +1,10 @@
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: '/',
},
});