Bladeren bron

Remove legacy sidebar, refactoring slim sidebar implementation

Thibaud Colas 3 jaren geleden
bovenliggende
commit
4dc5f3c595
74 gewijzigde bestanden met toevoegingen van 59 en 4387 verwijderingen
  1. 10 0
      client/scss/components/_grid.legacy.scss
  2. 2 1
      client/scss/components/_header.scss
  3. 0 50
      client/scss/components/_logo.scss
  4. 0 560
      client/scss/components/_main-nav.scss
  5. 3 4
      client/scss/core.scss
  6. 0 40
      client/scss/overrides/_utilities.hidden.scss
  7. 5 0
      client/scss/overrides/_utilities.legacy.scss
  8. 0 24
      client/scss/settings/_variables.scss
  9. 0 67
      client/scss/sidebar.scss
  10. 0 2
      client/src/components/Draftail/Draftail.scss
  11. 0 227
      client/src/components/Explorer/Explorer.scss
  12. 0 62
      client/src/components/Explorer/Explorer.test.js
  13. 0 50
      client/src/components/Explorer/Explorer.tsx
  14. 0 53
      client/src/components/Explorer/ExplorerHeader.test.js
  15. 0 97
      client/src/components/Explorer/ExplorerHeader.tsx
  16. 0 93
      client/src/components/Explorer/ExplorerItem.scss
  17. 0 54
      client/src/components/Explorer/ExplorerItem.test.js
  18. 0 83
      client/src/components/Explorer/ExplorerItem.tsx
  19. 0 228
      client/src/components/Explorer/ExplorerPanel.test.js
  20. 0 210
      client/src/components/Explorer/ExplorerPanel.tsx
  21. 0 39
      client/src/components/Explorer/ExplorerToggle.test.js
  22. 0 43
      client/src/components/Explorer/ExplorerToggle.tsx
  23. 0 29
      client/src/components/Explorer/PageCount.test.js
  24. 0 32
      client/src/components/Explorer/PageCount.tsx
  25. 0 117
      client/src/components/Explorer/__snapshots__/Explorer.test.js.snap
  26. 0 73
      client/src/components/Explorer/__snapshots__/ExplorerHeader.test.js.snap
  27. 0 180
      client/src/components/Explorer/__snapshots__/ExplorerItem.test.js.snap
  28. 0 349
      client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap
  29. 0 20
      client/src/components/Explorer/__snapshots__/ExplorerToggle.test.js.snap
  30. 0 31
      client/src/components/Explorer/__snapshots__/PageCount.test.js.snap
  31. 0 109
      client/src/components/Explorer/__snapshots__/actions.test.js.snap
  32. 0 87
      client/src/components/Explorer/actions.test.js
  33. 0 157
      client/src/components/Explorer/actions.ts
  34. 0 29
      client/src/components/Explorer/index.test.js
  35. 0 65
      client/src/components/Explorer/index.tsx
  36. 0 25
      client/src/components/Explorer/reducers/__snapshots__/explorer.test.js.snap
  37. 0 208
      client/src/components/Explorer/reducers/__snapshots__/nodes.test.js.snap
  38. 0 39
      client/src/components/Explorer/reducers/explorer.test.js
  39. 0 68
      client/src/components/Explorer/reducers/explorer.ts
  40. 0 9
      client/src/components/Explorer/reducers/index.ts
  41. 0 64
      client/src/components/Explorer/reducers/nodes.test.js
  42. 0 239
      client/src/components/Explorer/reducers/nodes.ts
  43. 1 1
      client/src/components/PageExplorer/PageExplorerPanel.test.js
  44. 8 0
      client/src/components/Sidebar/Sidebar.scss
  45. 2 9
      client/src/components/Sidebar/modules/MainMenu.scss
  46. 0 77
      client/src/entrypoints/admin/core.js
  47. 0 15
      client/src/entrypoints/admin/sidebar-legacy.js
  48. 0 27
      client/src/entrypoints/admin/sidebar-legacy.test.js
  49. 2 1
      client/src/entrypoints/admin/wagtailadmin.js
  50. 0 49
      client/src/includes/initSubmenus.js
  51. 8 17
      client/src/index.ts
  52. 0 1
      client/storybook/preview.js
  53. 0 9
      client/webpack.config.js
  54. 0 9
      docs/reference/settings.rst
  55. 0 10
      wagtail/admin/menu.py
  56. 6 1
      wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss
  57. 0 1
      wagtail/admin/static_src/wagtailadmin/scss/sidebar.scss
  58. 3 1
      wagtail/admin/templates/wagtailadmin/404.html
  59. 2 4
      wagtail/admin/templates/wagtailadmin/admin_base.html
  60. 2 37
      wagtail/admin/templates/wagtailadmin/base.html
  61. 0 5
      wagtail/admin/templates/wagtailadmin/shared/animated_logo.html
  62. 0 14
      wagtail/admin/templates/wagtailadmin/shared/animated_logo.stories.tsx
  63. 0 10
      wagtail/admin/templates/wagtailadmin/shared/explorer_menu_item.html
  64. 0 22
      wagtail/admin/templates/wagtailadmin/shared/main_nav.html
  65. 0 10
      wagtail/admin/templates/wagtailadmin/shared/menu_item.html
  66. 0 10
      wagtail/admin/templates/wagtailadmin/shared/menu_search.html
  67. 0 6
      wagtail/admin/templates/wagtailadmin/shared/menu_settings_menu_item.html
  68. 0 19
      wagtail/admin/templates/wagtailadmin/shared/menu_submenu_item.html
  69. 1 2
      wagtail/admin/templates/wagtailadmin/skeleton.html
  70. 0 61
      wagtail/admin/templatetags/wagtailadmin_tags.py
  71. 0 22
      wagtail/admin/tests/test_admin_search.py
  72. 1 9
      wagtail/admin/tests/test_menu.py
  73. 1 9
      wagtail/admin/wagtail_hooks.py
  74. 2 2
      wagtail/images/templates/wagtailimages/images/edit.html

+ 10 - 0
client/scss/components/_grid.legacy.scss

@@ -2,6 +2,16 @@
   @include clearfix();
   height: 100vh;
   transition: transform 0.2s ease;
+
+  @include media-breakpoint-up(sm) {
+    @include transition(padding-inline-start $menu-transition-duration ease);
+    transform: none;
+    padding-inline-start: $menu-width;
+
+    .sidebar-collapsed & {
+      padding-inline-start: $menu-width-slim;
+    }
+  }
 }
 
 .content-wrapper {

+ 2 - 1
client/scss/components/_header.scss

@@ -1,4 +1,5 @@
 @use 'sass:math';
+@use 'sass:color';
 
 header {
   padding-top: 1em;
@@ -49,7 +50,7 @@ header {
 
   .search-form .icon {
     @include svg-icon(1.3rem);
-    color: $nav-search-color;
+    color: color.adjust($color-white, $lightness: -20%);
     position: absolute;
     top: 0.3em;
     // Remove once we drop support for Safari 13.

+ 0 - 50
client/scss/components/_logo.scss

@@ -1,50 +0,0 @@
-@keyframes tail-wag {
-  from {
-    transform: rotate(-3deg);
-  }
-
-  to {
-    transform: rotate(7deg);
-  }
-}
-
-.logo {
-  display: flex;
-  align-items: center;
-  padding: 0.6em 1.2em;
-  color: #aaa;
-  -webkit-font-smoothing: auto;
-  position: relative;
-
-  &:hover {
-    color: $color-white;
-  }
-
-  @include media-breakpoint-up(sm) {
-    display: block;
-    margin: 2em auto;
-    text-align: center;
-  }
-}
-
-// Backwards-compatibility for branding_logo customisations in legacy sidebar.
-//RemovedInWagtail40Warning Remove when removing the legacy sidebar.
-.wagtail-logo-container__mobile {
-  margin-inline-end: 10px;
-  background-color: #555;
-  border-radius: 50%;
-  padding: 5px 7.5px;
-
-  .wagtail-logo {
-    width: 20px;
-    float: left;
-    border: 0;
-  }
-}
-
-// Media for Windows High Contrast mode
-@media (forced-colors: $media-forced-colours) {
-  .wagtail-logo-container__desktop {
-    background-color: $system-color-link-text;
-  }
-}

+ 0 - 560
client/scss/components/_main-nav.scss

@@ -1,560 +0,0 @@
-@use 'sass:map';
-
-.nav-wrapper {
-  position: relative;
-  margin-inline-start: -$menu-width;
-  width: $menu-width;
-  float: left;
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  background: $nav-grey-1;
-
-  .inner {
-    background: $nav-grey-1;
-    border-inline-end: 1px solid transparent; // ensure visible separation in Windows High Contrast mode
-
-    @include media-breakpoint-up(sm) {
-      // On medium, make it possible for the nav links to scroll.
-      display: flex;
-      flex-flow: column nowrap;
-    }
-  }
-}
-
-.nav-toggle.icon {
-  position: absolute;
-  padding-inline-start: $mobile-nice-padding;
-  cursor: pointer;
-  border: 1px solid transparent; // ensure visible separation in Windows High Contrast mode
-  background-color: transparent;
-
-  &:before {
-    position: relative;
-    top: 3px;
-    font-size: 40px;
-    color: $color-white;
-    line-height: 40px;
-    content: '\2261';
-  }
-}
-
-.nav-main {
-  ul,
-  li {
-    margin: 0;
-    padding: 0;
-    list-style-type: none;
-  }
-
-  li {
-    @include transition(border-color 0.2s ease);
-    position: relative;
-  }
-
-  a {
-    @include transition(border-color 0.2s ease);
-    -webkit-font-smoothing: auto;
-    text-decoration: none;
-    display: block;
-    color: $color-menu-text;
-    padding: 0.8em 1.7em;
-    font-size: 1em;
-    font-weight: normal;
-    // Note, font-weights lower than normal,
-    // and font-size smaller than 1em (80% ~= 12.8px),
-    // makes the strokes thinner than 1px on non-retina screens
-    // making the text semi-transparent
-    &:hover,
-    &:focus {
-      background-color: $nav-item-hover-bg;
-      color: $color-white;
-      text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3);
-    }
-  }
-
-  .menu-item a {
-    position: relative;
-    white-space: nowrap;
-    border-inline-start: 3px solid transparent;
-
-    &:before {
-      font-size: 1rem;
-      vertical-align: -15%;
-      margin-inline-end: 0.5em;
-    }
-
-    // only really used for spinners and settings menu
-    &:after {
-      font-size: 1.5em;
-      margin: 0;
-      position: absolute;
-      // Remove once we drop support for Safari 13.
-      // stylelint-disable-next-line property-disallowed-list
-      right: 0.5em;
-      inset-inline-end: 0.5em;
-      top: 0.5em;
-      margin-top: 0;
-    }
-  }
-
-  .menu-active {
-    background: $nav-item-active-bg;
-    text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3);
-
-    > a {
-      border-inline-start-color: $color-salmon;
-      color: $color-white;
-    }
-  }
-
-  .nav-footer-submenu {
-    a {
-      border-inline-start: 3px solid transparent;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-
-      &:before {
-        font-size: 1rem;
-        margin-inline-end: 0.5em;
-        vertical-align: -10%;
-      }
-    }
-  }
-
-  .account {
-    display: none;
-  }
-
-  *:focus {
-    @include show-focus-outline-inside;
-  }
-}
-
-.icon--menuitem {
-  width: 1.25em;
-  height: 1.25em;
-  margin-inline-end: 0.5em;
-  vertical-align: text-top;
-}
-
-.icon--submenu-trigger {
-  // The menus are collapsible on desktop only.
-  display: none;
-  @include media-breakpoint-up(sm) {
-    display: block;
-    width: 1.5em;
-    height: 1.5em;
-    position: absolute;
-    top: 0.8125em;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    right: 0.5em;
-    inset-inline-end: 0.5em;
-    @include transition(transform 0.3s ease);
-
-    .menu-item.submenu-active & {
-      transform-origin: 50% 50%;
-      transform: rotate(180deg);
-    }
-  }
-}
-
-.icon--submenu-header {
-  display: block;
-  width: 4rem;
-  height: 4rem;
-  margin: 75px auto 0.8em;
-  opacity: 0.15;
-}
-
-.nav-submenu {
-  background: $nav-submenu-bg;
-
-  h2 {
-    display: none;
-  }
-
-  .menu-item a {
-    white-space: normal;
-    padding: 0.9em 1.7em 0.9em 4.5em;
-
-    &:before {
-      margin-inline-start: -1.5em;
-    }
-
-    .icon--menuitem {
-      margin-inline-start: -1.75em;
-    }
-
-    &:hover {
-      background-color: rgba(100, 100, 100, 0.2);
-    }
-  }
-
-  li {
-    border: 0;
-  }
-
-  &__footer {
-    margin: 0;
-    padding: 0.9em 1.7em;
-    text-align: center;
-    color: $color-menu-text;
-  }
-}
-
-.nav-search {
-  position: relative;
-  padding: 0 1em 1em;
-  margin: 0;
-  width: 100%;
-  box-sizing: border-box;
-
-  label {
-    @include visuallyhidden();
-  }
-
-  input,
-  button {
-    border-radius: 0;
-    font-size: 1em;
-    border: 0;
-  }
-
-  input {
-    cursor: pointer;
-    border: 1px solid $nav-search-border;
-    background-color: $nav-search-bg;
-    color: $nav-search-color;
-    padding: 0.8em 2.5em 0.8em 1em;
-    font-weight: 600;
-
-    &:hover {
-      background-color: $nav-search-hover-bg;
-    }
-
-    &:active,
-    &:focus {
-      background-color: $nav-search-focus-bg;
-      color: $nav-search-focus-color;
-    }
-
-    &::placeholder {
-      color: $color-menu-text;
-    }
-  }
-
-  button {
-    background-color: transparent;
-    position: absolute;
-    top: 0;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    right: 1em;
-    inset-inline-end: 1em;
-    bottom: 0;
-    padding: 0;
-    width: 3em;
-
-    &:hover {
-      background-color: $nav-item-hover-bg;
-    }
-
-    &:active {
-      background-color: $nav-item-active-bg;
-    }
-  }
-}
-
-// Navigation open condition
-body.nav-open {
-  .wrapper {
-    transform: translate3d($menu-width, 0, 0);
-  }
-
-  .content-wrapper {
-    position: fixed;
-  }
-
-  footer {
-    bottom: 1px;
-  }
-}
-
-// Explorer open condition, widens navigation area
-body.explorer-open {
-  .wrapper {
-    transform: translate3d($menu-width-max, 0, 0);
-  }
-
-  .nav-wrapper {
-    margin-inline-start: -$menu-width-max;
-    width: $menu-width-max;
-  }
-
-  .nav-main {
-    display: none;
-  }
-}
-
-@include media-breakpoint-up(sm) {
-  .wrapper,
-  body.nav-open .wrapper {
-    transform: none;
-    padding-inline-start: $menu-width;
-
-    @include transition(padding-inline-start $menu-transition-duration ease);
-  }
-
-  body.sidebar-collapsed .wrapper {
-    padding-inline-start: $menu-width-slim;
-  }
-
-  .nav-wrapper {
-    // height and position necessary to force it to 100% height of screen (with some JS help)
-    position: absolute;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: 0;
-    inset-inline-start: 0;
-    height: 100%;
-    margin-inline-start: 0;
-
-    .inner {
-      height: 100%;
-      position: fixed;
-      width: $menu-width;
-      z-index: $nav-wrapper-inner-z-index;
-    }
-  }
-
-  .nav-toggle.unbutton {
-    display: none;
-  }
-
-  .nav-main {
-    overflow: auto;
-    margin-bottom: $nav-footer-closed-height;
-    @include transition(margin-bottom 0.2s ease);
-
-    .nav-footer {
-      position: fixed;
-      width: $menu-width;
-      bottom: 0;
-      background-color: $nav-footer-submenu-bg;
-    }
-
-    .nav-footer-submenu {
-      @include transition(max-height 0.2s ease);
-      max-height: 0;
-    }
-
-    &--open-footer {
-      margin-bottom: $nav-footer-open-height;
-
-      .nav-footer-submenu {
-        max-height: $nav-footer-submenu-height;
-      }
-    }
-
-    .account {
-      @include clearfix;
-      background: $nav-footer-account-bg;
-      color: $color-menu-text;
-      display: block;
-      cursor: pointer;
-
-      &:hover {
-        background-color: rgba(100, 100, 100, 0.15);
-        color: $color-white;
-        text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3);
-      }
-
-      .avatar {
-        float: left;
-        margin-inline-end: 0.9em;
-
-        &:before {
-          color: inherit;
-          border-color: inherit;
-        }
-      }
-
-      em {
-        box-sizing: border-box;
-        padding-inline-end: 1.8em;
-        margin-top: 1.2em;
-        font-style: normal;
-        font-weight: 700;
-        width: 110px;
-        overflow: hidden;
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        float: left;
-
-        &:after {
-          font-size: 1.5em;
-          position: absolute;
-          // Remove once we drop support for Safari 13.
-          // stylelint-disable-next-line property-disallowed-list
-          right: 0.25em;
-          inset-inline-end: 0.25em;
-        }
-      }
-    }
-  }
-
-  .nav-submenu {
-    transform: translate3d(0, 0, 0);
-    position: fixed;
-    height: 100vh;
-    width: 0;
-    padding: 0;
-    top: 0;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: $menu-width;
-    inset-inline-start: $menu-width;
-    overflow: hidden;
-    display: flex;
-    flex-direction: column;
-
-    h2,
-    &__list {
-      width: $menu-width;
-    }
-
-    h2 {
-      display: block;
-      padding: 0.2em 0;
-      font-size: 1.2em;
-      font-weight: 500;
-      text-align: center;
-      color: $color-menu-text;
-
-      &:before {
-        font-size: 4em;
-        display: block;
-        text-align: center;
-        margin: 0 0 0.2em;
-        width: 100%;
-        opacity: 0.15;
-      }
-    }
-
-    &__list {
-      overflow: auto;
-      flex-grow: 1;
-    }
-
-    &__footer {
-      line-height: $nav-footer-closed-height;
-      padding: 0;
-    }
-  }
-
-  li.submenu-active {
-    background: $nav-submenu-bg;
-
-    > a {
-      text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3);
-
-      &:hover {
-        background-color: transparent;
-      }
-    }
-
-    .nav-submenu {
-      @include transition(width 0.2s ease);
-      box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35);
-      width: $menu-width;
-
-      a {
-        padding-inline-start: 3.5em;
-      }
-    }
-  }
-
-  body.nav-open {
-    .content-wrapper {
-      position: relative;
-    }
-  }
-
-  body.explorer-open {
-    overflow: hidden;
-
-    &:after {
-      opacity: 1;
-      visibility: visible;
-    }
-
-    .wrapper {
-      transform: none;
-    }
-
-    .nav-wrapper {
-      margin-inline-start: 0;
-      width: $menu-width;
-    }
-
-    .nav-main {
-      display: block;
-    }
-  }
-}
-
-///////////////
-// Z-indexes //
-///////////////
-.nav-toggle {
-  z-index: 5;
-}
-
-// stylelint-disable-next-line no-duplicate-selectors
-.nav-wrapper {
-  z-index: 2;
-}
-
-// Avoiding a stacking context for the content-wrapper saves us a world
-// of pain when dealing with overlays that are appended to the end of
-// <body>, e.g. calendars. As long as content-wrapper remains floated,
-// the z-index shouldn't be required.
-
-// .content-wrapper {
-// z-index: 3;
-// }
-// stylelint-disable-next-line no-duplicate-selectors
-.nav-submenu {
-  z-index: 6;
-}
-
-footer,
-.logo {
-  z-index: 100;
-}
-
-@include media-breakpoint-up(sm) {
-  .nav-main {
-    .nav-footer {
-      z-index: 2;
-    }
-  }
-
-  .nav-submenu {
-    z-index: 500;
-  }
-
-  // Allows overspill of messages banner onto left menu, but also explorer
-  // to spill over main content
-  .nav-wrapper {
-    z-index: auto;
-  }
-
-  // footer is z-index: 100, so ensure the navigation sits on top of it.
-  .nav-wrapper.submenu-active {
-    z-index: 200;
-  }
-}

+ 3 - 4
client/scss/core.scss

@@ -100,7 +100,7 @@ These are classes for components.
 @import '../src/components/Transition/Transition';
 @import '../src/components/LoadingSpinner/LoadingSpinner';
 @import '../src/components/PublicationStatus/PublicationStatus';
-@import '../src/components/Explorer/Explorer';
+@import '../src/components/PageExplorer/PageExplorer';
 @import '../src/components/CommentApp/main';
 
 // Legacy
@@ -120,9 +120,7 @@ These are classes for components.
 @import 'components/messages.status';
 @import 'components/header';
 @import 'components/progressbar';
-@import 'components/main-nav';
 @import 'components/tooltips';
-@import 'components/logo';
 @import 'components/grid.legacy';
 @import 'components/breadcrumb';
 @import 'components/footer';
@@ -140,6 +138,8 @@ These are classes for components.
 @import 'components/comments-notification-dropdown';
 @import 'components/bulk_actions';
 
+@import '../src/components/Sidebar/Sidebar';
+
 /* OVERRIDES
 These are classes that provide overrides.
 * Higher specificity is allowed here because these are overrides and imported last.
@@ -151,7 +151,6 @@ These are classes that provide overrides.
 @import 'overrides/vendor.tippy';
 
 // UTILITIES: classes that do one simple thing.
-@import 'overrides/utilities.hidden';
 @import 'overrides/utilities.dropdowns';
 @import 'overrides/utilities.focus';
 @import 'overrides/utilities.visuallyhidden';

+ 0 - 40
client/scss/overrides/_utilities.hidden.scss

@@ -1,40 +0,0 @@
-// stylelint-disable declaration-no-important
-.u-hidden {
-  display: none !important;
-}
-
-.u-hidden\@sm {
-  @include media-breakpoint-up(sm) {
-    display: none !important;
-  }
-}
-
-.u-hidden\@xs {
-  @include media-breakpoint-down(xs) {
-    display: none !important;
-  }
-}
-
-.u-inline\@sm {
-  @include media-breakpoint-up(sm) {
-    display: inline !important;
-  }
-}
-
-.u-inline\@xs {
-  @include media-breakpoint-down(xs) {
-    display: inline !important;
-  }
-}
-
-.u-block\@sm {
-  @include media-breakpoint-up(sm) {
-    display: block !important;
-  }
-}
-
-.u-block\@xs {
-  @include media-breakpoint-down(xs) {
-    display: block !important;
-  }
-}

+ 5 - 0
client/scss/overrides/_utilities.legacy.scss

@@ -1,3 +1,8 @@
+.u-hidden {
+  // stylelint-disable-next-line declaration-no-important
+  display: none !important;
+}
+
 .clearfix {
   @include clearfix();
 }

+ 0 - 24
client/scss/settings/_variables.scss

@@ -142,32 +142,8 @@ $menu-transition-duration: 150ms;
 
 $focus-outline-width: 3px;
 
-$nav-wrapper-inner-z-index: 26;
-$draftail-editor-z-index: $nav-wrapper-inner-z-index + 1;
-
 $object-title-height: 40px;
 
-// Nav
-$nav-grey-1: color.adjust($color-white, $lightness: -80%);
-$nav-grey-2: color.adjust($color-white, $lightness: -60%);
-$nav-grey-3: #262626;
-$nav-item-hover-bg: rgba(100, 100, 100, 0.15);
-$nav-item-active-bg: color.adjust($color-white, $lightness: -90%);
-$nav-submenu-bg: color.adjust($color-white, $lightness: -85%);
-$nav-footer-account-bg: $nav-item-active-bg;
-$nav-footer-submenu-bg: $nav-submenu-bg;
-$nav-footer-closed-height: 50px;
-$nav-footer-submenu-height: 85px;
-$nav-footer-open-height: $nav-footer-closed-height + $nav-footer-submenu-height;
-
-// Nav search
-$nav-search-color: color.adjust($color-white, $lightness: -20%);
-$nav-search-border: color.adjust($color-white, $lightness: -40%);
-$nav-search-bg: $nav-grey-1;
-$nav-search-hover-bg: $nav-item-hover-bg;
-$nav-search-focus-color: $color-white;
-$nav-search-focus-bg: $nav-item-hover-bg;
-
 // Form Errors
 $color-text-error: color.change($color-red, $saturation: 69%, $lightness: 52%);
 $color-text-error-forced-color: color.change(

+ 0 - 67
client/scss/sidebar.scss

@@ -1,67 +0,0 @@
-/* =============================================================================
-/*  Wagtail CMS sidebar stylesheet entrypoint
-/* =============================================================================
-
-The sidebar is implemented in its own stylesheet so that it can be used
-by different frontend frameworks.
-
-This stylesheet follows the same conventions as the rest of Wagtail,
-see core.scss for details.
-
-==============================================================================*/
-
-/* SETTINGS
-These are variables, maps, and fonts.
-* No CSS should be produced by these files
-*/
-
-@import 'settings';
-
-/* TOOLS
-These are functions and mixins.
-* No CSS should be produced by these files.
-*/
-
-@import 'tools';
-
-/* GENERIC
-This is for resets and other rules that affect large collections of bare elements.
-* Changes to them should be very rare.
-*/
-
-@import 'generic/generic';
-
-/* ELEMENTS
-These are base styles for bare HTML elements.
-* Changes to them should be very rare.
-*/
-
-@import 'elements/elements';
-@import 'elements/typography';
-@import 'elements/forms';
-@import 'elements/root';
-
-/* OBJECTS
-These are classes related to layout, known as 'objects' in ITCSS or OOCSS.
-* This is for grids, wrappers, and other non-consmetic layout utilities.
-* These classes are prefixed with `.o-`.
-*/
-
-@import 'objects/objects';
-@import 'objects/avatar';
-
-/* COMPONENTS
-These are classes for components.
-* These classes (unless legacy) are prefixed with `.c-`.
-* React component styles live in the same folders as their React components,
-  which is the preferred pattern over housing them in the scss folder.
-*/
-
-@import '../src/components/Transition/Transition';
-@import '../src/components/LoadingSpinner/LoadingSpinner';
-@import '../src/components/PublicationStatus/PublicationStatus';
-@import '../src/components/PageExplorer/PageExplorer';
-@import '../src/components/Sidebar/Sidebar';
-
-// Legacy
-@import 'components/icons';

+ 0 - 2
client/src/components/Draftail/Draftail.scss

@@ -1,5 +1,3 @@
-$editor-z-index: $draftail-editor-z-index;
-
 $draftail-editor-text: $color-text-input;
 $draftail-editor-chrome: $color-white;
 $draftail-editor-chrome-text: $color-grey-2;

+ 0 - 227
client/src/components/Explorer/Explorer.scss

@@ -1,227 +0,0 @@
-$c-explorer-bg: #4c4e4d;
-$c-explorer-bg-dark: $nav-grey-1;
-$c-explorer-bg-active: rgba(0, 0, 0, 0.425);
-$c-explorer-secondary: #a5a5a5;
-$c-explorer-easing: cubic-bezier(0.075, 0.82, 0.165, 1);
-$menu-footer-height: 50px;
-
-@use 'sass:map';
-
-@import 'ExplorerItem';
-
-.explorer__wrapper,
-.explorer__wrapper * {
-  box-sizing: border-box;
-}
-
-.explorer__wrapper {
-  display: flex;
-  flex: 1;
-}
-
-.explorer {
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-
-  @include media-breakpoint-up(sm) {
-    width: 485px;
-    height: 100vh;
-    position: fixed;
-    z-index: 500;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: $menu-width;
-    inset-inline-start: $menu-width;
-  }
-
-  *:focus {
-    @include show-focus-outline-inside;
-  }
-}
-
-.c-explorer {
-  flex: 1;
-  position: relative;
-  overflow: hidden;
-  background: $c-explorer-bg;
-
-  @include media-breakpoint-up(sm) {
-    box-shadow: 2px 2px 5px $c-explorer-bg-active;
-  }
-}
-
-.c-explorer > .c-transition-group {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  z-index: 150;
-}
-
-.c-explorer__close {
-  padding: 1em;
-  color: $c-explorer-secondary;
-  border-bottom: 1px solid rgba(200, 200, 200, 0.1);
-  cursor: pointer;
-  display: none;
-
-  &:focus {
-    background-color: $c-explorer-bg-active;
-    color: $color-white;
-  }
-
-  // Overrides for default link hover.
-  &:hover {
-    color: $c-explorer-secondary;
-  }
-
-  @include media-breakpoint-down(xs) {
-    .explorer-open & {
-      display: block;
-    }
-  }
-}
-
-.c-explorer__drawer {
-  flex: 1;
-  overflow-y: auto;
-  -webkit-overflow-scrolling: touch;
-}
-
-.c-explorer__header {
-  display: block;
-  background-color: $c-explorer-bg-dark;
-  border-bottom: 1px solid $c-explorer-bg-dark;
-  color: $color-white;
-}
-
-.c-explorer__header__title {
-  color: inherit;
-
-  &:focus {
-    background-color: $c-explorer-bg-active;
-    color: $color-white;
-  }
-
-  // Overrides for default link hover.
-  &:hover {
-    color: $color-white;
-  }
-
-  @include hover {
-    background-color: $c-explorer-bg-active;
-  }
-}
-
-.c-explorer__header__title__inner {
-  width: 70%;
-  float: left;
-  padding: 1em 0.75em;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-
-  .icon {
-    color: $c-explorer-secondary;
-    margin-inline-end: 0.25rem;
-    font-size: 1rem;
-  }
-
-  .icon--explorer-header {
-    width: 1.25em;
-    height: 1.25em;
-    margin-inline-end: 0.25rem;
-    vertical-align: text-top;
-    color: $c-explorer-secondary;
-  }
-
-  @include media-breakpoint-up(sm) {
-    padding: 1em 1.5em;
-  }
-}
-
-.c-explorer__header__select {
-  $margin: 10px;
-  position: relative;
-
-  > select {
-    width: calc(30% - #{$margin * 2});
-    height: calc(100% - #{$margin * 2});
-    margin-top: $margin;
-    margin-inline-end: $margin;
-    float: right;
-    padding: 0;
-    padding-inline-start: 10px;
-
-    background-color: $c-explorer-bg-dark;
-    border-radius: 0;
-    border-color: #4c4e4d;
-    color: $color-white;
-
-    &:disabled {
-      border: 0;
-    }
-
-    &:hover:enabled {
-      cursor: pointer;
-    }
-
-    &:hover:disabled {
-      color: inherit;
-      background-color: inherit;
-      cursor: inherit;
-    }
-  }
-
-  // Add select arrow back on browsers where native ui has been removed
-  &-icon {
-    position: absolute;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    right: 1rem;
-    inset-inline-end: 1rem;
-    top: 1rem;
-    width: 1.25rem;
-    height: 1.25rem;
-    color: $color-grey-3;
-
-    .ie & {
-      display: none;
-    }
-  }
-}
-
-.c-explorer__placeholder {
-  padding: 1em;
-  color: $color-white;
-
-  @include media-breakpoint-up(sm) {
-    padding: 1em 1.75em;
-  }
-}
-
-.c-explorer__see-more {
-  display: block;
-  padding: 1em;
-  background: rgba(0, 0, 0, 0.3);
-  color: $color-white;
-
-  &:focus {
-    color: $c-explorer-secondary;
-    background: $c-explorer-bg-active;
-  }
-
-  // Overrides for default link hover.
-  &:hover {
-    color: $color-white;
-  }
-
-  @include hover {
-    background: $c-explorer-bg-active;
-  }
-
-  @include media-breakpoint-up(sm) {
-    padding: 1em 1.75em;
-    height: $menu-footer-height;
-  }
-}

+ 0 - 62
client/src/components/Explorer/Explorer.test.js

@@ -1,62 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { Provider } from 'react-redux';
-import { createStore, applyMiddleware, combineReducers } from 'redux';
-import thunkMiddleware from 'redux-thunk';
-import * as actions from './actions';
-import explorer from './reducers/explorer';
-import nodes from './reducers/nodes';
-import Explorer from './Explorer';
-
-const rootReducer = combineReducers({
-  explorer,
-  nodes,
-});
-
-const store = createStore(rootReducer, {}, applyMiddleware(thunkMiddleware));
-
-describe('Explorer', () => {
-  it('exists', () => {
-    expect(Explorer).toBeDefined();
-  });
-
-  it('renders', () => {
-    expect(shallow(<Explorer store={store} />).dive()).toMatchSnapshot();
-    expect(
-      shallow(
-        <Provider store={store}>
-          <Explorer />
-        </Provider>,
-      ).dive(),
-    ).toMatchSnapshot();
-  });
-
-  it('visible', () => {
-    store.dispatch(actions.toggleExplorer(1));
-    expect(shallow(<Explorer store={store} />).dive()).toMatchSnapshot();
-    expect(
-      shallow(<Explorer store={store} />)
-        .dive()
-        .dive(),
-    ).toMatchSnapshot();
-  });
-
-  describe('actions', () => {
-    let wrapper;
-
-    beforeEach(() => {
-      store.dispatch = jest.fn();
-      wrapper = shallow(<Explorer store={store} />);
-    });
-
-    it('gotoPage', () => {
-      wrapper.dive().prop('gotoPage')();
-      expect(store.dispatch).toHaveBeenCalled();
-    });
-
-    it('onClose', () => {
-      wrapper.dive().prop('onClose')();
-      expect(store.dispatch).toHaveBeenCalled();
-    });
-  });
-});

+ 0 - 50
client/src/components/Explorer/Explorer.tsx

@@ -1,50 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-
-import * as actions from './actions';
-import { State as NodeState } from './reducers/nodes';
-import { State } from './reducers';
-
-import ExplorerPanel from './ExplorerPanel';
-
-interface ExplorerProps {
-  isVisible: boolean;
-  depth: number;
-  currentPageId: number | null;
-  nodes: NodeState;
-  onClose(): void;
-  gotoPage(id: number, transition: number): void;
-}
-
-const Explorer: React.FunctionComponent<ExplorerProps> = ({
-  isVisible,
-  depth,
-  currentPageId,
-  nodes,
-  gotoPage,
-  onClose,
-}) =>
-  isVisible && currentPageId ? (
-    <ExplorerPanel
-      depth={depth}
-      page={nodes[currentPageId]}
-      nodes={nodes}
-      gotoPage={gotoPage}
-      onClose={onClose}
-    />
-  ) : null;
-
-const mapStateToProps = (state: State) => ({
-  isVisible: state.explorer.isVisible,
-  depth: state.explorer.depth,
-  currentPageId: state.explorer.currentPageId,
-  nodes: state.nodes,
-});
-
-const mapDispatchToProps = (dispatch) => ({
-  gotoPage: (id: number, transition: number) =>
-    dispatch(actions.gotoPage(id, transition)),
-  onClose: () => dispatch(actions.closeExplorer()),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Explorer);

+ 0 - 53
client/src/components/Explorer/ExplorerHeader.test.js

@@ -1,53 +0,0 @@
-import React from 'react';
-import { mount, shallow } from 'enzyme';
-
-import ExplorerHeader from './ExplorerHeader';
-
-const mockProps = {
-  page: {
-    meta: {
-      parent: {
-        id: 1,
-      },
-    },
-  },
-  depth: 2,
-  onClick: jest.fn(),
-};
-
-describe('ExplorerHeader', () => {
-  it('exists', () => {
-    expect(ExplorerHeader).toBeDefined();
-  });
-
-  it('basic', () => {
-    expect(shallow(<ExplorerHeader {...mockProps} />)).toMatchSnapshot();
-  });
-
-  it('#depth at root', () => {
-    expect(
-      shallow(<ExplorerHeader {...mockProps} depth={0} />),
-    ).toMatchSnapshot();
-  });
-
-  it('#page', () => {
-    const wrapper = shallow(
-      <ExplorerHeader
-        {...mockProps}
-        page={{
-          id: 'a',
-          admin_display_title: 'test',
-          meta: { parent: { id: 1 } },
-        }}
-      />,
-    );
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('#onClick', () => {
-    const wrapper = mount(<ExplorerHeader {...mockProps} />);
-    wrapper.find('Button').simulate('click');
-
-    expect(mockProps.onClick).toHaveBeenCalledTimes(1);
-  });
-});

+ 0 - 97
client/src/components/Explorer/ExplorerHeader.tsx

@@ -1,97 +0,0 @@
-import React from 'react';
-import { ADMIN_URLS } from '../../config/wagtailConfig';
-
-import { gettext } from '../../utils/gettext';
-import Button from '../../components/Button/Button';
-import Icon from '../../components/Icon/Icon';
-import { PageState } from './reducers/nodes';
-
-interface SelectLocaleProps {
-  locale?: string;
-  translations: Map<string, number>;
-  gotoPage(id: number, transition: number): void;
-}
-
-const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({
-  locale,
-  translations,
-  gotoPage,
-}) => {
-  /* eslint-disable camelcase */
-  const options = wagtailConfig.LOCALES.filter(
-    ({ code }) => code === locale || translations.get(code),
-  ).map(({ code, display_name }) => (
-    <option key={code} value={code}>
-      {display_name}
-    </option>
-  ));
-  /* eslint-enable camelcase */
-
-  const onChange = (e) => {
-    e.preventDefault();
-    const translation = translations.get(e.target.value);
-    if (translation) {
-      gotoPage(translation, 0);
-    }
-  };
-
-  return (
-    <div className="c-explorer__header__select">
-      <select value={locale} onChange={onChange} disabled={options.length < 2}>
-        {options}
-      </select>
-      <Icon name="arrow-down" className="c-explorer__header__select-icon" />
-    </div>
-  );
-};
-
-interface ExplorerHeaderProps {
-  page: PageState;
-  depth: number;
-  onClick(e: any): void;
-  gotoPage(id: number, transition: number): void;
-}
-
-/**
- * The bar at the top of the explorer, displaying the current level
- * and allowing access back to the parent level.
- */
-const ExplorerHeader: React.FunctionComponent<ExplorerHeaderProps> = ({
-  page,
-  depth,
-  onClick,
-  gotoPage,
-}) => {
-  const isRoot = depth === 0;
-  const isSiteRoot = page.id === 0;
-
-  return (
-    <div className="c-explorer__header">
-      <Button
-        href={!isSiteRoot ? `${ADMIN_URLS.PAGES}${page.id}/` : ADMIN_URLS.PAGES}
-        className="c-explorer__header__title"
-        onClick={onClick}
-      >
-        <div className="c-explorer__header__title__inner">
-          <Icon
-            name={isRoot ? 'home' : 'arrow-left'}
-            className="icon--explorer-header"
-          />
-          <span>{page.admin_display_title || gettext('Pages')}</span>
-        </div>
-      </Button>
-      {!isSiteRoot &&
-        page.meta.locale &&
-        page.translations &&
-        page.translations.size > 0 && (
-          <SelectLocale
-            locale={page.meta.locale}
-            translations={page.translations}
-            gotoPage={gotoPage}
-          />
-        )}
-    </div>
-  );
-};
-
-export default ExplorerHeader;

+ 0 - 93
client/src/components/Explorer/ExplorerItem.scss

@@ -1,93 +0,0 @@
-.c-explorer__item {
-  display: flex;
-  flex-flow: row nowrap;
-  border-bottom: 1px solid $c-explorer-bg-dark;
-}
-
-.c-explorer__item__link {
-  display: inline-flex;
-  align-items: center;
-  flex-grow: 1;
-  padding: 1.45em 1em;
-  cursor: pointer;
-
-  &:focus {
-    background: $c-explorer-bg-active;
-    color: $color-white;
-  }
-
-  // Overrides for default link hover.
-  &:hover {
-    color: $color-white;
-  }
-
-  @include hover {
-    background: $c-explorer-bg-active;
-  }
-
-  @include media-breakpoint-up(sm) {
-    padding: 1.45em 1.75em;
-  }
-}
-
-.c-explorer__item__link .icon {
-  width: 2em;
-  height: 2em;
-  color: $c-explorer-secondary;
-  margin-inline-end: 0.75rem;
-}
-
-.c-explorer__item__title {
-  margin: 0;
-  color: $color-white;
-  display: inline-block;
-}
-
-.c-explorer__item__action {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  width: 50px;
-  padding: 0 0.5em;
-  line-height: 1;
-  font-size: 2em;
-  cursor: pointer;
-  color: $c-explorer-secondary;
-  border: 0;
-  border-inline-start: solid 1px $c-explorer-bg-dark;
-
-  &:focus {
-    background: $c-explorer-bg-active;
-    color: $color-white;
-  }
-
-  // Overrides for default link hover.
-  &:hover {
-    color: $c-explorer-secondary;
-  }
-
-  @include hover {
-    background: $c-explorer-bg-active;
-    color: $color-white;
-  }
-
-  .icon::before {
-    margin-inline-end: 0;
-  }
-}
-
-.c-explorer__item__action--small {
-  font-size: 1.2em;
-}
-
-.icon--item-action {
-  width: 1em;
-  height: 1em;
-}
-
-.c-explorer__meta {
-  margin-inline-start: 0.5rem;
-  color: $c-explorer-secondary;
-  font-size: 12px;
-}

+ 0 - 54
client/src/components/Explorer/ExplorerItem.test.js

@@ -1,54 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import ExplorerItem from './ExplorerItem';
-
-const mockProps = {
-  item: {
-    id: 5,
-    admin_display_title: 'test',
-    meta: {
-      latest_revision_created_at: null,
-      status: {
-        live: true,
-        status: 'test',
-        has_unpublished_changes: false,
-      },
-      descendants: {
-        count: 0,
-      },
-      children: {
-        count: 0,
-      },
-    },
-  },
-  onClick: () => {},
-};
-
-describe('ExplorerItem', () => {
-  it('exists', () => {
-    expect(ExplorerItem).toBeDefined();
-  });
-
-  it('renders', () => {
-    expect(shallow(<ExplorerItem {...mockProps} />)).toMatchSnapshot();
-  });
-
-  it('children', () => {
-    const props = Object.assign({}, mockProps);
-    props.item.meta.children.count = 5;
-    expect(shallow(<ExplorerItem {...props} />)).toMatchSnapshot();
-  });
-
-  it('should show a publication status with unpublished changes', () => {
-    const props = Object.assign({}, mockProps);
-    props.item.meta.status.has_unpublished_changes = true;
-    expect(shallow(<ExplorerItem {...props} />)).toMatchSnapshot();
-  });
-
-  it('should show a publication status if not live', () => {
-    const props = Object.assign({}, mockProps);
-    props.item.meta.status.live = false;
-    expect(shallow(<ExplorerItem {...props} />)).toMatchSnapshot();
-  });
-});

+ 0 - 83
client/src/components/Explorer/ExplorerItem.tsx

@@ -1,83 +0,0 @@
-import React from 'react';
-
-import { gettext } from '../../utils/gettext';
-import { ADMIN_URLS, LOCALE_NAMES } from '../../config/wagtailConfig';
-import Icon from '../../components/Icon/Icon';
-import Button from '../../components/Button/Button';
-import PublicationStatus from '../../components/PublicationStatus/PublicationStatus';
-import { PageState } from './reducers/nodes';
-
-// Hoist icons in the explorer item, as it is re-rendered many times.
-const childrenIcon = <Icon name="folder-inverse" className="icon--menuitem" />;
-
-interface ExplorerItemProps {
-  item: PageState;
-  onClick(): void;
-}
-
-/**
- * One menu item in the page explorer, with different available actions
- * and information depending on the metadata of the page.
- */
-const ExplorerItem: React.FunctionComponent<ExplorerItemProps> = ({
-  item,
-  onClick,
-}) => {
-  const { id, admin_display_title: title, meta } = item;
-  const hasChildren = meta.children.count > 0;
-  const isPublished = meta.status.live && !meta.status.has_unpublished_changes;
-  const localeName =
-    meta.parent?.id === 1 &&
-    meta.locale &&
-    (LOCALE_NAMES.get(meta.locale) || meta.locale);
-
-  return (
-    <div className="c-explorer__item">
-      <Button
-        href={`${ADMIN_URLS.PAGES}${id}/`}
-        className="c-explorer__item__link"
-      >
-        {hasChildren ? childrenIcon : null}
-
-        <h3 className="c-explorer__item__title">{title}</h3>
-
-        {(!isPublished || localeName) && (
-          <span className="c-explorer__meta">
-            {localeName && (
-              <span className="o-pill c-status">{localeName}</span>
-            )}
-            {!isPublished && <PublicationStatus status={meta.status} />}
-          </span>
-        )}
-      </Button>
-      <Button
-        href={`${ADMIN_URLS.PAGES}${id}/edit/`}
-        className="c-explorer__item__action c-explorer__item__action--small"
-      >
-        <Icon
-          name="edit"
-          title={gettext("Edit '{title}'").replace('{title}', title || '')}
-          className="icon--item-action"
-        />
-      </Button>
-      {hasChildren ? (
-        <Button
-          className="c-explorer__item__action"
-          onClick={onClick}
-          href={`${ADMIN_URLS.PAGES}${id}/`}
-        >
-          <Icon
-            name="arrow-right"
-            title={gettext("View child pages of '{title}'").replace(
-              '{title}',
-              title || '',
-            )}
-            className="icon--item-action"
-          />
-        </Button>
-      ) : null}
-    </div>
-  );
-};
-
-export default ExplorerItem;

+ 0 - 228
client/src/components/Explorer/ExplorerPanel.test.js

@@ -1,228 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import ExplorerPanel from './ExplorerPanel';
-
-const mockProps = {
-  page: {
-    children: {
-      items: [],
-    },
-    meta: {
-      parent: null,
-    },
-  },
-  depth: 1,
-  onClose: jest.fn(),
-  gotoPage: jest.fn(),
-  nodes: {},
-};
-
-describe('ExplorerPanel', () => {
-  describe('general rendering', () => {
-    beforeEach(() => {
-      document.body.innerHTML = '<div data-explorer-menu-item></div>';
-    });
-
-    it('exists', () => {
-      expect(ExplorerPanel).toBeDefined();
-    });
-
-    it('renders', () => {
-      expect(shallow(<ExplorerPanel {...mockProps} />)).toMatchSnapshot();
-    });
-
-    it('#isFetching', () => {
-      expect(
-        shallow(
-          <ExplorerPanel
-            {...mockProps}
-            page={Object.assign({ isFetching: true }, mockProps.page)}
-          />,
-        ),
-      ).toMatchSnapshot();
-    });
-
-    it('#isError', () => {
-      expect(
-        shallow(
-          <ExplorerPanel
-            {...mockProps}
-            page={Object.assign({ isError: true }, mockProps.page)}
-          />,
-        ),
-      ).toMatchSnapshot();
-    });
-
-    it('no children', () => {
-      expect(
-        shallow(<ExplorerPanel {...mockProps} page={{ children: {} }} />),
-      ).toMatchSnapshot();
-    });
-
-    it('#items', () => {
-      expect(
-        shallow(
-          <ExplorerPanel
-            {...mockProps}
-            page={{ children: { items: [1, 2] } }}
-            nodes={{
-              1: {
-                id: 1,
-                admin_display_title: 'Test',
-                meta: { status: {}, type: 'test' },
-              },
-              2: {
-                id: 2,
-                admin_display_title: 'Foo',
-                meta: { status: {}, type: 'foo' },
-              },
-            }}
-          />,
-        ),
-      ).toMatchSnapshot();
-    });
-  });
-
-  describe('onHeaderClick', () => {
-    beforeEach(() => {
-      mockProps.gotoPage.mockReset();
-    });
-
-    it('calls gotoPage', () => {
-      shallow(
-        <ExplorerPanel
-          {...mockProps}
-          depth={2}
-          page={{ children: { items: [] }, meta: { parent: { id: 1 } } }}
-        />,
-      )
-        .find('ExplorerHeader')
-        .prop('onClick')({
-        preventDefault() {},
-        stopPropagation() {},
-      });
-
-      expect(mockProps.gotoPage).toHaveBeenCalled();
-    });
-
-    it('does not call gotoPage for first page', () => {
-      shallow(
-        <ExplorerPanel
-          {...mockProps}
-          depth={0}
-          page={{ children: { items: [] }, meta: { parent: { id: 1 } } }}
-        />,
-      )
-        .find('ExplorerHeader')
-        .prop('onClick')({
-        preventDefault() {},
-        stopPropagation() {},
-      });
-
-      expect(mockProps.gotoPage).not.toHaveBeenCalled();
-    });
-  });
-
-  describe('onItemClick', () => {
-    beforeEach(() => {
-      mockProps.gotoPage.mockReset();
-    });
-
-    it('calls gotoPage', () => {
-      shallow(
-        <ExplorerPanel
-          {...mockProps}
-          path={[1]}
-          page={{ children: { items: [1] } }}
-          nodes={{
-            1: {
-              id: 1,
-              admin_display_title: 'Test',
-              meta: { status: {}, type: 'test' },
-            },
-          }}
-        />,
-      )
-        .find('ExplorerItem')
-        .prop('onClick')({
-        preventDefault() {},
-        stopPropagation() {},
-      });
-
-      expect(mockProps.gotoPage).toHaveBeenCalled();
-    });
-  });
-
-  describe('hooks', () => {
-    it('componentWillReceiveProps push', () => {
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      expect(wrapper.setProps({ depth: 2 }).state('transition')).toBe('push');
-    });
-
-    it('componentWillReceiveProps pop', () => {
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      expect(wrapper.setProps({ depth: 0 }).state('transition')).toBe('pop');
-    });
-
-    it('componentDidMount', () => {
-      document.body.innerHTML = '<div data-explorer-menu-item></div>';
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      wrapper.instance().componentDidMount();
-      expect(
-        document
-          .querySelector('[data-explorer-menu-item]')
-          .classList.contains('submenu-active'),
-      ).toBe(true);
-      expect(document.body.classList.contains('explorer-open')).toBe(true);
-    });
-
-    it('componentWillUnmount', () => {
-      document.body.innerHTML =
-        '<div class="submenu-active" data-explorer-menu-item></div>';
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      wrapper.instance().componentWillUnmount();
-      expect(
-        document
-          .querySelector('[data-explorer-menu-item]')
-          .classList.contains('submenu-active'),
-      ).toBe(false);
-      expect(document.body.classList.contains('explorer-open')).toBe(false);
-    });
-  });
-
-  describe('clickOutside', () => {
-    afterEach(() => {
-      mockProps.onClose.mockReset();
-    });
-
-    it('triggers onClose when click is outside', () => {
-      document.body.innerHTML =
-        '<div data-explorer-menu-item></div><div data-explorer-menu></div><div id="t"></div>';
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      wrapper.instance().clickOutside({
-        target: document.querySelector('#t'),
-      });
-      expect(mockProps.onClose).toHaveBeenCalled();
-    });
-
-    it('does not trigger onClose when click is inside', () => {
-      document.body.innerHTML =
-        '<div data-explorer-menu-item></div><div data-explorer-menu><div id="t"></div></div>';
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      wrapper.instance().clickOutside({
-        target: document.querySelector('#t'),
-      });
-      expect(mockProps.onClose).not.toHaveBeenCalled();
-    });
-
-    it('pauses focus trap inside toggle', () => {
-      document.body.innerHTML =
-        '<div data-explorer-menu-item><div id="t"></div></div><div data-explorer-menu></div>';
-      const wrapper = shallow(<ExplorerPanel {...mockProps} />);
-      wrapper.instance().clickOutside({
-        target: document.querySelector('#t'),
-      });
-      expect(wrapper.state('paused')).toEqual(true);
-    });
-  });
-});

+ 0 - 210
client/src/components/Explorer/ExplorerPanel.tsx

@@ -1,210 +0,0 @@
-import React from 'react';
-import FocusTrap from 'focus-trap-react';
-
-import { gettext } from '../../utils/gettext';
-import { MAX_EXPLORER_PAGES } from '../../config/wagtailConfig';
-
-import Button from '../Button/Button';
-import LoadingSpinner from '../LoadingSpinner/LoadingSpinner';
-import Transition, { PUSH, POP } from '../Transition/Transition';
-import ExplorerHeader from './ExplorerHeader';
-import ExplorerItem from './ExplorerItem';
-import PageCount from './PageCount';
-import { State as NodeState, PageState } from './reducers/nodes';
-
-interface ExplorerPanelProps {
-  nodes: NodeState;
-  depth: number;
-  page: PageState;
-  onClose(): void;
-  gotoPage(id: number, transition: number): void;
-}
-
-interface ExplorerPanelState {
-  transition: typeof PUSH | typeof POP;
-  paused: boolean;
-}
-
-/**
- * The main panel of the page explorer menu, with heading,
- * menu items, and special states.
- */
-class ExplorerPanel extends React.Component<
-  ExplorerPanelProps,
-  ExplorerPanelState
-> {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      transition: PUSH,
-      paused: false,
-    };
-
-    this.onItemClick = this.onItemClick.bind(this);
-    this.onHeaderClick = this.onHeaderClick.bind(this);
-    this.clickOutside = this.clickOutside.bind(this);
-  }
-
-  componentWillReceiveProps(newProps) {
-    const { depth } = this.props;
-    const isPush = newProps.depth > depth;
-
-    this.setState({
-      transition: isPush ? PUSH : POP,
-    });
-  }
-
-  componentDidMount() {
-    document
-      .querySelector('[data-explorer-menu-item]')
-      ?.classList.add('submenu-active');
-    document.body.classList.add('explorer-open');
-    document.addEventListener('mousedown', this.clickOutside);
-    document.addEventListener('touchend', this.clickOutside);
-  }
-
-  componentWillUnmount() {
-    document
-      .querySelector('[data-explorer-menu-item]')
-      ?.classList.remove('submenu-active');
-    document.body.classList.remove('explorer-open');
-    document.removeEventListener('mousedown', this.clickOutside);
-    document.removeEventListener('touchend', this.clickOutside);
-  }
-
-  clickOutside(e) {
-    const { onClose } = this.props;
-    const explorer = document.querySelector('[data-explorer-menu]');
-    const toggle = document.querySelector('[data-explorer-menu-item]');
-
-    if (!explorer || !toggle) {
-      return;
-    }
-
-    const isInside = explorer.contains(e.target) || toggle.contains(e.target);
-    if (!isInside) {
-      onClose();
-    }
-
-    if (toggle.contains(e.target)) {
-      this.setState({
-        paused: true,
-      });
-    }
-  }
-
-  onItemClick(id, e) {
-    const { gotoPage } = this.props;
-
-    e.preventDefault();
-    e.stopPropagation();
-
-    gotoPage(id, 1);
-  }
-
-  onHeaderClick(e) {
-    const { page, depth, gotoPage } = this.props;
-    const parent = page.meta.parent?.id;
-
-    // Note: Checking depth as well in case the user started deep in the tree
-    if (depth > 0 && parent) {
-      e.preventDefault();
-      e.stopPropagation();
-
-      gotoPage(parent, -1);
-    }
-  }
-
-  renderChildren() {
-    const { page, nodes } = this.props;
-    let children;
-
-    if (!page.isFetchingChildren && !page.children.items) {
-      children = (
-        <div key="empty" className="c-explorer__placeholder">
-          {gettext('No results')}
-        </div>
-      );
-    } else {
-      children = (
-        <div key="children">
-          {page.children.items.map((id) => (
-            <ExplorerItem
-              key={id}
-              item={nodes[id]}
-              onClick={this.onItemClick.bind(null, id)}
-            />
-          ))}
-        </div>
-      );
-    }
-
-    return (
-      <div className="c-explorer__drawer">
-        {children}
-        {page.isFetchingChildren || page.isFetchingTranslations ? (
-          <div key="fetching" className="c-explorer__placeholder">
-            <LoadingSpinner />
-          </div>
-        ) : null}
-        {page.isError ? (
-          <div key="error" className="c-explorer__placeholder">
-            {gettext('Server Error')}
-          </div>
-        ) : null}
-      </div>
-    );
-  }
-
-  render() {
-    const { page, onClose, depth, gotoPage } = this.props;
-    const { transition, paused } = this.state;
-
-    return (
-      <FocusTrap
-        paused={
-          paused ||
-          !page ||
-          page.isFetchingChildren ||
-          page.isFetchingTranslations
-        }
-        focusTrapOptions={{
-          initialFocus: '.c-explorer__header__title',
-          onDeactivate: onClose,
-        }}
-      >
-        <div role="dialog" className="explorer">
-          <Button className="c-explorer__close">
-            {gettext('Close explorer')}
-          </Button>
-          <Transition
-            name={transition}
-            className="c-explorer"
-            component="nav"
-            label={gettext('Page explorer')}
-          >
-            <div key={depth} className="c-transition-group">
-              <ExplorerHeader
-                depth={depth}
-                page={page}
-                onClick={this.onHeaderClick}
-                gotoPage={gotoPage}
-              />
-
-              {this.renderChildren()}
-
-              {page.isError ||
-              (page.children.items &&
-                page.children.count > MAX_EXPLORER_PAGES) ? (
-                <PageCount page={page} />
-              ) : null}
-            </div>
-          </Transition>
-        </div>
-      </FocusTrap>
-    );
-  }
-}
-
-export default ExplorerPanel;

+ 0 - 39
client/src/components/Explorer/ExplorerToggle.test.js

@@ -1,39 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import configureMockStore from 'redux-mock-store';
-
-import ExplorerToggle from './ExplorerToggle';
-
-const store = configureMockStore()({});
-
-describe('ExplorerToggle', () => {
-  it('exists', () => {
-    expect(ExplorerToggle).toBeDefined();
-  });
-
-  it('basic', () => {
-    expect(
-      shallow(
-        <ExplorerToggle store={store}>
-          <span>To infinity and beyond!</span>
-        </ExplorerToggle>,
-      )
-        .find('ExplorerToggle')
-        .dive(),
-    ).toMatchSnapshot();
-  });
-
-  describe('actions', () => {
-    let wrapper;
-
-    beforeEach(() => {
-      store.dispatch = jest.fn();
-      wrapper = shallow(<ExplorerToggle store={store}>Test</ExplorerToggle>);
-    });
-
-    it('onToggle', () => {
-      wrapper.dive().prop('onToggle')();
-      expect(store.dispatch).toHaveBeenCalled();
-    });
-  });
-});

+ 0 - 43
client/src/components/Explorer/ExplorerToggle.tsx

@@ -1,43 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-
-import * as actions from './actions';
-
-import Button from '../../components/Button/Button';
-import Icon from '../../components/Icon/Icon';
-
-interface ExplorerToggleProps {
-  onToggle(): void;
-  children: React.ReactNode;
-}
-
-/**
- * A Button which toggles the explorer.
- */
-const ExplorerToggle: React.FunctionComponent<ExplorerToggleProps> = ({
-  children,
-  onToggle,
-}) => (
-  <Button dialogTrigger={true} onClick={onToggle}>
-    <Icon name="folder-open-inverse" className="icon--menuitem" />
-    {children}
-    <Icon name="arrow-right" className="icon--submenu-trigger" />
-  </Button>
-);
-
-const mapStateToProps = () => ({});
-
-const mapDispatchToProps = (dispatch) => ({
-  onToggle: (page) => dispatch(actions.toggleExplorer(page)),
-});
-
-const mergeProps = (_stateProps, dispatchProps, ownProps) => ({
-  children: ownProps.children,
-  onToggle: dispatchProps.onToggle.bind(null, ownProps.startPage),
-});
-
-export default connect(
-  mapStateToProps,
-  mapDispatchToProps,
-  mergeProps,
-)(ExplorerToggle);

+ 0 - 29
client/src/components/Explorer/PageCount.test.js

@@ -1,29 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import PageCount from './PageCount';
-
-const mockProps = {
-  page: {
-    id: 1,
-    children: {
-      count: 1,
-    },
-  },
-};
-
-describe('PageCount', () => {
-  it('exists', () => {
-    expect(PageCount).toBeDefined();
-  });
-
-  it('works', () => {
-    expect(shallow(<PageCount {...mockProps} />)).toMatchSnapshot();
-  });
-
-  it('plural', () => {
-    const props = Object.assign({}, mockProps);
-    props.page.children.count = 5;
-    expect(shallow(<PageCount {...props} />)).toMatchSnapshot();
-  });
-});

+ 0 - 32
client/src/components/Explorer/PageCount.tsx

@@ -1,32 +0,0 @@
-import React from 'react';
-
-import { gettext } from '../../utils/gettext';
-import { ADMIN_URLS } from '../../config/wagtailConfig';
-import Icon from '../Icon/Icon';
-
-interface PageCountProps {
-  page: {
-    id: number;
-    children: {
-      count: number;
-    };
-  };
-}
-
-const PageCount: React.FunctionComponent<PageCountProps> = ({ page }) => {
-  const count = page.children.count;
-
-  return (
-    <a href={`${ADMIN_URLS.PAGES}${page.id}/`} className="c-explorer__see-more">
-      {gettext('See all')}
-      <span>{` ${count} ${
-        count === 1
-          ? gettext('Page').toLowerCase()
-          : gettext('Pages').toLowerCase()
-      }`}</span>
-      <Icon name="arrow-right" />
-    </a>
-  );
-};
-
-export default PageCount;

+ 0 - 117
client/src/components/Explorer/__snapshots__/Explorer.test.js.snap

@@ -1,117 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Explorer renders 1`] = `
-<Explorer
-  currentPageId={null}
-  depth={0}
-  gotoPage={[Function]}
-  isVisible={false}
-  nodes={Object {}}
-  onClose={[Function]}
-  store={
-    Object {
-      "@@observable": [Function],
-      "dispatch": [Function],
-      "getState": [Function],
-      "replaceReducer": [Function],
-      "subscribe": [Function],
-    }
-  }
-/>
-`;
-
-exports[`Explorer renders 2`] = `<Connect(Explorer) />`;
-
-exports[`Explorer visible 1`] = `
-<Explorer
-  currentPageId={1}
-  depth={0}
-  gotoPage={[Function]}
-  isVisible={true}
-  nodes={
-    Object {
-      "1": Object {
-        "children": Object {
-          "count": 0,
-          "items": Array [],
-        },
-        "id": 0,
-        "isError": false,
-        "isFetchingChildren": true,
-        "isFetchingTranslations": false,
-        "meta": Object {
-          "children": Object {},
-          "parent": null,
-          "status": Object {
-            "has_unpublished_changes": true,
-            "live": false,
-            "status": "",
-          },
-        },
-      },
-    }
-  }
-  onClose={[Function]}
-  store={
-    Object {
-      "@@observable": [Function],
-      "dispatch": [Function],
-      "getState": [Function],
-      "replaceReducer": [Function],
-      "subscribe": [Function],
-    }
-  }
-/>
-`;
-
-exports[`Explorer visible 2`] = `
-<ExplorerPanel
-  depth={0}
-  gotoPage={[Function]}
-  nodes={
-    Object {
-      "1": Object {
-        "children": Object {
-          "count": 0,
-          "items": Array [],
-        },
-        "id": 0,
-        "isError": false,
-        "isFetchingChildren": true,
-        "isFetchingTranslations": false,
-        "meta": Object {
-          "children": Object {},
-          "parent": null,
-          "status": Object {
-            "has_unpublished_changes": true,
-            "live": false,
-            "status": "",
-          },
-        },
-      },
-    }
-  }
-  onClose={[Function]}
-  page={
-    Object {
-      "children": Object {
-        "count": 0,
-        "items": Array [],
-      },
-      "id": 0,
-      "isError": false,
-      "isFetchingChildren": true,
-      "isFetchingTranslations": false,
-      "meta": Object {
-        "children": Object {},
-        "parent": null,
-        "status": Object {
-          "has_unpublished_changes": true,
-          "live": false,
-          "status": "",
-        },
-      },
-    }
-  }
-/>
-`;

+ 0 - 73
client/src/components/Explorer/__snapshots__/ExplorerHeader.test.js.snap

@@ -1,73 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ExplorerHeader #depth at root 1`] = `
-<div
-  className="c-explorer__header"
->
-  <Button
-    className="c-explorer__header__title"
-    href="/admin/pages/undefined/"
-    onClick={[MockFunction]}
-  >
-    <div
-      className="c-explorer__header__title__inner"
-    >
-      <Icon
-        className="icon--explorer-header"
-        name="home"
-      />
-      <span>
-        Pages
-      </span>
-    </div>
-  </Button>
-</div>
-`;
-
-exports[`ExplorerHeader #page 1`] = `
-<div
-  className="c-explorer__header"
->
-  <Button
-    className="c-explorer__header__title"
-    href="/admin/pages/a/"
-    onClick={[MockFunction]}
-  >
-    <div
-      className="c-explorer__header__title__inner"
-    >
-      <Icon
-        className="icon--explorer-header"
-        name="arrow-left"
-      />
-      <span>
-        test
-      </span>
-    </div>
-  </Button>
-</div>
-`;
-
-exports[`ExplorerHeader basic 1`] = `
-<div
-  className="c-explorer__header"
->
-  <Button
-    className="c-explorer__header__title"
-    href="/admin/pages/undefined/"
-    onClick={[MockFunction]}
-  >
-    <div
-      className="c-explorer__header__title__inner"
-    >
-      <Icon
-        className="icon--explorer-header"
-        name="arrow-left"
-      />
-      <span>
-        Pages
-      </span>
-    </div>
-  </Button>
-</div>
-`;

+ 0 - 180
client/src/components/Explorer/__snapshots__/ExplorerItem.test.js.snap

@@ -1,180 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ExplorerItem children 1`] = `
-<div
-  className="c-explorer__item"
->
-  <Button
-    className="c-explorer__item__link"
-    href="/admin/pages/5/"
-  >
-    <Icon
-      className="icon--menuitem"
-      name="folder-inverse"
-    />
-    <h3
-      className="c-explorer__item__title"
-    >
-      test
-    </h3>
-  </Button>
-  <Button
-    className="c-explorer__item__action c-explorer__item__action--small"
-    href="/admin/pages/5/edit/"
-  >
-    <Icon
-      className="icon--item-action"
-      name="edit"
-      title="Edit 'test'"
-    />
-  </Button>
-  <Button
-    className="c-explorer__item__action"
-    href="/admin/pages/5/"
-    onClick={[Function]}
-  >
-    <Icon
-      className="icon--item-action"
-      name="arrow-right"
-      title="View child pages of 'test'"
-    />
-  </Button>
-</div>
-`;
-
-exports[`ExplorerItem renders 1`] = `
-<div
-  className="c-explorer__item"
->
-  <Button
-    className="c-explorer__item__link"
-    href="/admin/pages/5/"
-  >
-    <h3
-      className="c-explorer__item__title"
-    >
-      test
-    </h3>
-  </Button>
-  <Button
-    className="c-explorer__item__action c-explorer__item__action--small"
-    href="/admin/pages/5/edit/"
-  >
-    <Icon
-      className="icon--item-action"
-      name="edit"
-      title="Edit 'test'"
-    />
-  </Button>
-</div>
-`;
-
-exports[`ExplorerItem should show a publication status if not live 1`] = `
-<div
-  className="c-explorer__item"
->
-  <Button
-    className="c-explorer__item__link"
-    href="/admin/pages/5/"
-  >
-    <Icon
-      className="icon--menuitem"
-      name="folder-inverse"
-    />
-    <h3
-      className="c-explorer__item__title"
-    >
-      test
-    </h3>
-    <span
-      className="c-explorer__meta"
-    >
-      <PublicationStatus
-        status={
-          Object {
-            "has_unpublished_changes": true,
-            "live": false,
-            "status": "test",
-          }
-        }
-      />
-    </span>
-  </Button>
-  <Button
-    className="c-explorer__item__action c-explorer__item__action--small"
-    href="/admin/pages/5/edit/"
-  >
-    <Icon
-      className="icon--item-action"
-      name="edit"
-      title="Edit 'test'"
-    />
-  </Button>
-  <Button
-    className="c-explorer__item__action"
-    href="/admin/pages/5/"
-    onClick={[Function]}
-  >
-    <Icon
-      className="icon--item-action"
-      name="arrow-right"
-      title="View child pages of 'test'"
-    />
-  </Button>
-</div>
-`;
-
-exports[`ExplorerItem should show a publication status with unpublished changes 1`] = `
-<div
-  className="c-explorer__item"
->
-  <Button
-    className="c-explorer__item__link"
-    href="/admin/pages/5/"
-  >
-    <Icon
-      className="icon--menuitem"
-      name="folder-inverse"
-    />
-    <h3
-      className="c-explorer__item__title"
-    >
-      test
-    </h3>
-    <span
-      className="c-explorer__meta"
-    >
-      <PublicationStatus
-        status={
-          Object {
-            "has_unpublished_changes": true,
-            "live": true,
-            "status": "test",
-          }
-        }
-      />
-    </span>
-  </Button>
-  <Button
-    className="c-explorer__item__action c-explorer__item__action--small"
-    href="/admin/pages/5/edit/"
-  >
-    <Icon
-      className="icon--item-action"
-      name="edit"
-      title="Edit 'test'"
-    />
-  </Button>
-  <Button
-    className="c-explorer__item__action"
-    href="/admin/pages/5/"
-    onClick={[Function]}
-  >
-    <Icon
-      className="icon--item-action"
-      name="arrow-right"
-      title="View child pages of 'test'"
-    />
-  </Button>
-</div>
-`;

+ 0 - 349
client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap

@@ -1,349 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ExplorerPanel general rendering #isError 1`] = `
-<FocusTrap
-  _createFocusTrap={[Function]}
-  active={true}
-  focusTrapOptions={
-    Object {
-      "initialFocus": ".c-explorer__header__title",
-      "onDeactivate": [MockFunction],
-    }
-  }
-  paused={false}
->
-  <div
-    className="explorer"
-    role="dialog"
-  >
-    <Button
-      className="c-explorer__close"
-    >
-      Close explorer
-    </Button>
-    <Transition
-      className="c-explorer"
-      component="nav"
-      duration={210}
-      label="Page explorer"
-      name="push"
-    >
-      <div
-        className="c-transition-group"
-        key="1"
-      >
-        <ExplorerHeader
-          depth={1}
-          gotoPage={[MockFunction]}
-          onClick={[Function]}
-          page={
-            Object {
-              "children": Object {
-                "items": Array [],
-              },
-              "isError": true,
-              "meta": Object {
-                "parent": null,
-              },
-            }
-          }
-        />
-        <div
-          className="c-explorer__drawer"
-        >
-          <div
-            key="children"
-          />
-          <div
-            className="c-explorer__placeholder"
-            key="error"
-          >
-            Server Error
-          </div>
-        </div>
-        <PageCount
-          page={
-            Object {
-              "children": Object {
-                "items": Array [],
-              },
-              "isError": true,
-              "meta": Object {
-                "parent": null,
-              },
-            }
-          }
-        />
-      </div>
-    </Transition>
-  </div>
-</FocusTrap>
-`;
-
-exports[`ExplorerPanel general rendering #isFetching 1`] = `
-<FocusTrap
-  _createFocusTrap={[Function]}
-  active={true}
-  focusTrapOptions={
-    Object {
-      "initialFocus": ".c-explorer__header__title",
-      "onDeactivate": [MockFunction],
-    }
-  }
-  paused={false}
->
-  <div
-    className="explorer"
-    role="dialog"
-  >
-    <Button
-      className="c-explorer__close"
-    >
-      Close explorer
-    </Button>
-    <Transition
-      className="c-explorer"
-      component="nav"
-      duration={210}
-      label="Page explorer"
-      name="push"
-    >
-      <div
-        className="c-transition-group"
-        key="1"
-      >
-        <ExplorerHeader
-          depth={1}
-          gotoPage={[MockFunction]}
-          onClick={[Function]}
-          page={
-            Object {
-              "children": Object {
-                "items": Array [],
-              },
-              "isFetching": true,
-              "meta": Object {
-                "parent": null,
-              },
-            }
-          }
-        />
-        <div
-          className="c-explorer__drawer"
-        >
-          <div
-            key="children"
-          />
-        </div>
-      </div>
-    </Transition>
-  </div>
-</FocusTrap>
-`;
-
-exports[`ExplorerPanel general rendering #items 1`] = `
-<FocusTrap
-  _createFocusTrap={[Function]}
-  active={true}
-  focusTrapOptions={
-    Object {
-      "initialFocus": ".c-explorer__header__title",
-      "onDeactivate": [MockFunction],
-    }
-  }
-  paused={false}
->
-  <div
-    className="explorer"
-    role="dialog"
-  >
-    <Button
-      className="c-explorer__close"
-    >
-      Close explorer
-    </Button>
-    <Transition
-      className="c-explorer"
-      component="nav"
-      duration={210}
-      label="Page explorer"
-      name="push"
-    >
-      <div
-        className="c-transition-group"
-        key="1"
-      >
-        <ExplorerHeader
-          depth={1}
-          gotoPage={[MockFunction]}
-          onClick={[Function]}
-          page={
-            Object {
-              "children": Object {
-                "items": Array [
-                  1,
-                  2,
-                ],
-              },
-            }
-          }
-        />
-        <div
-          className="c-explorer__drawer"
-        >
-          <div
-            key="children"
-          >
-            <ExplorerItem
-              item={
-                Object {
-                  "admin_display_title": "Test",
-                  "id": 1,
-                  "meta": Object {
-                    "status": Object {},
-                    "type": "test",
-                  },
-                }
-              }
-              key="1"
-              onClick={[Function]}
-            />
-            <ExplorerItem
-              item={
-                Object {
-                  "admin_display_title": "Foo",
-                  "id": 2,
-                  "meta": Object {
-                    "status": Object {},
-                    "type": "foo",
-                  },
-                }
-              }
-              key="2"
-              onClick={[Function]}
-            />
-          </div>
-        </div>
-      </div>
-    </Transition>
-  </div>
-</FocusTrap>
-`;
-
-exports[`ExplorerPanel general rendering no children 1`] = `
-<FocusTrap
-  _createFocusTrap={[Function]}
-  active={true}
-  focusTrapOptions={
-    Object {
-      "initialFocus": ".c-explorer__header__title",
-      "onDeactivate": [MockFunction],
-    }
-  }
-  paused={false}
->
-  <div
-    className="explorer"
-    role="dialog"
-  >
-    <Button
-      className="c-explorer__close"
-    >
-      Close explorer
-    </Button>
-    <Transition
-      className="c-explorer"
-      component="nav"
-      duration={210}
-      label="Page explorer"
-      name="push"
-    >
-      <div
-        className="c-transition-group"
-        key="1"
-      >
-        <ExplorerHeader
-          depth={1}
-          gotoPage={[MockFunction]}
-          onClick={[Function]}
-          page={
-            Object {
-              "children": Object {},
-            }
-          }
-        />
-        <div
-          className="c-explorer__drawer"
-        >
-          <div
-            className="c-explorer__placeholder"
-            key="empty"
-          >
-            No results
-          </div>
-        </div>
-      </div>
-    </Transition>
-  </div>
-</FocusTrap>
-`;
-
-exports[`ExplorerPanel general rendering renders 1`] = `
-<FocusTrap
-  _createFocusTrap={[Function]}
-  active={true}
-  focusTrapOptions={
-    Object {
-      "initialFocus": ".c-explorer__header__title",
-      "onDeactivate": [MockFunction],
-    }
-  }
-  paused={false}
->
-  <div
-    className="explorer"
-    role="dialog"
-  >
-    <Button
-      className="c-explorer__close"
-    >
-      Close explorer
-    </Button>
-    <Transition
-      className="c-explorer"
-      component="nav"
-      duration={210}
-      label="Page explorer"
-      name="push"
-    >
-      <div
-        className="c-transition-group"
-        key="1"
-      >
-        <ExplorerHeader
-          depth={1}
-          gotoPage={[MockFunction]}
-          onClick={[Function]}
-          page={
-            Object {
-              "children": Object {
-                "items": Array [],
-              },
-              "meta": Object {
-                "parent": null,
-              },
-            }
-          }
-        />
-        <div
-          className="c-explorer__drawer"
-        >
-          <div
-            key="children"
-          />
-        </div>
-      </div>
-    </Transition>
-  </div>
-</FocusTrap>
-`;

+ 0 - 20
client/src/components/Explorer/__snapshots__/ExplorerToggle.test.js.snap

@@ -1,20 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ExplorerToggle basic 1`] = `
-<Button
-  dialogTrigger={true}
-  onClick={[Function]}
->
-  <Icon
-    className="icon--menuitem"
-    name="folder-open-inverse"
-  />
-  <span>
-    To infinity and beyond!
-  </span>
-  <Icon
-    className="icon--submenu-trigger"
-    name="arrow-right"
-  />
-</Button>
-`;

+ 0 - 31
client/src/components/Explorer/__snapshots__/PageCount.test.js.snap

@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PageCount plural 1`] = `
-<a
-  className="c-explorer__see-more"
-  href="/admin/pages/1/"
->
-  See all
-  <span>
-     5 pages
-  </span>
-  <Icon
-    name="arrow-right"
-  />
-</a>
-`;
-
-exports[`PageCount works 1`] = `
-<a
-  className="c-explorer__see-more"
-  href="/admin/pages/1/"
->
-  See all
-  <span>
-     1 page
-  </span>
-  <Icon
-    name="arrow-right"
-  />
-</a>
-`;

+ 0 - 109
client/src/components/Explorer/__snapshots__/actions.test.js.snap

@@ -1,109 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`actions gotoPage creates action 1`] = `
-Array [
-  Object {
-    "payload": Object {
-      "id": 5,
-      "transition": 1,
-    },
-    "type": "GOTO_PAGE",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_CHILDREN_START",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_TRANSLATIONS_START",
-  },
-]
-`;
-
-exports[`actions gotoPage triggers getChildren 1`] = `
-Array [
-  Object {
-    "payload": Object {
-      "id": 5,
-      "transition": 1,
-    },
-    "type": "GOTO_PAGE",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_CHILDREN_START",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_TRANSLATIONS_START",
-  },
-]
-`;
-
-exports[`actions toggleExplorer close 1`] = `
-Array [
-  Object {
-    "payload": undefined,
-    "type": "CLOSE_EXPLORER",
-  },
-]
-`;
-
-exports[`actions toggleExplorer open 1`] = `
-Array [
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "OPEN_EXPLORER",
-  },
-]
-`;
-
-exports[`actions toggleExplorer open at root 1`] = `
-Array [
-  Object {
-    "payload": Object {
-      "id": 1,
-    },
-    "type": "OPEN_EXPLORER",
-  },
-  Object {
-    "payload": Object {
-      "id": 1,
-    },
-    "type": "GET_CHILDREN_START",
-  },
-]
-`;
-
-exports[`actions toggleExplorer open first time 1`] = `
-Array [
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "OPEN_EXPLORER",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_CHILDREN_START",
-  },
-  Object {
-    "payload": Object {
-      "id": 5,
-    },
-    "type": "GET_TRANSLATIONS_START",
-  },
-]
-`;

+ 0 - 87
client/src/components/Explorer/actions.test.js

@@ -1,87 +0,0 @@
-import configureMockStore from 'redux-mock-store';
-import thunk from 'redux-thunk';
-
-import * as actions from './actions';
-
-const middlewares = [thunk];
-const mockStore = configureMockStore(middlewares);
-
-const stubState = {
-  explorer: {
-    isVisible: true,
-  },
-  nodes: {
-    5: {
-      isFetching: true,
-      children: {},
-    },
-  },
-};
-
-describe('actions', () => {
-  describe('closeExplorer', () => {
-    it('exists', () => {
-      expect(actions.closeExplorer).toBeDefined();
-    });
-
-    it('creates action', () => {
-      expect(actions.closeExplorer().type).toEqual('CLOSE_EXPLORER');
-    });
-  });
-
-  describe('toggleExplorer', () => {
-    it('exists', () => {
-      expect(actions.toggleExplorer).toBeDefined();
-    });
-
-    it('close', () => {
-      const store = mockStore(stubState);
-      store.dispatch(actions.toggleExplorer(5));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-
-    it('open', () => {
-      const stub = Object.assign({}, stubState);
-      stub.explorer.isVisible = false;
-      const store = mockStore(stub);
-      store.dispatch(actions.toggleExplorer(5));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-
-    it('open first time', () => {
-      const stub = { explorer: stubState.explorer, nodes: {} };
-      stub.explorer.isVisible = false;
-      const store = mockStore(stub);
-      store.dispatch(actions.toggleExplorer(5));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-
-    it('open at root', () => {
-      const stub = Object.assign({}, stubState);
-      stub.explorer.isVisible = false;
-      const store = mockStore(stub);
-      store.dispatch(actions.toggleExplorer(1));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-  });
-
-  describe('gotoPage', () => {
-    it('exists', () => {
-      expect(actions.gotoPage).toBeDefined();
-    });
-
-    it('creates action', () => {
-      const store = mockStore(stubState);
-      store.dispatch(actions.gotoPage(5, 1));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-
-    it('triggers getChildren', () => {
-      const stub = Object.assign({}, stubState);
-      stub.nodes[5].isFetching = false;
-      const store = mockStore(stub);
-      store.dispatch(actions.gotoPage(5, 1));
-      expect(store.getActions()).toMatchSnapshot();
-    });
-  });
-});

+ 0 - 157
client/src/components/Explorer/actions.ts

@@ -1,157 +0,0 @@
-import { ThunkAction } from 'redux-thunk';
-
-import * as admin from '../../api/admin';
-import { createAction } from '../../utils/actions';
-import { MAX_EXPLORER_PAGES } from '../../config/wagtailConfig';
-
-import { State, Action } from './reducers';
-
-type ThunkActionType = ThunkAction<void, State, unknown, Action>;
-
-const getPageSuccess = createAction(
-  'GET_PAGE_SUCCESS',
-  (id: number, data: admin.WagtailPageAPI) => ({ id, data }),
-);
-const getPageFailure = createAction(
-  'GET_PAGE_FAILURE',
-  (id: number, error: Error) => ({ id, error }),
-);
-
-/**
- * Gets a page from the API.
- */
-function getPage(id: number): ThunkActionType {
-  return (dispatch) =>
-    admin.getPage(id).then(
-      (data) => {
-        dispatch(getPageSuccess(id, data));
-      },
-      (error) => {
-        dispatch(getPageFailure(id, error));
-      },
-    );
-}
-
-const getChildrenStart = createAction('GET_CHILDREN_START', (id: number) => ({
-  id,
-}));
-const getChildrenSuccess = createAction(
-  'GET_CHILDREN_SUCCESS',
-  (id, items: admin.WagtailPageAPI[], meta: any) => ({ id, items, meta }),
-);
-const getChildrenFailure = createAction(
-  'GET_CHILDREN_FAILURE',
-  (id: number, error: Error) => ({ id, error }),
-);
-
-/**
- * Gets the children of a node from the API.
- */
-function getChildren(id: number, offset = 0): ThunkActionType {
-  return (dispatch) => {
-    dispatch(getChildrenStart(id));
-
-    return admin
-      .getPageChildren(id, {
-        offset: offset,
-      })
-      .then(
-        ({ items, meta }) => {
-          const nbPages = offset + items.length;
-          dispatch(getChildrenSuccess(id, items, meta));
-
-          // Load more pages if necessary. Only one request is created even though
-          // more might be needed, thus naturally throttling the loading.
-          if (nbPages < meta.total_count && nbPages < MAX_EXPLORER_PAGES) {
-            dispatch(getChildren(id, nbPages));
-          }
-        },
-        (error) => {
-          dispatch(getChildrenFailure(id, error));
-        },
-      );
-  };
-}
-
-const getTranslationsStart = createAction('GET_TRANSLATIONS_START', (id) => ({
-  id,
-}));
-const getTranslationsSuccess = createAction(
-  'GET_TRANSLATIONS_SUCCESS',
-  (id, items) => ({ id, items }),
-);
-const getTranslationsFailure = createAction(
-  'GET_TRANSLATIONS_FAILURE',
-  (id, error) => ({ id, error }),
-);
-
-/**
- * Gets the translations of a node from the API.
- */
-function getTranslations(id) {
-  return (dispatch) => {
-    dispatch(getTranslationsStart(id));
-
-    return admin.getAllPageTranslations(id, { onlyWithChildren: true }).then(
-      (items) => {
-        dispatch(getTranslationsSuccess(id, items));
-      },
-      (error) => {
-        dispatch(getTranslationsFailure(id, error));
-      },
-    );
-  };
-}
-
-const openExplorer = createAction('OPEN_EXPLORER', (id) => ({ id }));
-export const closeExplorer = createAction('CLOSE_EXPLORER');
-
-export function toggleExplorer(id: number): ThunkActionType {
-  return (dispatch, getState) => {
-    const { explorer, nodes } = getState();
-
-    if (explorer.isVisible) {
-      dispatch(closeExplorer());
-    } else {
-      const page = nodes[id];
-
-      dispatch(openExplorer(id));
-
-      if (!page) {
-        dispatch(getChildren(id));
-
-        if (id !== 1) {
-          dispatch(getTranslations(id));
-        }
-      }
-
-      // We need to get the title of the starting page, only if it is not the site's root.
-      const isNotRoot = id !== 1;
-      if (isNotRoot) {
-        dispatch(getPage(id));
-      }
-    }
-  };
-}
-
-const gotoPagePrivate = createAction(
-  'GOTO_PAGE',
-  (id: number, transition: number) => ({ id, transition }),
-);
-
-export function gotoPage(id: number, transition: number): ThunkActionType {
-  return (dispatch, getState) => {
-    const { nodes } = getState();
-    const page = nodes[id];
-
-    dispatch(gotoPagePrivate(id, transition));
-
-    if (page && !page.isFetchingChildren && !(page.children.count > 0)) {
-      dispatch(getChildren(id));
-    }
-
-    if (page && !page.isFetchingTranslations && page.translations == null) {
-      dispatch(getTranslations(id));
-    }
-  };
-}

+ 0 - 29
client/src/components/Explorer/index.test.js

@@ -1,29 +0,0 @@
-import Explorer, { ExplorerToggle, initExplorer } from './index';
-
-describe('Explorer index', () => {
-  it('exists', () => {
-    expect(Explorer).toBeDefined();
-  });
-
-  describe('ExplorerToggle', () => {
-    it('exists', () => {
-      expect(ExplorerToggle).toBeDefined();
-    });
-  });
-
-  describe('initExplorer', () => {
-    it('exists', () => {
-      expect(initExplorer).toBeInstanceOf(Function);
-    });
-
-    it('works', () => {
-      document.body.innerHTML =
-        '<div><div id="e"></div><div id="t">Test</div></div>';
-      const explorerNode = document.querySelector('#e');
-      const toggleNode = document.querySelector('#t');
-
-      initExplorer(explorerNode, toggleNode);
-      expect(document.body.innerHTML).toContain('href');
-    });
-  });
-});

+ 0 - 65
client/src/components/Explorer/index.tsx

@@ -1,65 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { Provider } from 'react-redux';
-import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
-import thunkMiddleware from 'redux-thunk';
-
-// import { perfMiddleware } from '../../utils/performance';
-import Explorer from './Explorer';
-import ExplorerToggle from './ExplorerToggle';
-import explorer from './reducers/explorer';
-import nodes from './reducers/nodes';
-
-/**
- * Initialises the explorer component on the given nodes.
- */
-const initExplorer = (explorerNode, toggleNode) => {
-  const rootReducer = combineReducers({
-    explorer,
-    nodes,
-  });
-
-  const middleware = [thunkMiddleware];
-
-  // Uncomment this to use performance measurements.
-  // if (process.env.NODE_ENV !== 'production') {
-  //   middleware.push(perfMiddleware);
-  // }
-
-  const store = createStore(
-    rootReducer,
-    {},
-    compose(
-      applyMiddleware(...middleware),
-      // Expose store to Redux DevTools extension.
-      window.__REDUX_DEVTOOLS_EXTENSION__
-        ? window.__REDUX_DEVTOOLS_EXTENSION__()
-        : (func) => func,
-    ),
-  );
-
-  const startPage = parseInt(
-    toggleNode.getAttribute('data-explorer-start-page'),
-    10,
-  );
-
-  ReactDOM.render(
-    <Provider store={store}>
-      <ExplorerToggle startPage={startPage}>
-        {toggleNode.textContent}
-      </ExplorerToggle>
-    </Provider>,
-    toggleNode.parentNode,
-  );
-
-  ReactDOM.render(
-    <Provider store={store}>
-      <Explorer />
-    </Provider>,
-    explorerNode,
-  );
-};
-
-export default Explorer;
-
-export { ExplorerToggle, initExplorer };

+ 0 - 25
client/src/components/Explorer/reducers/__snapshots__/explorer.test.js.snap

@@ -1,25 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`explorer OPEN_EXPLORER 1`] = `
-Object {
-  "currentPageId": 1,
-  "depth": 0,
-  "isVisible": true,
-}
-`;
-
-exports[`explorer POP_PAGE 1`] = `
-Object {
-  "currentPageId": null,
-  "depth": 0,
-  "isVisible": false,
-}
-`;
-
-exports[`explorer PUSH_PAGE 1`] = `
-Object {
-  "currentPageId": null,
-  "depth": 0,
-  "isVisible": false,
-}
-`;

+ 0 - 208
client/src/components/Explorer/reducers/__snapshots__/nodes.test.js.snap

@@ -1,208 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`nodes GET_CHILDREN_FAILURE 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 0,
-    "isError": true,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": true,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes GET_CHILDREN_START 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 0,
-    "isError": false,
-    "isFetchingChildren": true,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes GET_CHILDREN_SUCCESS 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 3,
-      "items": Array [
-        3,
-        4,
-        5,
-      ],
-    },
-    "id": 0,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-  "3": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 3,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-  "4": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 4,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-  "5": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 5,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes GET_PAGE_FAILURE 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 0,
-    "isError": true,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": true,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes GET_PAGE_SUCCESS 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 0,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes OPEN_EXPLORER 1`] = `
-Object {
-  "1": Object {
-    "children": Object {
-      "count": 0,
-      "items": Array [],
-    },
-    "id": 0,
-    "isError": false,
-    "isFetchingChildren": false,
-    "isFetchingTranslations": false,
-    "meta": Object {
-      "children": Object {},
-      "parent": null,
-      "status": Object {
-        "has_unpublished_changes": true,
-        "live": false,
-        "status": "",
-      },
-    },
-  },
-}
-`;
-
-exports[`nodes empty state 1`] = `Object {}`;

+ 0 - 39
client/src/components/Explorer/reducers/explorer.test.js

@@ -1,39 +0,0 @@
-import explorer from './explorer';
-
-describe('explorer', () => {
-  const initialState = explorer(undefined, {});
-
-  it('exists', () => {
-    expect(explorer).toBeDefined();
-  });
-
-  it('returns the initial state if no input is provided', () => {
-    expect(explorer(undefined, {})).toEqual(initialState);
-  });
-
-  it('OPEN_EXPLORER', () => {
-    const action = { type: 'OPEN_EXPLORER', payload: { id: 1 } };
-    expect(explorer(initialState, action)).toMatchSnapshot();
-  });
-
-  it('CLOSE_EXPLORER', () => {
-    expect(explorer(initialState, { type: 'CLOSE_EXPLORER' })).toEqual(
-      initialState,
-    );
-  });
-
-  it('PUSH_PAGE', () => {
-    expect(
-      explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } }),
-    ).toMatchSnapshot();
-  });
-
-  it('POP_PAGE', () => {
-    const state = explorer(initialState, {
-      type: 'PUSH_PAGE',
-      payload: { id: 100 },
-    });
-    const action = { type: 'POP_PAGE', payload: { id: 100 } };
-    expect(explorer(state, action)).toMatchSnapshot();
-  });
-});

+ 0 - 68
client/src/components/Explorer/reducers/explorer.ts

@@ -1,68 +0,0 @@
-export interface State {
-  isVisible: boolean;
-  depth: number;
-  currentPageId: number | null;
-}
-
-const defaultState: State = {
-  isVisible: false,
-  depth: 0,
-  currentPageId: null,
-};
-
-export const OPEN_EXPLORER = 'OPEN_EXPLORER';
-interface OpenExplorerAction {
-  type: typeof OPEN_EXPLORER;
-  payload: {
-    id: number;
-  };
-}
-
-export const CLOSE_EXPLORER = 'CLOSE_EXPLORER';
-interface CloseExplorerAction {
-  type: typeof CLOSE_EXPLORER;
-}
-
-export const GOTO_PAGE = 'GOTO_PAGE';
-interface GotoPageAction {
-  type: typeof GOTO_PAGE;
-  payload: {
-    id: number;
-    transition: number;
-  };
-}
-
-export type Action = OpenExplorerAction | CloseExplorerAction | GotoPageAction;
-
-/**
- * Oversees the state of the explorer. Defines:
- * - Where in the page tree the explorer is at.
- * - Whether the explorer is open or not.
- */
-export default function explorer(
-  prevState = defaultState,
-  action: Action,
-): State {
-  switch (action.type) {
-    case OPEN_EXPLORER:
-      // Provide a starting page when opening the explorer.
-      return {
-        isVisible: true,
-        depth: 0,
-        currentPageId: action.payload.id,
-      };
-
-    case CLOSE_EXPLORER:
-      return defaultState;
-
-    case GOTO_PAGE:
-      return {
-        isVisible: prevState.isVisible,
-        depth: prevState.depth + action.payload.transition,
-        currentPageId: action.payload.id,
-      };
-
-    default:
-      return prevState;
-  }
-}

+ 0 - 9
client/src/components/Explorer/reducers/index.ts

@@ -1,9 +0,0 @@
-import { State as ExplorerState, Action as ExplorerAction } from './explorer';
-import { State as NodeState, Action as NodeAction } from './nodes';
-
-export interface State {
-  explorer: ExplorerState;
-  nodes: NodeState;
-}
-
-export type Action = ExplorerAction | NodeAction;

+ 0 - 64
client/src/components/Explorer/reducers/nodes.test.js

@@ -1,64 +0,0 @@
-import nodes from './nodes';
-
-describe('nodes', () => {
-  const initialState = nodes(undefined, {});
-
-  it('exists', () => {
-    expect(nodes).toBeDefined();
-  });
-
-  it('empty state', () => {
-    expect(initialState).toMatchSnapshot();
-  });
-
-  it('OPEN_EXPLORER', () => {
-    const action = { type: 'OPEN_EXPLORER', payload: { id: 1 } };
-    expect(nodes(initialState, action)).toMatchSnapshot();
-  });
-
-  it('GET_PAGE_SUCCESS', () => {
-    const action = { type: 'GET_PAGE_SUCCESS', payload: { id: 1, data: {} } };
-    expect(nodes(initialState, action)).toMatchSnapshot();
-  });
-
-  it('GET_PAGE_FAILURE', () => {
-    const state = nodes(initialState, {
-      type: 'OPEN_EXPLORER',
-      payload: { id: 1 },
-    });
-    const action = { type: 'GET_PAGE_FAILURE', payload: { id: 1 } };
-    expect(nodes(state, action)).toMatchSnapshot();
-  });
-
-  it('GET_CHILDREN_START', () => {
-    const action = { type: 'GET_CHILDREN_START', payload: { id: 1 } };
-    expect(nodes(initialState, action)).toMatchSnapshot();
-  });
-
-  it('GET_CHILDREN_SUCCESS', () => {
-    const state = nodes(initialState, {
-      type: 'OPEN_EXPLORER',
-      payload: { id: 1 },
-    });
-    const action = {
-      type: 'GET_CHILDREN_SUCCESS',
-      payload: {
-        id: 1,
-        items: [{ id: 3 }, { id: 4 }, { id: 5 }],
-        meta: {
-          total_count: 3,
-        },
-      },
-    };
-    expect(nodes(state, action)).toMatchSnapshot();
-  });
-
-  it('GET_CHILDREN_FAILURE', () => {
-    const state = nodes(initialState, {
-      type: 'OPEN_EXPLORER',
-      payload: { id: 1 },
-    });
-    const action = { type: 'GET_CHILDREN_FAILURE', payload: { id: 1 } };
-    expect(nodes(state, action)).toMatchSnapshot();
-  });
-});

+ 0 - 239
client/src/components/Explorer/reducers/nodes.ts

@@ -1,239 +0,0 @@
-import { WagtailPageAPI } from '../../../api/admin';
-import { OPEN_EXPLORER, CLOSE_EXPLORER } from './explorer';
-
-export interface PageState extends WagtailPageAPI {
-  isFetchingChildren: boolean;
-  isFetchingTranslations: boolean;
-  isError: boolean;
-  children: {
-    items: any[];
-    count: number;
-  };
-  translations?: Map<string, number>;
-}
-
-const defaultPageState: PageState = {
-  id: 0,
-  isFetchingChildren: false,
-  isFetchingTranslations: false,
-  isError: false,
-  children: {
-    items: [],
-    count: 0,
-  },
-  meta: {
-    status: {
-      status: '',
-      live: false,
-      has_unpublished_changes: true,
-    },
-    parent: null,
-    children: {},
-  },
-};
-
-interface OpenExplorerAction {
-  type: typeof OPEN_EXPLORER;
-  payload: {
-    id: number;
-  };
-}
-
-interface CloseExplorerAction {
-  type: typeof CLOSE_EXPLORER;
-}
-
-export const GET_PAGE_SUCCESS = 'GET_PAGE_SUCCESS';
-interface GetPageSuccess {
-  type: typeof GET_PAGE_SUCCESS;
-  payload: {
-    id: number;
-    data: WagtailPageAPI;
-  };
-}
-
-export const GET_CHILDREN_START = 'GET_CHILDREN_START';
-interface GetChildrenStart {
-  type: typeof GET_CHILDREN_START;
-  payload: {
-    id: number;
-  };
-}
-
-export const GET_CHILDREN_SUCCESS = 'GET_CHILDREN_SUCCESS';
-interface GetChildrenSuccess {
-  type: typeof GET_CHILDREN_SUCCESS;
-  payload: {
-    id: number;
-    meta: {
-      total_count: number;
-    };
-    items: WagtailPageAPI[];
-  };
-}
-
-export const GET_TRANSLATIONS_START = 'GET_TRANSLATIONS_START';
-interface GetTranslationsStart {
-  type: typeof GET_TRANSLATIONS_START;
-  payload: {
-    id: number;
-  };
-}
-
-export const GET_TRANSLATIONS_SUCCESS = 'GET_TRANSLATIONS_SUCCESS';
-interface GetTranslationsSuccess {
-  type: typeof GET_TRANSLATIONS_SUCCESS;
-  payload: {
-    id: number;
-    meta: {
-      total_count: number;
-    };
-    items: WagtailPageAPI[];
-  };
-}
-
-export const GET_PAGE_FAILURE = 'GET_PAGE_FAILURE';
-interface GetPageFailure {
-  type: typeof GET_PAGE_FAILURE;
-  payload: {
-    id: number;
-  };
-}
-
-export const GET_CHILDREN_FAILURE = 'GET_CHILDREN_FAILURE';
-interface GetChildrenFailure {
-  type: typeof GET_CHILDREN_FAILURE;
-  payload: {
-    id: number;
-  };
-}
-
-export const GET_TRANSLATIONS_FAILURE = 'GET_TRANSLATIONS_FAILURE';
-interface GetTranslationsFailure {
-  type: typeof GET_TRANSLATIONS_FAILURE;
-  payload: {
-    id: number;
-  };
-}
-
-export type Action =
-  | OpenExplorerAction
-  | CloseExplorerAction
-  | GetPageSuccess
-  | GetChildrenStart
-  | GetChildrenSuccess
-  | GetTranslationsStart
-  | GetTranslationsSuccess
-  | GetPageFailure
-  | GetChildrenFailure
-  | GetTranslationsFailure;
-
-/**
- * A single page node in the explorer.
- */
-const node = (state = defaultPageState, action: Action): PageState => {
-  switch (action.type) {
-    case GET_PAGE_SUCCESS:
-      return Object.assign({}, state, action.payload.data, {
-        isError: false,
-      });
-
-    case GET_CHILDREN_START:
-      return Object.assign({}, state, {
-        isFetchingChildren: true,
-      });
-
-    case GET_TRANSLATIONS_START:
-      return Object.assign({}, state, {
-        isFetchingTranslations: true,
-      });
-
-    case GET_CHILDREN_SUCCESS:
-      return Object.assign({}, state, {
-        isFetchingChildren: false,
-        isError: false,
-        children: {
-          items: state.children.items
-            .slice()
-            .concat(action.payload.items.map((item) => item.id)),
-          count: action.payload.meta.total_count,
-        },
-      });
-
-    case GET_TRANSLATIONS_SUCCESS:
-      // eslint-disable-next-line no-case-declarations
-      const translations = new Map();
-
-      action.payload.items.forEach((item) => {
-        translations.set(item.meta.locale, item.id);
-      });
-
-      return Object.assign({}, state, {
-        isFetchingTranslations: false,
-        isError: false,
-        translations,
-      });
-
-    case GET_PAGE_FAILURE:
-    case GET_CHILDREN_FAILURE:
-    case GET_TRANSLATIONS_FAILURE:
-      return Object.assign({}, state, {
-        isFetchingChildren: false,
-        isFetchingTranslations: true,
-        isError: true,
-      });
-
-    default:
-      return state;
-  }
-};
-
-export interface State {
-  [id: number]: PageState;
-}
-
-const defaultState: State = {};
-
-/**
- * Contains all of the page nodes in one object.
- */
-export default function nodes(state = defaultState, action: Action) {
-  switch (action.type) {
-    case OPEN_EXPLORER: {
-      return Object.assign({}, state, {
-        [action.payload.id]: Object.assign({}, defaultPageState),
-      });
-    }
-
-    case GET_PAGE_SUCCESS:
-    case GET_CHILDREN_START:
-    case GET_TRANSLATIONS_START:
-    case GET_PAGE_FAILURE:
-    case GET_CHILDREN_FAILURE:
-    case GET_TRANSLATIONS_FAILURE:
-      return Object.assign({}, state, {
-        // Delegate logic to single-node reducer.
-        [action.payload.id]: node(state[action.payload.id], action),
-      });
-
-    case GET_CHILDREN_SUCCESS:
-    case GET_TRANSLATIONS_SUCCESS:
-      // eslint-disable-next-line no-case-declarations
-      const newState = Object.assign({}, state, {
-        [action.payload.id]: node(state[action.payload.id], action),
-      });
-
-      action.payload.items.forEach((item) => {
-        newState[item.id] = Object.assign({}, defaultPageState, item);
-      });
-
-      return newState;
-
-    case CLOSE_EXPLORER: {
-      return defaultState;
-    }
-
-    default:
-      return state;
-  }
-}

+ 1 - 1
client/src/components/PageExplorer/PageExplorerPanel.test.js

@@ -20,7 +20,7 @@ const mockProps = {
 describe('PageExplorerPanel', () => {
   describe('general rendering', () => {
     beforeEach(() => {
-      document.body.innerHTML = '<div data-explorer-menu-item></div>';
+      document.body.innerHTML = '<div></div>';
     });
 
     it('exists', () => {

+ 8 - 0
client/src/components/Sidebar/Sidebar.scss

@@ -87,6 +87,14 @@
   &--mobile &__collapse-toggle {
     display: none;
   }
+
+  .icon--menuitem {
+    width: 1.25em;
+    height: 1.25em;
+    min-width: 1.25em;
+    margin-inline-end: 0.5em;
+    vertical-align: text-top;
+  }
 }
 
 // This is a separate component as it needs to display in the header

+ 2 - 9
client/src/components/Sidebar/modules/MainMenu.scss

@@ -42,14 +42,6 @@
     @include show-focus-outline-inside;
   }
 
-  .icon--menuitem {
-    width: 1.25em;
-    height: 1.25em;
-    min-width: 1.25em;
-    margin-inline-end: 0.5em;
-    vertical-align: text-top;
-  }
-
   .icon--submenu-header {
     display: block;
     width: 4rem;
@@ -134,7 +126,8 @@
     width: $menu-width !important; // Override collapsed style
 
     > ul {
-      max-height: $nav-footer-submenu-height;
+      $footer-submenu-height: 85px;
+      max-height: $footer-submenu-height;
       visibility: visible;
     }
   }

+ 0 - 77
client/src/entrypoints/admin/core.js

@@ -224,83 +224,6 @@ $(() => {
   // Add class to the body from which transitions may be hung so they don't appear to transition as the page loads
   $('body').addClass('ready');
 
-  // Enable toggle to open/close nav
-  $(document).on('click', '#nav-toggle', () => {
-    $('body').toggleClass('nav-open');
-    if (!$('body').hasClass('nav-open')) {
-      $('body').addClass('nav-closed');
-    } else {
-      $('body').removeClass('nav-closed');
-    }
-  });
-
-  // Enable toggle to open/close user settings
-  // eslint-disable-next-line func-names
-  $(document).on('click', '#account-settings', function () {
-    $('.nav-main').toggleClass('nav-main--open-footer');
-    $(this).find('em').toggleClass('icon-arrow-down-after icon-arrow-up-after');
-  });
-
-  // Resize nav to fit height of window. This is an unimportant bell/whistle to make it look nice
-  // eslint-disable-next-line func-names
-  const fitNav = function () {
-    $('.nav-wrapper').css('min-height', $(window).height());
-  };
-
-  fitNav();
-
-  $(window).on('resize', () => {
-    fitNav();
-  });
-
-  // Logo interactivity
-  function initLogo() {
-    const sensitivity = 8; // the amount of times the user must stroke the wagtail to trigger the animation
-
-    const $logoContainer = $('[data-animated-logo-container]');
-    let lastMouseX = 0;
-    let lastDir = '';
-    let dirChangeCount = 0;
-
-    function enableWag() {
-      $logoContainer.removeClass('logo-serious').addClass('logo-playful');
-    }
-
-    function disableWag() {
-      $logoContainer.removeClass('logo-playful').addClass('logo-serious');
-    }
-
-    $logoContainer.on('mousemove', (event) => {
-      const mouseX = event.pageX;
-      let dir;
-
-      if (mouseX > lastMouseX) {
-        dir = 'r';
-      } else if (mouseX < lastMouseX) {
-        dir = 'l';
-      }
-
-      if (dir !== lastDir && lastDir !== '') {
-        dirChangeCount += 1;
-      }
-
-      if (dirChangeCount > sensitivity) {
-        enableWag();
-      }
-
-      lastMouseX = mouseX;
-      lastDir = dir;
-    });
-
-    $logoContainer.on('mouseleave', () => {
-      dirChangeCount = 0;
-      disableWag();
-    });
-
-    disableWag();
-  }
-  initLogo();
-
   // Enable nice focus effects on all fields. This enables help text on hover.
   // eslint-disable-next-line func-names
   $(document).on('focus mouseover', 'input,textarea,select', function () {

+ 0 - 15
client/src/entrypoints/admin/sidebar-legacy.js

@@ -1,15 +0,0 @@
-import { initExplorer } from '../../components/Explorer';
-import { initSubmenus } from '../../includes/initSubmenus';
-import { initSkipLink } from '../../includes/initSkipLink';
-
-document.addEventListener('DOMContentLoaded', () => {
-  const explorerNode = document.querySelector('[data-explorer-menu]');
-  const toggleNode = document.querySelector('[data-explorer-start-page]');
-
-  if (explorerNode && toggleNode) {
-    initExplorer(explorerNode, toggleNode);
-  }
-
-  initSubmenus();
-  initSkipLink();
-});

+ 0 - 27
client/src/entrypoints/admin/sidebar-legacy.test.js

@@ -1,27 +0,0 @@
-jest.mock('../../components/Explorer');
-
-const Explorer = require('../../components/Explorer');
-
-document.addEventListener = jest.fn();
-
-require('./sidebar-legacy');
-
-describe('sidebar-legacy', () => {
-  const [event, listener] = document.addEventListener.mock.calls[0];
-
-  it('DOMContentLoaded', () => {
-    expect(event).toBe('DOMContentLoaded');
-  });
-
-  it('init', () => {
-    listener();
-    expect(Explorer.initExplorer).not.toHaveBeenCalled();
-  });
-
-  it('init with DOM', () => {
-    document.body.innerHTML =
-      '<div data-explorer-menu></div><div data-explorer-start-page></div>';
-    listener();
-    expect(Explorer.initExplorer).toHaveBeenCalled();
-  });
-});

+ 2 - 1
client/src/entrypoints/admin/wagtailadmin.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-import { Icon, Portal, initUpgradeNotification } from '../..';
+import { Icon, Portal, initUpgradeNotification, initSkipLink } from '../..';
 
 if (process.env.NODE_ENV === 'development') {
   // Run react-axe in development only, so it does not affect performance
@@ -21,4 +21,5 @@ window.wagtail.components = {
  */
 document.addEventListener('DOMContentLoaded', () => {
   initUpgradeNotification();
+  initSkipLink();
 });

+ 0 - 49
client/src/includes/initSubmenus.js

@@ -1,49 +0,0 @@
-/**
- * Initialises the Submenus within the primary Wagtail menu (excluding the Explorer menu)
- */
-
-const initSubmenus = () => {
-  const primaryNavContainer = document.querySelector('[data-nav-primary]');
-
-  if (!primaryNavContainer) {
-    return;
-  }
-
-  const subMenuTriggers = document.querySelectorAll(
-    '[data-nav-primary-submenu-trigger]',
-  );
-  const activeClass = 'submenu-active';
-
-  subMenuTriggers.forEach((subMenuTrigger) => {
-    subMenuTrigger.addEventListener('click', (clickEvent) => {
-      const submenuContainer = subMenuTrigger.parentNode;
-
-      primaryNavContainer.classList.remove(activeClass);
-      subMenuTriggers.forEach((sm) => sm.classList.remove(activeClass));
-
-      primaryNavContainer.classList.toggle(activeClass);
-      submenuContainer.classList.toggle(activeClass);
-
-      document.addEventListener('mousedown', (e) => {
-        if (
-          !submenuContainer.contains(e.target) &&
-          subMenuTrigger !== e.target
-        ) {
-          primaryNavContainer.classList.remove(activeClass);
-          submenuContainer.classList.remove(activeClass);
-        }
-      });
-
-      document.addEventListener('keydown', (e) => {
-        if (e.key === 'Escape') {
-          primaryNavContainer.classList.remove(activeClass);
-          submenuContainer.classList.remove(activeClass);
-        }
-      });
-
-      clickEvent.preventDefault();
-    });
-  });
-};
-
-export { initSubmenus };

+ 8 - 17
client/src/index.ts

@@ -3,20 +3,11 @@
  * Re-exports components and other modules via a cleaner API.
  */
 
-import Button from './components/Button/Button';
-import Icon from './components/Icon/Icon';
-import LoadingSpinner from './components/LoadingSpinner/LoadingSpinner';
-import Portal from './components/Portal/Portal';
-import PublicationStatus from './components/PublicationStatus/PublicationStatus';
-import Transition from './components/Transition/Transition';
-import { initUpgradeNotification } from './components/UpgradeNotification';
-
-export {
-  Button,
-  Icon,
-  LoadingSpinner,
-  Portal,
-  PublicationStatus,
-  Transition,
-  initUpgradeNotification,
-};
+export { default as Button } from './components/Button/Button';
+export { default as Icon } from './components/Icon/Icon';
+export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner';
+export { default as Portal } from './components/Portal/Portal';
+export { default as PublicationStatus } from './components/PublicationStatus/PublicationStatus';
+export { default as Transition } from './components/Transition/Transition';
+export { initUpgradeNotification } from './components/UpgradeNotification';
+export { initSkipLink } from './includes/initSkipLink';

+ 0 - 1
client/storybook/preview.js

@@ -1,7 +1,6 @@
 import '../tests/stubs';
 
 import '../../wagtail/admin/static_src/wagtailadmin/scss/core.scss';
-import '../../wagtail/admin/static_src/wagtailadmin/scss/sidebar.scss';
 import './preview.scss';
 
 export const parameters = {

+ 0 - 9
client/webpack.config.js

@@ -45,7 +45,6 @@ module.exports = function exports(env, argv) {
       'page-editor',
       'privacy-switch',
       'sidebar',
-      'sidebar-legacy',
       'task-chooser-modal',
       'task-chooser',
       'telepath/blocks',
@@ -187,14 +186,6 @@ module.exports = function exports(env, argv) {
     'panels',
     'streamfield.scss',
   );
-  sassEntry[getOutputPath('admin', 'css', 'sidebar')] = path.resolve(
-    'wagtail',
-    'admin',
-    'static_src',
-    'wagtailadmin',
-    'scss',
-    'sidebar.scss',
-  );
   sassEntry[getOutputPath('admin', 'css', 'userbar')] = path.resolve(
     'wagtail',
     'admin',

+ 0 - 9
docs/reference/settings.rst

@@ -237,15 +237,6 @@ If a user has not uploaded a profile picture, Wagtail will look for an avatar li
 
 Changes whether the Submit for Moderation button is displayed in the action menu.
 
-``WAGTAIL_SLIM_SIDEBAR``
-------------------------
-
-.. code-block:: python
-
-  WAGTAIL_SLIM_SIDEBAR = False
-
-Disables Wagtail’s slim sidebar to use the legacy sidebar instead. The legacy sidebar and this setting will be removed in Wagtail 2.18.
-
 Comments
 ========
 

+ 0 - 10
wagtail/admin/menu.py

@@ -10,8 +10,6 @@ from wagtail.coreutils import cautious_slugify
 
 
 class MenuItem(metaclass=MediaDefiningClass):
-    template = "wagtailadmin/shared/menu_item.html"
-
     def __init__(
         self, label, url, name=None, classnames="", icon_name="", attrs=None, order=1000
     ):
@@ -121,8 +119,6 @@ class Menu:
 
 
 class SubmenuMenuItem(MenuItem):
-    template = "wagtailadmin/shared/menu_submenu_item.html"
-
     """A MenuItem which wraps an inner Menu object"""
 
     def __init__(self, label, menu, **kwargs):
@@ -136,12 +132,6 @@ class SubmenuMenuItem(MenuItem):
     def is_active(self, request):
         return bool(self.menu.active_menu_items(request))
 
-    def get_context(self, request):
-        context = super().get_context(request)
-        context["menu_html"] = self.menu.render_html(request)
-        context["request"] = request
-        return context
-
     def render_component(self, request):
         return SubMenuItemComponent(
             self.name,

+ 6 - 1
wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss

@@ -28,10 +28,15 @@
   align-items: center;
 }
 
-.wagtail-logo-container__desktop {
+.page404__logo {
   float: left;
   width: 400px;
   height: 500px;
+
+  // Media for Windows High Contrast mode
+  @media (forced-colors: $media-forced-colours) {
+    background-color: $system-color-link-text;
+  }
 }
 
 .page404__text-container {

+ 0 - 1
wagtail/admin/static_src/wagtailadmin/scss/sidebar.scss

@@ -1 +0,0 @@
-@import '../../../../../client/scss/sidebar';

+ 3 - 1
wagtail/admin/templates/wagtailadmin/404.html

@@ -14,7 +14,9 @@
     <main class="page404__bg">
         <div class="w-w-full w-h-full w-max-w-6xl w-mx-auto w-flex w-items-center w-justify-center">
             {% block branding_logo %}
-                {% include "wagtailadmin/shared/animated_logo.html" %}
+                <div class="page404__logo w-hidden sm:w-block">
+                    <img src="{% versioned_static 'wagtailadmin/images/wagtail-logo.svg' %}" alt=""/>
+                </div>
             {% endblock %}
 
             <div class="page404__text-container">

+ 2 - 4
wagtail/admin/templates/wagtailadmin/admin_base.html

@@ -6,8 +6,6 @@
     <link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/vendor/jquery.tagit.css' %}">
     <link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/core.css' %}" type="text/css" />
 
-    {% main_nav_css %}
-
     {% hook_output 'insert_global_admin_css' %}
 
     {% block extra_css %}{% endblock %}
@@ -54,8 +52,8 @@
     <script src="{% versioned_static 'wagtailadmin/js/core.js' %}"></script>
     <script src="{% versioned_static 'wagtailadmin/js/vendor.js' %}"></script>
     <script src="{% versioned_static 'wagtailadmin/js/wagtailadmin.js' %}"></script>
-
-    {% main_nav_js %}
+    <script src="{% versioned_static 'wagtailadmin/js/telepath/telepath.js' %}"></script>
+    <script src="{% versioned_static 'wagtailadmin/js/sidebar.js' %}"></script>
 
     {% hook_output 'insert_global_admin_js' %}
 

+ 2 - 37
wagtail/admin/templates/wagtailadmin/base.html

@@ -2,41 +2,9 @@
 {% load wagtailadmin_tags wagtailcore_tags i18n %}
 
 {% block furniture %}
-    {% slim_sidebar_enabled as slim_sidebar_enabled %}
     <template data-wagtail-sidebar-branding-logo>{% block branding_logo %}{% endblock %}</template>
-    {% if slim_sidebar_enabled %}
-        {% sidebar_props %}
-        <aside id="wagtail-sidebar" data-wagtail-sidebar></aside>
-    {% else %}
-        <aside id="wagtail-sidebar" class="nav-wrapper" data-nav-primary>
-            <div class="inner">
-                <a href="{% url 'wagtailadmin_home' %}" class="logo" aria-label="{% trans 'Dashboard' %}">
-                    {# Mobile-only logo: #}
-                    <div class="wagtail-logo-container__mobile u-hidden@sm">
-                        <img class="wagtail-logo wagtail-logo__full" src="{% versioned_static 'wagtailadmin/images/wagtail-logo.svg' %}" alt="" width="80" />
-                    </div>
-
-                    {# Desktop logo (animated): #}
-                    {% include "wagtailadmin/shared/animated_logo.html" %}
-                    <span class="u-hidden@sm">{% trans "Dashboard" %}</span>
-                </a>
-
-                {% menu_search %}
-                {% main_nav %}
-            </div>
-            <div class="explorer__wrapper" data-explorer-menu></div>
-        </aside>
-        {# Backwards-compatibility for branding_logo customisations in legacy sidebar. #}
-        {# RemovedInWagtail40Warning Remove when removing the legacy sidebar. #}
-        <script>
-            const branding_logo = document.querySelector('[data-wagtail-sidebar-branding-logo]');
-            const legacySidebar = document.querySelector('[data-nav-primary]');
-            if (branding_logo && branding_logo.innerHTML && legacySidebar) {
-                const link = legacySidebar.querySelector('a');
-                link.innerHTML = `${branding_logo.innerHTML}<span class="u-hidden@sm">{% trans "Dashboard" %}</span>`;
-            }
-        </script>
-    {% endif %}
+    {% sidebar_props %}
+    <aside id="wagtail-sidebar" data-wagtail-sidebar></aside>
 
     <main class="content-wrapper" id="main">
         <div class="content">
@@ -64,9 +32,6 @@
                 {% endif %}
             </div>
 
-            {% if not slim_sidebar_enabled %}
-                <button type="button" id="nav-toggle" class="nav-toggle icon button unbutton" ><span class="visuallyhidden">{% trans "Toggle sidebar" %}</span></button>
-            {% endif %}
             {% block content %}{% endblock %}
         </div>
     </main>

+ 0 - 5
wagtail/admin/templates/wagtailadmin/shared/animated_logo.html

@@ -1,5 +0,0 @@
-{% load wagtailadmin_tags %}
-
-<div class="wagtail-logo-container__desktop w-hidden sm:w-block">
-    <img src="{% versioned_static 'wagtailadmin/images/wagtail-logo.svg' %}" alt=""/>
-</div>

+ 0 - 14
wagtail/admin/templates/wagtailadmin/shared/animated_logo.stories.tsx

@@ -1,14 +0,0 @@
-import React from 'react';
-import TemplatePattern from '../../../../../client/storybook/TemplatePattern';
-
-import template from './animated_logo.html';
-
-export default {
-  parameters: {
-    docs: {
-      source: { code: template },
-    },
-  },
-};
-
-export const AnimatedLogo = () => <TemplatePattern filename={__filename} />;

+ 0 - 10
wagtail/admin/templates/wagtailadmin/shared/explorer_menu_item.html

@@ -1,10 +0,0 @@
-{% load wagtailadmin_tags %}
-
-{% if start_page_id %}
-    <li class="menu-item{% if active %} menu-active{% endif %}" data-explorer-menu-item>
-        <a href="{{ url }}" data-explorer-start-page="{{ start_page_id }}">
-            {% if icon_name %}{% icon icon_name 'icon--menuitem' %}{% endif %}
-            {{ label }}
-        </a>
-    </li>
-{% endif %}

+ 0 - 22
wagtail/admin/templates/wagtailadmin/shared/main_nav.html

@@ -1,22 +0,0 @@
-{% load wagtailadmin_tags %}
-{% load i18n %}
-<nav class="nav-main">
-    <ul>
-        {{ menu_html }}
-
-        <li class="nav-footer" id="footer">
-            <div class="account" id="account-settings" title="{% trans 'Edit your account' %}">
-                <span class="avatar square avatar-on-dark">
-                    <img src="{% avatar_url request.user size=50 %}" alt="" />
-                </span>
-                <em class="icon icon-arrow-up-after">{{ request.user.first_name|default:request.user.get_username }}</em>
-            </div>
-
-            <ul class="nav-footer-submenu">
-                <li><a href="{% url 'wagtailadmin_account' %}" class="icon icon-user">{% trans "Account settings" %}</a></li>
-                <li><a href="{% url 'wagtailadmin_logout' %}" class="icon icon-logout">{% trans "Log out" %}</a></li>
-            </ul>
-
-        </li>
-    </ul>
-</nav>

+ 0 - 10
wagtail/admin/templates/wagtailadmin/shared/menu_item.html

@@ -1,10 +0,0 @@
-{% load wagtailadmin_tags %}
-
-<li class="menu-item{% if active %} menu-active{% endif %}">
-    <a href="{{ url }}"
-        class="{{ classnames }}"
-        {{ attr_string }}>
-        {% if icon_name %}{% icon icon_name 'icon--menuitem' %}{% endif %}
-        {{ label }}
-    </a>
-</li>

+ 0 - 10
wagtail/admin/templates/wagtailadmin/shared/menu_search.html

@@ -1,10 +0,0 @@
-{% load i18n %}
-
-<form class="nav-search" action="{{ search_url }}" method="get">
-    <div>
-        <label for="menu-search-q">{% trans "Search" %}</label>
-        <input type="text" id="menu-search-q" name="q" placeholder="{% trans 'Search' %}" />
-        <button class="button" type="submit">{% trans "Search" %}</button>
-    </div>
-</form>
-

+ 0 - 6
wagtail/admin/templates/wagtailadmin/shared/menu_settings_menu_item.html

@@ -1,6 +0,0 @@
-{% extends "wagtailadmin/shared/menu_submenu_item.html" %}
-{% load wagtailcore_tags %}
-
-{% block menu_footer %}
-    <p class="nav-submenu__footer">Wagtail v.{% wagtail_version %}</p>
-{% endblock %}

+ 0 - 19
wagtail/admin/templates/wagtailadmin/shared/menu_submenu_item.html

@@ -1,19 +0,0 @@
-{% load wagtailadmin_tags %}
-
-<li class="menu-item{% if active %} menu-active{% endif %}">
-    <a href="#" data-nav-primary-submenu-trigger class="{{ classnames }}"{{ attr_string }}>
-        {% if icon_name %}{% icon icon_name 'icon--menuitem' %}{% endif %}
-        {{ label }}
-        {% icon 'arrow-right' 'icon--submenu-trigger' %}
-    </a>
-    <div class="nav-submenu">
-        <h2 id="nav-submenu-{{ name }}-title" class="{{ classnames }}">
-            {% if icon_name %}{% icon icon_name 'icon--submenu-header' %}{% endif %}
-            {{ label }}
-        </h2>
-        <ul class="nav-submenu__list" aria-labelledby="nav-submenu-{{ name }}-title">
-            {{ menu_html }}
-        </ul>
-        {% block menu_footer %}{% endblock %}
-    </div>
-</li>

+ 1 - 2
wagtail/admin/templates/wagtailadmin/skeleton.html

@@ -16,9 +16,8 @@
 
         {% block branding_favicon %}{% endblock %}
     </head>
-    {% slim_sidebar_enabled as slim_sidebar_enabled %}
     {% sidebar_collapsed as sidebar_collapsed %}
-    <body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if slim_sidebar_enabled and sidebar_collapsed %}sidebar-collapsed{% endif %} {% if messages %}has-messages{% endif %} focus-outline-on">
+    <body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if sidebar_collapsed %}sidebar-collapsed{% endif %} {% if messages %}has-messages{% endif %}">
         <div data-sprite></div>
         <script>
             function loadIconSprite() {

+ 0 - 61
wagtail/admin/templatetags/wagtailadmin_tags.py

@@ -8,10 +8,8 @@ from django.contrib.admin.utils import quote
 from django.contrib.humanize.templatetags.humanize import intcomma
 from django.contrib.messages.constants import DEFAULT_TAGS as MESSAGE_TAGS
 from django.db.models import Min, QuerySet
-from django.forms import Media
 from django.shortcuts import resolve_url as resolve_url_func
 from django.template.defaultfilters import stringfilter
-from django.template.loader import render_to_string
 from django.templatetags.static import static
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
@@ -57,33 +55,6 @@ register = template.Library()
 register.filter("intcomma", intcomma)
 
 
-@register.simple_tag(takes_context=True)
-def menu_search(context):
-    request = context["request"]
-
-    search_areas = admin_search_areas.search_items_for_request(request)
-    if not search_areas:
-        return ""
-    search_area = search_areas[0]
-
-    return render_to_string(
-        "wagtailadmin/shared/menu_search.html",
-        {
-            "search_url": search_area.url,
-        },
-    )
-
-
-@register.inclusion_tag("wagtailadmin/shared/main_nav.html", takes_context=True)
-def main_nav(context):
-    request = context["request"]
-
-    return {
-        "menu_html": admin_menu.render_html(request),
-        "request": request,
-    }
-
-
 @register.inclusion_tag("wagtailadmin/shared/breadcrumb.html", takes_context=True)
 def explorer_breadcrumb(
     context,
@@ -137,33 +108,6 @@ def search_other(context, current=None):
     }
 
 
-@register.simple_tag
-def main_nav_js():
-    if slim_sidebar_enabled():
-        return Media(
-            js=[
-                versioned_static("wagtailadmin/js/telepath/telepath.js"),
-                versioned_static("wagtailadmin/js/sidebar.js"),
-            ]
-        )
-
-    else:
-        return (
-            Media(js=[versioned_static("wagtailadmin/js/sidebar-legacy.js")])
-            + admin_menu.media["js"]
-        )
-
-
-@register.simple_tag
-def main_nav_css():
-    if slim_sidebar_enabled():
-        return Media(css={"all": [versioned_static("wagtailadmin/css/sidebar.css")]})
-
-    else:
-        # Legacy sidebar CSS in core.css
-        return admin_menu.media["css"]
-
-
 @register.filter("ellipsistrim")
 def ellipsistrim(value, max_length):
     if len(value) > max_length:
@@ -850,11 +794,6 @@ def locale_label_from_id(locale_id):
     return get_locales_display_names().get(locale_id)
 
 
-@register.simple_tag()
-def slim_sidebar_enabled():
-    return getattr(settings, "WAGTAIL_SLIM_SIDEBAR", True)
-
-
 @register.simple_tag(takes_context=True)
 def sidebar_collapsed(context):
     request = context.get("request")

+ 0 - 22
wagtail/admin/tests/test_admin_search.py

@@ -19,12 +19,6 @@ class BaseSearchAreaTestCase(WagtailTestUtils, TestCase):
         template = Template("{% load wagtailadmin_tags %}{% search_other %}")
         return template.render(Context({"request": request}))
 
-    def menu_search(self, current_url="/admin/", data=None):
-        request = self.rf.get(current_url, data=data)
-        request.user = self.user
-        template = Template("{% load wagtailadmin_tags %}{% menu_search %}")
-        return template.render(Context({"request": request}))
-
 
 class TestSearchAreas(BaseSearchAreaTestCase):
     def setUp(self):
@@ -62,10 +56,6 @@ class TestSearchAreas(BaseSearchAreaTestCase):
             html=True,
         )
 
-    def test_menu_search(self):
-        rendered = self.menu_search()
-        self.assertIn(reverse("wagtailadmin_pages:search"), rendered)
-
     def test_search_other(self):
         rendered = self.search_other()
         self.assertIn(reverse("wagtailadmin_pages:search"), rendered)
@@ -109,14 +99,6 @@ class TestSearchAreaNoPagePermissions(BaseSearchAreaTestCase):
             '{"_type": "wagtail.sidebar.SearchModule", "_args": ["/customsearch/"]}',
         )
 
-    def test_menu_search(self):
-        """
-        The search form should go to the custom search, not the page search.
-        """
-        rendered = self.menu_search()
-        self.assertNotIn(reverse("wagtailadmin_pages:search"), rendered)
-        self.assertIn('action="/customsearch/"', rendered)
-
     def test_search_other(self):
         """The pages search link should be hidden, custom search should be visible."""
         rendered = self.search_other()
@@ -125,7 +107,3 @@ class TestSearchAreaNoPagePermissions(BaseSearchAreaTestCase):
 
         self.assertNotIn("Pages", rendered)
         self.assertIn("My Search", rendered)
-
-    def test_no_searches(self):
-        rendered = self.menu_search(data={"hide-option": "true"})
-        self.assertEqual(rendered, "")

+ 1 - 9
wagtail/admin/tests/test_menu.py

@@ -1,4 +1,4 @@
-from django.test import RequestFactory, TestCase, override_settings
+from django.test import RequestFactory, TestCase
 from django.urls import reverse
 
 from wagtail import hooks
@@ -32,14 +32,6 @@ class TestMenuRendering(TestCase, WagtailTestUtils):
         response = self.client.get(reverse("wagtailadmin_home"))
         self.assertContains(response, "sidebar-collapsed")
 
-    @override_settings(WAGTAIL_SLIM_SIDEBAR=False)
-    def test_not_collapsed_with_legacy(self):
-        """Sidebar should only remember its collapsed state with the slim implementation."""
-        # Sidebar should not be collapsed because the feature flag is not enabled
-        self.client.cookies["wagtail_sidebar_collapsed"] = "1"
-        response = self.client.get(reverse("wagtailadmin_home"))
-        self.assertNotContains(response, "sidebar-collapsed")
-
     def test_simple_menu(self):
         # Note: initialise the menu before registering hooks as this is what happens in reality.
         # (the real menus are initialised at the module level in admin/menu.py)

+ 1 - 9
wagtail/admin/wagtail_hooks.py

@@ -55,8 +55,6 @@ from wagtail.whitelist import allow_without_attributes, attribute_rule, check_ur
 
 
 class ExplorerMenuItem(MenuItem):
-    template = "wagtailadmin/shared/explorer_menu_item.html"
-
     def is_shown(self, request):
         return user_has_any_page_permission(request.user)
 
@@ -97,8 +95,6 @@ def register_explorer_menu_item():
 
 
 class SettingsMenuItem(SubmenuMenuItem):
-    template = "wagtailadmin/shared/menu_settings_menu_item.html"
-
     def render_component(self, request):
         return SubMenuItemComponent(
             self.name,
@@ -841,10 +837,6 @@ def register_core_features(features):
     )
 
 
-class ReportsMenuItem(SubmenuMenuItem):
-    template = "wagtailadmin/shared/menu_submenu_item.html"
-
-
 class LockedPagesMenuItem(MenuItem):
     def is_shown(self, request):
         return UserPagePermissionsProxy(request.user).can_remove_locks()
@@ -917,7 +909,7 @@ def register_aging_pages_report_menu_item():
 
 @hooks.register("register_admin_menu_item")
 def register_reports_menu():
-    return ReportsMenuItem(_("Reports"), reports_menu, icon_name="site", order=9000)
+    return SubmenuMenuItem(_("Reports"), reports_menu, icon_name="site", order=9000)
 
 
 @hooks.register("register_icons")

+ 2 - 2
wagtail/images/templates/wagtailimages/images/edit.html

@@ -52,7 +52,7 @@
                         {% endif %}
                     {% endfor %}
                 </ul>
-                <div class="u-hidden@xs">
+                <div class="w-hidden sm:w-block">
                     <input type="submit" value="{% trans 'Save' %}" class="button" />
                     {% if user_can_delete %}
                         <a href="{% url 'wagtailimages:delete' image.id %}{% if next %}?next={{ next|urlencode }}{% endif %}" class="button button-secondary no">{% trans "Delete image" %}</a>
@@ -108,7 +108,7 @@
             </div>
         </div>
 
-        <div class="row row-flush nice-padding u-hidden@sm">
+        <div class="row row-flush nice-padding sm:!w-hidden">
             <div class="col5">
                 <input type="submit" value="{% trans 'Save' %}" class="button" />
                 {% if user_can_delete %}