UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

157 lines (151 loc) • 6.76 kB
/* toggle-button.tsx generated by @compiled/babel-plugin v3.0.2 */ import "./toggle-button.compiled.css"; import { ax, ix } from "@compiled/react/runtime"; import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { bind } from 'bind-event-listener'; import { useAnalyticsEvents } from '@atlaskit/analytics-next/useAnalyticsEvents'; import SidebarCollapseIcon from '@atlaskit/icon/core/sidebar-collapse'; import SidebarExpandIcon from '@atlaskit/icon/core/sidebar-expand'; import { useOpenLayerObserver } from '@atlaskit/layering/use-open-layer-observer'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { requestClosePopovers } from '@atlaskit/top-layer/page-surface/request-close-popovers'; import { IconButton } from '../../top-nav-items/themed/icon-button'; import { SideNavToggleButtonAttachRef } from './side-nav-toggle-button-attach-ref'; import { sideNavToggleTooltipKeyboardShortcut } from './side-nav-toggle-tooltip-keyboard-shortcut'; import { SideNavVisibilityState } from './side-nav-visibility-state'; import { useIsSideNavShortcutEnabled } from './use-is-side-nav-shortcut-enabled'; import { useSideNavVisibility } from './use-side-nav-visibility'; import { useToggleSideNav } from './use-toggle-side-nav'; const toggleButtonTooltipOptions = { // We're disabling pointer events on the tooltip to prevent it from blocking mouse events, so that the side nav flyout stays open // when moving the mouse from the top bar to the side nav. ignoreTooltipPointerEvents: true, // A press swaps the label between `collapseLabel` and `expandLabel`, so keep the // tooltip open to show the new label. hasNewContentOnTriggerClick: true }; // For duplicate "mouseenter" issue when changing icons (see below) const silentIconStyles = null; /** * __SideNavToggleButton__ * * Button for toggling the side nav. It should be used in the top bar. */ export const SideNavToggleButton = ({ defaultCollapsed = false, expandLabel, collapseLabel, testId, interactionName, onClick }) => { const { isExpandedOnDesktop: isSideNavExpandedOnDesktop, isExpandedOnMobile: isSideNavExpandedOnMobile } = useSideNavVisibility({ defaultCollapsed }); const sideNavState = useContext(SideNavVisibilityState); // When default state is provided to `Root` the state in context will already be // initialized in SSR const [isSideNavExpanded, setIsSideNavExpanded] = useState(sideNavState === null ? !defaultCollapsed : isSideNavExpandedOnDesktop); const ref = useContext(SideNavToggleButtonAttachRef); /** * Addresses HOT-121458 by ensuring that the toggle button element in context is always up to date. * * My theory is that something to do with SSR, hydration and suspense was causing the * underlying HTML element to change but without causing the toggle button to remount. * * This meant the effect calling `ref()` did not re-run, and the value in context became stale. */ const [element, setElement] = useState(null); useEffect(() => { ref(element); }, [element, ref]); useEffect(() => { const { matches } = window.matchMedia('(min-width: 64rem)'); setIsSideNavExpanded(matches ? isSideNavExpandedOnDesktop : isSideNavExpandedOnMobile); }, [isSideNavExpandedOnDesktop, isSideNavExpandedOnMobile]); useEffect(() => { // When screen size changes, ensure we use the correct visibility state const mediaQueryList = window.matchMedia('(min-width: 64rem)'); return bind(mediaQueryList, { type: 'change', listener() { setIsSideNavExpanded(mediaQueryList.matches ? isSideNavExpandedOnDesktop : isSideNavExpandedOnMobile); } }); }, [isSideNavExpandedOnDesktop, isSideNavExpandedOnMobile]); const toggleVisibility = useToggleSideNav({ trigger: 'toggle-button' }); const { createAnalyticsEvent } = useAnalyticsEvents(); const openLayerObserver = useOpenLayerObserver({ isEnabled: !fg('platform-dst-top-layer') }); const handleClick = useCallback((event, analyticsEvent) => { onClick === null || onClick === void 0 ? void 0 : onClick(event, analyticsEvent, { isSideNavVisible: isSideNavExpanded }); toggleVisibility(); // Not gated. Top-layer tooltips (`platform-dst-top-layer-tooltip`) are not known to the observer. requestClosePopovers(); openLayerObserver === null || openLayerObserver === void 0 ? void 0 : openLayerObserver.closeLayers(); }, [isSideNavExpanded, onClick, openLayerObserver, toggleVisibility]); const handlePointerEnter = useCallback(() => { // Hovers don't do anything on mobile, so not capturing const isDesktop = window.matchMedia('(min-width: 64rem)').matches; if (!isDesktop) { return; } const navigationAnalyticsEvent = createAnalyticsEvent({ source: 'topNav', actionSubject: 'sideNav', action: 'hovered', actionSubjectId: 'sideNavButton', attributes: { itemState: isSideNavExpanded ? 'expanded' : 'collapsed', screen: 'desktop' } }); navigationAnalyticsEvent.fire('navigation'); }, [createAnalyticsEvent, isSideNavExpanded]); /** * ## Behaviour * It is intentional that collapse icon will be used while the flyout is open. * The icon is tied to the expanded / collapse state, and not the flyout state. * * ## Why a function? * Unfortunately, changing the icon inside an <IconButton> when the user is over * the button will cause the svg element to be replaced, which can trigger a * "mouseenter" event. This is problematic when the user is already over the button, * as it can result in a "mouseenter" event after the user manually entered the button. * * `icon` accepts a function for the `icon` prop (ie a render prop), so we don't need to * memoize it, or pull it out into a new function */ const icon = props => /*#__PURE__*/React.createElement("span", { className: ax(["_1e0c1bgi _lcxvglyw"]) }, isSideNavExpanded ? /*#__PURE__*/React.createElement(SidebarCollapseIcon, props) : /*#__PURE__*/React.createElement(SidebarExpandIcon, props)); const isShortcutEnabled = useIsSideNavShortcutEnabled(); const tooltipProps = useMemo(() => ({ ...toggleButtonTooltipOptions, shortcut: isShortcutEnabled ? sideNavToggleTooltipKeyboardShortcut : undefined }), [isShortcutEnabled]); return /*#__PURE__*/React.createElement(IconButton, { appearance: "subtle", label: isSideNavExpanded ? collapseLabel : expandLabel, icon: icon, onClick: handleClick, onPointerEnter: handlePointerEnter, testId: testId, isTooltipDisabled: false, interactionName: interactionName, ref: setElement, tooltip: tooltipProps }); };