Compare commits

...

315 Commits

Author SHA1 Message Date
Brian Smith
8d46de8fe3 feat!: remove Paragon 21 support (#578)
BREAKING CHANGE: consumers must now use Paragon 22
2025-03-17 16:00:03 -04:00
renovate[bot]
8341f17d46 chore(deps): update dependency @openedx/paragon to v22.16.0 (#577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 05:55:14 +00:00
renovate[bot]
d7c3e5a687 chore(deps): update dependency @edx/frontend-platform to v8.3.1 (#576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 05:59:56 +00:00
renovate[bot]
07b1c5bde1 chore(deps): update dependency @openedx/paragon to v22.15.3 (#572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 05:29:00 +00:00
renovate[bot]
5512faa9b0 chore(deps): update dependency @edx/frontend-platform to v8.2.1 (#570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 05:38:39 +00:00
Kyle McCormick
48c49fe0b2 revert: fix: Remove Studio Maintenance link (#565)
This reverts commit a229c34535.

We are temporarily re-introducing the Maintenance link, as the Maintenance
Announcements tool is still in use, as discussed on:
https://github.com/openedx/edx-platform/pull/35852

For more details, see the related edx-platform revert:
https://github.com/openedx/edx-platform/pull/36107

In the future, this will be re-removed:
https://github.com/openedx/edx-platform/issues/36263
2025-02-19 14:25:06 -05:00
renovate[bot]
8c7778218b chore(deps): update dependency @edx/browserslist-config to v1.5.0 (#569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 06:14:04 +00:00
Feanil Patel
0dedbbd589 docs: Document the owner and drop the old metadata file.
This is going to be changing soon with the module federation work so I
think it makes sense to be maintained by the committers-frontend group.

I'm also cleaning up the old openedx.yaml file which is obsolete and out
of date while I'm adding the new metadata that should be up-to-date.
2025-02-14 16:55:15 -03:00
renovate[bot]
ef0b101fea chore(deps): update dependency @edx/frontend-platform to v8.1.5 (#566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 08:48:36 +00:00
renovate[bot]
edb22316b8 chore(deps): update dependency @openedx/paragon to v22.13.0 (#564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 06:16:38 +00:00
renovate[bot]
227a97afa1 chore(deps): update dependency @edx/browserslist-config to v1.4.0 (#563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 05:11:57 +00:00
renovate[bot]
d01486e5f7 chore(deps): update dependency react-router-dom to v6.28.1 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 06:17:00 +00:00
renovate[bot]
a58f1eaf19 chore(deps): update dependency @edx/frontend-platform to v8.1.3 (#561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 06:24:09 +00:00
Brian Smith
a5024c3fde fix: move overflow: hidden to address mixed-decls warning (#549)
https://sass-lang.com/documentation/breaking-changes/mixed-decls/
2024-12-09 12:20:38 -05:00
renovate[bot]
d7be18e717 chore(deps): update dependency @openedx/frontend-build to v14.2.2 (#559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 06:44:08 +00:00
renovate[bot]
5e405da37e fix(deps): update dependency @openedx/frontend-plugin-framework to v1.4.1 (#558)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 07:19:05 +00:00
renovate[bot]
901f39f42c chore(deps): update dependency @openedx/frontend-build to v14.2.0 (#557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 06:30:07 +00:00
Brian Smith
346a636b76 feat: add pluginProps to CourseInfoSlot (#550) 2024-11-18 14:22:32 -05:00
renovate[bot]
34dcc88880 chore(deps): update dependency @openedx/paragon to v22.10.0 (#554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 05:02:52 +00:00
Kyle McCormick
a229c34535 fix: Remove Studio Maintenance link (#553)
This Studio Maintenance app has been broken for a long time,
so it is being removed from edx-platform:
https://github.com/openedx/edx-platform/pull/35852
2024-11-15 10:52:36 -05:00
renovate[bot]
5d7b4fecf4 chore(deps): update dependency react-router-dom to v6.28.0 (#548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 07:42:32 +00:00
renovate[bot]
f04130a7c6 fix(deps): update dependency @openedx/frontend-plugin-framework to v1.4.0 2024-11-04 07:18:59 +00:00
Peter Kulko
cb7774b325 feat: improved SPA routes 2024-11-01 13:03:59 -03:00
Bilal Qamar
3e4eb21d8c test: Remove support for Node 18 (#536) 2024-10-31 16:05:16 -04:00
Brian Smith
a346dccd4c feat: add frontend-plugin-framework slots (#545)
Add the following `frontend-plugin-framework` slots:
* `logo_slot`
* `desktop_main_menu_slot`
* `desktop_secondary_menu_slot`
* `mobile_main_menu_slot`
* `course_info_slot`
* `learning_help_slot`
* `desktop_logged_out_items_slot`
* `mobile_logged_out_items_slot`
* `mobile_user_menu_slot`
* `desktop_user_menu_slot`
* `learning_user_menu_slot`
* `learning_logged_out_items_slot`
* `desktop_header_slot`
2024-10-22 12:18:11 -04:00
renovate[bot]
c64a201072 chore(deps): update dependency @openedx/paragon to v22.9.0 2024-10-21 04:45:01 +00:00
renovate[bot]
6496642643 chore(deps): update dependency react-router-dom to v6.27.0 2024-10-14 04:43:06 +00:00
renovate[bot]
a6c36654b4 chore(deps): update dependency @edx/frontend-platform to v8.1.2 2024-10-07 07:34:11 +00:00
Rômulo Penido
ae5253c822 feat: expose containerProps in StudioHeader [FC-0062] (#529) 2024-10-01 09:20:34 -04:00
renovate[bot]
e44001e945 chore(deps): update dependency @openedx/paragon to v22.8.1 2024-09-30 10:31:20 +00:00
renovate[bot]
e07cf665a4 chore(deps): update dependency @openedx/frontend-build to v14.1.5 2024-09-30 06:36:53 +00:00
Brian Smith
8213ee7460 feat: add frontend-plugin-framework LogoSlot (#528) 2024-09-26 16:07:41 -04:00
renovate[bot]
8a7d6eecdf chore(deps): update dependency react-router-dom to v6.26.2 2024-09-23 07:57:54 +00:00
renovate[bot]
a2497eeb22 chore(deps): update dependency @openedx/frontend-build to v14.1.4 2024-09-23 07:57:36 +00:00
Bilal Qamar
a703abad76 build: Upgrade to Node 20 (#535) 2024-09-19 17:50:06 -04:00
Bilal Qamar
3f4d987d12 test: Add Node 20 to CI matrix (#533) 2024-09-16 11:42:21 -04:00
renovate[bot]
45e551ea44 chore(deps): update dependency @openedx/frontend-build to v14.1.2 2024-09-09 06:27:07 +00:00
renovate[bot]
a0d7fd7cf2 chore(deps): update dependency @openedx/frontend-build to v14.1.1 2024-09-02 07:12:22 +00:00
renovate[bot]
97d1bdedfb chore(deps): update dependency @openedx/frontend-build to v14.1.0 2024-08-19 05:29:18 +00:00
renovate[bot]
4351a09c9f chore(deps): update dependency react-router-dom to v6.26.1 2024-08-19 05:29:07 +00:00
renovate[bot]
9647a74507 chore(deps): update dependency @openedx/frontend-build to v14.0.15 2024-08-12 06:28:28 +00:00
renovate[bot]
9cef77349f chore(deps): update dependency react-router-dom to v6.26.0 2024-08-05 04:27:40 +00:00
renovate[bot]
ad7c42bcf9 chore(deps): update dependency @testing-library/dom to v10.4.0 2024-07-29 07:46:13 +00:00
renovate[bot]
266386fe24 chore(deps): update dependency @openedx/frontend-build to v14.0.14 2024-07-29 04:31:36 +00:00
renovate[bot]
f42ee37e16 chore(deps): update dependency @edx/frontend-platform to v8.1.1 2024-07-29 04:31:28 +00:00
renovate[bot]
354f9fdc38 fix(deps): update font awesome to v6.6.0 2024-07-22 07:37:43 +00:00
renovate[bot]
85b07acfb5 chore(deps): update dependency react-router-dom to v6.25.1 2024-07-22 07:37:34 +00:00
renovate[bot]
3e647f7394 chore(deps): update dependency @testing-library/dom to v10.3.2 2024-07-22 07:37:20 +00:00
renovate[bot]
848b0f37b9 chore(deps): update dependency @openedx/paragon to v22.7.0 2024-07-22 04:22:28 +00:00
renovate[bot]
818b3800aa chore(deps): update dependency @edx/frontend-platform to v8.1.0 2024-07-15 07:42:56 +00:00
renovate[bot]
63e47bc45a chore(deps): update dependency react-router-dom to v6.24.1 2024-07-15 07:42:35 +00:00
renovate[bot]
7ba5371f69 chore(deps): update dependency react-router-dom to v6.24.0 2024-07-01 04:14:48 +00:00
renovate[bot]
9f0c286897 chore(deps): update dependency @testing-library/dom to v10.2.0 2024-07-01 04:14:36 +00:00
renovate[bot]
4cc5b91d6d chore(deps): update dependency @openedx/paragon to v22.6.1 2024-06-24 08:01:58 +00:00
renovate[bot]
3d75a72f0c chore(deps): update dependency @openedx/frontend-build to v14.0.10 2024-06-24 04:49:34 +00:00
Adolfo R. Brandes
0541dc194e build: Update codecov and use token
Update codecov to the latest version and start using the org-wide token for uploads.

See https://github.com/openedx/wg-frontend/issues/179
2024-06-17 11:59:43 -03:00
renovate[bot]
b92127fd12 chore(deps): update dependency @openedx/frontend-build to v14.0.9 2024-06-17 04:24:47 +00:00
renovate[bot]
b2b9f3fa00 chore(deps): update dependency @openedx/paragon to v22.6.0 2024-06-03 07:27:47 +00:00
renovate[bot]
b9b6282b4b chore(deps): update dependency @edx/frontend-platform to v8.0.4 2024-06-03 07:27:20 +00:00
renovate[bot]
8606585978 chore(deps): update dependency @testing-library/dom to v10.1.0 2024-05-27 07:11:48 +00:00
renovate[bot]
159072779f chore(deps): update dependency @openedx/paragon to v22.5.1 2024-05-27 04:16:29 +00:00
renovate[bot]
de843d330d fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.2 2024-05-27 04:15:58 +00:00
renovate[bot]
d554de89ca fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.1 2024-05-20 07:06:56 +00:00
renovate[bot]
72be96c230 chore(deps): update dependency @edx/frontend-platform to v8.0.3 2024-05-20 07:06:23 +00:00
renovate[bot]
564f34a7c6 chore(deps): update dependency react-router-dom to v6.23.1 2024-05-13 06:55:33 +00:00
renovate[bot]
ab15b3d2bf chore(deps): update dependency @edx/frontend-platform to v8.0.2 2024-05-13 06:55:03 +00:00
Ahtesham Quraish
59db41c61e fix: provide onClick feature
Provide onClick facility for all the kind of menus
VAN-1914
2024-05-07 15:55:57 +01:00
renovate[bot]
0415c00353 chore(deps): update dependency react-router-dom to v6.23.0 2024-04-29 04:22:05 +00:00
renovate[bot]
9d01c074e0 chore(deps): update dependency @openedx/paragon to v22.3.1 2024-04-29 04:21:31 +00:00
Bilal Qamar
83c5b0258f feat: Updated frontend-build to bump jest version to v29 (#369)
* refactor: updated frontend-build to bump jest version to v29

* refactor: resolved failing tests

* refactor: updated frontend-build & failing snapshots

* refactor: updated snapshots

* refactor: updated frontend-build to alpha, updated snapshots

* fix: fixed peer deps issues

* refactor: updated frontend-build to the version which has eslint alpha synced with master

* refactor: updated eslintrc

* refactor: updated ci workflow

* feat: updated frontend-build and frontend-platform major versions

* refactor: updated package-lock

* refactor: updated peerDependency as per suggestion

---------

Co-authored-by: Muhammad Abdullah Waheed <abdullah.waheed@arbisoft.com>
2024-04-23 13:57:10 +05:00
renovate[bot]
45246ad5ee chore(deps): update dependency @openedx/paragon to v22.2.2 2024-04-22 05:14:02 +00:00
renovate[bot]
487b2590bd chore(deps): update dependency @edx/frontend-platform to v7.1.4 2024-04-22 05:13:44 +00:00
Syed Sajjad Hussain Shah
6cab3f3f3e Merge pull request #466 from openedx/sajjad/VAN-1823-custom-header
feat: enable header to accept custom menus
2024-04-19 11:36:03 +05:00
renovate[bot]
e3c8ec027e chore(deps): update dependency @testing-library/dom to v10 2024-04-15 07:39:31 +00:00
renovate[bot]
1e899c1c48 chore(deps): update dependency react-router-dom to v6.22.3 2024-04-15 04:27:37 +00:00
renovate[bot]
370b193df3 fix(deps): update font awesome to v6.5.2 2024-04-08 06:51:52 +00:00
renovate[bot]
58c34abd66 chore(deps): update dependency @openedx/frontend-build to v13.1.4 2024-04-08 06:51:23 +00:00
Rômulo Penido
c9942c1552 feat: add search menu button to header (#474) 2024-04-03 14:36:39 -04:00
renovate[bot]
432dbb5e6b chore(deps): update dependency @openedx/paragon to v22.2.1 2024-04-01 07:23:00 +00:00
renovate[bot]
02748fab13 chore(deps): update dependency @edx/brand to v1.2.3 2024-04-01 07:22:25 +00:00
renovate[bot]
3a5506c646 chore(deps): update dependency @openedx/frontend-build to v13.1.0 2024-04-01 07:18:24 +00:00
renovate[bot]
10619ceb5e chore(deps): update dependency @openedx/paragon to v22.1.1 2024-03-25 15:49:02 +00:00
renovate[bot]
2694492a7c chore(deps): update dependency @edx/frontend-platform to v7.1.3 2024-03-25 13:34:02 +00:00
renovate[bot]
be7d0d97e4 chore(deps): update dependency @openedx/frontend-build to v13.0.29 2024-03-25 10:26:14 +00:00
Syed Sajjad Hussain Shah
e6aa4be4f6 fix: update docs 2024-03-25 12:49:25 +05:00
Syed Sajjad Hussain Shah
f2c236c828 fix: remove logo URL 2024-03-25 12:49:25 +05:00
Syed Sajjad Hussain Shah
db912e6dae feat: enable header to accept custom menus 2024-03-25 12:49:25 +05:00
Samir Sabri
abb08be08e feat!: remove Transifex calls for OEP-58 (#416) 2024-03-22 13:08:10 -04:00
Bilal Qamar
6c6ccc7f20 chore(deps): update frontend-platform to v7 (#469)
* chore(deps): update frontend-platform to v7

* refactor: updated eslintrc
2024-03-22 11:34:40 +05:00
Adolfo R. Brandes
2494ad2b57 build: start releasing alpha packages 2024-03-20 13:09:30 -03:00
Jenkins
3b2a2bfa95 chore(i18n): update translations 2024-01-28 15:31:28 -05:00
renovate[bot]
30b91791e3 chore(deps): update dependency react-router-dom to v6.21.3 2024-01-22 09:41:11 +00:00
Brian Smith
08592aeec7 fix: update paragon dep to support v22 (#460) 2024-01-19 11:30:44 -05:00
Brian Smith
c1d143ace2 fix: update frontend-platform peer dependency (#459)
This updates the `frontend-platform` peer dependency to require a version that has paragon in the `openedx` scope as a peer dependency, as opposed to the `edx` scope
2024-01-18 12:59:14 -05:00
Brian Smith
54a879aec2 chore!: move paragon to peer dependency using @openedx scope (#456)
BREAKING CHANGE: consuming applications must now provide `paragon` from the `@openedx` scope
2024-01-16 11:06:27 -05:00
renovate[bot]
b5b37f1d64 chore(deps): update dependency @testing-library/dom to v9.3.4 2024-01-15 13:19:09 +00:00
renovate[bot]
36be99ace0 chore(deps): update dependency @openedx/frontend-build to v13.0.27 2024-01-15 11:42:22 +00:00
dependabot[bot]
222bc19bd0 chore(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#454)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-11 17:14:11 +05:00
renovate[bot]
3d827e64ea chore(deps): update dependency redux-saga to v1.3.0 (#453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-08 15:40:28 +05:00
renovate[bot]
8a247abd6a fix(deps): update dependency @openedx/paragon to v21.13.0 2024-01-01 12:25:49 +00:00
renovate[bot]
a6943fbaeb chore(deps): update dependency @edx/frontend-platform to v6.2.0 2024-01-01 10:17:53 +00:00
Mashal Malik
19292cd5b6 refactor: replace @edx/paragon with @openedx/paragon (#446)
* refactor: replace @edx/paragon with @openedx/paragon

* fix: replaced frontend-build to @openedx to fix jest issues

* refactor: removed old unused package

---------

Co-authored-by: Abdullah Waheed <abdullah.waheed@arbisoft.com>
2024-01-01 13:08:15 +05:00
Syed Ali Abbas Zaidi
b6374a5c05 feat: migrate enzyme with RTL (#439)
Co-authored-by: Abdullah Waheed <abdullah.waheed@arbisoft.com>
2023-12-25 16:23:33 +05:00
renovate[bot]
f6a4036b49 fix(deps): update font awesome to v6.5.1 2023-12-25 09:29:05 +00:00
renovate[bot]
12a845ad33 chore(deps): update dependency react-router-dom to v6.21.1 2023-12-25 09:07:12 +00:00
renovate[bot]
a4a7456726 chore(deps): update dependency react-router-dom to v6.21.0 2023-12-18 13:48:34 +00:00
renovate[bot]
6fdf73fed3 chore(deps): update dependency @edx/frontend-build to v13.0.14 2023-12-18 10:46:30 +00:00
renovate[bot]
32b8079744 chore(deps): update dependency @edx/frontend-platform to v6.1.2 2023-12-11 12:36:07 +00:00
renovate[bot]
b9f7fe74c8 chore(deps): update dependency @edx/frontend-build to v13.0.12 2023-12-11 10:39:07 +00:00
dependabot[bot]
5a259a76df chore(deps-dev): bump @adobe/css-tools from 4.3.1 to 4.3.2 (#440)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 13:10:03 +05:00
Jenkins
d2ad5ee2a4 chore(i18n): update translations 2023-12-03 15:31:05 -05:00
renovate[bot]
67fefe814b chore(deps): update dependency react-router-dom to v6.20.0 2023-11-27 11:37:21 +00:00
renovate[bot]
9a5b1fa5e7 chore(deps): update dependency react-router-dom to v6.19.0 2023-11-20 13:25:44 +00:00
renovate[bot]
55f21aeeaa chore(deps): update dependency @edx/frontend-build to v13.0.8 2023-11-20 09:19:45 +00:00
Jenkins
fc24e61a1c chore(i18n): update translations 2023-11-19 15:31:00 -05:00
Kristin Aoki
bace8286fd fix: studio dropdown hover styles (#433) 2023-11-16 11:18:00 -05:00
renovate[bot]
665653e9a5 chore(deps): update actions/setup-node action to v4 (#429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-16 11:46:31 +05:00
Bilal Qamar
0cc2282c44 feat: bumped frontend-platform version (#432) 2023-11-15 17:21:06 +05:00
Bilal Qamar
455ffd345c Revert "chore: bumped frontend-platform version (#430)" (#431)
This reverts commit dde02a0739.
2023-11-15 17:11:01 +05:00
Bilal Qamar
dde02a0739 chore: bumped frontend-platform version (#430) 2023-11-15 16:51:34 +05:00
renovate[bot]
81cb72f10b chore(deps): update dependency @edx/frontend-build to v13.0.5 2023-11-13 10:40:46 +00:00
Kristin Aoki
e285a91408 fix: admin home link in user menu (#427) 2023-11-07 12:17:41 -05:00
Kristin Aoki
50142adb85 Merge pull request #426 from openedx/KristinAoki/fix-broken-studio-home-link
fix: broken studio home link
2023-11-06 10:17:33 -05:00
KristinAoki
7281804fbd fix: broken studio home link 2023-11-06 09:13:01 -05:00
renovate[bot]
b446534992 chore(deps): update dependency react-router-dom to v6.18.0 2023-11-06 12:07:03 +00:00
renovate[bot]
3adc305aec chore(deps): update dependency @edx/frontend-build to v13.0.4 2023-11-06 11:36:45 +00:00
Jenkins
65446ce9c3 chore(i18n): update translations 2023-11-05 15:32:11 -05:00
renovate[bot]
f3637b5624 fix(deps): update dependency axios-mock-adapter to v1.22.0 2023-10-30 12:47:59 +00:00
Muhammad Abdullah Waheed
3c1d2152aa feat: babel-plugin-react-intl to babel-plugin-formatjs migration (#404)
* feat: babel-plugin-react-intl to babel-plugin-formatjs migration

* fix: upgraded frontend-build to fix security issue

* fix: upgraded frontend-build to fix security issue

* fix: upgraded frontend build again

* refactor: upgraded to latest frontend-build and resolved vulnerabilities
2023-10-30 15:17:10 +05:00
renovate[bot]
48b22ea41e chore(deps): update dependency react-router-dom to v6.17.0 2023-10-30 10:09:57 +00:00
renovate[bot]
ed76e40862 fix(deps): update dependency @edx/paragon to v21.5.6 2023-10-30 07:33:58 +00:00
Bilal Qamar
d5b07cc38f chore: bumped frontend-platform version (#417) 2023-10-24 13:54:00 +05:00
Feanil Patel
b9509bb890 chore: Update to the new version of brand-openedx in the new scope. (#414)
Part of https://github.com/openedx/axim-engineering/issues/23

This updates the `@edx/brand` alias to point to the `brand-openedx` package at
the `openedx` scope. This does not impact imports because this package is used
via an alias.
2023-10-20 17:18:23 -04:00
dependabot[bot]
c87a1049c8 chore(deps-dev): bump @babel/traverse from 7.22.5 to 7.23.2 (#413)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.5 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 16:52:03 +05:00
Bilal Qamar
e0c22e781d feat: bumped frontend-platform to v6 (#412) 2023-10-18 16:05:45 +05:00
renovate[bot]
f1605d1f27 fix(deps): update dependency @edx/paragon to v21.5.3 2023-10-16 13:15:31 +00:00
renovate[bot]
5d7826c26c chore(deps): update dependency @edx/frontend-platform to v5.5.4 2023-10-16 09:30:30 +00:00
renovate[bot]
5d075b0cdc chore(deps): update actions/checkout action to v4 (#395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-12 12:10:30 +05:00
renovate[bot]
1a86685f11 fix(deps): update dependency @edx/paragon to v21.3.1 2023-10-09 11:14:33 +00:00
renovate[bot]
e31597509c chore(deps): update dependency @edx/frontend-platform to v5.5.1 2023-10-09 11:11:49 +00:00
Jenkins
c79c137fd6 chore(i18n): update translations 2023-10-08 16:31:21 -04:00
Mashal Malik
76f735ed39 refactor: updated README file to reflect template changes (#380)
* refactor: update README File

* refactor: update README file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: update readMe file

* refactor: replace npm install with npm ci
2023-10-04 12:29:28 +05:00
Kristin Aoki
2298791aeb feat: update studio header to be more accessible 2023-10-02 11:37:48 -04:00
KristinAoki
9f48ccc66b feat: update studio header to be more accessible 2023-10-02 10:05:22 -04:00
Mashal Malik
398479d2e7 refactor: add @openedx in renovate automate configuration (#400) 2023-10-02 18:58:01 +05:00
renovate[bot]
3d6d815373 chore(deps): update dependency react-router-dom to v6.16.0 2023-10-02 12:47:21 +00:00
renovate[bot]
287bf50a46 chore(deps): update dependency @edx/frontend-build to v12.9.17 2023-10-02 10:28:13 +00:00
renovate[bot]
677e872320 chore(deps): update dependency jest to v29.7.0 2023-09-25 13:14:25 +00:00
renovate[bot]
d8ecfd6fa3 chore(deps): update dependency @edx/frontend-platform to v5.4.0 2023-09-25 10:21:00 +00:00
renovate[bot]
9a6074868b fix(deps): update dependency @edx/paragon to v21 (#393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 11:31:13 +05:00
renovate[bot]
077ecf38a4 chore(deps): update dependency @testing-library/dom to v9.3.3 2023-09-18 12:39:04 +00:00
renovate[bot]
7d6aa276ec chore(deps): update dependency @edx/frontend-platform to v5.3.2 2023-09-18 11:31:49 +00:00
renovate[bot]
115e69fad3 chore(deps): update dependency @edx/frontend-platform to v5.3.1 2023-09-07 16:42:05 +00:00
Mashal Malik
3e04f76c81 refactor: update lock file version (#391) 2023-08-31 11:29:46 +05:00
renovate[bot]
83eeb88eab chore(deps): update dependency jest to v29.6.4 2023-08-28 06:07:32 +00:00
renovate[bot]
a05570ed37 chore(deps): update dependency react-router-dom to v6.15.0 2023-08-21 09:53:30 +00:00
renovate[bot]
058a846978 chore(deps): update dependency @edx/frontend-build to v12.9.10 2023-08-21 08:26:22 +00:00
renovate[bot]
04cd95824e chore(deps): update dependency @edx/frontend-platform to v5.1.0 2023-08-18 20:03:31 +00:00
Syed Ali Abbas Zaidi
678b95f60d feat: upgrade react router to v6 (#316)
* feat: upgrade react router to v6

* chore: update peer deps
2023-08-16 16:46:49 +05:00
renovate[bot]
599f513624 fix(deps): update dependency @edx/paragon to v20.46.2 2023-08-14 10:18:32 +00:00
renovate[bot]
e8ce471f6f fix(deps): update font awesome to v6.4.2 2023-08-14 06:15:41 +00:00
renovate[bot]
4e727748af chore(deps): update dependency @edx/frontend-platform to v4.6.1 2023-08-07 10:37:18 +00:00
renovate[bot]
79c5bbff68 chore(deps): update dependency @edx/frontend-build to v12.9.4 2023-08-07 08:25:09 +00:00
Jenkins
a95b600a00 chore(i18n): update translations 2023-08-06 16:31:03 -04:00
Awais Ansari
464e952a1e Merge pull request #379 from openedx/aansari/INF-993
feat: deprecate notification tray in learning header
2023-08-02 14:35:38 +05:00
Awais Ansari
018ca18a4e feat: deprecate notification tray in learning header 2023-08-01 18:50:12 +05:00
renovate[bot]
4b89e7561f chore(deps): update dependency jest to v29.6.2 2023-07-31 09:53:25 +00:00
renovate[bot]
da08e721c2 chore(deps): update dependency @edx/frontend-build to v12.9.3 2023-07-31 07:34:58 +00:00
renovate[bot]
d037e96a0c chore(deps): update dependency @testing-library/jest-dom to v5.17.0 2023-07-24 12:16:59 +00:00
renovate[bot]
050dd30d8f chore(deps): update dependency @edx/frontend-build to v12.9.2 2023-07-24 09:55:32 +00:00
renovate[bot]
3dd41030d3 fix(deps): update dependency @edx/paragon to v20.45.5 2023-07-24 06:29:20 +00:00
Awais Ansari
27228f093d Merge pull request #370 from openedx/Ayesha/INF-951
fix: clicking on notification redirecting to correct URL
2023-07-14 13:04:49 +05:00
Muhammad Adeel Tajamul
83f3241e26 fix: course name was not visible in notification (#371) 2023-07-14 08:53:02 +05:00
ayeshoali
e153aefc13 fix: clicking on notification redirecting to correct URL 2023-07-13 15:48:35 +05:00
Bilal Qamar
936c8714b7 fix: updated frontend-build to resolve word-wrap ReDoS vulnerability (#368) 2023-07-11 17:31:29 +05:00
renovate[bot]
6b18f28933 chore(deps): update dependency jest to v29.6.1 2023-07-10 10:41:00 +00:00
ayesha waris
0ce451cfd2 feat: integrated notifications tray with backend apis (#363)
* feat: integrated notifications tray with backend apis

* test: updates test cases

* refactor: loader added and resolves minor nits

* test: fixes test cases related to pagination

* refactor: moved pagination to normalised data
2023-07-10 13:18:44 +05:00
renovate[bot]
42e831a693 chore(deps): update dependency @edx/frontend-build to v12.8.60 2023-07-10 07:38:50 +00:00
Jenkins
9af7eaf587 chore(i18n): update translations 2023-07-09 16:30:57 -04:00
Bilal Qamar
3fbbde1044 fix: updated frontend-build to bump eslint version (#364) 2023-07-06 18:14:00 +05:00
Bilal Qamar
c3f0282be4 feat: update react & react-dom to v17 (#346)
* feat: update react & react-dom to v17

* build: update paragon version

* build: update lock file

* refactor: updated frontend-platform

---------

Co-authored-by: mashal-m <mashal.malik@arbisoft.com>
2023-07-05 12:04:53 +05:00
sundasnoreen12
ceb9c13542 Merge pull request #360 from openedx/sundas/INF-917
test: added test cases for UI components
2023-07-03 23:20:05 -07:00
SundasNoreen
7229bff3ff refactor: added mock funtion for notification count api 2023-07-03 16:30:03 +05:00
renovate[bot]
0695d4f400 fix(deps): update font awesome to v6.4.0 2023-07-03 08:13:31 +00:00
SundasNoreen
de8783c708 refactor: fixed review points 2023-06-27 16:28:58 +05:00
renovate[bot]
e2540bc3a0 fix(deps): update dependency @edx/paragon to v20.45.0 2023-06-26 19:55:23 +00:00
renovate[bot]
ffb5a765e2 chore(deps): update dependency @edx/reactifex to v2.2.0 2023-06-26 14:58:49 +00:00
renovate[bot]
952e543217 fix(deps): update dependency axios-mock-adapter to v1.21.5 2023-06-26 11:48:04 +00:00
SundasNoreen
0e6a272f2b test: added test cases for UI components 2023-06-26 13:39:09 +05:00
renovate[bot]
45a1da9f5e chore(deps): update dependency @edx/frontend-build to v12.8.57 2023-06-26 06:43:40 +00:00
sundasnoreen12
022515d1d2 Merge pull request #355 from openedx/sundas/INF-903
feat: binded show notification tray status with the backend api
2023-06-20 01:59:04 -07:00
ayeshoali
2d737aae7f refactor: fixed data updation in redux 2023-06-20 13:31:15 +05:00
SundasNoreen
4c4db14eac feat: binded show notification tray status with the backend api 2023-06-19 18:00:06 +05:00
sundasnoreen12
911cea6a0e Merge pull request #350 from openedx/sundas/INF-878
test: added redux, selector and api cases
2023-06-19 05:10:57 -07:00
SundasNoreen
a52ddfd9bd refactor: changed api url 2023-06-19 16:58:32 +05:00
SundasNoreen
8175ba897a test: added failed and denied test cases of redux 2023-06-19 16:04:55 +05:00
renovate[bot]
cfda72b2e2 chore(deps): update dependency @testing-library/dom to v9.3.1 2023-06-19 10:46:30 +00:00
SundasNoreen
4483a734bc refactor: fixed issues of review 2023-06-19 15:33:47 +05:00
renovate[bot]
db1903cdce chore(deps): update dependency @edx/frontend-build to v12.8.54 2023-06-19 08:12:52 +00:00
Jenkins
71851b13a6 chore(i18n): update translations 2023-06-18 16:30:53 -04:00
SundasNoreen
6efa31092d test: added redux, selector and api cases 2023-06-15 17:27:11 +05:00
SundasNoreen
c3541a3d79 test: added notification redux test cases 2023-06-15 13:30:38 +05:00
sundasnoreen12
dad01fcd78 Merge pull request #340 from openedx/sundas/INF-820
feat: added notification UI
2023-06-15 01:12:31 -07:00
ayeshoali
30e6eed60d refactor: fixes extra spaces in index.scss 2023-06-15 12:39:23 +05:00
renovate[bot]
de69ed3dd9 fix(deps): update dependency @edx/paragon to v20.44.0 2023-06-12 14:13:06 +00:00
renovate[bot]
1d55df323f chore(deps): update dependency @edx/frontend-build to v12.8.51 2023-06-12 11:46:29 +00:00
SundasNoreen
4e718f85de refactor: fixed all review points 2023-06-12 12:56:15 +05:00
SundasNoreen
a211547a1d refactor: removed backend api calls 2023-06-08 21:08:54 +05:00
Awais Ansari
784e9afccf fix: add appName param in getNotifications function 2023-06-06 15:16:06 +05:00
SundasNoreen
4b23d8c4e4 fix: lint issue 2023-06-06 13:15:12 +05:00
ayeshoali
6d02e63d08 fix: fixes lint errors 2023-06-06 13:04:20 +05:00
ayeshoali
b1feed2443 fix: fixes UI according to figma 2023-06-06 12:48:10 +05:00
SundasNoreen
cabf4e3f27 refactor: fixed code refactor and added new slices and selector 2023-06-05 19:55:46 +05:00
Awais Ansari
78a40d47c1 refactor: code and style modifications 2023-06-05 17:52:52 +05:00
renovate[bot]
c7178afe6b chore(deps): update dependency @edx/frontend-build to v12.8.40 2023-06-05 10:10:38 +00:00
Awais Ansari
18a6840037 fix: Ui modifications 2023-06-05 14:51:30 +05:00
renovate[bot]
583a487c38 chore(deps): update dependency @edx/frontend-platform to v4.5.1 2023-06-05 08:50:33 +00:00
SundasNoreen
3276496523 feat: added redux store implementation 2023-06-05 12:15:41 +05:00
Awais Ansari
7ab55175b5 fix: redux structure updates 2023-06-01 19:57:45 +05:00
SundasNoreen
72e82005c0 feat: added notification APIs 2023-06-01 13:07:53 +05:00
SundasNoreen
c4df727178 refactor: refactor components 2023-05-31 09:28:45 +05:00
renovate[bot]
1f6766175d chore(deps): update dependency @edx/frontend-platform to v4.5.0 2023-05-29 09:37:10 +00:00
renovate[bot]
2ac8988a9b chore(deps): update dependency @edx/frontend-build to v12.8.38 2023-05-29 07:50:58 +00:00
Awais Ansari
642be093c7 fix: bell icon design change 2023-05-26 20:52:18 +05:00
SundasNoreen
86939a2559 refactor: added notification icon in learning header 2023-05-26 17:07:05 +05:00
SundasNoreen
8ed18f3d69 refactor: UI refactor based on figma 2023-05-25 22:41:32 +05:00
SundasNoreen
061746da9f refactor: used paragon icons and updated css 2023-05-24 15:14:41 +05:00
SundasNoreen
de77aa5f0c feat: notification tray closes when clicked outside 2023-05-23 12:16:22 +05:00
SundasNoreen
7034d10536 refactor: fixed snapshot and store structure in header test file 2023-05-23 10:40:43 +05:00
SundasNoreen
4ce7311809 refactor: removed unused states 2023-05-22 15:17:04 +05:00
SundasNoreen
e76f5b6937 feat: added add more notification button functionality 2023-05-22 15:05:26 +05:00
SundasNoreen
f8fc794458 feat: added notification tray 2023-05-22 14:59:24 +05:00
sundasnoreen12
a5069edd94 feat: added notification UI 2023-05-15 16:36:41 +05:00
renovate[bot]
2543926c95 fix(deps): update dependency @edx/paragon to v20.36.0 2023-05-15 07:37:37 +00:00
renovate[bot]
c5eb43a2a5 chore(deps): update dependency @testing-library/dom to v9.3.0 2023-05-15 07:33:45 +00:00
renovate[bot]
256fa5c9d8 fix(deps): update dependency @edx/paragon to v20.34.0 2023-05-08 10:40:34 +00:00
renovate[bot]
267cce9f89 chore(deps): update dependency @edx/frontend-build to v12.8.27 2023-05-08 10:37:16 +00:00
renovate[bot]
4f59c80a12 fix(deps): update dependency @edx/paragon to v20.32.3 2023-05-01 07:58:51 +00:00
renovate[bot]
59afd596ab chore(deps): update dependency @edx/frontend-build to v12.8.16 2023-05-01 07:55:36 +00:00
Jenkins
0c83268163 chore(i18n): update translations 2023-04-23 16:30:51 -04:00
Bilal Qamar
c5f5fa9281 feat!: upgraded to node v18, added .nvmrc and updated workflows (#332)
BREAKING CHANGE: Ending support of @edx/frontend-platform v2 and v3 and now only support v4
2023-04-20 19:10:05 +05:00
Muhammad Abdullah Waheed
e247aee372 fix: updated readme for installing dependencies (#331) 2023-04-20 17:36:10 +05:00
Bilal Qamar
f6ae5a4bdd refactor: reverted in favor of major release due to updated platform peer dependencies (#330) 2023-04-20 16:52:44 +05:00
Brian Smith
057d16d3c1 fix: release frontend-platform version update 2023-04-19 13:04:58 -04:00
Omar Al-Ithawi
93bb38d0bd chore(deps): update frontend-platform version (#329)
gets the intl-imports.js script for FC-0012 OEP-58.
2023-04-13 16:56:46 -04:00
renovate[bot]
01405eaff9 fix(deps): update dependency @edx/paragon to v20.30.1 2023-04-10 14:48:47 +00:00
renovate[bot]
59fa6e2a35 chore(deps): update dependency @edx/frontend-build to v12.8.6 2023-04-10 11:18:27 +00:00
Mashal Malik
9fc3a0e835 refactor: update peer dependency for react and react-dom (#326)
* refactor: remove react16 from peer dependency

* build: update lock file

* fix: fix lint issue

* refactor: add 17 support of react in peerDep
2023-04-05 16:47:15 +05:00
renovate[bot]
22e157adf6 fix(deps): update dependency @edx/paragon to v20.29.0 2023-04-03 12:46:02 +00:00
renovate[bot]
6b81f69eba chore(deps): update dependency @testing-library/dom to v9.2.0 2023-04-03 10:35:08 +00:00
Bilal Qamar
cefa84006c fix: reverted semantic release to v16 (#321) 2023-03-30 11:04:45 +05:00
Muhammad Abdullah Waheed
a54309dd63 fix: updated version of semantic-release-action (#322) 2023-03-29 23:33:58 +05:00
Bilal Qamar
aeb0fd2be7 feat: upgraded to node v18, added .nvmrc and updated workflows (#314)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* refactor: upgraded frontend-build & frontend-platform, updated workflows

* refactor: moved platform from devDependencies
2023-03-29 16:44:29 +05:00
renovate[bot]
26eb2bb4c7 chore(deps): update dependency @edx/frontend-platform to v3.6.0 2023-03-27 10:18:25 +00:00
renovate[bot]
8083079954 chore(deps): update dependency @edx/frontend-build to v12.7.0 2023-03-27 10:15:17 +00:00
renovate[bot]
d4fc8489ea fix(deps): update dependency @edx/paragon to v20.28.5 2023-03-20 11:56:19 +00:00
renovate[bot]
4020a81bd4 chore(deps): update dependency redux-saga to v1.2.3 2023-03-20 11:53:34 +00:00
renovate[bot]
acf1adba80 chore(deps): update dependency jest to v29.5.0 2023-03-13 09:54:48 +00:00
renovate[bot]
a204ff8c03 chore(deps): update dependency @testing-library/dom to v9.0.1 2023-03-13 09:47:40 +00:00
Mashal Malik
e8ccc4b707 chore: Update transifex api from v2 to v3 (#310)
* chore: Update transifex api from v2 to v3

* refactor: remove duplicate line
2023-03-06 17:58:12 +05:00
renovate[bot]
4d86780c73 chore(deps): update dependency @testing-library/dom to v9 2023-02-27 11:44:52 +00:00
renovate[bot]
fdbb83f51e fix(deps): update font awesome to v6.3.0 2023-02-27 11:38:21 +00:00
Feanil Patel
f6e4664d37 Merge pull request #304 from openedx/repo_checks/ensure_workflows
Update standard workflow files.
2023-02-24 11:45:23 -05:00
Feanil Patel
65177fcdcc build: Updating a missing workflow file add-depr-ticket-to-depr-board.yml.
The .github/workflows/add-depr-ticket-to-depr-board.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-23 13:57:40 -05:00
Feanil Patel
7601249fb6 build: Creating a missing workflow file add-remove-label-on-comment.yml.
The .github/workflows/add-remove-label-on-comment.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-23 13:57:40 -05:00
Feanil Patel
c8ef3dad93 build: Creating a missing workflow file self-assign-issue.yml.
The .github/workflows/self-assign-issue.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-23 13:57:39 -05:00
renovate[bot]
b02fe00c71 fix(deps): update dependency @edx/paragon to v20.28.4 2023-02-20 11:19:51 +00:00
renovate[bot]
4404aede33 chore(deps): update dependency jest to v29.4.3 2023-02-20 11:12:57 +00:00
renovate[bot]
1b0edb10c4 chore(deps): update dependency @testing-library/dom to v8.20.0 2023-02-13 12:12:41 +00:00
renovate[bot]
546adff45e chore(deps): update dependency @edx/brand to v1.2.0 2023-02-13 12:05:28 +00:00
renovate[bot]
94b14fd618 chore(deps): update dependency redux-saga to v1.2.2 2023-02-06 13:51:48 +00:00
renovate[bot]
5b8a9a587b chore(deps): update dependency redux to v4.2.1 2023-02-06 13:44:55 +00:00
renovate[bot]
2650cb59b3 chore(deps): update actions/setup-node action to v3 (#212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-06 15:11:28 +05:00
renovate[bot]
bc2b13175a chore(deps): update dependency husky to v8 (#235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-06 14:30:50 +05:00
renovate[bot]
85e8094833 fix(deps): update font awesome to v6 (#281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-06 14:17:48 +05:00
dependabot[bot]
aff8dda3ee chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#287)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-01 17:15:00 +05:00
dependabot[bot]
51b505552d chore(deps): bump cookiejar from 2.1.3 to 2.1.4 (#296)
Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/bmeck/node-cookiejar/releases)
- [Commits](https://github.com/bmeck/node-cookiejar/commits)

---
updated-dependencies:
- dependency-name: cookiejar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-01 17:14:36 +05:00
dependabot[bot]
3648f1b6be build(deps): bump json5 from 1.0.1 to 1.0.2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 14:12:31 +00:00
Bilal Qamar
c78b6964b9 chore: updated frontend-build version to v12.4.19 (#297) 2023-01-25 18:51:25 +05:00
renovate[bot]
664d05134b fix(deps): update dependency @edx/paragon to v20.27.0 2023-01-23 10:38:54 +00:00
renovate[bot]
b969522cd0 chore(deps): update dependency @edx/frontend-build to v12.4.16 2023-01-23 10:34:46 +00:00
renovate[bot]
0cd8210ea7 chore(deps): update dependency @testing-library/dom to v8.19.1 2023-01-16 10:17:13 +00:00
renovate[bot]
1c763c2102 chore(deps): update dependency @edx/frontend-build to v12.4.15 2023-01-09 09:27:54 +00:00
Mashal Malik
073003284a Moving code coverage from codecov package to CI (#289)
* fix: removed derpeciated package codecov

* fix: install edx/paragon 20.20.0 fixed version

* fix: specified paragron 20.20.0 version

Co-authored-by: Shahroz Ahmad <shahroz.ahmad@arbisoft.com>
2022-12-29 12:25:49 +05:00
Bilal Qamar
92fdf85c9a feat: paragon updated to v20 & frontend-build version updated
* feat: paragon updated to v20 & frontend-build version updated

* refactor: moved paragon from devDependencies to satisfy eslint rule

* refactor: updated snapshots
2022-12-09 15:57:09 +05:00
Sagirov Eugeniy
5ee8a8c75c feat: Account pages. Updated menu items urls. 2022-12-02 12:28:15 +00:00
Abdullah Waheed
536d67404f refactor: updated renovate config to auto update minor and patch versions of edx dependencies 2022-11-30 13:17:21 +00:00
Bilal Qamar
9d99bfcec6 refactor: updated snapshots 2022-11-25 16:53:50 +05:00
Bilal Qamar
3180c9d973 refactor: moved paragon from devDependencies to satisfy eslint rule 2022-11-25 16:48:29 +05:00
Bilal Qamar
1645274d9f feat: paragon updated to v20 & frontend-build version updated 2022-11-25 16:36:06 +05:00
Bilal Qamar
84e43cb038 refactor: bumped loader-utils 2022-11-25 16:26:11 +05:00
julianajlk
994b21c0c1 fix: Change frontend-platform peer dependency to v2 or v3 range 2022-11-14 20:19:37 +00:00
renovate[bot]
940b45ba7e chore(deps): update dependency jest to v29 2022-11-14 07:52:30 +00:00
dependabot[bot]
4efa0a07ae build(deps): bump loader-utils from 1.4.0 to 1.4.1 (#278)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-10 17:34:00 +05:00
renovate[bot]
2bd6879bda chore(deps): update actions/checkout action to v3 (#211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-10 17:18:45 +05:00
Muhammad Abdullah Waheed
b479f0b376 Merge pull request #205 from openedx/dependabot/npm_and_yarn/async-2.6.4
build(deps): bump async from 2.6.3 to 2.6.4
2022-11-08 18:40:15 +05:00
Leangseu Kim
dfdcbc0a8d feat: upgrade frontend platform to version 3 2022-11-07 12:45:43 +00:00
renovate[bot]
c3b02a2946 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.7 2022-11-07 09:36:45 +00:00
renovate[bot]
f6c1a8bcc1 chore(deps): update dependency redux-saga to v1.2.1 2022-10-31 07:42:11 +00:00
Bilal Qamar
6c02962e0d refactor: updated frontend-build & resolved eslint issues 2022-10-26 10:37:57 -03:00
renovate[bot]
acaf98f0b1 chore(deps): update dependency @testing-library/dom to v8.19.0 2022-10-24 08:15:34 +00:00
Adolfo R. Brandes
90351083aa Merge pull request #256 from openedx/abdullahwaheed/transifex-languages-list-update
Supported Transifex languages in Makefile
2022-10-20 16:15:57 -03:00
Adolfo R. Brandes
6f75684ad9 Merge pull request #271 from brian-smith-tcril/studio-header-component
refactor: make studio header more flexible
2022-10-20 14:24:44 -03:00
Brian Smith
a54f099d68 refactor: make studio header more flexible 2022-10-19 10:20:10 -04:00
Diana Huang
02d081dd26 Merge pull request #270 from openedx/diana/transifex-call
fix: Add transifex flag.
2022-10-17 16:28:12 -04:00
Diana Huang
468acc80f0 fix: Add transifex flag.
The Transifex cli started requiring the -t or --translations flag in the pull command in order to fetch translations.

https://github.com/edx/edx-arch-experiments/issues/77
2022-10-17 16:05:53 -04:00
renovate[bot]
90fdd13fbc chore(deps): update dependency react-router-dom to v5.3.4 2022-10-17 08:57:14 +00:00
renovate[bot]
faf1b8522a chore(deps): update dependency react-redux to v7.2.9 2022-10-10 09:15:51 +00:00
Jenkins
e8a28b09bc chore(i18n): update translations 2022-10-09 16:30:19 -04:00
renovate[bot]
c611df3f69 chore(deps): update dependency jest-chain to v1.1.6 2022-10-03 08:05:06 +00:00
Jenkins
ab371f1c3a chore(i18n): update translations 2022-10-02 16:30:36 -04:00
renovate[bot]
fb2002a004 chore(deps): update dependency @testing-library/dom to v8.18.1 2022-09-26 10:35:10 +00:00
renovate[bot]
f955ec4434 chore(deps): update dependency @testing-library/dom to v8.18.0 2022-09-19 09:59:12 +00:00
Abdullah Waheed
de9eb63b07 feat: added new translations in Makefile and updated all the translations 2022-09-06 20:05:09 +05:00
dependabot[bot]
64f55150b6 build(deps): bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-18 11:56:29 +00:00
154 changed files with 13290 additions and 41626 deletions

View File

@@ -1,4 +1,6 @@
ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
ACCOUNT_PROFILE_URL=http://localhost:1995
ACCOUNT_SETTINGS_URL=http://localhost:1997
BASE_URL=localhost:8080
CREDENTIALS_BASE_URL=http://localhost:18150
CSRF_TOKEN_API_PATH=/csrf/api/v1/token

View File

@@ -1,3 +1,4 @@
const { createConfig } = require('@edx/frontend-build');
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('eslint');
module.exports = createConfig('eslint');

View File

@@ -16,4 +16,4 @@ jobs:
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}

View File

@@ -0,0 +1,20 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "label: " it tries to apply
# the label indicated in rest of comment.
# If the comment starts with "remove label: ", it tries
# to remove the indicated label.
# Note: Labels are allowed to have spaces and this script does
# not parse spaces (as often a space is legitimate), so the command
# "label: really long lots of words label" will apply the
# label "really long lots of words label"
name: Allows for the adding and removing of labels via comment
on:
issue_comment:
types: [created]
jobs:
add_remove_labels:
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master

View File

@@ -9,18 +9,15 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Nodejs
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes
@@ -34,4 +31,7 @@ jobs:
- name: i18n_extract
run: npm run i18n_extract
- name: Coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

View File

@@ -10,5 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

View File

@@ -3,19 +3,22 @@ on:
push:
branches:
- master
- alpha
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 16
node-version: ${{ env.NODE_VER }}
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes
@@ -27,11 +30,14 @@ jobs:
- name: i18n_extract
run: npm run i18n_extract
- name: Coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
- name: Build
run: npm run build
- name: Release
uses: cycjimmy/semantic-release-action@v2
uses: cycjimmy/semantic-release-action@v3
with:
semantic_version: 16
env:

12
.github/workflows/self-assign-issue.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "assign me" it assigns the author to the
# ticket (case insensitive)
name: Assign comment author to ticket if they say "assign me"
on:
issue_comment:
types: [created]
jobs:
self_assign_by_comment:
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master

3
.gitignore vendored
View File

@@ -7,3 +7,6 @@ temp
src/i18n/transifex_input.json
module.config.js
.idea/
.vscode
src/i18n/messages

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20

View File

@@ -1,5 +1,8 @@
{
"branch": "master",
"branches": [
"master",
{name: "alpha", prerelease: true}
],
"tagFormat": "v${version}",
"verifyConditions": [
"@semantic-release/npm",

View File

@@ -1,9 +0,0 @@
[main]
host = https://www.transifex.com
[o:open-edx:p:edx-platform:r:frontend-component-header]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -1,14 +1,9 @@
transifex_resource = frontend-component-header
transifex_langs = "ar,fr,fr_CA,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
transifex_temp = ./temp/babel-plugin-formatjs
build:
rm -rf ./dist
@@ -19,7 +14,7 @@ build:
@rm -rf dist/__mocks__
requirements:
npm install
npm ci
i18n.extract:
# Pulling display strings from .jsx files into .json files...
@@ -37,21 +32,6 @@ detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# 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...
$(transifex_utils) $(transifex_temp) --comments
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --languages=$(transifex_langs)
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...

View File

@@ -2,22 +2,42 @@
frontend-component-header
#########################
|Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|license| |Build Status| |Codecov| |npm_version| |npm_downloads| |semantic-release|
********
Overview
Purpose
********
A generic header for Open edX micro-frontend applications.
************
Requirements
Getting Started
************
Prerequisites
=============
The `devstack`_ is currently recommended as a development environment for your
new MFE. If you start it with ``make dev.up.lms`` that should give you
everything you need as a companion to this frontend.
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Devstack: https://github.com/openedx/devstack
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
Requirements
============
This component uses ``@edx/frontend-platform`` services such as i18n, analytics, configuration, and the ``AppContext`` React component, and expects that it has been loaded into a micro-frontend that has been properly initialized via ``@edx/frontend-platform``'s ``initialize`` function. `Please visit the frontend template application to see an example. <https://github.com/openedx/frontend-template-application/blob/master/src/index.jsx>`_
Environment Variables
=====================
====================
* ``LMS_BASE_URL`` - The URL of the LMS of your Open edX instance.
* ``LOGOUT_URL`` - The URL of the API endpoint which performs a user logout.
@@ -26,13 +46,14 @@ Environment Variables
Defaults to "localhost" in development.
* ``LOGO_URL`` - The URL of the site's logo. This logo is displayed in the header.
* ``ORDER_HISTORY_URL`` - The URL of the order history page.
* ``ACCOUNT_PROFILE_URL`` - The URL of the account profile page.
* ``ACCOUNT_SETTINGS_URL`` - The URL of the account settings page.
* ``AUTHN_MINIMAL_HEADER`` - A boolean flag which hides the main menu, user menu, and logged-out
menu items when truthy. This is intended to be used in micro-frontends like
frontend-app-authentication in which these menus are considered distractions from the user's task.
************
Installation
************
============
To install this header into your Open edX micro-frontend, run the following command in your MFE:
@@ -40,9 +61,33 @@ To install this header into your Open edX micro-frontend, run the following comm
This will make the component available to be imported into your application.
*****
Cloning and Startup
===================
.. code-block::
1. Clone your new repo:
``git clone https://github.com/openedx/frontend-component-header.git``
2. Use node v18.x.
The current version of the micro-frontend build scripts support node 18.
Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an .nvmrc file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
3. Install npm dependencies:
``cd frontend-component-header && npm ci``
4. Start the dev server:
``npm start``
Usage
*****
=====
This library has the following exports:
@@ -50,20 +95,24 @@ This library has the following exports:
* ``messages``: Internationalization messages suitable for use with `@edx/frontend-platform/i18n <https://edx.github.io/frontend-platform/module-Internationalization.html>`_
* ``dist/index.scss``: A SASS file which contains style information for the component. It should be imported into the micro-frontend's own SCSS file.
Plugins
-------
This can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
The parts of this that can be customized in that manner are documented `here </src/plugin-slots>`_.
Examples
========
* `An example of component and messages usage. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L21>`_
* `An example of SCSS file usage. <https://github.com/openedx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L8>`_
***********
Development
***********
===========
Install dependencies::
npm i
npm ci
Start the development server::
@@ -73,6 +122,63 @@ Build a production distribution::
npm run build
License
=======
The code in this repository is licensed under the AGPLv3 unless otherwise
noted.
Please see `LICENSE <LICENSE>`_ for details.
Contributing
============
Contributions are very welcome. Please read `How To Contribute`_ for details.
.. _How To Contribute: https://openedx.org/r/how-to-contribute
This project is currently accepting all types of contributions, bug fixes,
security fixes, maintenance work, or new features. However, please make sure
to have a discussion about your new feature idea with the maintainers prior to
beginning development to maximize the chances of your change being accepted.
You can start a conversation by creating a new issue on this repo summarizing
your idea.
Getting Help
===========
If you're having trouble, we have discussion forums at
https://discuss.openedx.org where you can connect with others in the community.
Our real-time conversations are on Slack. You can request a `Slack
invitation`_, then join our `community Slack workspace`_. Because this is a
frontend repository, the best place to discuss it would be in the `#wg-frontend
channel`_.
For anything non-trivial, the best path is to open an issue in this repository
with as many details about the issue you are facing as you can provide.
https://github.com/openedx/frontend-component-header/issues
For more information about these options, see the `Getting Help`_ page.
.. _Slack invitation: https://openedx.org/slack
.. _community Slack workspace: https://openedx.slack.com/
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
.. _Getting Help: https://openedx.org/community/connect
The Open edX Code of Conduct
============================
All community members are expected to follow the `Open edX Code of Conduct`_.
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
Reporting Security Issues
=========================
Please do not report security issues in public. Please email security@openedx.org.
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-component-header.svg?branch=master
:target: https://travis-ci.com/edx/frontend-component-header
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-component-header
@@ -84,4 +190,4 @@ Build a production distribution::
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-component-header.svg
:target: @edx/frontend-component-header
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
:target: https://github.com/semantic-release/semantic-release
:target: https://github.com/semantic-release/semantic-release

View File

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

14
catalog-info.yaml Normal file
View File

@@ -0,0 +1,14 @@
# This file records information about this repo. Its use is described in OEP-55:
# https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0055-proc-project-maintainers.html
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: "frontend-component-header"
description: "A generic header for the Open edX micro-frontend applications."
annotations:
openedx.org/arch-interest-groups: ""
spec:
owner: group:committers-frontend
type: "library"
lifecycle: "production"

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,111 @@
.. title:: Custom Header Component Documentation
Custom Header Component
=======================
Overview
--------
The ``Header`` component is used to display a header with a provided ``mainMenuItems``,
``secondaryMenuItems``, and ``userMenuItems`` props. If props are provided, the component will use them; otherwise,
if any of the props ``(mainMenuItems, secondaryMenuItems, userMenuItems)`` are not provided, default
items will be displayed. This component provides flexibility in customization, making it suitable for a wide
range of applications.
Props Details
-------------
The `Header` component accepts the following **optional** props for customization:
``mainMenuItems``
*****************
The main menu items is a list of menu items objects. On desktop screens, these items are displayed on the left side next to the logo icon.
On mobile screens, the main menu is displayed as a dropdown menu triggered by a hamburger icon. The main menu dropdown appears below the logo when opened.
Example:
::
[
{ type: 'item', href: '/courses', content: 'Courses', isActive: true },
{ type: 'item', href: '/programs', content: 'Programs' },
{ type: 'item', href: '/discover', content: 'Discover New', disabled, true },
{
type: 'submenu',
content: 'Sub Menu Item',
submenuContent: (
<>
<div className="mb-1"><a rel="noopener" href="#">Submenu item 1</a></div>
<div className="mb-1"><a rel="noopener" href="#">Submenu item 2</a></div>
</>
),
},
]
**Submenu Implementation**
To implement a submenu, set the type to ``submenu`` and provide a ``submenuContent`` property.
The submenuContent should be a React component (as shown in above example) that can be rendered.
**Note:**
- The ``type`` should be ``item`` or ``submenu``. If type is ``submenu``, it should contain ``submenuContent`` instead of ``href``.
- If any item is to be disabled, we can pass optional ``disabled: true`` in that item object and
- If any item is to be active, we can pass optional ``isActive: true`` in that item object
secondaryMenuItems
******************
The secondary menu items has same structure as ``mainMenuItems``. On desktop screen, these items are displayed on the right of header just before the userMenu avatar and on mobile screen,
these items are displayed below the mainMenu items in dropdown.
Example:
::
[
{ type: 'item', href: '/help', content: 'Help' },
]
userMenuItems
*************
The user menu items is list of objects. On desktop screens, these items are displayed as a dropdown menu on the most right side of the header. The dropdown is opened by clicking on the avatar icon, which is typically located at the far right of the header.
On mobile screens, the user menu is also displayed as a dropdown menu, appearing under the avatar icon.
Each object represents a group in the user menu. Each object contains the ``heading`` and
list of menu items to be displayed in that group. Heading is optional and will be displayed only if passed. There can
be multiple groups. For a normal user menu, a single group can be passed with empty heading.
Example:
::
[
{
heading: '',
items: [
{ type: 'item', href: '/profile', content: 'Profile' },
{ type: 'item', href: '/logout', content: 'Logout' }
]
},
]
Screenshots
***********
Desktop:
.. image:: ./images/desktop_header.png
Mobile:
.. image:: ./images/mobile_main_menu.png
.. image:: ./images/mobile_user_menu.png
Some Important Notes
--------------------
- Intl formatted strings should be passed in content attribute.
- Only menu items in the main menu can be disabled.
- Menu items in the main menu and user menu can have ``isActive`` prop.

View File

@@ -7,6 +7,7 @@ import { AppContext, AppProvider } from '@edx/frontend-platform/react';
import Header from '@edx/frontend-component-header';
import './index.scss';
import StudioHeader from '../src/studio-header/StudioHeader';
subscribe(APP_READY, () => {
ReactDOM.render(
@@ -32,7 +33,35 @@ subscribe(APP_READY, () => {
}}>
<Header />
</AppContext.Provider>
<h5 className="mt-2">Logged in state</h5>
<h5 className="mt-2 mb-5">Logged in state</h5>
<AppContext.Provider value={{
authenticatedUser: {
userId: '123abc',
username: 'testuser',
roles: [],
administrator: false,
},
config: getConfig(),
}}>
<StudioHeader
number="run123"
org="testX"
title="Course Name"
isHiddenMainMenu={false}
mainMenuDropdowns={[
{
id: 'content-dropdown',
buttonTitle: 'Content',
items: [{
href: '#',
title: 'Outline',
}],
},
]}
outlineLink="#"
/>
</AppContext.Provider>
<h5 className="mt-2">Logged in state for Studio header</h5>
</AppProvider>,
document.getElementById('root'),
);

View File

@@ -1,6 +1,6 @@
@import "@edx/brand/paragon/fonts";
@import "@edx/brand/paragon/variables";
@import "@edx/paragon/scss/core/core";
@import "@openedx/paragon/scss/core/core";
@import "@edx/brand/paragon/overrides";
@import "@edx/frontend-component-header/index";

View File

@@ -1,4 +1,4 @@
const { createConfig } = require('@edx/frontend-build');
const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('jest', {
setupFilesAfterEnv: [

View File

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

49033
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
},
"scripts": {
"build": "make build",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
@@ -33,44 +33,47 @@
},
"homepage": "https://github.com/openedx/frontend-component-header#readme",
"devDependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-build": "11.0.2",
"@edx/frontend-platform": "2.6.2",
"@edx/paragon": "19.25.3",
"codecov": "3.8.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"husky": "7.0.4",
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-platform": "8.3.1",
"@edx/reactifex": "^2.1.1",
"@openedx/frontend-build": "14.2.2",
"@openedx/paragon": "22.16.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "10.4.9",
"husky": "8.0.3",
"jest": "29.7.0",
"jest-chain": "1.1.6",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.8",
"react-router-dom": "5.3.3",
"react-test-renderer": "16.14.0",
"reactifex": "1.1.1",
"redux": "4.2.0",
"redux-saga": "1.1.3",
"@testing-library/dom": "8.17.1",
"@testing-library/jest-dom": "5.16.5",
"jest": "28.1.3",
"jest-chain": "1.1.5",
"@testing-library/react": "10.4.9"
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "7.2.9",
"react-router-dom": "6.28.1",
"react-test-renderer": "17.0.2",
"redux": "4.2.1",
"redux-saga": "1.3.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/frontend-plugin-framework": "^1.3.0",
"axios-mock-adapter": "1.22.0",
"babel-polyfill": "6.26.0",
"classnames": "^2.5.1",
"jest-environment-jsdom": "^29.7.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.5",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.2.0"
"react-transition-group": "4.4.5"
},
"peerDependencies": {
"@edx/frontend-platform": "^2.0.0",
"@edx/paragon": ">= 7.0.0 < 21.0.0",
"@edx/frontend-platform": "^7.0.0 || ^8.0.0",
"@openedx/paragon": "^22.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0",
"react-dom": "^16.9.0"
"react": "^16.9.0 || ^17.0.0",
"react-dom": "^16.9.0 || ^17.0.0",
"react-router-dom": "^6.14.2"
}
}

View File

@@ -22,6 +22,11 @@
"pin"
],
"automerge": true
},
{
"matchPackagePatterns": ["@edx", "@openedx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
],
"timezone": "America/New_York"

View File

@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { AvatarIcon } from './Icons';
function Avatar({
const Avatar = ({
size,
src,
alt,
className,
}) {
}) => {
const avatar = src ? (
<img className="d-block w-100 h-100" src={src} alt={alt} />
) : (
@@ -23,7 +23,7 @@ function Avatar({
{avatar}
</span>
);
}
};
Avatar.propTypes = {
src: PropTypes.string,

View File

@@ -1,214 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
// i18n
import messages from './Header.messages';
// Assets
import { CaretIcon } from './Icons';
class DesktopHeader extends React.Component {
constructor(props) { // eslint-disable-line no-useless-constructor
super(props);
}
renderMainMenu() {
const { mainMenu } = this.props;
// Nodes are accepted as a prop
if (!Array.isArray(mainMenu)) {
return mainMenu;
}
return mainMenu.map((menuItem) => {
const {
type,
href,
content,
submenuContent,
} = menuItem;
if (type === 'item') {
return (
<a key={`${type}-${content}`} className="nav-link" href={href}>{content}</a>
);
}
return (
<Menu key={`${type}-${content}`} tag="div" className="nav-item" respondToPointerEvents>
<MenuTrigger tag="a" className="nav-link d-inline-flex align-items-center" href={href}>
{content} <CaretIcon role="img" aria-hidden focusable="false" />
</MenuTrigger>
<MenuContent className="pin-left pin-right shadow py-2">
{submenuContent}
</MenuContent>
</Menu>
);
});
}
// Renders an optional App Menu for
renderAppMenu() {
const { appMenu } = this.props;
const { content: appMenuContent, menuItems } = appMenu;
return (
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
<MenuTrigger tag="a" className="nav-link d-inline-flex align-items-center">
{appMenuContent} <CaretIcon role="img" aria-hidden focusable="false" />
</MenuTrigger>
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
{menuItems.map(({ type, href, content }) => (
<a className={`dropdown-${type}`} key={`${type}-${content}`} href={href}>{content}</a>
))}
</MenuContent>
</Menu>
);
}
renderUserMenu() {
const {
userMenu,
avatar,
username,
intl,
} = this.props;
return (
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
<MenuTrigger
tag="button"
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
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" />
</MenuTrigger>
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
{userMenu.map(({ type, href, content }) => (
<a className={`dropdown-${type}`} key={`${type}-${content}`} href={href}>{content}</a>
))}
</MenuContent>
</Menu>
);
}
renderLoggedOutItems() {
const { loggedOutItems } = this.props;
return loggedOutItems.map((item, i, arr) => (
<a
key={`${item.type}-${item.content}`}
className={i < arr.length - 1 ? 'btn mr-2 btn-link' : 'btn mr-2 btn-outline-primary'}
href={item.href}
>
{item.content}
</a>
));
}
render() {
const {
logo,
logoAltText,
logoDestination,
loggedIn,
intl,
appMenu,
} = 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">
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
<div className={`container-fluid ${logoClasses}`}>
<div className="nav-container position-relative d-flex align-items-center">
{logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} />}
<nav
aria-label={intl.formatMessage(messages['header.label.main.nav'])}
className="nav main-nav"
>
{this.renderMainMenu()}
</nav>
{appMenu ? (
<nav
aria-label={intl.formatMessage(messages['header.label.app.nav'])}
className="nav app-nav"
>
{this.renderAppMenu()}
</nav>
) : null}
<nav
aria-label={intl.formatMessage(messages['header.label.secondary.nav'])}
className="nav secondary-menu-container align-items-center ml-auto"
>
{loggedIn ? this.renderUserMenu() : this.renderLoggedOutItems()}
</nav>
</div>
</div>
</header>
);
}
}
DesktopHeader.propTypes = {
mainMenu: PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]),
userMenu: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
loggedOutItems: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
logo: PropTypes.string,
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
loggedIn: PropTypes.bool,
// i18n
intl: intlShape.isRequired,
// appMenu
appMenu: PropTypes.shape(
{
content: PropTypes.string,
menuItems: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string,
href: PropTypes.string,
content: PropTypes.string,
}),
),
},
),
};
DesktopHeader.defaultProps = {
mainMenu: [],
userMenu: [],
loggedOutItems: [],
logo: null,
logoAltText: null,
logoDestination: null,
avatar: null,
username: null,
loggedIn: false,
appMenu: null,
};
export default injectIntl(DesktopHeader);

View File

@@ -10,8 +10,9 @@ import {
subscribe,
} from '@edx/frontend-platform';
import DesktopHeader from './DesktopHeader';
import MobileHeader from './MobileHeader';
import PropTypes from 'prop-types';
import DesktopHeaderSlot from './plugin-slots/DesktopHeaderSlot';
import MobileHeaderSlot from './plugin-slots/MobileHeaderSlot';
import messages from './Header.messages';
@@ -30,50 +31,68 @@ subscribe(APP_CONFIG_INITIALIZED, () => {
}, 'Header additional config');
});
function Header({ intl }) {
/**
* Header component for the application.
* Displays a header with the provided main menu, secondary menu, and user menu when the user is authenticated.
* If any of the props (mainMenuItems, secondaryMenuItems, userMenuItems) are not provided, default
* items are displayed.
* For more details on how to use this component, please refer to this document:
* https://github.com/openedx/frontend-component-header/blob/master/docs/using_custom_header.rst
*
* @param {list} mainMenuItems - The list of main menu items to display.
* See the documentation for the structure of main menu item.
* @param {list} secondaryMenuItems - The list of secondary menu items to display.
* See the documentation for the structure of secondary menu item.
* @param {list} userMenuItems - The list of user menu items to display.
* See the documentation for the structure of user menu item.
*/
const Header = ({
intl, mainMenuItems, secondaryMenuItems, userMenuItems,
}) => {
const { authenticatedUser, config } = useContext(AppContext);
const mainMenu = [
const defaultMainMenu = [
{
type: 'item',
href: `${config.LMS_BASE_URL}/dashboard`,
content: intl.formatMessage(messages['header.links.courses']),
},
];
const defaultUserMenu = authenticatedUser === null ? [] : [{
heading: '',
items: [
{
type: 'item',
href: `${config.LMS_BASE_URL}/dashboard`,
content: intl.formatMessage(messages['header.user.menu.dashboard']),
},
{
type: 'item',
href: `${config.ACCOUNT_PROFILE_URL}/u/${authenticatedUser.username}`,
content: intl.formatMessage(messages['header.user.menu.profile']),
},
{
type: 'item',
href: config.ACCOUNT_SETTINGS_URL,
content: intl.formatMessage(messages['header.user.menu.account.settings']),
},
// Users should only see Order History if have a ORDER_HISTORY_URL define in the environment.
...(config.ORDER_HISTORY_URL ? [{
type: 'item',
href: config.ORDER_HISTORY_URL,
content: intl.formatMessage(messages['header.user.menu.order.history']),
}] : []),
{
type: 'item',
href: config.LOGOUT_URL,
content: intl.formatMessage(messages['header.user.menu.logout']),
},
],
}];
const orderHistoryItem = {
type: 'item',
href: config.ORDER_HISTORY_URL,
content: intl.formatMessage(messages['header.user.menu.order.history']),
};
const userMenu = authenticatedUser === null ? [] : [
{
type: 'item',
href: `${config.LMS_BASE_URL}/dashboard`,
content: intl.formatMessage(messages['header.user.menu.dashboard']),
},
{
type: 'item',
href: `${config.LMS_BASE_URL}/u/${authenticatedUser.username}`,
content: intl.formatMessage(messages['header.user.menu.profile']),
},
{
type: 'item',
href: `${config.LMS_BASE_URL}/account/settings`,
content: intl.formatMessage(messages['header.user.menu.account.settings']),
},
{
type: 'item',
href: config.LOGOUT_URL,
content: intl.formatMessage(messages['header.user.menu.logout']),
},
];
// Users should only see Order History if have a ORDER_HISTORY_URL define in the environment.
if (config.ORDER_HISTORY_URL) {
userMenu.splice(-1, 0, orderHistoryItem);
}
const mainMenu = mainMenuItems || defaultMainMenu;
const secondaryMenu = secondaryMenuItems || [];
const userMenu = authenticatedUser === null ? [] : userMenuItems || defaultUserMenu;
const loggedOutItems = [
{
@@ -96,24 +115,48 @@ function Header({ intl }) {
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
secondaryMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : secondaryMenu,
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,
loggedOutItems: getConfig().AUTHN_MINIMAL_HEADER ? [] : loggedOutItems,
};
return (
<>
<Responsive maxWidth={768}>
<MobileHeader {...props} />
<Responsive maxWidth={769}>
<MobileHeaderSlot props={props} />
</Responsive>
<Responsive minWidth={769}>
<DesktopHeader {...props} />
<DesktopHeaderSlot props={props} />
</Responsive>
</>
);
}
};
Header.defaultProps = {
mainMenuItems: null,
secondaryMenuItems: null,
userMenuItems: null,
};
Header.propTypes = {
intl: intlShape.isRequired,
mainMenuItems: PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]),
secondaryMenuItems: PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]),
userMenuItems: PropTypes.arrayOf(PropTypes.shape({
heading: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
isActive: PropTypes.bool,
})),
})),
};
export default injectIntl(Header);

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import TestRenderer from 'react-test-renderer';
@@ -6,28 +7,31 @@ import { Context as ResponsiveContext } from 'react-responsive';
import Header from './index';
const HeaderComponent = ({ width, contextValue }) => (
<ResponsiveContext.Provider value={width}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={contextValue}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
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 contextValue = {
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,
},
};
const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;
const wrapper = TestRenderer.create(component);
@@ -35,31 +39,22 @@ describe('<Header />', () => {
});
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 contextValue = {
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,
},
};
const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;
const wrapper = TestRenderer.create(component);
@@ -67,26 +62,17 @@ describe('<Header />', () => {
});
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 contextValue = {
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,
},
};
const component = <HeaderComponent width={{ width: 500 }} contextValue={contextValue} />;
const wrapper = TestRenderer.create(component);
@@ -94,31 +80,22 @@ describe('<Header />', () => {
});
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 contextValue = {
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,
},
};
const component = <HeaderComponent width={{ width: 500 }} contextValue={contextValue} />;
const wrapper = TestRenderer.create(component);

View File

@@ -1,6 +1,6 @@
import React from 'react';
export const MenuIcon = props => (
export const MenuIcon = (props) => (
<svg
width="24px"
height="24px"
@@ -14,7 +14,7 @@ export const MenuIcon = props => (
</svg>
);
export const AvatarIcon = props => (
export const AvatarIcon = (props) => (
<svg
width="24px"
height="24px"
@@ -29,7 +29,7 @@ export const AvatarIcon = props => (
</svg>
);
export const CaretIcon = props => (
export const CaretIcon = (props) => (
<svg
width="16px"
height="16px"

View File

@@ -1,35 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
function Logo({ src, alt, ...attributes }) {
return (
<img src={src} alt={alt} {...attributes} />
);
}
Logo.propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};
function LinkedLogo({
const Logo = ({
href,
src,
alt,
...attributes
}) {
return (
<a href={href} {...attributes}>
<img className="d-block" src={src} alt={alt} />
</a>
);
}
}) => (
<a href={href} className="logo" {...attributes}>
<img className="d-block" src={src} alt={alt} />
</a>
);
LinkedLogo.propTypes = {
export const logoDataShape = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};
export { LinkedLogo, Logo };
Logo.propTypes = logoDataShape;
export default Logo;

View File

@@ -2,12 +2,10 @@ 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}`,
...attributes,
});
}
const MenuTrigger = ({ tag, className, ...attributes }) => React.createElement(tag, {
className: `menu-trigger ${className}`,
...attributes,
});
MenuTrigger.propTypes = {
tag: PropTypes.string,
className: PropTypes.string,
@@ -16,14 +14,13 @@ MenuTrigger.defaultProps = {
tag: 'div',
className: null,
};
const MenuTriggerType = <MenuTrigger />.type;
const MenuTriggerComp = <MenuTrigger />;
const MenuTriggerType = MenuTriggerComp.type;
function MenuContent({ tag, className, ...attributes }) {
return React.createElement(tag, {
className: ['menu-content', className].join(' '),
...attributes,
});
}
const MenuContent = ({ tag, className, ...attributes }) => React.createElement(tag, {
className: ['menu-content', className].join(' '),
...attributes,
});
MenuContent.propTypes = {
tag: PropTypes.string,
className: PropTypes.string,

View File

@@ -1,99 +0,0 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import {
APP_CONFIG_INITIALIZED,
ensureConfig,
mergeConfig,
subscribe,
} from '@edx/frontend-platform';
import DesktopHeader from './DesktopHeader';
import messages from './Header.messages';
ensureConfig([
'STUDIO_BASE_URL',
'LOGOUT_URL',
'LOGIN_URL',
'SITE_NAME',
'LOGO_URL',
'ORDER_HISTORY_URL',
], 'StudioHeader component');
subscribe(APP_CONFIG_INITIALIZED, () => {
mergeConfig({
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
}, 'StudioHeader additional config');
});
function StudioHeader({ intl, mainMenu, appMenu }) {
const { authenticatedUser, config } = useContext(AppContext);
const userMenu = authenticatedUser === null ? [] : [
{
type: 'item',
href: `${config.STUDIO_BASE_URL}`,
content: intl.formatMessage(messages['header.user.menu.studio.home']),
},
{
type: 'item',
href: `${config.STUDIO_BASE_URL}/maintenance`,
content: intl.formatMessage(messages['header.user.menu.studio.maintenance']),
},
{
type: 'item',
href: config.LOGOUT_URL,
content: intl.formatMessage(messages['header.user.menu.logout']),
},
];
const props = {
logo: config.LOGO_URL,
logoAltText: config.SITE_NAME,
logoDestination: config.STUDIO_BASE_URL,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu,
userMenu,
appMenu,
loggedOutItems: [],
};
return <DesktopHeader {...props} />;
}
StudioHeader.propTypes = {
intl: intlShape.isRequired,
appMenu: PropTypes.shape(
{
content: PropTypes.string,
href: PropTypes.string,
menuItems: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string,
href: PropTypes.string,
content: PropTypes.string,
}),
),
},
),
mainMenu: PropTypes.arrayOf(
PropTypes.shape(
{
type: PropTypes.string,
href: PropTypes.string,
content: PropTypes.string,
},
),
),
};
StudioHeader.defaultProps = {
appMenu: null,
mainMenu: [],
};
export default injectIntl(StudioHeader);

View File

@@ -1,135 +0,0 @@
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 { StudioHeader } from './index';
describe('<StudioHeader />', () => {
it('renders correctly', () => {
const component = (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_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,
},
}}
>
<StudioHeader />
</AppContext.Provider>
</IntlProvider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly with the optional app menu', () => {
const appMenu = {
content: 'App Menu',
menuItems: [
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 1',
},
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 2',
},
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 3',
},
],
};
const component = (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_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,
},
}}
>
<StudioHeader appMenu={appMenu} />
</AppContext.Provider>
</IntlProvider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly with the optional main menu', () => {
const mainMenu = [
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 1',
},
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 2',
},
{
type: 'dropdown',
href: 'https://menu-href-url.org',
content: 'Content 3',
},
];
const component = (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_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,
},
}}
>
<StudioHeader mainMenu={mainMenu} />
</AppContext.Provider>
</IntlProvider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -33,6 +33,7 @@ exports[`<Header /> renders correctly for anonymous desktop 1`] = `
<a
className="nav-link"
href="http://localhost:18000/dashboard"
onClick={null}
>
Courses
</a>
@@ -93,7 +94,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
height="24px"
role="img"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}
@@ -163,7 +164,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}
@@ -175,7 +176,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
height="24px"
role="img"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}
@@ -229,6 +230,7 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<a
className="nav-link"
href="http://localhost:18000/dashboard"
onClick={null}
>
Courses
</a>
@@ -253,7 +255,7 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
{
"height": "1.5em",
"width": "1.5em",
}
@@ -265,7 +267,7 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
height="24px"
role="img"
style={
Object {
{
"height": "1.5em",
"width": "1.5em",
}
@@ -339,7 +341,7 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
height="24px"
role="img"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}
@@ -409,7 +411,7 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}
@@ -421,7 +423,7 @@ exports[`<Header /> renders correctly for authenticated mobile 1`] = `
height="24px"
role="img"
style={
Object {
{
"height": "1.5rem",
"width": "1.5rem",
}

View File

@@ -1,425 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<StudioHeader /> renders correctly 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<img
alt="edX"
className="logo"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
<nav
aria-label="Main"
className="nav main-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[`<StudioHeader /> renders correctly with the optional app menu 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<img
alt="edX"
className="logo"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
<nav
aria-label="Main"
className="nav main-nav"
/>
<nav
aria-label="App"
className="nav app-nav"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<a
aria-expanded={false}
aria-haspopup="menu"
className="menu-trigger nav-link d-inline-flex align-items-center"
onClick={[Function]}
>
App Menu
<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>
</a>
</div>
</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[`<StudioHeader /> renders correctly with the optional main menu 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<img
alt="edX"
className="logo"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
<nav
aria-label="Main"
className="nav main-nav"
>
<div
className="menu nav-item"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<a
aria-expanded={false}
aria-haspopup="menu"
className="menu-trigger nav-link d-inline-flex align-items-center"
href="https://menu-href-url.org"
onClick={[Function]}
>
Content 1
<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>
</a>
</div>
<div
className="menu nav-item"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<a
aria-expanded={false}
aria-haspopup="menu"
className="menu-trigger nav-link d-inline-flex align-items-center"
href="https://menu-href-url.org"
onClick={[Function]}
>
Content 2
<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>
</a>
</div>
<div
className="menu nav-item"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<a
aria-expanded={false}
aria-haspopup="menu"
className="menu-trigger nav-link d-inline-flex align-items-center"
href="https://menu-href-url.org"
onClick={[Function]}
>
Content 3
<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>
</a>
</div>
</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>
`;

View File

@@ -0,0 +1,153 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from '../Menu';
import Avatar from '../Avatar';
import LogoSlot from '../plugin-slots/LogoSlot';
import DesktopLoggedOutItemsSlot from '../plugin-slots/DesktopLoggedOutItemsSlot';
import { desktopLoggedOutItemsDataShape } from './DesktopLoggedOutItems';
import DesktopMainMenuSlot from '../plugin-slots/DesktopMainMenuSlot';
import { desktopHeaderMainOrSecondaryMenuDataShape } from './DesktopHeaderMainOrSecondaryMenu';
import DesktopSecondaryMenuSlot from '../plugin-slots/DesktopSecondaryMenuSlot';
import DesktopUserMenuSlot from '../plugin-slots/DesktopUserMenuSlot';
import { desktopUserMenuDataShape } from './DesktopHeaderUserMenu';
// i18n
import messages from '../Header.messages';
// Assets
import { CaretIcon } from '../Icons';
class DesktopHeader extends React.Component {
constructor(props) { // eslint-disable-line no-useless-constructor
super(props);
}
renderMainMenu() {
const { mainMenu } = this.props;
return <DesktopMainMenuSlot menu={mainMenu} />;
}
renderSecondaryMenu() {
const { secondaryMenu } = this.props;
return <DesktopSecondaryMenuSlot menu={secondaryMenu} />;
}
renderUserMenu() {
const {
userMenu,
avatar,
username,
intl,
} = this.props;
return (
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
<MenuTrigger
tag="button"
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
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" />
</MenuTrigger>
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
<DesktopUserMenuSlot menu={userMenu} />
</MenuContent>
</Menu>
);
}
renderLoggedOutItems() {
const { loggedOutItems } = this.props;
return <DesktopLoggedOutItemsSlot items={loggedOutItems} />;
}
render() {
const {
logo,
logoAltText,
logoDestination,
loggedIn,
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">
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
<div className={`container-fluid ${logoClasses}`}>
<div className="nav-container position-relative d-flex align-items-center">
<LogoSlot {...logoProps} />
<nav
aria-label={intl.formatMessage(messages['header.label.main.nav'])}
className="nav main-nav"
>
{this.renderMainMenu()}
</nav>
<nav
aria-label={intl.formatMessage(messages['header.label.secondary.nav'])}
className="nav secondary-menu-container align-items-center ml-auto"
>
{loggedIn
? (
<>
{this.renderSecondaryMenu()}
{this.renderUserMenu()}
</>
) : this.renderLoggedOutItems()}
</nav>
</div>
</div>
</header>
);
}
}
export const desktopHeaderDataShape = {
mainMenu: desktopHeaderMainOrSecondaryMenuDataShape,
secondaryMenu: desktopHeaderMainOrSecondaryMenuDataShape,
userMenu: desktopUserMenuDataShape,
loggedOutItems: desktopLoggedOutItemsDataShape,
logo: PropTypes.string,
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
loggedIn: PropTypes.bool,
};
DesktopHeader.propTypes = {
mainMenu: desktopHeaderDataShape.mainMenu,
secondaryMenu: desktopHeaderDataShape.secondaryMenumainMenu,
userMenu: desktopHeaderDataShape.userMenumainMenu,
loggedOutItems: desktopHeaderDataShape.loggedOutItemsmainMenu,
logo: desktopHeaderDataShape.logomainMenu,
logoAltText: desktopHeaderDataShape.logoAltTextmainMenu,
logoDestination: desktopHeaderDataShape.logoDestinationmainMenu,
avatar: desktopHeaderDataShape.avatarmainMenu,
username: desktopHeaderDataShape.usernamemainMenu,
loggedIn: desktopHeaderDataShape.loggedInmainMenu,
// i18n
intl: intlShape.isRequired,
};
DesktopHeader.defaultProps = {
mainMenu: [],
secondaryMenu: [],
userMenu: [],
loggedOutItems: [],
logo: null,
logoAltText: null,
logoDestination: null,
avatar: null,
username: null,
loggedIn: false,
};
export default injectIntl(DesktopHeader);

View File

@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu, MenuTrigger, MenuContent } from '../Menu';
import { CaretIcon } from '../Icons';
const DesktopHeaderMainOrSecondaryMenu = ({ menu }) => {
// Nodes are accepted as a prop
if (!Array.isArray(menu)) {
return menu;
}
return menu.map((menuItem) => {
const {
type,
href,
content,
submenuContent,
disabled,
isActive,
onClick,
} = menuItem;
if (type === 'item') {
return (
<a
key={`${type}-${content}`}
className={`nav-link${disabled ? ' disabled' : ''}${isActive ? ' active' : ''}`}
href={href}
onClick={onClick || null}
>
{content}
</a>
);
}
return (
<Menu key={`${type}-${content}`} tag="div" className="nav-item" respondToPointerEvents>
<MenuTrigger onClick={onClick || null} tag="a" className="nav-link d-inline-flex align-items-center" href={href}>
{content} <CaretIcon role="img" aria-hidden focusable="false" />
</MenuTrigger>
<MenuContent className="pin-left pin-right shadow py-2">
{submenuContent}
</MenuContent>
</Menu>
);
});
};
export const desktopHeaderMainOrSecondaryMenuDataShape = PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]);
DesktopHeaderMainOrSecondaryMenu.propTypes = {
menu: desktopHeaderMainOrSecondaryMenuDataShape,
};
export default DesktopHeaderMainOrSecondaryMenu;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
const DesktopHeaderUserMenu = ({ menu }) => menu.map((group, index) => (
// eslint-disable-next-line react/jsx-no-comment-textnodes,react/no-array-index-key
<React.Fragment key={index}>
{group.heading && <div className="dropdown-header" role="heading" aria-level="1">{group.heading}</div>}
{group.items.map(({
type, content, href, disabled, isActive, onClick,
}) => (
<a
className={`dropdown-${type}${isActive ? ' active' : ''}${disabled ? ' disabled' : ''}`}
key={`${type}-${content}`}
href={href}
onClick={onClick || null}
>
{content}
</a>
))}
{index < menu.length - 1 && <div className="dropdown-divider" role="separator" />}
</React.Fragment>
));
export const desktopUserMenuDataShape = PropTypes.arrayOf(PropTypes.shape({
heading: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
isActive: PropTypes.bool,
onClick: PropTypes.func,
})),
}));
DesktopHeaderUserMenu.propTypes = {
menu: desktopUserMenuDataShape,
};
export default DesktopHeaderUserMenu;

View File

@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
const DesktopLoggedOutItems = ({ items }) => items.map((item, i, arr) => (
<a
key={`${item.type}-${item.content}`}
className={i < arr.length - 1 ? 'btn mr-2 btn-link' : 'btn mr-2 btn-outline-primary'}
href={item.href}
>
{item.content}
</a>
));
export const desktopLoggedOutItemsDataShape = PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
}));
DesktopLoggedOutItems.propTypes = {
items: desktopLoggedOutItemsDataShape,
};
export default DesktopLoggedOutItems;

View File

@@ -1,34 +1 @@
import arMessages from './messages/ar.json';
import caMessages from './messages/ca.json';
import heMessages from './messages/he.json';
import idMessages from './messages/id.json';
import plMessages from './messages/pl.json';
import ruMessages from './messages/ru.json';
import thMessages from './messages/th.json';
import ukMessages from './messages/uk.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 kokrMessages from './messages/ko_KR.json';
import ptbrMessages from './messages/pt_BR.json';
import zhcnMessages from './messages/zh_CN.json';
const messages = {
ar: arMessages,
ca: caMessages,
he: heMessages,
id: idMessages,
pl: plMessages,
ru: ruMessages,
th: thMessages,
uk: ukMessages,
'es-419': es419Messages,
fr: frMessages,
'zh-cn': zhcnMessages,
'ko-kr': kokrMessages,
'pt-br': ptbrMessages,
};
export default messages;
export default {};

View File

@@ -1,33 +0,0 @@
{
"general.register.sentenceCase": "التسجيل",
"general.signIn.sentenceCase": "تسجيل الدخول",
"header.links.courses": "مساقات",
"header.links.programs": "برامج",
"header.links.content.search": "استكشف الجديد",
"header.links.schools": "المدارس والشركاء",
"header.user.menu.dashboard": "لوحة المعلومات",
"header.user.menu.profile": "الملف الشخصي",
"header.user.menu.account.settings": "حساب",
"header.user.menu.order.history": "سجل الطلبات",
"header.user.menu.logout": "تسجيل الخروج",
"header.user.menu.login": "تسجيل الدخول",
"header.user.menu.register": "تسجيل ",
"header.user.menu.studio.home": "Studio Home",
"header.user.menu.studio.maintenance": "Maintenance",
"header.label.account.nav": "حساب",
"header.label.account.menu": "قائمة الحساب",
"header.label.account.menu.for": "قائمة الحساب للمستخدم {username}",
"header.label.main.nav": "الرئيسية",
"header.label.main.menu": "القائمة الرئيسية",
"header.label.main.header": "الرئيسية",
"header.label.secondary.nav": "فرعي",
"header.label.skip.nav": "التخطي إلى المحتوى الرئيسي",
"header.label.app.nav": "App",
"header.menu.dashboard.label": "لوحة المعلومات",
"header.help.label": "مساعدة",
"header.menu.profile.label": "الملف الشخصي",
"header.menu.account.label": "حساب",
"header.menu.orderHistory.label": "سجل الطلبات",
"header.navigation.skipNavLink": "التخطي إلى المحتوى الرئيسي",
"header.menu.signOut.label": "تسجيل الخروج"
}

View File

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

View File

@@ -1,33 +0,0 @@
{
"general.register.sentenceCase": "Registrarse",
"general.signIn.sentenceCase": "Iniciar sesión",
"header.links.courses": "Cursos",
"header.links.programs": "Programas",
"header.links.content.search": "Encontrar nuevo",
"header.links.schools": "Escuelas y Socios",
"header.user.menu.dashboard": "Panel de Control",
"header.user.menu.profile": "Perfil",
"header.user.menu.account.settings": "Cuenta",
"header.user.menu.order.history": "Historial de órdenes",
"header.user.menu.logout": "Cerrar sesión",
"header.user.menu.login": "Login",
"header.user.menu.register": "Registrarse",
"header.user.menu.studio.home": "Inicio Studio",
"header.user.menu.studio.maintenance": "Mantenimiento",
"header.label.account.nav": "Cuenta",
"header.label.account.menu": "Menú de la cuenta",
"header.label.account.menu.for": "Menú de la cuenta para {username}",
"header.label.main.nav": "Principal",
"header.label.main.menu": "Menú Principal",
"header.label.main.header": "Principal",
"header.label.secondary.nav": "Secondary",
"header.label.skip.nav": "Ir al contenido principal",
"header.label.app.nav": "Aplicación",
"header.menu.dashboard.label": "Panel de Control",
"header.help.label": "Ayuda",
"header.menu.profile.label": "Perfil",
"header.menu.account.label": "Cuenta",
"header.menu.orderHistory.label": "Historial de órdenes",
"header.navigation.skipNavLink": "Dirígete al contenido principal.",
"header.menu.signOut.label": "Cerrar sesión"
}

View File

@@ -1,33 +0,0 @@
{
"general.register.sentenceCase": "S'inscrire",
"general.signIn.sentenceCase": "Connectez-vous",
"header.links.courses": "Cours",
"header.links.programs": "Programmes",
"header.links.content.search": "Explorer les cours",
"header.links.schools": "Écoles et partenaires",
"header.user.menu.dashboard": "Tableau de bord",
"header.user.menu.profile": "Profil",
"header.user.menu.account.settings": "Compte",
"header.user.menu.order.history": "Historique des commandes",
"header.user.menu.logout": "Déconnexion",
"header.user.menu.login": "Connexion",
"header.user.menu.register": "S'inscrire",
"header.user.menu.studio.home": "Accueil Studio",
"header.user.menu.studio.maintenance": "Maintenance",
"header.label.account.nav": "Compte",
"header.label.account.menu": "Menu du compte",
"header.label.account.menu.for": "Menu du compte pour {username}",
"header.label.main.nav": "Principal",
"header.label.main.menu": "Menu Principal",
"header.label.main.header": "Principal",
"header.label.secondary.nav": "Secondaire",
"header.label.skip.nav": "Passer au contenu principal",
"header.label.app.nav": "Application",
"header.menu.dashboard.label": "Tableau de bord",
"header.help.label": "Aide",
"header.menu.profile.label": "Profil",
"header.menu.account.label": "Compte",
"header.menu.orderHistory.label": "Historique des commandes",
"header.navigation.skipNavLink": "Passer au contenu principal",
"header.menu.signOut.label": "Se déconnecter"
}

View File

@@ -1,33 +0,0 @@
{
"general.register.sentenceCase": "Inscription",
"general.signIn.sentenceCase": "Connexion",
"header.links.courses": "Cours",
"header.links.programs": "Programmes",
"header.links.content.search": "Découvrir les nouveautés",
"header.links.schools": "Écoles et Partenaires",
"header.user.menu.dashboard": "Tableau de bord",
"header.user.menu.profile": "Profil",
"header.user.menu.account.settings": "Compte",
"header.user.menu.order.history": "Historique des commandes",
"header.user.menu.logout": "Déconnexion",
"header.user.menu.login": "Connexion",
"header.user.menu.register": "S'inscrire",
"header.user.menu.studio.home": "Accueil Studio",
"header.user.menu.studio.maintenance": "Entretien",
"header.label.account.nav": "Compte",
"header.label.account.menu": "Menu de compte",
"header.label.account.menu.for": "Menu de compte pour {username}",
"header.label.main.nav": "Principal",
"header.label.main.menu": "Menu principal",
"header.label.main.header": "Principal",
"header.label.secondary.nav": "Secondaire",
"header.label.skip.nav": "Passer au contenu de cette vue",
"header.label.app.nav": "Application",
"header.menu.dashboard.label": "Tableau de bord",
"header.help.label": "Aide",
"header.menu.profile.label": "Profil",
"header.menu.account.label": "Compte",
"header.menu.orderHistory.label": "Historique des commandes",
"header.navigation.skipNavLink": "Passer au contenu principal.",
"header.menu.signOut.label": "Se déconnecter"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
{
"general.register.sentenceCase": "Register",
"general.signIn.sentenceCase": "Sign in",
"header.links.courses": "Courses",
"header.links.programs": "Programs",
"header.links.content.search": "Discover New",
"header.links.schools": "Schools & Partners",
"header.user.menu.dashboard": "Dashboard",
"header.user.menu.profile": "Profile",
"header.user.menu.account.settings": "Account",
"header.user.menu.order.history": "Order History",
"header.user.menu.logout": "Logout",
"header.user.menu.login": "Login",
"header.user.menu.register": "Sign Up",
"header.user.menu.studio.home": "Studio Home",
"header.user.menu.studio.maintenance": "Maintenance",
"header.label.account.nav": "Account",
"header.label.account.menu": "Account Menu",
"header.label.account.menu.for": "Account menu for {username}",
"header.label.main.nav": "Main",
"header.label.main.menu": "Main Menu",
"header.label.main.header": "Main",
"header.label.secondary.nav": "Secondary",
"header.label.skip.nav": "Skip to main content",
"header.label.app.nav": "App",
"header.menu.dashboard.label": "Dashboard",
"header.help.label": "Help",
"header.menu.profile.label": "Profile",
"header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out"
}

View File

@@ -1,7 +1,7 @@
import Header from './Header';
import LearningHeader from './learning-header/LearningHeader';
import messages from './i18n/index';
import StudioHeader from './StudioHeader';
import StudioHeader from './studio-header';
export { LearningHeader, messages, StudioHeader };

View File

@@ -3,6 +3,7 @@ $blue: #007db8;
$white: #fff;
@import './Menu/menu.scss';
@import './studio-header/StudioHeader.scss';
.dropdown-item a {
text-decoration: none;
@@ -27,7 +28,7 @@ $white: #fff;
.learning-header {
min-width: 0;
.course-title-lockup {
min-width: 0;
@@ -42,9 +43,9 @@ $white: #fff;
.user-dropdown {
.btn {
height: 3rem;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
padding: 0 0.5rem;
}
// @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
// padding: 0 0.5rem;
// }
}
}
}
@@ -87,6 +88,15 @@ $white: #fff;
height: 100%;
}
}
.secondary-menu-container {
.nav-link:hover,
.nav-link:focus,
.nav-link.active,
.expanded .nav-link {
background: $component-active-bg;
color: $component-active-color;
}
}
.main-nav {
.nav-link {
padding: 1.125rem 1rem;

View File

@@ -3,29 +3,25 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import LearningLoggedOutItemsSlot from '../plugin-slots/LearningLoggedOutItemsSlot';
import genericMessages from '../generic/messages';
function AnonymousUserMenu({ intl }) {
return (
<div>
<Button
className="mr-3"
variant="outline-primary"
href={`${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`}
>
{intl.formatMessage(genericMessages.registerSentenceCase)}
</Button>
<Button
variant="primary"
href={`${getLoginRedirectUrl(global.location.href)}`}
>
{intl.formatMessage(genericMessages.signInSentenceCase)}
</Button>
</div>
);
}
const AnonymousUserMenu = ({ intl }) => {
const buttonsInfo = [
{
message: intl.formatMessage(genericMessages.registerSentenceCase),
href: `${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`,
},
{
message: intl.formatMessage(genericMessages.signInSentenceCase),
href: getLoginRedirectUrl(global.location.href),
variant: 'primary',
},
];
return <LearningLoggedOutItemsSlot buttonsInfo={buttonsInfo} />;
};
AnonymousUserMenu.propTypes = {
intl: intlShape.isRequired,

View File

@@ -3,51 +3,52 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Dropdown } from '@edx/paragon';
import { Dropdown } from '@openedx/paragon';
import LearningUserMenuSlot from '../plugin-slots/LearningUserMenuSlot';
import messages from './messages';
function AuthenticatedUserDropdown({ intl, username }) {
const dashboardMenuItem = (
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages.dashboard)}
</Dropdown.Item>
);
const AuthenticatedUserDropdown = ({ intl, username }) => {
const dropdownItems = [
{
message: intl.formatMessage(messages.dashboard),
href: `${getConfig().LMS_BASE_URL}/dashboard`,
},
{
message: intl.formatMessage(messages.profile),
href: `${getConfig().ACCOUNT_PROFILE_URL}/u/${username}`,
},
{
message: intl.formatMessage(messages.account),
href: getConfig().ACCOUNT_SETTINGS_URL,
},
...(getConfig().ORDER_HISTORY_URL ? [{
message: intl.formatMessage(messages.orderHistory),
href: getConfig().ORDER_HISTORY_URL,
}] : []),
{
message: intl.formatMessage(messages.signOut),
href: getConfig().LOGOUT_URL,
},
];
return (
<>
<a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
<Dropdown className="user-dropdown">
<Dropdown.Toggle variant="outline-primary">
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
<span data-hj-suppress className="d-none d-md-inline">
{username}
</span>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
{dashboardMenuItem}
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/u/${username}`}>
{intl.formatMessage(messages.profile)}
</Dropdown.Item>
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
{intl.formatMessage(messages.account)}
</Dropdown.Item>
{ getConfig().ORDER_HISTORY_URL && (
<Dropdown.Item href={getConfig().ORDER_HISTORY_URL}>
{intl.formatMessage(messages.orderHistory)}
</Dropdown.Item>
)}
<Dropdown.Item href={getConfig().LOGOUT_URL}>
{intl.formatMessage(messages.signOut)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</>
<Dropdown className="user-dropdown ml-3">
<Dropdown.Toggle variant="outline-primary">
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
<span data-hj-suppress className="d-none d-md-inline">
{username}
</span>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
<LearningUserMenuSlot items={dropdownItems} />
</Dropdown.Menu>
</Dropdown>
);
}
};
AuthenticatedUserDropdown.propTypes = {
intl: intlShape.isRequired,

View File

@@ -6,35 +6,19 @@ import { AppContext } from '@edx/frontend-platform/react';
import AnonymousUserMenu from './AnonymousUserMenu';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import LogoSlot from '../plugin-slots/LogoSlot';
import CourseInfoSlot from '../plugin-slots/CourseInfoSlot';
import { courseInfoDataShape } from './LearningHeaderCourseInfo';
import messages from './messages';
import LearningHelpSlot from '../plugin-slots/LearningHelpSlot';
function LinkedLogo({
href,
src,
alt,
...attributes
}) {
return (
<a href={href} {...attributes}>
<img className="d-block" src={src} alt={alt} />
</a>
);
}
LinkedLogo.propTypes = {
href: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
};
function LearningHeader({
const LearningHeader = ({
courseOrg, courseNumber, courseTitle, intl, showUserDropdown,
}) {
}) => {
const { authenticatedUser } = useContext(AppContext);
const headerLogo = (
<LinkedLogo
className="logo"
<LogoSlot
href={`${getConfig().LMS_BASE_URL}/dashboard`}
src={getConfig().LOGO_URL}
alt={getConfig().SITE_NAME}
@@ -46,27 +30,29 @@ function LearningHeader({
<a className="sr-only sr-only-focusable" href="#main-content">{intl.formatMessage(messages.skipNavLink)}</a>
<div className="container-xl py-2 d-flex align-items-center">
{headerLogo}
<div className="flex-grow-1 course-title-lockup" style={{ lineHeight: 1 }}>
<span className="d-block small m-0">{courseOrg} {courseNumber}</span>
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
<div className="flex-grow-1 course-title-lockup d-flex" style={{ lineHeight: 1 }}>
<CourseInfoSlot courseOrg={courseOrg} courseNumber={courseNumber} courseTitle={courseTitle} />
</div>
{showUserDropdown && authenticatedUser && (
<>
<LearningHelpSlot />
<AuthenticatedUserDropdown
username={authenticatedUser.username}
/>
</>
)}
{showUserDropdown && !authenticatedUser && (
<AnonymousUserMenu />
<AnonymousUserMenu />
)}
</div>
</header>
);
}
};
LearningHeader.propTypes = {
courseOrg: PropTypes.string,
courseNumber: PropTypes.string,
courseTitle: PropTypes.string,
courseOrg: courseInfoDataShape.courseOrg,
courseNumber: courseInfoDataShape.courseNumber,
courseTitle: courseInfoDataShape.courseTitle,
intl: intlShape.isRequired,
showUserDropdown: PropTypes.bool,
};

View File

@@ -12,7 +12,7 @@ describe('Header', () => {
it('displays user button', () => {
render(<Header />);
expect(screen.getByRole('button')).toHaveTextContent(authenticatedUser.username);
expect(screen.getByText(authenticatedUser.username)).toBeInTheDocument();
});
it('displays course data', () => {

View File

@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
const LearningHeaderCourseInfo = ({
courseOrg,
courseNumber,
courseTitle,
}) => (
<div style={{ minWidth: 0 }}>
<span className="d-block small m-0">{courseOrg} {courseNumber}</span>
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
</div>
);
export const courseInfoDataShape = {
courseOrg: PropTypes.string,
courseNumber: PropTypes.string,
courseTitle: PropTypes.string,
};
LearningHeaderCourseInfo.propTypes = courseInfoDataShape;
export default LearningHeaderCourseInfo;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
const LearningHeaderHelpLink = () => {
const intl = useIntl();
return (
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
);
};
export default LearningHeaderHelpLink;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown } from '@openedx/paragon';
const LearningHeaderUserMenuItems = ({ items }) => items.map((item) => (
<Dropdown.Item href={item.href}>
{item.message}
</Dropdown.Item>
));
export const learningHeaderUserMenuDataShape = {
items: PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.string,
href: PropTypes.string,
})),
};
LearningHeaderUserMenuItems.propTypes = learningHeaderUserMenuDataShape;
export default LearningHeaderUserMenuItems;

View File

@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from '@openedx/paragon';
const LearningLoggedOutButtons = ({ buttonsInfo }) => buttonsInfo.map(buttonInfo => (
<Button
className="ml-3"
variant={buttonInfo.variant ?? 'outline-primary'}
href={buttonInfo.href}
>
{buttonInfo.message}
</Button>
));
export const learningHeaderLoggedOutItemsDataShape = {
buttonsInfo: PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.string,
href: PropTypes.string,
variant: PropTypes.string,
})),
};
LearningLoggedOutButtons.propTypes = learningHeaderLoggedOutItemsDataShape;
export default LearningLoggedOutButtons;

View File

@@ -4,15 +4,21 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
import { Menu, MenuTrigger, MenuContent } from '../Menu';
import Avatar from '../Avatar';
import LogoSlot from '../plugin-slots/LogoSlot';
import MobileLoggedOutItemsSlot from '../plugin-slots/MobileLoggedOutItemsSlot';
import { mobileHeaderLoggedOutItemsDataShape } from './MobileLoggedOutItems';
import MobileMainMenuSlot from '../plugin-slots/MobileMainMenuSlot';
import { mobileHeaderMainMenuDataShape } from './MobileHeaderMainMenu';
import MobileUserMenuSlot from '../plugin-slots/MobileUserMenuSlot';
import { mobileHeaderUserMenuDataShape } from './MobileHeaderUserMenu';
// i18n
import messages from './Header.messages';
import messages from '../Header.messages';
// Assets
import { MenuIcon } from './Icons';
import { MenuIcon } from '../Icons';
class MobileHeader extends React.Component {
constructor(props) { // eslint-disable-line no-useless-constructor
@@ -20,65 +26,18 @@ class MobileHeader extends React.Component {
}
renderMainMenu() {
const { mainMenu } = this.props;
// Nodes are accepted as a prop
if (!Array.isArray(mainMenu)) {
return mainMenu;
}
return mainMenu.map((menuItem) => {
const {
type,
href,
content,
submenuContent,
} = menuItem;
if (type === 'item') {
return (
<a key={`${type}-${content}`} className="nav-link" href={href}>
{content}
</a>
);
}
return (
<Menu key={`${type}-${content}`} tag="div" className="nav-item">
<MenuTrigger tag="a" role="button" tabIndex="0" className="nav-link">
{content}
</MenuTrigger>
<MenuContent className="position-static pin-left pin-right py-2">
{submenuContent}
</MenuContent>
</Menu>
);
});
const { mainMenu, secondaryMenu } = this.props;
return <MobileMainMenuSlot menu={[...mainMenu, ...secondaryMenu]} />;
}
renderUserMenuItems() {
const { userMenu } = this.props;
return userMenu.map(({ type, href, content }) => (
<li className="nav-item" key={`${type}-${content}`}>
<a className="nav-link" href={href}>{content}</a>
</li>
));
return <MobileUserMenuSlot menu={userMenu} />;
}
renderLoggedOutItems() {
const { loggedOutItems } = this.props;
return loggedOutItems.map(({ type, href, content }, i, arr) => (
<li className="nav-item px-3 my-2" key={`${type}-${content}`}>
<a
className={i < arr.length - 1 ? 'btn btn-block btn-outline-primary' : 'btn btn-block btn-primary'}
href={href}
>
{content}
</a>
</li>
));
return <MobileLoggedOutItemsSlot items={loggedOutItems} />;
}
render() {
@@ -128,7 +87,7 @@ class MobileHeader extends React.Component {
</div>
) : null}
<div className={`w-100 d-flex ${logoClasses}`}>
{ logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} itemType="http://schema.org/Organization" />}
<LogoSlot {...logoProps} itemType="http://schema.org/Organization" />
</div>
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
<div className="w-100 d-flex justify-content-end align-items-center">
@@ -152,22 +111,11 @@ class MobileHeader extends React.Component {
}
}
MobileHeader.propTypes = {
mainMenu: PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]),
userMenu: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
loggedOutItems: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
export const mobileHeaderDataShape = {
mainMenu: mobileHeaderMainMenuDataShape,
secondaryMenu: mobileHeaderMainMenuDataShape,
userMenu: mobileHeaderUserMenuDataShape,
loggedOutItems: mobileHeaderLoggedOutItemsDataShape,
logo: PropTypes.string,
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
@@ -175,6 +123,20 @@ MobileHeader.propTypes = {
username: PropTypes.string,
loggedIn: PropTypes.bool,
stickyOnMobile: PropTypes.bool,
};
MobileHeader.propTypes = {
mainMenu: mobileHeaderDataShape.mainMenu,
secondaryMenu: mobileHeaderDataShape.secondaryMenu,
userMenu: mobileHeaderDataShape.userMenu,
loggedOutItems: mobileHeaderDataShape.loggedOutItems,
logo: mobileHeaderDataShape.logo,
logoAltText: mobileHeaderDataShape.logoAltText,
logoDestination: mobileHeaderDataShape.logoDestination,
avatar: mobileHeaderDataShape.avatar,
username: mobileHeaderDataShape.username,
loggedIn: mobileHeaderDataShape.loggedIn,
stickyOnMobile: mobileHeaderDataShape.stickyOnMobile,
// i18n
intl: intlShape.isRequired,
@@ -182,6 +144,7 @@ MobileHeader.propTypes = {
MobileHeader.defaultProps = {
mainMenu: [],
secondaryMenu: [],
userMenu: [],
loggedOutItems: [],
logo: null,

View File

@@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu, MenuTrigger, MenuContent } from '../Menu';
const MobileHeaderMainMenu = ({ menu }) => {
// Nodes are accepted as a prop
if (!Array.isArray(menu)) {
return menu;
}
return menu.map((menuItem) => {
const {
type,
href,
content,
submenuContent,
disabled,
isActive,
onClick,
} = menuItem;
if (type === 'item') {
return (
<a
key={`${type}-${content}`}
className={`nav-link${disabled ? ' disabled' : ''}${isActive ? ' active' : ''}`}
href={href}
onClick={onClick || null}
>
{content}
</a>
);
}
return (
<Menu key={`${type}-${content}`} tag="div" className="nav-item">
<MenuTrigger onClick={onClick || null} tag="a" role="button" tabIndex="0" className="nav-link">
{content}
</MenuTrigger>
<MenuContent className="position-static pin-left pin-right py-2">
{submenuContent}
</MenuContent>
</Menu>
);
});
};
export const mobileHeaderMainMenuDataShape = PropTypes.oneOfType([
PropTypes.node,
PropTypes.array,
]);
MobileHeaderMainMenu.propTypes = {
menu: mobileHeaderMainMenuDataShape,
};
export default MobileHeaderMainMenu;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
const MobileHeaderUserMenu = ({ menu }) => menu.map((group) => (
group.items.map(({
type, content, href, disabled, isActive, onClick,
}) => (
<li className="nav-item" key={`${type}-${content}`}>
<a
className={`nav-link${isActive ? ' active' : ''}${disabled ? ' disabled' : ''}`}
href={href}
onClick={onClick || null}
>
{content}
</a>
</li>
))
));
export const mobileHeaderUserMenuDataShape = PropTypes.arrayOf(PropTypes.shape({
heading: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
isActive: PropTypes.bool,
onClick: PropTypes.func,
})),
}));
MobileHeaderUserMenu.propTypes = {
menu: mobileHeaderUserMenuDataShape,
};
export default MobileHeaderUserMenu;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
const MobileLoggedOutItems = ({ items }) => items.map(({ type, href, content }, i, arr) => (
<li className="nav-item px-3 my-2" key={`${type}-${content}`}>
<a
className={i < arr.length - 1 ? 'btn btn-block btn-outline-primary' : 'btn btn-block btn-primary'}
href={href}
>
{content}
</a>
</li>
));
export const mobileHeaderLoggedOutItemsDataShape = PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
}));
MobileLoggedOutItems.propTypes = {
menu: mobileHeaderLoggedOutItemsDataShape,
};
export default MobileLoggedOutItems;

View File

@@ -0,0 +1,125 @@
# Course Info Slot
### Slot ID: `course_info_slot`
## Description
This slot is used to replace/modify/hide the course info.
## Examples
### Replace Course Title
The following `env.config.jsx` will replace the course title.
![Screenshot of replaced course title](./images/replace_course_title.png)
```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const replaceCourseTitle = ( widget ) => {
widget.content.courseTitle = "Custom Course Title";
return widget;
};
const config = {
pluginSlots: {
course_info_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: replaceCourseTitle,
},
]
},
},
}
export default config;
```
### Replace Course Info with Custom Component
The following `env.config.jsx` will replace the course info entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of replaced course info with custom component](./images/replace_course_info_with_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
course_info_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_course_info_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
}
},
}
export default config;
```
### Add Custom Components before and after Course Info
The following `env.config.jsx` will place custom components before and after the course info (in this case centered `h1`s with 🌜 and 🌛).
![Screenshot of added custom components before and after course info](./images/add_custom_components_before_and_after_course_info.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
course_info_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_course_info_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h3 style={{
marginTop: 'auto',
marginBottom: 'auto',
marginRight: '0.5rem',
}}>🌜</h3>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_course_info_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h3 style={{
marginTop: 'auto',
marginBottom: 'auto',
marginLeft: '0.5rem',
}}>🌛</h3>
),
},
},
]
},
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import LearningHeaderCourseInfo, { courseInfoDataShape } from '../../learning-header/LearningHeaderCourseInfo';
const CourseInfoSlot = ({
courseOrg,
courseNumber,
courseTitle,
...attributes
}) => (
<PluginSlot
id="course_info_slot"
slotOptions={{
mergeProps: true,
}}
pluginProps={{
courseOrg,
courseNumber,
courseTitle,
}}
>
<LearningHeaderCourseInfo
courseOrg={courseOrg}
courseNumber={courseNumber}
courseTitle={courseTitle}
{...attributes}
/>
</PluginSlot>
);
CourseInfoSlot.propTypes = courseInfoDataShape;
export default CourseInfoSlot;

View File

@@ -0,0 +1,41 @@
# Desktop Header Slot
### Slot ID: `desktop_header_slot`
## Description
This slot is used to replace/modify/hide the entire desktop header.
## Examples
### Custom Component
The following `env.config.jsx` will replace the desktop header entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of custom component](./images/desktop_header_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_header_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_desktop_header_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import DesktopHeader, { desktopHeaderDataShape } from '../../desktop-header/DesktopHeader';
const DesktopHeaderSlot = ({
props,
}) => (
<PluginSlot
id="desktop_header_slot"
slotOptions={{
mergeProps: true,
}}
>
<DesktopHeader {...props} />
</PluginSlot>
);
DesktopHeaderSlot.propTypes = desktopHeaderDataShape;
export default DesktopHeaderSlot;

View File

@@ -0,0 +1,134 @@
# Desktop Logged Out Items Slot
### Slot ID: `desktop_logged_out_items_slot`
## Description
This slot is used to replace/modify/hide the items shown on desktop when the user is logged out.
## Examples
### Modify Items
The following `env.config.jsx` will modify the items shown on desktop when the user is logged out.
![Screenshot of modified items](./images/desktop_logged_out_items_modify_items.png)
```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const modifyLoggedOutItems = ( widget ) => {
widget.content.items = [
{
type: 'item',
href: 'https://openedx.org/',
content: 'openedx.org',
},
{
type: 'item',
href: 'https://docs.openedx.org/en/latest/',
content: 'Documentation',
},
{
type: 'item',
href: 'https://discuss.openedx.org/',
content: 'Forums',
}
];
return widget;
};
const config = {
pluginSlots: {
desktop_logged_out_items_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifyLoggedOutItems,
},
]
},
},
}
export default config;
```
### Replace with Custom Component
The following `env.config.jsx` will replace the items shown on desktop when the user is logged out entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of custom component](./images/desktop_logged_out_items_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_logged_out_items_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_logged_out_items_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
},
},
}
export default config;
```
### Add Custom Components before and after
The following `env.config.jsx` will place custom components before and after the items shown on desktop when the user is logged out (in this case centered `h1`s with 🌜 and 🌛).
![Screenshot of custom components before and after](./images/desktop_logged_out_items_custom_components_before_after.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_logged_out_items_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_logged_out_items_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌜</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_logged_out_items_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌛</h1>
),
},
},
]
},
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import DesktopLoggedOutItems, { desktopLoggedOutItemsDataShape } from '../../desktop-header/DesktopLoggedOutItems';
const DesktopLoggedOutItemsSlot = ({
items,
}) => (
<PluginSlot
id="desktop_logged_out_items_slot"
slotOptions={{
mergeProps: true,
}}
>
<DesktopLoggedOutItems items={items} />
</PluginSlot>
);
DesktopLoggedOutItemsSlot.propTypes = {
items: desktopLoggedOutItemsDataShape,
};
export default DesktopLoggedOutItemsSlot;

View File

@@ -0,0 +1,134 @@
# Desktop Main Menu Slot
### Slot ID: `desktop_main_menu_slot`
## Description
This slot is used to replace/modify/hide the desktop main menu.
## Examples
### Modify Items
The following `env.config.jsx` will modify the items in the desktop main menu.
![Screenshot of modified items](./images/desktop_main_menu_modify_items.png)
```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const modifyMainMenu = ( widget ) => {
widget.content.menu = [
{
type: 'item',
href: 'https://openedx.org/',
content: 'openedx.org',
},
{
type: 'item',
href: 'https://docs.openedx.org/en/latest/',
content: 'Documentation',
},
{
type: 'item',
href: 'https://discuss.openedx.org/',
content: 'Forums',
}
];
return widget;
};
const config = {
pluginSlots: {
desktop_main_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifyMainMenu,
},
]
},
},
}
export default config;
```
### Replace Menu with Custom Component
The following `env.config.jsx` will replace the desktop main menu entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of custom component](./images/desktop_main_menu_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_main_menu_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_main_menu_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
},
},
}
export default config;
```
### Add Custom Components before and after Menu
The following `env.config.jsx` will place custom components before and after the desktop main menu (in this case centered `h1`s with 🌜 and 🌛).
![Screenshot of custom components before and after](./images/desktop_main_menu_custom_components_before_after.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_main_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_main_menu_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌜</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_main_menu_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌛</h1>
),
},
},
]
},
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import DesktopHeaderMainOrSecondaryMenu, { desktopHeaderMainOrSecondaryMenuDataShape } from '../../desktop-header/DesktopHeaderMainOrSecondaryMenu';
const DesktopMainMenuSlot = ({
menu,
}) => (
<PluginSlot
id="desktop_main_menu_slot"
slotOptions={{
mergeProps: true,
}}
>
<DesktopHeaderMainOrSecondaryMenu menu={menu} />
</PluginSlot>
);
DesktopMainMenuSlot.propTypes = {
menu: desktopHeaderMainOrSecondaryMenuDataShape,
};
export default DesktopMainMenuSlot;

View File

@@ -0,0 +1,129 @@
# Desktop Secondary Menu Slot
### Slot ID: `desktop_secondary_menu_slot`
## Description
This slot is used to replace/modify/hide the desktop secondary menu.
## Examples
### Modify Items
The following `env.config.jsx` will modify the items in the desktop secondary menu.
![Screenshot of modified items](./images/desktop_secondary_menu_modify_items.png)
```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const modifySecondaryMenu = ( widget ) => {
widget.content.menu = [
{
type: 'item',
href: 'https://www.youtube.com/c/openedx',
content: 'Open edX on YouTube',
},
{
type: 'item',
href: 'https://github.com/openedx/',
content: 'Open edX on GitHub',
}
];
return widget;
};
const config = {
pluginSlots: {
desktop_secondary_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifySecondaryMenu,
},
]
},
},
}
export default config;
```
### Replace Menu with Custom Component
The following `env.config.jsx` will replace the desktop secondary menu entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of custom component](./images/desktop_secondary_menu_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_secondary_menu_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_secondary_menu_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
},
},
}
export default config;
```
### Add Custom Components before and after Menu
The following `env.config.jsx` will place custom components before and after the desktop secondary menu (in this case centered `h1`s with 🌜 and 🌛).
![Screenshot of custom components before and after](./images/desktop_secondary_menu_custom_components_before_after.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_secondary_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_secondary_menu_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌜</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_secondary_menu_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌛</h1>
),
},
},
]
},
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import DesktopHeaderMainOrSecondaryMenu, { desktopHeaderMainOrSecondaryMenuDataShape } from '../../desktop-header/DesktopHeaderMainOrSecondaryMenu';
const DesktopSecondaryMenuSlot = ({
menu,
}) => (
<PluginSlot
id="desktop_secondary_menu_slot"
slotOptions={{
mergeProps: true,
}}
>
<DesktopHeaderMainOrSecondaryMenu menu={menu} />
</PluginSlot>
);
DesktopSecondaryMenuSlot.propTypes = {
menu: desktopHeaderMainOrSecondaryMenuDataShape,
};
export default DesktopSecondaryMenuSlot;

View File

@@ -0,0 +1,141 @@
# Desktop User Menu Slot
### Slot ID: `desktop_user_menu_slot`
## Description
This slot is used to replace/modify/hide the desktop user menu.
## Examples
### Modify Items
The following `env.config.jsx` will modify the items in the desktop user menu.
![Screenshot of modified items](./images/desktop_user_menu_modify_items.png)
```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const modifyUserMenu = ( widget ) => {
widget.content.menu = [
{
items: [
{
type: 'item',
href: 'https://openedx.org/',
content: 'openedx.org',
},
{
type: 'item',
href: 'https://docs.openedx.org/en/latest/',
content: 'Documentation',
},
]
},
{
items: [
{
type: 'item',
href: 'https://discuss.openedx.org/',
content: 'Forums',
}
]
}
];
return widget;
};
const config = {
pluginSlots: {
desktop_user_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifyUserMenu,
},
]
},
},
}
export default config;
```
### Replace Menu with Custom Component
The following `env.config.jsx` will replace the desktop user menu entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of custom component](./images/desktop_user_menu_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_user_menu_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_user_menu_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
},
},
}
export default config;
```
### Add Custom Components before and after Menu
The following `env.config.jsx` will place custom components before and after the desktop user menu (in this case centered `h1`s with 🌞 and 🌚).
![Screenshot of custom components before and after](./images/desktop_user_menu_custom_components_before_after.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
desktop_user_menu_slot: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_user_menu_component',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌞</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_user_menu_component',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🌚</h1>
),
},
},
]
},
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import DesktopHeaderUserMenu, { desktopUserMenuDataShape } from '../../desktop-header/DesktopHeaderUserMenu';
const DesktopUserMenuSlot = ({
menu,
}) => (
<PluginSlot
id="desktop_user_menu_slot"
slotOptions={{
mergeProps: true,
}}
>
<DesktopHeaderUserMenu menu={menu} />
</PluginSlot>
);
DesktopUserMenuSlot.propTypes = {
menu: desktopUserMenuDataShape,
};
export default DesktopUserMenuSlot;

View File

@@ -0,0 +1,41 @@
# Learning Help Slot
### Slot ID: `learning_help_slot`
## Description
This slot is used to replace/modify/hide the learning help link.
## Examples
### Custom Component
The following `env.config.jsx` will replace the help link entirely (in this case with a centered 🗺️ `h1`)
![Screenshot of replaced learning help with custom component](./images/learning_help_custom_component.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
learning_help_slot: {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_learning_help_component',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🗺</h1>
),
},
},
]
}
},
}
export default config;
```

Some files were not shown because too many files have changed in this diff Show More