Przeglądaj źródła

Change slim sidebar to be fully usable in slim mode. Fix #7918 (#8197)

Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>

- Removing the peeking attribute so the sidebar only opens when intentionally set to expanded mode by using expand or search or account functionalities
- Adding tooltips on link item hovers
- Expanding of slim sidebar when search is clicked and when account options are clicked
Steve Stein 3 lat temu
rodzic
commit
af4c4d0653
28 zmienionych plików z 554 dodań i 405 usunięć
  1. 1 0
      client/scss/core.scss
  2. 21 0
      client/scss/overrides/_vendor.tippy.scss
  3. 1 1
      client/scss/settings/_variables.scss
  4. 2 2
      client/src/components/PageExplorer/PageExplorerItem.scss
  5. 2 12
      client/src/components/Sidebar/Sidebar.scss
  6. 56 52
      client/src/components/Sidebar/Sidebar.tsx
  7. 14 16
      client/src/components/Sidebar/SidebarPanel.scss
  8. 26 17
      client/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap
  9. 20 12
      client/src/components/Sidebar/menu/LinkMenuItem.tsx
  10. 11 4
      client/src/components/Sidebar/menu/MenuItem.scss
  11. 1 0
      client/src/components/Sidebar/menu/MenuItem.tsx
  12. 17 13
      client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx
  13. 10 4
      client/src/components/Sidebar/menu/SubMenuItem.scss
  14. 16 13
      client/src/components/Sidebar/menu/SubMenuItem.tsx
  15. 23 18
      client/src/components/Sidebar/menu/__snapshots__/PageExplorererMenuItem.test.js.snap
  16. 19 14
      client/src/components/Sidebar/menu/__snapshots__/SubMenuItem.test.js.snap
  17. 26 1
      client/src/components/Sidebar/modules/MainMenu.scss
  18. 3 0
      client/src/components/Sidebar/modules/MainMenu.test.js
  19. 71 39
      client/src/components/Sidebar/modules/MainMenu.tsx
  20. 0 68
      client/src/components/Sidebar/modules/Search.scss
  21. 90 25
      client/src/components/Sidebar/modules/Search.tsx
  22. 8 42
      client/src/components/Sidebar/modules/WagtailBranding.scss
  23. 7 4
      client/src/components/Sidebar/modules/WagtailBranding.tsx
  24. 20 4
      client/src/components/Sidebar/modules/WagtailLogo.tsx
  25. 45 41
      client/src/components/Sidebar/modules/__snapshots__/MainMenu.test.js.snap
  26. 3 0
      client/tailwind.config.js
  27. 39 3
      package-lock.json
  28. 2 0
      package.json

+ 1 - 0
client/scss/core.scss

@@ -148,6 +148,7 @@ These are classes that provide overrides.
 // VENDOR: overrides of vendor styles.
 @import 'overrides/vendor.datetimepicker';
 @import 'overrides/vendor.tagit';
+@import 'overrides/vendor.tippy';
 
 // UTILITIES: classes that do one simple thing.
 @import 'overrides/utilities.hidden';

+ 21 - 0
client/scss/overrides/_vendor.tippy.scss

@@ -0,0 +1,21 @@
+@import '../../../node_modules/tippy.js/dist/tippy';
+
+.tippy-box {
+  @apply w-bg-primary w-text-white w-text-14;
+}
+
+.tippy-box[data-placement^='top'] > .tippy-arrow::before {
+  @apply w-border-t-primary;
+}
+
+.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
+  @apply w-border-b-primary;
+}
+
+.tippy-box[data-placement^='left'] > .tippy-arrow::before {
+  @apply w-border-l-primary;
+}
+
+.tippy-box[data-placement^='right'] > .tippy-arrow::before {
+  @apply w-border-r-primary;
+}

+ 1 - 1
client/scss/settings/_variables.scss

@@ -127,7 +127,7 @@ $font-wagtail-icons: wagtail;
 // misc sizing
 $thumbnail-width: 130px;
 $menu-width: 200px;
-$menu-width-slim: 60px;
+$menu-width-slim: 65px;
 
 $menu-width-max: 320px;
 

+ 2 - 2
client/src/components/PageExplorer/PageExplorerItem.scss

@@ -3,7 +3,7 @@
 }
 
 .c-page-explorer__item__link {
-  @apply w-inline-flex w-items-start sm:w-items-center w-flex-wrap w-grow w-cursor-pointer w-gap-1;
+  @apply w-inline-flex w-items-start sm:w-items-center w-flex-wrap w-grow w-cursor-pointer w-gap-1 w-transition;
   padding: 1.45em 1em;
 
   &:focus,
@@ -35,7 +35,7 @@
 }
 
 .c-page-explorer__item__action {
-  @apply w-text-white/85;
+  @apply w-text-white/85 w-transition;
   display: inline-flex;
   align-items: center;
   justify-content: center;

+ 2 - 12
client/src/components/Sidebar/Sidebar.scss

@@ -23,6 +23,7 @@
   }
 
   @include media-breakpoint-up(sm) {
+    position: static;
     inset-inline-end: $sidebar-toggle-spacing;
 
     // Remove once we drop support for Safari 13.
@@ -79,23 +80,13 @@
 
   &__collapse-toggle {
     @include sidebar-toggle;
-    display: grid;
+    // All other styling is done with utility classes on this element
   }
 
   // When in mobile mode, hide the collapse-toggle and show the nav-toggle (which is defined in the .sidebar-nav-toggle class below)
   &--mobile &__collapse-toggle {
     display: none;
   }
-
-  // This element should cover all the area beneath the collapse toggle
-  // It's only used to attach mouse enter/exit event handlers to control peeking
-  &__peek-hover-area {
-    margin-top: $sidebar-toggle-size;
-    display: grid;
-    grid-template-columns: 1fr;
-    overflow-y: auto;
-    overflow-x: hidden;
-  }
 }
 
 // This is a separate component as it needs to display in the header
@@ -118,5 +109,4 @@
 @import 'menu/MenuItem';
 @import 'menu/SubMenuItem';
 @import 'modules/MainMenu';
-@import 'modules/Search';
 @import 'modules/WagtailBranding';

+ 56 - 52
client/src/components/Sidebar/Sidebar.tsx

@@ -17,6 +17,8 @@ export interface ModuleRenderContext {
   key: number;
   slim: boolean;
   expandingOrCollapsing: boolean;
+  onAccountExpand: () => void;
+  onSearchClick: () => void;
   currentPath: string;
   strings: Strings;
   navigate(url: string): Promise<void>;
@@ -46,7 +48,6 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
   // 'collapsed' is a persistent state that is controlled by the arrow icon at the top
   // It records the user's general preference for a collapsed/uncollapsed menu
   // This is just a hint though, and we may still collapse the menu if the screen is too small
-  // Also, we may display the full menu temporarily in collapsed mode (see 'peeking' below)
   const [collapsed, setCollapsed] = React.useState(collapsedOnLoad);
 
   // Call onExpandCollapse(true) if menu is initialised in collapsed state
@@ -56,11 +57,6 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
     }
   }, []);
 
-  // 'peeking' is a temporary state to allow the user to peek in the menu while it is collapsed, or hidden.
-  // When peeking is true, the menu renders as if it's not collapsed, but as an overlay instead of occupying
-  // space next to the content
-  const [peeking, setPeeking] = React.useState(false);
-
   // 'visibleOnMobile' indicates whether the sidebar is currently visible on mobile
   // On mobile, the sidebar is completely hidden by default and must be opened manually
   const [visibleOnMobile, setVisibleOnMobile] = React.useState(false);
@@ -80,6 +76,7 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
         setVisibleOnMobile(false);
       }
     }
+
     window.addEventListener('resize', handleResize);
     handleResize();
     return () => window.removeEventListener('resize', handleResize);
@@ -88,7 +85,7 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
   // Whether or not to display the menu with slim layout.
   // Separate from 'collapsed' as the menu can still be displayed with an expanded
   // layout while in 'collapsed' mode if the user is 'peeking' into it (see above)
-  const slim = collapsed && !peeking && !isMobile;
+  const slim = collapsed && !isMobile;
 
   // 'expandingOrCollapsing' is set to true whilst the the menu is transitioning between slim and expanded layouts
   const [expandingOrCollapsing, setExpandingOrCollapsing] =
@@ -124,40 +121,33 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
     };
   };
 
-  // Switch peeking on/off when the mouse cursor hovers the sidebar or focus is on the sidebar
-  const [mouseHover, setMouseHover] = React.useState(false);
   const [focused, setFocused] = React.useState(false);
 
-  const onMouseEnterHandler = () => {
-    setMouseHover(true);
-  };
-
-  const onMouseLeaveHandler = () => {
-    setMouseHover(false);
+  const onBlurHandler = () => {
+    if (focused) {
+      setFocused(false);
+      setCollapsed(true);
+    }
   };
 
   const onFocusHandler = () => {
-    setFocused(true);
+    if (focused) {
+      setCollapsed(false);
+      setFocused(true);
+    }
   };
 
-  const onBlurHandler = () => {
-    setFocused(false);
+  const onSearchClick = () => {
+    if (slim) {
+      onClickCollapseToggle();
+    }
   };
 
-  // We need a stop peeking timeout to stop the sidebar moving as someone tab's though the menu
-  const stopPeekingTimeout = React.useRef<any>(null);
-
-  React.useEffect(() => {
-    if (mouseHover || focused) {
-      clearTimeout(stopPeekingTimeout.current);
-      setPeeking(true);
-    } else {
-      clearTimeout(stopPeekingTimeout.current);
-      stopPeekingTimeout.current = setTimeout(() => {
-        setPeeking(false);
-      }, SIDEBAR_TRANSITION_DURATION);
+  const onAccountExpand = () => {
+    if (slim) {
+      onClickCollapseToggle();
     }
-  }, [mouseHover, focused]);
+  };
 
   // Render modules
   const renderedModules = modules.map((module, index) =>
@@ -165,6 +155,8 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
       key: index,
       slim,
       expandingOrCollapsing,
+      onAccountExpand,
+      onSearchClick,
       currentPath,
       strings,
       navigate,
@@ -181,31 +173,43 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ({
           (isMobile && !visibleOnMobile ? ' sidebar--hidden' : '')
         }
       >
-        <div className="sidebar__inner">
-          <button
-            onClick={onClickCollapseToggle}
-            aria-label={strings.TOGGLE_SIDEBAR}
-            aria-expanded={slim ? 'false' : 'true'}
-            type="button"
-            className="button sidebar__collapse-toggle hover:w-bg-primary-200 hover:text-white hover:opacity-100"
+        <div
+          className="sidebar__inner"
+          onFocus={onFocusHandler}
+          onBlur={onBlurHandler}
+        >
+          <div
+            className={`${
+              slim ? 'w-justify-center' : 'w-justify-end'
+            } w-flex  w-items-center`}
           >
-            <Icon
-              name="expand-right"
-              className={`w-transition motion-reduce:w-transition-none
+            <button
+              onClick={onClickCollapseToggle}
+              aria-label={strings.TOGGLE_SIDEBAR}
+              aria-expanded={slim ? 'false' : 'true'}
+              type="button"
+              className={`
+                ${!slim ? 'w-mr-4' : ''}
+                button
+                sidebar__collapse-toggle
+                w-flex
+                w-justify-center
+                w-items-center
+                sm:w-mt-4
+                hover:w-bg-primary-200
+                hover:text-white
+                hover:opacity-100`}
+            >
+              <Icon
+                name="expand-right"
+                className={`w-transition motion-reduce:w-transition-none
                 ${!collapsed ? '-w-rotate-180' : ''}
                 `}
-            />
-          </button>
-
-          <div
-            className="sidebar__peek-hover-area"
-            onMouseEnter={onMouseEnterHandler}
-            onMouseLeave={onMouseLeaveHandler}
-            onFocus={onFocusHandler}
-            onBlur={onBlurHandler}
-          >
-            {renderedModules}
+              />
+            </button>
           </div>
+
+          {renderedModules}
         </div>
       </div>
       <button

+ 14 - 16
client/src/components/Sidebar/SidebarPanel.scss

@@ -1,9 +1,10 @@
 .sidebar-panel {
+  @apply w-transition w-duration-150;
   // With CSS variable allows panels with different widths to animate properly
   --width: #{$menu-width};
 
   visibility: hidden;
-  transform: translate3d(0, 0, 0);
+  transform: translateX(-100%);
   position: fixed;
   height: 100vh;
   padding: 0;
@@ -17,20 +18,9 @@
   flex-direction: column;
   overflow: hidden;
 
-  @include transition(
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left $menu-transition-duration ease,
-    inset-inline-start $menu-transition-duration ease
-  );
-
   @include media-breakpoint-up(sm) {
     z-index: var(--z-index);
     width: var(--width);
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: calc(#{$menu-width} - var(--width));
-    inset-inline-start: calc(#{$menu-width} - var(--width));
   }
 
   @media (forced-colors: $media-forced-colours) {
@@ -43,12 +33,18 @@
     box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35);
   }
 
+  // Showing the submenu options panel in mobile mode
+  .sidebar--mobile .sidebar-sub-menu-item--open &,
+  .sidebar--mobile .sidebar-page-explorer-item.sidebar-menu-item--active & {
+    transform: translateX(0);
+  }
+
   @include media-breakpoint-up(sm) {
     @at-root .sidebar--slim #{&} {
       // Remove once we drop support for Safari 13.
       // stylelint-disable-next-line property-disallowed-list
-      left: calc(#{$menu-width-slim} - var(--width));
-      inset-inline-start: calc(#{$menu-width-slim} - var(--width));
+      left: $menu-width-slim;
+      inset-inline-start: $menu-width-slim;
     }
     // Don't apply this to nested submenus though
     @at-root .sidebar--slim .sidebar-panel #{&} {
@@ -63,13 +59,15 @@
       // stylelint-disable-next-line property-disallowed-list
       left: $menu-width;
       inset-inline-start: $menu-width;
+      transform: translateX(0);
 
       // Don't apply this to nested submenus though
       @at-root .sidebar--slim .sidebar-panel #{&} {
         // Remove once we drop support for Safari 13.
         // stylelint-disable-next-line property-disallowed-list
-        left: $menu-width;
-        inset-inline-start: $menu-width;
+        left: $menu-width-slim;
+        inset-inline-start: $menu-width-slim;
+        transform: translateX(0);
       }
     }
   }

+ 26 - 17
client/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap

@@ -7,27 +7,36 @@ exports[`Sidebar should render with the minimum required props 1`] = `
   >
     <div
       className="sidebar__inner"
+      onBlur={[Function]}
+      onFocus={[Function]}
     >
-      <button
-        aria-expanded="true"
-        className="button sidebar__collapse-toggle hover:w-bg-primary-200 hover:text-white hover:opacity-100"
-        onClick={[Function]}
-        type="button"
+      <div
+        className="w-justify-end w-flex  w-items-center"
       >
-        <Icon
-          className="w-transition motion-reduce:w-transition-none
+        <button
+          aria-expanded="true"
+          className="
+                w-mr-4
+                button
+                sidebar__collapse-toggle
+                w-flex
+                w-justify-center
+                w-items-center
+                sm:w-mt-4
+                hover:w-bg-primary-200
+                hover:text-white
+                hover:opacity-100"
+          onClick={[Function]}
+          type="button"
+        >
+          <Icon
+            className="w-transition motion-reduce:w-transition-none
                 -w-rotate-180
                 "
-          name="expand-right"
-        />
-      </button>
-      <div
-        className="sidebar__peek-hover-area"
-        onBlur={[Function]}
-        onFocus={[Function]}
-        onMouseEnter={[Function]}
-        onMouseLeave={[Function]}
-      />
+            name="expand-right"
+          />
+        </button>
+      </div>
     </div>
   </div>
   <button

+ 20 - 12
client/src/components/Sidebar/menu/LinkMenuItem.tsx

@@ -2,10 +2,11 @@ import * as React from 'react';
 
 import Icon from '../../Icon/Icon';
 import { MenuItemDefinition, MenuItemProps } from './MenuItem';
+import Tippy from '@tippyjs/react';
 
 export const LinkMenuItem: React.FunctionComponent<
   MenuItemProps<LinkMenuItemDefinition>
-> = ({ item, path, state, dispatch, navigate }) => {
+> = ({ item, slim, path, state, dispatch, navigate }) => {
   const isCurrent = state.activePath === path;
   const isActive = state.activePath.startsWith(path);
   const isInSubMenu = path.split('.').length > 2;
@@ -40,17 +41,23 @@ export const LinkMenuItem: React.FunctionComponent<
 
   return (
     <li className={className}>
-      <a
-        href={item.url}
-        aria-current={isCurrent ? 'page' : undefined}
-        onClick={onClick}
-        className={`sidebar-menu-item__link ${item.classNames}`}
+      <Tippy
+        disabled={!slim || isInSubMenu}
+        content={item.label}
+        placement="right"
       >
-        {item.iconName && (
-          <Icon name={item.iconName} className="icon--menuitem" />
-        )}
-        <span className="menuitem-label">{item.label}</span>
-      </a>
+        <a
+          href={item.url}
+          aria-current={isCurrent ? 'page' : undefined}
+          onClick={onClick}
+          className={`sidebar-menu-item__link ${item.classNames}`}
+        >
+          {item.iconName && (
+            <Icon name={item.iconName} className="icon--menuitem" />
+          )}
+          <span className="menuitem-label">{item.label}</span>
+        </a>
+      </Tippy>
     </li>
   );
 };
@@ -76,12 +83,13 @@ export class LinkMenuItemDefinition implements MenuItemDefinition {
     this.classNames = classnames;
   }
 
-  render({ path, state, dispatch, navigate }) {
+  render({ path, slim, state, dispatch, navigate }) {
     return (
       <LinkMenuItem
         key={this.name}
         item={this}
         path={path}
+        slim={slim}
         state={state}
         dispatch={dispatch}
         navigate={navigate}

+ 11 - 4
client/src/components/Sidebar/menu/MenuItem.scss

@@ -5,6 +5,7 @@
   position: relative;
 
   &__link {
+    @apply w-text-14;
     @include transition(
       border-color $menu-transition-duration ease,
       background-color $menu-transition-duration ease
@@ -15,14 +16,12 @@
     box-sizing: border-box;
     white-space: nowrap;
     border-inline-start: 3px solid transparent;
-
     -webkit-font-smoothing: auto;
     border: 0;
     background: transparent;
     text-align: start;
     color: $color-menu-text;
     padding: 11px 20px;
-    font-size: 13px;
     font-weight: 400;
 
     // Note, font-weights lower than normal,
@@ -88,7 +87,15 @@
 }
 
 .sidebar--slim {
-  .menuitem-label {
-    opacity: 0;
+  .sidebar-menu-item {
+    .menuitem-label {
+      opacity: 0;
+    }
+  }
+
+  .sidebar-menu-item--in-sub-menu {
+    .menuitem-label {
+      opacity: 1;
+    }
   }
 }

+ 1 - 0
client/src/components/Sidebar/menu/MenuItem.tsx

@@ -18,6 +18,7 @@ export interface MenuItemDefinition {
 
 export interface MenuItemProps<T> {
   path: string;
+  slim: boolean;
   state: MenuState;
   item: T;
   dispatch(action: MenuAction): void;

+ 17 - 13
client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx

@@ -11,10 +11,11 @@ import {
 } from '../../PageExplorer/actions';
 import { SidebarPanel } from '../SidebarPanel';
 import { SIDEBAR_TRANSITION_DURATION } from '../Sidebar';
+import Tippy from '@tippyjs/react';
 
 export const PageExplorerMenuItem: React.FunctionComponent<
   MenuItemProps<PageExplorerMenuItemDefinition>
-> = ({ path, item, state, dispatch, navigate }) => {
+> = ({ path, slim, item, state, dispatch, navigate }) => {
   const isOpen = state.navigationPath.startsWith(path);
   const isActive = isOpen || state.activePath.startsWith(path);
   const depth = path.split('.').length;
@@ -72,17 +73,19 @@ export const PageExplorerMenuItem: React.FunctionComponent<
 
   return (
     <li className={className}>
-      <button
-        onClick={onClick}
-        className="sidebar-menu-item__link"
-        aria-haspopup="menu"
-        aria-expanded={isOpen ? 'true' : 'false'}
-        type="button"
-      >
-        <Icon name="folder-open-inverse" className="icon--menuitem" />
-        <span className="menuitem-label">{item.label}</span>
-        <Icon className={sidebarTriggerIconClassName} name="arrow-right" />
-      </button>
+      <Tippy disabled={isOpen || !slim} content={item.label} placement="right">
+        <button
+          onClick={onClick}
+          className="sidebar-menu-item__link"
+          aria-haspopup="menu"
+          aria-expanded={isOpen ? 'true' : 'false'}
+          type="button"
+        >
+          <Icon name="folder-open-inverse" className="icon--menuitem" />
+          <span className="menuitem-label">{item.label}</span>
+          <Icon className={sidebarTriggerIconClassName} name="arrow-right" />
+        </button>
+      </Tippy>
       <div>
         <SidebarPanel
           isVisible={isVisible}
@@ -112,12 +115,13 @@ export class PageExplorerMenuItemDefinition extends LinkMenuItemDefinition {
     this.startPageId = startPageId;
   }
 
-  render({ path, state, dispatch, navigate }) {
+  render({ path, slim, state, dispatch, navigate }) {
     return (
       <PageExplorerMenuItem
         key={this.name}
         item={this}
         path={path}
+        slim={slim}
         state={state}
         dispatch={dispatch}
         navigate={navigate}

+ 10 - 4
client/src/components/Sidebar/menu/SubMenuItem.scss

@@ -1,4 +1,5 @@
 .sidebar-sub-menu-trigger-icon {
+  $root: &;
   display: block;
   width: 20px;
   height: 20px;
@@ -27,6 +28,10 @@
     height: 16px;
     transform: translate3d(13px, 0, 0);
   }
+
+  .sidebar--slim &--open {
+    transform: translate3d(13px, 0, 0) rotate(180deg);
+  }
 }
 
 .sidebar-sub-menu-panel {
@@ -40,7 +45,7 @@
 
   > h2 {
     // w-min-h-[160px] and w-mt-[35px] classes are to vertically align the title and icon combination to the search input on the left
-    @apply w-min-h-[160px] w-mt-[35px] w-text-white w-mb-0 w-inline-flex w-flex-col w-justify-center w-items-center;
+    @apply w-min-h-[160px] w-mt-[45px] w-px-4 w-box-border w-text-center w-text-white w-mb-0 w-inline-flex w-flex-col w-justify-center w-items-center;
 
     &:before {
       font-size: 4em;
@@ -50,6 +55,10 @@
       width: 100%;
       opacity: 0.15;
     }
+
+    @at-root .sidebar--slim & {
+      @apply w-mt-3;
+    }
   }
 
   ul > li {
@@ -81,9 +90,6 @@
     box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35);
   }
 
-  @at-root .sidebar--slim #{&} {
-    transform: translate3d($menu-width-slim - $menu-width, 0, 0);
-  }
   // Don't apply this to nested submenus though
   @at-root .sidebar--slim .sidebar-sub-menu-panel #{&} {
     transform: translate3d(0, 0, 0);

+ 16 - 13
client/src/components/Sidebar/menu/SubMenuItem.tsx

@@ -6,6 +6,7 @@ import { renderMenu } from '../modules/MainMenu';
 import { SidebarPanel } from '../SidebarPanel';
 import { SIDEBAR_TRANSITION_DURATION } from '../Sidebar';
 import { MenuItemDefinition, MenuItemProps } from './MenuItem';
+import Tippy from '@tippyjs/react';
 
 interface SubMenuItemProps extends MenuItemProps<SubMenuItemDefinition> {
   slim: boolean;
@@ -65,19 +66,21 @@ export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ({
 
   return (
     <li className={className}>
-      <button
-        onClick={onClick}
-        className={`sidebar-menu-item__link ${item.classNames}`}
-        aria-haspopup="menu"
-        aria-expanded={isOpen ? 'true' : 'false'}
-        type="button"
-      >
-        {item.iconName && (
-          <Icon name={item.iconName} className="icon--menuitem" />
-        )}
-        <span className="menuitem-label">{item.label}</span>
-        <Icon className={sidebarTriggerIconClassName} name="arrow-right" />
-      </button>
+      <Tippy disabled={isOpen || !slim} content={item.label} placement="right">
+        <button
+          onClick={onClick}
+          className={`sidebar-menu-item__link ${item.classNames}`}
+          aria-haspopup="menu"
+          aria-expanded={isOpen ? 'true' : 'false'}
+          type="button"
+        >
+          {item.iconName && (
+            <Icon name={item.iconName} className="icon--menuitem" />
+          )}
+          <span className="menuitem-label">{item.label}</span>
+          <Icon className={sidebarTriggerIconClassName} name="arrow-right" />
+        </button>
+      </Tippy>
       <SidebarPanel isVisible={isVisible} isOpen={isOpen} depth={depth}>
         <div className="sidebar-sub-menu-panel">
           <h2

+ 23 - 18
client/src/components/Sidebar/menu/__snapshots__/PageExplorererMenuItem.test.js.snap

@@ -4,25 +4,30 @@ exports[`PageExplorerMenuItem should render with the minimum required props 1`]
 <li
   className="sidebar-menu-item sidebar-page-explorer-item"
 >
-  <button
-    aria-expanded="false"
-    aria-haspopup="menu"
-    className="sidebar-menu-item__link"
-    onClick={[Function]}
-    type="button"
+  <ForwardRef(TippyWrapper)
+    disabled={true}
+    placement="right"
   >
-    <Icon
-      className="icon--menuitem"
-      name="folder-open-inverse"
-    />
-    <span
-      className="menuitem-label"
-    />
-    <Icon
-      className="sidebar-sub-menu-trigger-icon"
-      name="arrow-right"
-    />
-  </button>
+    <button
+      aria-expanded="false"
+      aria-haspopup="menu"
+      className="sidebar-menu-item__link"
+      onClick={[Function]}
+      type="button"
+    >
+      <Icon
+        className="icon--menuitem"
+        name="folder-open-inverse"
+      />
+      <span
+        className="menuitem-label"
+      />
+      <Icon
+        className="sidebar-sub-menu-trigger-icon"
+        name="arrow-right"
+      />
+    </button>
+  </ForwardRef(TippyWrapper)>
   <div>
     <SidebarPanel
       depth={2}

+ 19 - 14
client/src/components/Sidebar/menu/__snapshots__/SubMenuItem.test.js.snap

@@ -4,21 +4,26 @@ exports[`SubMenuItem should render with the minimum required props 1`] = `
 <li
   className="sidebar-menu-item sidebar-sub-menu-item sidebar-menu-item--active"
 >
-  <button
-    aria-expanded="false"
-    aria-haspopup="menu"
-    className="sidebar-menu-item__link "
-    onClick={[Function]}
-    type="button"
+  <ForwardRef(TippyWrapper)
+    disabled={true}
+    placement="right"
   >
-    <span
-      className="menuitem-label"
-    />
-    <Icon
-      className="sidebar-sub-menu-trigger-icon"
-      name="arrow-right"
-    />
-  </button>
+    <button
+      aria-expanded="false"
+      aria-haspopup="menu"
+      className="sidebar-menu-item__link "
+      onClick={[Function]}
+      type="button"
+    >
+      <span
+        className="menuitem-label"
+      />
+      <Icon
+        className="sidebar-sub-menu-trigger-icon"
+        name="arrow-right"
+      />
+    </button>
+  </ForwardRef(TippyWrapper)>
   <SidebarPanel
     depth={2}
     isOpen={false}

+ 26 - 1
client/src/components/Sidebar/modules/MainMenu.scss

@@ -2,9 +2,32 @@
 .sidebar-main-menu {
   overflow: auto;
   overflow-x: hidden;
-  margin-bottom: 60px;
+  // So the last items in the menu will be seen when the menu is vertically scrollable
+  margin-bottom: 52px;
+  // Scrollbar styling for firefox
+  scrollbar-color: theme('colors.grey.200');
+  scrollbar-width: thin;
+
   @include transition(margin-bottom $menu-transition-duration ease);
 
+  //Custom scrollbar styling for windows/mac and slim mode
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+
+  &::-webkit-scrollbar-button {
+    @apply w-hidden;
+    // Hide the scrollbar arrows on windows
+  }
+
+  &::-webkit-scrollbar-thumb {
+    @apply w-bg-grey-200 w-rounded-sm;
+  }
+
+  &::-webkit-scrollbar-track {
+    @apply w-bg-transparent;
+  }
+
   &--open-footer {
     margin-bottom: 127px;
   }
@@ -84,6 +107,8 @@
   }
 
   &__account {
+    @include show-focus-outline-inside();
+
     &-toggle {
       @apply w-pl-2 w-inline-flex w-justify-between w-w-full w-translate-x-0 w-transition w-duration-150;
     }

+ 3 - 0
client/src/components/Sidebar/modules/MainMenu.test.js

@@ -5,6 +5,7 @@ import { Menu } from './MainMenu';
 describe('Menu', () => {
   const strings = {};
   const user = { avatarUrl: 'https://gravatar/profile' };
+  const onAccountExpand = jest.fn();
 
   it('should render with the minimum required props', () => {
     const wrapper = shallow(
@@ -13,6 +14,7 @@ describe('Menu', () => {
         menuItems={[]}
         strings={strings}
         user={user}
+        onAccountExpand={onAccountExpand}
       />,
     );
 
@@ -26,6 +28,7 @@ describe('Menu', () => {
         menuItems={[]}
         strings={strings}
         user={user}
+        onAccountExpand={onAccountExpand}
       />,
     );
 

+ 71 - 39
client/src/components/Sidebar/modules/MainMenu.tsx

@@ -5,6 +5,7 @@ import { LinkMenuItemDefinition } from '../menu/LinkMenuItem';
 import { MenuItemDefinition } from '../menu/MenuItem';
 import { SubMenuItemDefinition } from '../menu/SubMenuItem';
 import { ModuleDefinition, Strings } from '../Sidebar';
+import Tippy from '@tippyjs/react';
 
 export function renderMenu(
   path: string,
@@ -64,6 +65,7 @@ interface MenuProps {
   user: MainMenuModuleDefinition['user'];
   slim: boolean;
   expandingOrCollapsing: boolean;
+  onAccountExpand: () => void;
   currentPath: string;
   strings: Strings;
 
@@ -75,6 +77,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ({
   accountMenuItems,
   user,
   expandingOrCollapsing,
+  onAccountExpand,
   slim,
   currentPath,
   strings,
@@ -90,6 +93,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ({
     activePath: '',
   });
   const accountSettingsOpen = state.navigationPath.startsWith('.account');
+  const isVisible = !slim || expandingOrCollapsing;
 
   // Whenever currentPath or menu changes, work out new activePath
   React.useEffect(() => {
@@ -161,17 +165,30 @@ export const Menu: React.FunctionComponent<MenuProps> = ({
     };
   }, []);
 
+  // Determine if the sidebar is expanded from account button click
+  const [expandedFromAccountClick, setExpandedFromAccountClick] =
+    React.useState<boolean>(false);
+
   // Whenever the parent Sidebar component collapses or expands, close any open menus
   React.useEffect(() => {
-    if (expandingOrCollapsing) {
+    if (expandingOrCollapsing && !expandedFromAccountClick) {
       dispatch({
         type: 'set-navigation-path',
         path: '',
       });
     }
+    if (expandedFromAccountClick) {
+      setExpandedFromAccountClick(false);
+    }
   }, [expandingOrCollapsing]);
 
   const onClickAccountSettings = () => {
+    // Pass account expand information to Sidebar component
+    onAccountExpand();
+    if (slim) {
+      setExpandedFromAccountClick(true);
+    }
+
     if (accountSettingsOpen) {
       dispatch({
         type: 'set-navigation-path',
@@ -199,47 +216,53 @@ export const Menu: React.FunctionComponent<MenuProps> = ({
       <div
         className={
           'sidebar-footer' +
-          (accountSettingsOpen ? ' sidebar-footer--open' : '')
+          (accountSettingsOpen ? ' sidebar-footer--open' : '') +
+          (isVisible ? ' sidebar-footer--visible' : '')
         }
       >
-        <button
-          className="
-          sidebar-footer__account
-          w-bg-primary
-          w-text-white
-          w-flex
-          w-items-center
-          w-relative
-          w-p-0
-          w-w-full
-          w-appearance-none
-          w-border-0
-          w-overflow-hidden
-          w-px-5
-          w-py-3
-          hover:w-bg-primary-200
-          focus:w-bg-primary-200
-          w-transition"
-          title={strings.EDIT_YOUR_ACCOUNT}
-          onClick={onClickAccountSettings}
-          aria-label={strings.EDIT_YOUR_ACCOUNT}
-          aria-haspopup="menu"
-          aria-expanded={accountSettingsOpen ? 'true' : 'false'}
-          type="button"
+        <Tippy
+          disabled={!slim}
+          content={strings.EDIT_YOUR_ACCOUNT}
+          placement="right"
         >
-          <div className="avatar avatar-on-dark w-flex-shrink-0 !w-w-[28px] !w-h-[28px]">
-            <img src={user.avatarUrl} alt="" />
-          </div>
-          <div className="sidebar-footer__account-toggle">
-            <div className="sidebar-footer__account-label w-label-3">
-              {user.name}
+          <button
+            className="
+            sidebar-footer__account
+            w-bg-primary
+            w-text-white
+            w-flex
+            w-items-center
+            w-relative
+            w-w-full
+            w-appearance-none
+            w-border-0
+            w-overflow-hidden
+            w-px-5
+            w-py-3
+            hover:w-bg-primary-200
+            focus:w-bg-primary-200
+            w-transition"
+            title={strings.EDIT_YOUR_ACCOUNT}
+            onClick={onClickAccountSettings}
+            aria-label={strings.EDIT_YOUR_ACCOUNT}
+            aria-haspopup="menu"
+            aria-expanded={accountSettingsOpen ? 'true' : 'false'}
+            type="button"
+          >
+            <div className="avatar avatar-on-dark w-flex-shrink-0 !w-w-[28px] !w-h-[28px]">
+              <img src={user.avatarUrl} alt="" />
+            </div>
+            <div className="sidebar-footer__account-toggle">
+              <div className="sidebar-footer__account-label w-label-3">
+                {user.name}
+              </div>
+              <Icon
+                className="w-w-4 w-h-4 w-text-white"
+                name={accountSettingsOpen ? 'arrow-down' : 'arrow-up'}
+              />
             </div>
-            <Icon
-              className="w-w-4 w-h-4 w-text-white"
-              name={accountSettingsOpen ? 'arrow-down' : 'arrow-up'}
-            />
-          </div>
-        </button>
+          </button>
+        </Tippy>
 
         <ul>
           {renderMenu('', accountMenuItems, slim, state, dispatch, navigate)}
@@ -267,7 +290,15 @@ export class MainMenuModuleDefinition implements ModuleDefinition {
     this.user = user;
   }
 
-  render({ slim, expandingOrCollapsing, key, currentPath, strings, navigate }) {
+  render({
+    slim,
+    expandingOrCollapsing,
+    onAccountExpand,
+    key,
+    currentPath,
+    strings,
+    navigate,
+  }) {
     return (
       <Menu
         menuItems={this.menuItems}
@@ -275,6 +306,7 @@ export class MainMenuModuleDefinition implements ModuleDefinition {
         user={this.user}
         slim={slim}
         expandingOrCollapsing={expandingOrCollapsing}
+        onAccountExpand={onAccountExpand}
         key={key}
         currentPath={currentPath}
         strings={strings}

+ 0 - 68
client/src/components/Sidebar/modules/Search.scss

@@ -1,68 +0,0 @@
-// stylelint-disable declaration-no-important
-.sidebar-search {
-  @apply w-relative w-box-border w-flex w-items-center w-flex-row w-h-[42px] w-px-5;
-  $root: &;
-
-  .sidebar--slim & {
-    @apply w-justify-center w-p-0;
-  }
-
-  &__label {
-    @include visuallyhidden;
-  }
-
-  // Beat specificity
-  input:not([type='submit']) {
-    @apply w-pl-[45px];
-    @include show-focus-outline-inside();
-    position: absolute;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: 0;
-    inset-inline-start: 0;
-    top: 0;
-    font-size: 13px;
-    font-weight: 400;
-    background-color: transparent;
-    border: 0;
-    border-radius: 0;
-    color: $color-menu-text;
-    -webkit-font-smoothing: auto;
-
-    .sidebar--slim & {
-      opacity: 0;
-    }
-
-    &::placeholder {
-      color: $color-menu-text;
-    }
-  }
-
-  &__submit {
-    @include show-focus-outline-inside();
-    background-color: transparent;
-    border: 0;
-    border-radius: 0;
-    color: #ccc;
-    padding: 0;
-    width: 35px;
-    height: 35px;
-    transition: opacity $menu-transition-duration ease,
-      width $menu-transition-duration ease;
-
-    svg {
-      margin-inline-end: 20px;
-      transition: margin-inline-end $menu-transition-duration ease;
-    }
-
-    .sidebar--slim & {
-      svg {
-        margin-inline-end: 0;
-      }
-    }
-
-    &:hover {
-      background-color: transparent;
-    }
-  }
-}

+ 90 - 25
client/src/components/Sidebar/modules/Search.tsx

@@ -1,11 +1,18 @@
 import * as React from 'react';
 
 import Icon from '../../Icon/Icon';
-import { ModuleDefinition, Strings } from '../Sidebar';
+import {
+  ModuleDefinition,
+  Strings,
+  SIDEBAR_TRANSITION_DURATION,
+} from '../Sidebar';
+
+import Tippy from '@tippyjs/react';
 
 interface SearchInputProps {
   slim: boolean;
   expandingOrCollapsing: boolean;
+  onSearchClick: () => void;
   searchUrl: string;
   strings: Strings;
 
@@ -15,11 +22,13 @@ interface SearchInputProps {
 export const SearchInput: React.FunctionComponent<SearchInputProps> = ({
   slim,
   expandingOrCollapsing,
+  onSearchClick,
   searchUrl,
   strings,
   navigate,
 }) => {
   const isVisible = !slim || expandingOrCollapsing;
+  const searchInput = React.useRef<HTMLInputElement>(null);
 
   const onSubmitForm = (e: React.FormEvent<HTMLFormElement>) => {
     if (e.target instanceof HTMLFormElement) {
@@ -36,36 +45,84 @@ export const SearchInput: React.FunctionComponent<SearchInputProps> = ({
     }
   };
 
-  const className =
-    'sidebar-search' +
-    (slim ? ' sidebar-search--slim' : '') +
-    (isVisible ? ' sidebar-search--visible' : '');
-
   return (
     <form
       role="search"
-      className={className}
+      className={`w-h-[42px] w-relative w-box-border w-flex w-items-center w-justify-start w-flex-row w-flex-shrink-0`}
       action={searchUrl}
       method="get"
       onSubmit={onSubmitForm}
     >
-      <button
-        className="button sidebar-search__submit"
-        type="submit"
-        aria-label={strings.SEARCH}
-      >
-        <Icon className="icon--menuitem" name="search" />
-      </button>
-      <label className="sidebar-search__label" htmlFor="menu-search-q">
-        {strings.SEARCH}
-      </label>
-      <input
-        className="sidebar-search__input"
-        type="text"
-        id="menu-search-q"
-        name="q"
-        placeholder={strings.SEARCH}
-      />
+      <div className="w-flex w-flex-row w-items-center w-h-full">
+        <Tippy
+          disabled={isVisible || !slim}
+          content={strings.SEARCH}
+          placement="right"
+        >
+          {/* Use padding left 23px to align icon in slim mode and padding right 18px to ensure focus is full width */}
+          <button
+            className={`
+          ${slim ? 'w-pr-[18px]' : 'w-pr-0'}
+          w-w-full
+          w-pl-[23px]
+          w-h-[35px]
+          w-bg-transparent
+          w-outline-offset-inside
+          w-border-0
+          w-rounded-none
+          w-text-white/80
+          w-z-10
+          hover:w-text-white
+          focus:w-text-white
+          hover:w-bg-transparent`}
+            type="submit"
+            aria-label={strings.SEARCH}
+            onClick={(e) => {
+              if (slim) {
+                e.preventDefault();
+                onSearchClick();
+
+                // Focus search input after transition when button is clicked in slim mode
+                setTimeout(() => {
+                  if (searchInput.current) {
+                    searchInput.current.focus();
+                  }
+                }, SIDEBAR_TRANSITION_DURATION);
+              }
+            }}
+          >
+            <Icon className="icon--menuitem" name="search" />
+          </button>
+        </Tippy>
+
+        <label className="w-sr-only" htmlFor="menu-search-q">
+          {strings.SEARCH}
+        </label>
+
+        {/* Classes marked important to trump the base input styling set in _forms.scss */}
+        <input
+          className={`
+            ${slim || !isVisible ? 'w-hidden' : ''}
+            !w-pl-[45px]
+            !w-subpixel-antialiased
+            !w-absolute
+            !w-left-0
+            !w-font-normal
+            !w-top-0
+            !w-text-14
+            !w-bg-transparent
+            !w-border-0
+            !w-rounded-none
+            !w-text-white/80
+            !w-outline-offset-inside
+            placeholder:!w-text-white/80`}
+          type="text"
+          id="menu-search-q"
+          name="q"
+          placeholder={strings.SEARCH}
+          ref={searchInput}
+        />
+      </div>
     </form>
   );
 };
@@ -77,13 +134,21 @@ export class SearchModuleDefinition implements ModuleDefinition {
     this.searchUrl = searchUrl;
   }
 
-  render({ slim, key, expandingOrCollapsing, strings, navigate }) {
+  render({
+    slim,
+    key,
+    expandingOrCollapsing,
+    onSearchClick,
+    strings,
+    navigate,
+  }) {
     return (
       <SearchInput
         searchUrl={this.searchUrl}
         slim={slim}
         key={key}
         expandingOrCollapsing={expandingOrCollapsing}
+        onSearchClick={onSearchClick}
         strings={strings}
         navigate={navigate}
       />

+ 8 - 42
client/src/components/Sidebar/modules/WagtailBranding.scss

@@ -18,7 +18,7 @@
   align-items: center;
   color: #aaa;
   -webkit-font-smoothing: auto;
-  margin: 1.8em auto 2.5em;
+  margin: 4em auto 1.8em;
   text-align: center;
   width: 100px;
   height: 100px;
@@ -28,10 +28,15 @@
   box-sizing: border-box;
   border-radius: 100%;
 
+  @include media-breakpoint-up(sm) {
+    margin: 1.8em auto 2.5em;
+  }
+
   // Reduce overall size when in slim mode
   .sidebar--slim & {
+    @include show-focus-outline-inside();
     width: 60px;
-    transform: none;
+    height: 60px;
   }
 
   // Remove background on 404 page
@@ -63,30 +68,13 @@
 
   // Bird wrapper
   &__icon-wrapper {
-    @apply w-bg-white/15 w-overflow-hidden hover:w-overflow-visible;
+    @apply w-bg-white/15 w-relative w-overflow-hidden hover:w-overflow-visible;
     margin: auto;
-    position: absolute;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    left: 0;
-    inset-inline-start: 0;
-    top: 0;
     width: 100px;
     height: 100px;
     border-radius: 50%;
-    // Remove once we drop support for Safari 13.
-    // stylelint-disable-next-line property-disallowed-list
-    transition: left $menu-transition-duration ease,
-      inset-inline-start $menu-transition-duration ease,
-      top $menu-transition-duration ease, width $menu-transition-duration ease,
-      height $menu-transition-duration ease;
 
     .sidebar--slim & {
-      // Remove once we drop support for Safari 13.
-      // stylelint-disable-next-line property-disallowed-list
-      left: 10px;
-      inset-inline-start: 10px;
-      top: 10px;
       width: 40px;
       height: 40px;
     }
@@ -97,28 +85,6 @@
       position: static;
     }
   }
-
-  // Bird icons
-  &__icon {
-    .sidebar--slim & {
-      width: 42px;
-      height: 51px;
-      top: 10px;
-      // Remove once we drop support for Safari 13.
-      // stylelint-disable-next-line property-disallowed-list
-      left: -9px;
-      inset-inline-start: -9px;
-    }
-
-    // TODO: Fix legacy specificity issues
-    &[data-part='eye--open'] {
-      display: inline !important;
-    }
-
-    &[data-part='eye--closed'] {
-      display: none !important;
-    }
-  }
 }
 
 .sidebar-custom-branding {

+ 7 - 4
client/src/components/Sidebar/modules/WagtailBranding.tsx

@@ -5,6 +5,7 @@ import WagtailLogo from './WagtailLogo';
 interface WagtailBrandingProps {
   homeUrl: string;
   strings: Strings;
+  slim: boolean;
   currentPath: string;
   navigate(url: string): void;
 }
@@ -12,6 +13,7 @@ interface WagtailBrandingProps {
 const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({
   homeUrl,
   strings,
+  slim,
   currentPath,
   navigate,
 }) => {
@@ -79,7 +81,7 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({
   };
 
   const desktopClassName =
-    'sidebar-wagtail-branding' +
+    'sidebar-wagtail-branding w-transition-all w-duration-150' +
     (isWagging ? ' sidebar-wagtail-branding--wagging' : '');
 
   return (
@@ -92,8 +94,8 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({
       onMouseMove={onMouseMove}
       onMouseLeave={onMouseLeave}
     >
-      <div className="sidebar-wagtail-branding__icon-wrapper">
-        <WagtailLogo />
+      <div className="sidebar-wagtail-branding__icon-wrapper w-transition-all w-duration-150">
+        <WagtailLogo slim={slim} />
       </div>
     </a>
   );
@@ -106,11 +108,12 @@ export class WagtailBrandingModuleDefinition implements ModuleDefinition {
     this.homeUrl = homeUrl;
   }
 
-  render({ strings, key, navigate, currentPath }) {
+  render({ strings, slim, key, navigate, currentPath }) {
     return (
       <WagtailBranding
         key={key}
         homeUrl={this.homeUrl}
+        slim={slim}
         strings={strings}
         navigate={navigate}
         currentPath={currentPath}

+ 20 - 4
client/src/components/Sidebar/modules/WagtailLogo.tsx

@@ -2,16 +2,32 @@ import React from 'react';
 
 interface WagtailLogoProps {
   className?: string;
+  slim: boolean;
 }
 
-const WagtailLogo = ({ className }: WagtailLogoProps) => {
-  const feathersClasses = 'group-hover:w-text-black';
+const WagtailLogo = ({ className, slim }: WagtailLogoProps) => {
+  const feathersClasses =
+    'group-hover:w-text-black w-transition-all w-duration-150';
 
   return (
     <svg
       className={`
-        ${className || ''}
-         sidebar-wagtail-branding__icon !w-overflow-visible w-group w-text-primary w-transition w-delay-150 w-duration-150 hover:w-scale-75 hover:w-rotate-6 hover:w-translate-y-[-20px] hover:w-translate-x-[10px] w-z-10 w-absolute w-w-[100px] w-h-[125px] w-top-[25px] w-left-[-20px]
+         sidebar-wagtail-branding__icon
+         !w-overflow-visible
+         w-group
+         w-text-primary
+         w-z-10
+         w-absolute
+         w-transition-all
+         w-duration-150
+         hover:w-scale-75
+         hover:w-rotate-6
+         ${className || ''}
+         ${
+           slim
+             ? 'w-w-[42px] w-h-[51px] w-top-2.5 w-left-[-9px] hover:-w-translate-y-1.5 hover:w-translate-x-1'
+             : 'w-w-[100px] w-h-[125px] w-top-[25px] -w-left-5 hover:w-translate-x-2.5 hover:-w-translate-y-5'
+         }
       `}
       width="430"
       height="537"

+ 45 - 41
client/src/components/Sidebar/modules/__snapshots__/MainMenu.test.js.snap

@@ -10,51 +10,55 @@ exports[`Menu should render with the minimum required props 1`] = `
     />
   </nav>
   <div
-    className="sidebar-footer"
+    className="sidebar-footer sidebar-footer--visible"
   >
-    <button
-      aria-expanded="false"
-      aria-haspopup="menu"
-      className="
-          sidebar-footer__account
-          w-bg-primary
-          w-text-white
-          w-flex
-          w-items-center
-          w-relative
-          w-p-0
-          w-w-full
-          w-appearance-none
-          w-border-0
-          w-overflow-hidden
-          w-px-5
-          w-py-3
-          hover:w-bg-primary-200
-          focus:w-bg-primary-200
-          w-transition"
-      onClick={[Function]}
-      type="button"
+    <ForwardRef(TippyWrapper)
+      disabled={true}
+      placement="right"
     >
-      <div
-        className="avatar avatar-on-dark w-flex-shrink-0 !w-w-[28px] !w-h-[28px]"
-      >
-        <img
-          alt=""
-          src="https://gravatar/profile"
-        />
-      </div>
-      <div
-        className="sidebar-footer__account-toggle"
+      <button
+        aria-expanded="false"
+        aria-haspopup="menu"
+        className="
+            sidebar-footer__account
+            w-bg-primary
+            w-text-white
+            w-flex
+            w-items-center
+            w-relative
+            w-w-full
+            w-appearance-none
+            w-border-0
+            w-overflow-hidden
+            w-px-5
+            w-py-3
+            hover:w-bg-primary-200
+            focus:w-bg-primary-200
+            w-transition"
+        onClick={[Function]}
+        type="button"
       >
         <div
-          className="sidebar-footer__account-label w-label-3"
-        />
-        <Icon
-          className="w-w-4 w-h-4 w-text-white"
-          name="arrow-up"
-        />
-      </div>
-    </button>
+          className="avatar avatar-on-dark w-flex-shrink-0 !w-w-[28px] !w-h-[28px]"
+        >
+          <img
+            alt=""
+            src="https://gravatar/profile"
+          />
+        </div>
+        <div
+          className="sidebar-footer__account-toggle"
+        >
+          <div
+            className="sidebar-footer__account-label w-label-3"
+          />
+          <Icon
+            className="w-w-4 w-h-4 w-text-white"
+            name="arrow-up"
+          />
+        </div>
+      </button>
+    </ForwardRef(TippyWrapper)>
     <ul />
   </div>
 </Fragment>

+ 3 - 0
client/tailwind.config.js

@@ -73,6 +73,9 @@ module.exports = {
         15: '0.15',
         85: '0.85',
       },
+      outlineOffset: {
+        inside: '-3px',
+      },
     },
   },
   plugins: [

+ 39 - 3
package-lock.json

@@ -8,6 +8,7 @@
       "name": "wagtail",
       "version": "1.0.0",
       "dependencies": {
+        "@tippyjs/react": "^4.2.6",
         "draft-js": "^0.10.5",
         "draftail": "^1.4.1",
         "draftjs-filters": "^2.5.0",
@@ -23,6 +24,7 @@
         "redux-thunk": "^2.3.0",
         "reselect": "^4.0.0",
         "telepath-unpack": "^0.0.3",
+        "tippy.js": "^6.3.7",
         "uuid": "^8.3.2"
       },
       "devDependencies": {
@@ -3112,7 +3114,6 @@
       "version": "2.11.2",
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
       "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==",
-      "dev": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/popperjs"
@@ -9436,6 +9437,18 @@
         "react-dom": "^16.8.0 || ^17.0.0"
       }
     },
+    "node_modules/@tippyjs/react": {
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+      "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+      "dependencies": {
+        "tippy.js": "^6.3.1"
+      },
+      "peerDependencies": {
+        "react": ">=16.8",
+        "react-dom": ">=16.8"
+      }
+    },
     "node_modules/@tootallnate/once": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -27477,6 +27490,14 @@
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
       "dev": true
     },
+    "node_modules/tippy.js": {
+      "version": "6.3.7",
+      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+      "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+      "dependencies": {
+        "@popperjs/core": "^2.9.0"
+      }
+    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -31768,8 +31789,7 @@
     "@popperjs/core": {
       "version": "2.11.2",
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
-      "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==",
-      "dev": true
+      "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
     },
     "@sinonjs/commons": {
       "version": "1.8.3",
@@ -36771,6 +36791,14 @@
         "store2": "^2.12.0"
       }
     },
+    "@tippyjs/react": {
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+      "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+      "requires": {
+        "tippy.js": "^6.3.1"
+      }
+    },
     "@tootallnate/once": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -50810,6 +50838,14 @@
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
       "dev": true
     },
+    "tippy.js": {
+      "version": "6.3.7",
+      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+      "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+      "requires": {
+        "@popperjs/core": "^2.9.0"
+      }
+    },
     "tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",

+ 2 - 0
package.json

@@ -95,6 +95,7 @@
     "webpack-cli": "^4.9.1"
   },
   "dependencies": {
+    "@tippyjs/react": "^4.2.6",
     "draft-js": "^0.10.5",
     "draftail": "^1.4.1",
     "draftjs-filters": "^2.5.0",
@@ -110,6 +111,7 @@
     "redux-thunk": "^2.3.0",
     "reselect": "^4.0.0",
     "telepath-unpack": "^0.0.3",
+    "tippy.js": "^6.3.7",
     "uuid": "^8.3.2"
   },
   "scripts": {