@wordpress/interface
Version:
Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.
269 lines (266 loc) • 9.37 kB
JavaScript
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { Button, Panel, Slot, Fill, __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { check, starEmpty, starFilled } from '@wordpress/icons';
import { useEffect, useRef, useState } from '@wordpress/element';
import { store as viewportStore } from '@wordpress/viewport';
import { store as preferencesStore } from '@wordpress/preferences';
import { useReducedMotion, useViewportMatch, usePrevious } from '@wordpress/compose';
import { usePluginContext } from '@wordpress/plugins';
/**
* Internal dependencies
*/
import ComplementaryAreaHeader from '../complementary-area-header';
import ComplementaryAreaMoreMenuItem from '../complementary-area-more-menu-item';
import ComplementaryAreaToggle from '../complementary-area-toggle';
import PinnedItems from '../pinned-items';
import { store as interfaceStore } from '../../store';
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const ANIMATION_DURATION = 0.3;
function ComplementaryAreaSlot({
scope,
...props
}) {
return /*#__PURE__*/_jsx(Slot, {
name: `ComplementaryArea/${scope}`,
...props
});
}
const SIDEBAR_WIDTH = 280;
const variants = {
open: {
width: SIDEBAR_WIDTH
},
closed: {
width: 0
},
mobileOpen: {
width: '100vw'
}
};
function ComplementaryAreaFill({
activeArea,
isActive,
scope,
children,
className,
id
}) {
const disableMotion = useReducedMotion();
const isMobileViewport = useViewportMatch('medium', '<');
// This is used to delay the exit animation to the next tick.
// The reason this is done is to allow us to apply the right transition properties
// When we switch from an open sidebar to another open sidebar.
// we don't want to animate in this case.
const previousActiveArea = usePrevious(activeArea);
const previousIsActive = usePrevious(isActive);
const [, setState] = useState({});
useEffect(() => {
setState({});
}, [isActive]);
const transition = {
type: 'tween',
duration: disableMotion || isMobileViewport || !!previousActiveArea && !!activeArea && activeArea !== previousActiveArea ? 0 : ANIMATION_DURATION,
ease: [0.6, 0, 0.4, 1]
};
return /*#__PURE__*/_jsx(Fill, {
name: `ComplementaryArea/${scope}`,
children: /*#__PURE__*/_jsx(AnimatePresence, {
initial: false,
children: (previousIsActive || isActive) && /*#__PURE__*/_jsx(motion.div, {
variants: variants,
initial: "closed",
animate: isMobileViewport ? 'mobileOpen' : 'open',
exit: "closed",
transition: transition,
className: "interface-complementary-area__fill",
children: /*#__PURE__*/_jsx("div", {
id: id,
className: className,
style: {
width: isMobileViewport ? '100vw' : SIDEBAR_WIDTH
},
children: children
})
})
})
});
}
function useAdjustComplementaryListener(scope, identifier, activeArea, isActive, isSmall) {
const previousIsSmallRef = useRef(false);
const shouldOpenWhenNotSmallRef = useRef(false);
const {
enableComplementaryArea,
disableComplementaryArea
} = useDispatch(interfaceStore);
useEffect(() => {
// If the complementary area is active and the editor is switching from
// a big to a small window size.
if (isActive && isSmall && !previousIsSmallRef.current) {
disableComplementaryArea(scope);
// Flag the complementary area to be reopened when the window size
// goes from small to big.
shouldOpenWhenNotSmallRef.current = true;
} else if (
// If there is a flag indicating the complementary area should be
// enabled when we go from small to big window size and we are going
// from a small to big window size.
shouldOpenWhenNotSmallRef.current && !isSmall && previousIsSmallRef.current) {
// Remove the flag indicating the complementary area should be
// enabled.
shouldOpenWhenNotSmallRef.current = false;
enableComplementaryArea(scope, identifier);
} else if (
// If the flag is indicating the current complementary should be
// reopened but another complementary area becomes active, remove
// the flag.
shouldOpenWhenNotSmallRef.current && activeArea && activeArea !== identifier) {
shouldOpenWhenNotSmallRef.current = false;
}
if (isSmall !== previousIsSmallRef.current) {
previousIsSmallRef.current = isSmall;
}
}, [isActive, isSmall, scope, identifier, activeArea, disableComplementaryArea, enableComplementaryArea]);
}
function ComplementaryArea({
children,
className,
closeLabel = __('Close plugin'),
identifier: identifierProp,
header,
headerClassName,
icon: iconProp,
isPinnable = true,
panelClassName,
scope,
name,
title,
toggleShortcut,
isActiveByDefault
}) {
const context = usePluginContext();
const icon = iconProp || context.icon;
const identifier = identifierProp || `${context.name}/${name}`;
// This state is used to delay the rendering of the Fill
// until the initial effect runs.
// This prevents the animation from running on mount if
// the complementary area is active by default.
const [isReady, setIsReady] = useState(false);
const {
isLoading,
isActive,
isPinned,
activeArea,
isSmall,
isLarge,
showIconLabels
} = useSelect(select => {
const {
getActiveComplementaryArea,
isComplementaryAreaLoading,
isItemPinned
} = select(interfaceStore);
const {
get
} = select(preferencesStore);
const _activeArea = getActiveComplementaryArea(scope);
return {
isLoading: isComplementaryAreaLoading(scope),
isActive: _activeArea === identifier,
isPinned: isItemPinned(scope, identifier),
activeArea: _activeArea,
isSmall: select(viewportStore).isViewportMatch('< medium'),
isLarge: select(viewportStore).isViewportMatch('large'),
showIconLabels: get('core', 'showIconLabels')
};
}, [identifier, scope]);
const isMobileViewport = useViewportMatch('medium', '<');
useAdjustComplementaryListener(scope, identifier, activeArea, isActive, isSmall);
const {
enableComplementaryArea,
disableComplementaryArea,
pinItem,
unpinItem
} = useDispatch(interfaceStore);
useEffect(() => {
// Set initial visibility: For large screens, enable if it's active by
// default. For small screens, always initially disable.
if (isActiveByDefault && activeArea === undefined && !isSmall) {
enableComplementaryArea(scope, identifier);
} else if (activeArea === undefined && isSmall) {
disableComplementaryArea(scope, identifier);
}
setIsReady(true);
}, [activeArea, isActiveByDefault, scope, identifier, isSmall, enableComplementaryArea, disableComplementaryArea]);
if (!isReady) {
return;
}
return /*#__PURE__*/_jsxs(_Fragment, {
children: [isPinnable && /*#__PURE__*/_jsx(PinnedItems, {
scope: scope,
children: isPinned && /*#__PURE__*/_jsx(ComplementaryAreaToggle, {
scope: scope,
identifier: identifier,
isPressed: isActive && (!showIconLabels || isLarge),
"aria-expanded": isActive,
"aria-disabled": isLoading,
label: title,
icon: showIconLabels ? check : icon,
showTooltip: !showIconLabels,
variant: showIconLabels ? 'tertiary' : undefined,
size: "compact",
shortcut: toggleShortcut
})
}), name && isPinnable && /*#__PURE__*/_jsx(ComplementaryAreaMoreMenuItem, {
target: name,
scope: scope,
icon: icon,
children: title
}), /*#__PURE__*/_jsxs(ComplementaryAreaFill, {
activeArea: activeArea,
isActive: isActive,
className: clsx('interface-complementary-area', className),
scope: scope,
id: identifier.replace('/', ':'),
children: [/*#__PURE__*/_jsx(ComplementaryAreaHeader, {
className: headerClassName,
closeLabel: closeLabel,
onClose: () => disableComplementaryArea(scope),
toggleButtonProps: {
label: closeLabel,
size: 'compact',
shortcut: toggleShortcut,
scope,
identifier
},
children: header || /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx("h2", {
className: "interface-complementary-area-header__title",
children: title
}), isPinnable && !isMobileViewport && /*#__PURE__*/_jsx(Button, {
className: "interface-complementary-area__pin-unpin-item",
icon: isPinned ? starFilled : starEmpty,
label: isPinned ? __('Unpin from toolbar') : __('Pin to toolbar'),
onClick: () => (isPinned ? unpinItem : pinItem)(scope, identifier),
isPressed: isPinned,
"aria-expanded": isPinned,
size: "compact"
})]
})
}), /*#__PURE__*/_jsx(Panel, {
className: panelClassName,
children: children
})]
})]
});
}
ComplementaryArea.Slot = ComplementaryAreaSlot;
export default ComplementaryArea;
//# sourceMappingURL=index.js.map