WagtailBranding.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import * as React from 'react';
  2. import { ModuleDefinition } from '../Sidebar';
  3. import WagtailLogo from './WagtailLogo';
  4. interface WagtailBrandingProps {
  5. homeUrl: string;
  6. slim: boolean;
  7. currentPath: string;
  8. navigate(url: string): void;
  9. }
  10. const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({
  11. homeUrl,
  12. slim,
  13. currentPath,
  14. navigate,
  15. }) => {
  16. const brandingLogo = React.useMemo(
  17. () =>
  18. document.querySelector<HTMLTemplateElement>(
  19. '[data-wagtail-sidebar-branding-logo]',
  20. ),
  21. [],
  22. );
  23. const hasCustomBranding = brandingLogo && brandingLogo.innerHTML !== '';
  24. const onClick = (e: React.MouseEvent) => {
  25. // Do not capture click events with modifier keys or non-main buttons.
  26. if (e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button !== 0)) {
  27. return;
  28. }
  29. e.preventDefault();
  30. navigate(homeUrl);
  31. };
  32. // Render differently if custom branding is provided.
  33. // This will only ever render once, so rendering before hooks is ok.
  34. if (hasCustomBranding) {
  35. return (
  36. <a
  37. className="sidebar-custom-branding"
  38. href={homeUrl}
  39. aria-label={gettext('Dashboard')}
  40. aria-current={currentPath === homeUrl ? 'page' : undefined}
  41. dangerouslySetInnerHTML={{
  42. __html: brandingLogo ? brandingLogo.innerHTML : '',
  43. }}
  44. />
  45. );
  46. }
  47. // Tail wagging
  48. // If the pointer changes direction 8 or more times without leaving, wag the tail!
  49. const lastMouseX = React.useRef(0);
  50. const lastDir = React.useRef<'r' | 'l'>('r');
  51. const dirChangeCount = React.useRef(0);
  52. const [isWagging, setIsWagging] = React.useState(false);
  53. const onMouseMove = (e: React.MouseEvent) => {
  54. const mouseX = e.pageX;
  55. const dir: 'r' | 'l' = mouseX > lastMouseX.current ? 'r' : 'l';
  56. if (mouseX !== lastMouseX.current && dir !== lastDir.current) {
  57. dirChangeCount.current += 1;
  58. }
  59. if (dirChangeCount.current > 8) {
  60. setIsWagging(true);
  61. }
  62. lastMouseX.current = mouseX;
  63. lastDir.current = dir;
  64. };
  65. const onMouseLeave = () => {
  66. setIsWagging(false);
  67. dirChangeCount.current = 0;
  68. };
  69. const desktopClassName =
  70. 'sidebar-wagtail-branding w-transition-all w-duration-150' +
  71. (isWagging ? ' sidebar-wagtail-branding--wagging' : '');
  72. return (
  73. <a
  74. className={desktopClassName}
  75. href={homeUrl}
  76. aria-label={gettext('Dashboard')}
  77. aria-current={currentPath === homeUrl ? 'page' : undefined}
  78. onClick={onClick}
  79. onMouseMove={onMouseMove}
  80. onMouseLeave={onMouseLeave}
  81. >
  82. <div className="sidebar-wagtail-branding__icon-wrapper w-transition-all w-duration-150">
  83. <WagtailLogo slim={slim} />
  84. </div>
  85. </a>
  86. );
  87. };
  88. export class WagtailBrandingModuleDefinition implements ModuleDefinition {
  89. homeUrl: string;
  90. constructor(homeUrl: string) {
  91. this.homeUrl = homeUrl;
  92. }
  93. render({ strings, slim, key, navigate, currentPath }) {
  94. return (
  95. <WagtailBranding
  96. key={key}
  97. homeUrl={this.homeUrl}
  98. slim={slim}
  99. navigate={navigate}
  100. currentPath={currentPath}
  101. />
  102. );
  103. }
  104. }