UNPKG

orcs-design-system

Version:
654 lines (651 loc) 25.5 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["as", "Component", "fullWidth", "ariaLabel", "aria-label"], _excluded2 = ["theme", "onToggle", "toggleState", "direction", "menuWidth", "customTriggerComponent", "renderTrigger", "children", "ariaLabel", "onTriggerFocus", "closeMenu", "closeOnClick", "data-testid", "variant", "disabled"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React, { useState, useImperativeHandle, createContext, useContext, useMemo, useId, useLayoutEffect, useEffect } from "react"; import styled, { css, ThemeProvider } from "styled-components"; import PropTypes from "prop-types"; import { NavLink } from "react-router-dom"; import { space, layout } from "styled-system"; import { themeGet } from "@styled-system/theme-get"; import { commonKeys } from "../../hooks/keypress"; import useActionMenu from "./useActionMenu"; import { crossFadeIn, crossFadeOut, beforeDotCollapsing, beforeDotExpanding, afterDotCollapsing, afterDotExpanding, beforeCrossExpanding, beforeCrossCollapsing, afterCrossExpanding, afterCrossCollapsing } from "./ActionsMenu.animations"; import { FloatingFocusManager, FloatingPortal, useMergeRefs } from "@floating-ui/react"; import { getFloatingUiRootElement, getFloatingUiZIndex } from "../../utils/floatingUiHelpers"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const ActionMenuContext = /*#__PURE__*/createContext({}); const StyledActionsMenuContainer = /*#__PURE__*/styled.div.withConfig({ displayName: "StyledActionsMenuContainer", componentId: "sc-yvbni2-0" })(["pointer-events:none;opacity:0;visibility:hidden;&.hack-for-legacy-tests{position:absolute;pointer-events:none;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border-width:0;}&.visible{visibility:visible;opacity:1;pointer-events:auto;}"]); const Wrapper = /*#__PURE__*/styled.div.withConfig({ displayName: "Wrapper", componentId: "sc-yvbni2-1" })(["", " ", " position:relative;width:auto;"], space, layout); const Control = /*#__PURE__*/styled.button.withConfig({ displayName: "Control", componentId: "sc-yvbni2-2" })(["position:relative;display:flex;align-items:center;justify-content:center;-moz-appearance:none;-webkit-appearance:none;appearance:none;box-shadow:none;text-decoration:none;text-align:center;border-radius:", ";transition:", ";cursor:", ";width:", ";height:", ";border-radius:", ";border:", ";", " ", " &:hover,&:focus{outline:0;border-color:", ";", "}&[data-state=\"open\"] .actionsMenu__dots{&::before{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}&::after{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}}&[data-state=\"open\"] .actionsMenu__cross{animation:500ms ", " ease-in-out forwards;&::before{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}&::after{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}}&[data-state=\"closed\"] .actionsMenu__dots{&::before{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}&::after{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}}&[data-state=\"closed\"] .actionsMenu__cross{animation:none;opacity:0;&::before{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}&::after{animation:500ms ", " cubic-bezier(0.68,-0.6,0.32,1.6) forwards;}}&[data-state=\"closed\"][data-has-been-open=\"true\"] .actionsMenu__cross{animation:500ms ", " ease-in-out forwards;}"], props => themeGet("radii.2")(props), props => themeGet("transition.transitionDefault")(props), props => props.disabled ? "not-allowed" : "pointer", props => props.variant === "circle" ? "35px" : "30px", props => props.variant === "circle" ? "35px" : "30px", props => props.variant === "circle" ? "50%" : themeGet("radii.2")(props), props => props.variant === "circle" ? "solid 2px" : "solid 1px", props => props.variant === "default" && css(["background-color:", ";border-color:", ";"], props.disabled ? themeGet("colors.greyLighter")(props) : "white", props.disabled ? themeGet("colors.greyLighter")(props) : themeGet("colors.greyLight")(props)), props => props.variant === "circle" && css(["background-color:", ";border-color:", ";"], props.disabled ? themeGet("colors.greyLighter")(props) : themeGet("colors.greyDarkest")(props), props.disabled ? themeGet("colors.greyLighter")(props) : themeGet("colors.greyDarkest")(props)), props => props.disabled ? themeGet("colors.greyLighter")(props) : themeGet("colors.primary")(props), props => !props.disabled && props.variant === "default" && css(["background-color:", ";"], themeGet("colors.greyLightest")(props)), beforeDotCollapsing, afterDotCollapsing, crossFadeIn, beforeCrossExpanding, afterCrossExpanding, beforeDotExpanding, afterDotExpanding, beforeCrossCollapsing, afterCrossCollapsing, crossFadeOut); const DOT_SIZE = 4; const DOT_OFFSET = 7; // Distance from center dot to top/bottom dots const Dots = /*#__PURE__*/styled.div.withConfig({ displayName: "Dots", componentId: "sc-yvbni2-3" })(["position:relative;border-radius:50%;height:", "px;width:", "px;background-color:", ";transform:translateZ(0);&::before,&::after{content:\"\";display:block;position:absolute;left:0;top:0;border-radius:50%;height:", "px;width:", "px;background-color:", ";}&::before{transform:translateY(-", "px);}&::after{transform:translateY(", "px);}"], DOT_SIZE, DOT_SIZE, props => props.variant === "circle" ? themeGet("colors.white")(props) : themeGet("colors.greyDarker")(props), DOT_SIZE, DOT_SIZE, props => props.variant === "circle" ? themeGet("colors.white")(props) : themeGet("colors.greyDarker")(props), DOT_OFFSET, DOT_OFFSET); const Cross = /*#__PURE__*/styled.div.withConfig({ displayName: "Cross", componentId: "sc-yvbni2-4" })(["opacity:0;position:absolute;left:calc(50% - 2px);top:calc(50% - 2px);pointer-events:none;&::before,&::after{content:\"\";display:block;position:absolute;border-radius:2px;height:4px;width:4px;background-color:", ";}&::before{transform:rotate(-45deg);}&::after{transform:rotate(45deg);}"], props => props.variant === "circle" ? themeGet("colors.white")(props) : themeGet("colors.greyDarker")(props)); const Menu = /*#__PURE__*/styled.div.withConfig({ displayName: "Menu", componentId: "sc-yvbni2-5" })(["display:block;width:", ";z-index:5;background-color:", ";border:1px solid ", ";box-shadow:", ";border-radius:", ";"], props => props.menuWidth ? props.menuWidth : "auto", themeGet("colors.white"), themeGet("colors.greyLight"), themeGet("shadows.boxDefault"), props => themeGet("radii.2")(props)); export const ActionsMenuHeading = /*#__PURE__*/styled(props => { const { actionMenu } = useContext(ActionMenuContext); const id = useId(); // // Only sets `aria-labelledby` on the Popover root element // // if this component is mounted inside it. useLayoutEffect(() => { var _actionMenu$setLabelI; actionMenu === null || actionMenu === void 0 || (_actionMenu$setLabelI = actionMenu.setLabelId) === null || _actionMenu$setLabelI === void 0 || _actionMenu$setLabelI.call(actionMenu, id); return () => { var _actionMenu$setLabelI2; return actionMenu === null || actionMenu === void 0 || (_actionMenu$setLabelI2 = actionMenu.setLabelId) === null || _actionMenu$setLabelI2 === void 0 ? void 0 : _actionMenu$setLabelI2.call(actionMenu, undefined); }; }, [id, actionMenu]); return /*#__PURE__*/_jsx("div", _objectSpread(_objectSpread({}, props), {}, { onKeyUp: e => { if (e.key === commonKeys.ENTER && props !== null && props !== void 0 && props.canClick) { props === null || props === void 0 || props.onClick(); } } })); }).attrs({ tabIndex: "0", role: "button" }).withConfig({ displayName: "ActionsMenuHeading", componentId: "sc-yvbni2-6" })(["color:", ";padding:8px;width:100%;font-size:", ";font-weight:", ";border-bottom:solid 1px ", ";white-space:nowrap;cursor:", ";"], props => themeGet("colors.greyDark")(props), props => themeGet("fontSizes.0")(props), props => themeGet("fontWeights.1")(props), props => themeGet("colors.greyLighter")(props), props => props.canClick ? "pointer" : "default"); const ActionMenuItemStyles = /*#__PURE__*/css(["white-space:nowrap;display:block;width:100%;text-align:left;cursor:pointer;margin:0;padding:8px;appearance:none;background-color:", ";border:none;border-bottom:solid 1px ", ";border-radius:0;color:", ";font-size:", ";line-height:", ";font-family:", ";font-weight:", ";text-decoration:none;transition:", ";&:hover{background-color:", ";}&:first-child{border-radius:", " ", " 0 0;}&:last-child{border:0;border-radius:0 0 ", " ", ";}&:only-child{border-radius:", ";}&#other{padding:6px 8px;}"], props => props.selected ? themeGet("colors.success20")(props) : "transparent", themeGet("colors.white"), themeGet("colors.greyDarkest"), themeGet("fontSizes.0"), themeGet("fontSizes.0"), themeGet("fonts.main"), themeGet("fontWeights.2"), themeGet("transition.transitionDefault"), themeGet("colors.primaryLightest"), themeGet("radii.2"), themeGet("radii.2"), themeGet("radii.2"), themeGet("radii.2"), themeGet("radii.2")); const StyledNavLink = /*#__PURE__*/styled(NavLink).withConfig({ displayName: "StyledNavLink", componentId: "sc-yvbni2-7" })(["text-decoration:none;"]); export const ActionsMenuItem = /*#__PURE__*/styled(props => { var _actionMenu$getItemPr; const { id, onItemClick, actionMenu } = useContext(ActionMenuContext); // fullWidth and ariaLabel stripped so they are not forwarded to DOM (use aria-label) const { as, Component: CustomComponent, fullWidth, ariaLabel, "aria-label": ariaLabelProp } = props, others = _objectWithoutProperties(props, _excluded); void fullWidth; const ariaLabelValue = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : ariaLabelProp; const Component = as ? as : others.href ? "a" : "button"; const disabled = props.disabled; let newProps = _objectSpread(_objectSpread(_objectSpread({}, others), ariaLabelValue != null ? { "aria-label": ariaLabelValue } : {}), (actionMenu === null || actionMenu === void 0 || (_actionMenu$getItemPr = actionMenu.getItemProps) === null || _actionMenu$getItemPr === void 0 ? void 0 : _actionMenu$getItemPr.call(actionMenu)) || {}); const { onClick: originalOnClick } = newProps; const onClick = useMemo(() => e => { onItemClick === null || onItemClick === void 0 || onItemClick(e); originalOnClick === null || originalOnClick === void 0 || originalOnClick(e); }, [originalOnClick, onItemClick]); if (props.to) { if (CustomComponent) { return /*#__PURE__*/_jsx(CustomComponent, _objectSpread(_objectSpread({ to: props.to }, newProps), {}, { $fullWidth: Boolean(props.fullWidth), onClick: onClick, disabled: disabled })); } return /*#__PURE__*/_jsx(StyledNavLink, { to: props.to, children: /*#__PURE__*/_jsx(Component, _objectSpread({}, newProps)) }); } if (Component === "button") { newProps = _objectSpread(_objectSpread({}, others), {}, { type: "button", ["data-action-menu-id"]: id }); } if (CustomComponent) return /*#__PURE__*/_jsx(CustomComponent, _objectSpread(_objectSpread({}, newProps), {}, { $fullWidth: Boolean(props.fullWidth), onClick: onClick, disabled: disabled })); return /*#__PURE__*/_jsx(Component, _objectSpread(_objectSpread({}, newProps), {}, { onClick: onClick, disabled: disabled })); }).attrs({ role: "menuitem" }).withConfig({ displayName: "ActionsMenuItem", componentId: "sc-yvbni2-8" })(["", ""], _ref => { let { Component } = _ref; return Component ? "" : ActionMenuItemStyles; }); export const ActionsMenuBody = _ref2 => { let { theme, onToggle, toggleState, // direction - Deprecated direction = "right-start", menuWidth, customTriggerComponent, renderTrigger, children, ariaLabel = "Options Menu", onTriggerFocus, closeMenu, closeOnClick = false, "data-testid": dataTestId = "ActionsMenu", variant = "default", disabled = false } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2); const id = useId(); const [hasBeenOpen, setHasBeenOpen] = useState(false); const actionMenu = useActionMenu({ placement: direction, open: toggleState, onOpenChange: (_, e) => { if (e) { onToggle === null || onToggle === void 0 || onToggle(e); } } }); useEffect(() => { if (actionMenu.open) { setHasBeenOpen(true); } }, [actionMenu.open]); const childrenRef = children.ref; const triggerRef = useMergeRefs([actionMenu.refs.setReference, childrenRef]); const ref = useMergeRefs([actionMenu.refs.setFloating]); const triggerProps = useMemo(() => _objectSpread({ "aria-label": ariaLabel, onFocus: onTriggerFocus, id, disabled }, actionMenu.getReferenceProps(_objectSpread(_objectSpread({}, props), {}, { onClick: disabled ? undefined : onToggle, ref: triggerRef, "data-state": actionMenu.open ? "open" : "closed", "data-has-been-open": hasBeenOpen ? "true" : undefined, "data-testid": dataTestId }))), [ariaLabel, onTriggerFocus, id, actionMenu, onToggle, props, triggerRef, dataTestId, disabled, hasBeenOpen]); let triggerComponent = /*#__PURE__*/_jsxs(Control, _objectSpread(_objectSpread({}, triggerProps), {}, { variant: variant, disabled: disabled, children: [/*#__PURE__*/_jsx(Dots, { className: "actionsMenu__dots", variant: variant }), /*#__PURE__*/_jsx(Cross, { className: "actionsMenu__cross", variant: variant })] })); if (renderTrigger) { triggerComponent = renderTrigger(triggerProps); } if (customTriggerComponent) { triggerComponent = /*#__PURE__*/_jsx("div", _objectSpread(_objectSpread({ role: "button" }, triggerProps), {}, { children: customTriggerComponent })); } const value = useMemo(() => ({ id, onItemClick: e => { if (closeOnClick) { closeMenu(e); } }, actionMenu }), [closeOnClick, closeMenu, id, actionMenu]); const style = useMemo(() => _objectSpread(_objectSpread({}, actionMenu.floatingStyles), {}, { zIndex: getFloatingUiZIndex(actionMenu.refs.reference) }), [actionMenu.floatingStyles, actionMenu.refs.reference]); const menuDataTestId = useMemo(() => "".concat(dataTestId, "__menu"), [dataTestId]); const { getFloatingProps } = actionMenu; const floatingProps = useMemo(() => getFloatingProps(props), [getFloatingProps, props]); const component = /*#__PURE__*/_jsx(ActionMenuContext.Provider, { value: value, children: /*#__PURE__*/_jsxs(Wrapper, _objectSpread(_objectSpread({}, props), {}, { children: [triggerComponent, toggleState ? /*#__PURE__*/_jsx(FloatingPortal, { root: getFloatingUiRootElement(actionMenu.refs.reference), children: /*#__PURE__*/_jsx(FloatingFocusManager, { context: actionMenu.context, modal: true, children: /*#__PURE__*/_jsx(StyledActionsMenuContainer, _objectSpread(_objectSpread({ "aria-labelledby": actionMenu.labelId, "data-testid": menuDataTestId }, floatingProps), {}, { style: style, className: "actionMenu-content visible", "aria-hidden": "false", ref: ref, children: /*#__PURE__*/_jsx(Menu, { menuWidth: menuWidth, isOpen: toggleState, children: children }) })) }) }) : /*#__PURE__*/ /* * HACK: Fixing all the broken tests in teamform-app-ui is too time consuming * right this moment with a lot of the tests asserting against ActionsMenu items. * Rendering the markup even when closed but in a hidden state ensures that tests pass. * Ideally, we would update all the tests in teamform-app-ui to open the ActionsMenu * before assertion. **/ _jsx(StyledActionsMenuContainer, { "aria-labelledby": actionMenu.labelId, "data-testid": menuDataTestId, className: "actionMenu-content hack-for-legacy-tests", children: /*#__PURE__*/_jsx(Menu, { menuWidth: menuWidth, isOpen: toggleState, children: children }) })] })) }); return theme ? /*#__PURE__*/_jsx(ThemeProvider, { theme: theme, children: component }) : component; }; ActionsMenuBody.propTypes = { onTriggerFocus: PropTypes.func, onToggle: PropTypes.func.isRequired, closeMenu: PropTypes.func.isRequired, toggleState: PropTypes.bool.isRequired, closeOnClick: PropTypes.bool, direction: PropTypes.string, placement: PropTypes.string, menuTopPosition: PropTypes.string, menuLeftPosition: PropTypes.string, menuRightPosition: PropTypes.string, menuWidth: PropTypes.string, customTriggerComponent: PropTypes.node, renderTrigger: PropTypes.func, "data-testid": PropTypes.string, children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), theme: PropTypes.object, ariaLabel: PropTypes.string, variant: PropTypes.oneOf(["default", "circle"]), disabled: PropTypes.bool }; const ActionsMenu = /*#__PURE__*/React.forwardRef((props, ref) => { const [toggleState, setToggle] = useState(false); const onToggle = e => { e.stopPropagation(); setToggle(!toggleState); }; useImperativeHandle(ref, () => ({ closeMenu: () => { setToggle(false); } })); return /*#__PURE__*/_jsx(ActionsMenuBody, _objectSpread(_objectSpread({}, props), {}, { closeMenu: () => setToggle(false), toggleState: toggleState, onToggle: onToggle, children: props.children })); }); ActionsMenu.propTypes = { isOpen: PropTypes.bool, direction: PropTypes.oneOf(["left", "right", "top", "bottom", "top-start", "top-end", "bottom-start", "bottom-end", "left-start", "left-end", "right-start", "right-end"]), customTriggerComponent: PropTypes.node, renderTrigger: PropTypes.func, closeOnClick: PropTypes.bool, children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), /** Specifies the colour theme */ theme: PropTypes.object, /** Specifies the aria-label for the button */ ariaLabel: PropTypes.string, /** Specifies the variant of the ActionsMenu */ variant: PropTypes.oneOf(["default", "circle"]), /** Specifies whether the ActionsMenu is disabled */ disabled: PropTypes.bool }; ActionsMenu.__docgenInfo = { "description": "", "methods": [], "displayName": "ActionsMenu", "props": { "isOpen": { "description": "", "type": { "name": "bool" }, "required": false }, "direction": { "description": "", "type": { "name": "enum", "value": [{ "value": "\"left\"", "computed": false }, { "value": "\"right\"", "computed": false }, { "value": "\"top\"", "computed": false }, { "value": "\"bottom\"", "computed": false }, { "value": "\"top-start\"", "computed": false }, { "value": "\"top-end\"", "computed": false }, { "value": "\"bottom-start\"", "computed": false }, { "value": "\"bottom-end\"", "computed": false }, { "value": "\"left-start\"", "computed": false }, { "value": "\"left-end\"", "computed": false }, { "value": "\"right-start\"", "computed": false }, { "value": "\"right-end\"", "computed": false }] }, "required": false }, "customTriggerComponent": { "description": "", "type": { "name": "node" }, "required": false }, "renderTrigger": { "description": "", "type": { "name": "func" }, "required": false }, "closeOnClick": { "description": "", "type": { "name": "bool" }, "required": false }, "children": { "description": "", "type": { "name": "union", "value": [{ "name": "node" }, { "name": "arrayOf", "value": { "name": "node" } }] }, "required": false }, "theme": { "description": "Specifies the colour theme", "type": { "name": "object" }, "required": false }, "ariaLabel": { "description": "Specifies the aria-label for the button", "type": { "name": "string" }, "required": false }, "variant": { "description": "Specifies the variant of the ActionsMenu", "type": { "name": "enum", "value": [{ "value": "\"default\"", "computed": false }, { "value": "\"circle\"", "computed": false }] }, "required": false }, "disabled": { "description": "Specifies whether the ActionsMenu is disabled", "type": { "name": "bool" }, "required": false } } }; export default ActionsMenu; ActionsMenuBody.__docgenInfo = { "description": "", "methods": [], "displayName": "ActionsMenuBody", "props": { "direction": { "defaultValue": { "value": "\"right-start\"", "computed": false }, "description": "", "type": { "name": "string" }, "required": false }, "ariaLabel": { "defaultValue": { "value": "\"Options Menu\"", "computed": false }, "description": "", "type": { "name": "string" }, "required": false }, "closeOnClick": { "defaultValue": { "value": "false", "computed": false }, "description": "", "type": { "name": "bool" }, "required": false }, "data-testid": { "defaultValue": { "value": "\"ActionsMenu\"", "computed": false }, "description": "", "type": { "name": "string" }, "required": false }, "variant": { "defaultValue": { "value": "\"default\"", "computed": false }, "description": "", "type": { "name": "enum", "value": [{ "value": "\"default\"", "computed": false }, { "value": "\"circle\"", "computed": false }] }, "required": false }, "disabled": { "defaultValue": { "value": "false", "computed": false }, "description": "", "type": { "name": "bool" }, "required": false }, "onTriggerFocus": { "description": "", "type": { "name": "func" }, "required": false }, "onToggle": { "description": "", "type": { "name": "func" }, "required": true }, "closeMenu": { "description": "", "type": { "name": "func" }, "required": true }, "toggleState": { "description": "", "type": { "name": "bool" }, "required": true }, "placement": { "description": "", "type": { "name": "string" }, "required": false }, "menuTopPosition": { "description": "", "type": { "name": "string" }, "required": false }, "menuLeftPosition": { "description": "", "type": { "name": "string" }, "required": false }, "menuRightPosition": { "description": "", "type": { "name": "string" }, "required": false }, "menuWidth": { "description": "", "type": { "name": "string" }, "required": false }, "customTriggerComponent": { "description": "", "type": { "name": "node" }, "required": false }, "renderTrigger": { "description": "", "type": { "name": "func" }, "required": false }, "children": { "description": "", "type": { "name": "union", "value": [{ "name": "node" }, { "name": "arrayOf", "value": { "name": "node" } }] }, "required": false }, "theme": { "description": "", "type": { "name": "object" }, "required": false } } };