UNPKG

11.1 kBJavaScriptView Raw
1import { __rest } from "tslib";
2import * as React from 'react';
3import styles from '@patternfly/react-styles/css/components/Menu/menu';
4import { css } from '@patternfly/react-styles';
5import topOffset from '@patternfly/react-tokens/dist/esm/c_menu_m_flyout__menu_top_offset';
6import rightOffset from '@patternfly/react-tokens/dist/esm/c_menu_m_flyout__menu_m_left_right_offset';
7import leftOffset from '@patternfly/react-tokens/dist/esm/c_menu_m_flyout__menu_left_offset';
8import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon';
9import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
10import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
11import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
12import { MenuContext, MenuItemContext } from './MenuContext';
13import { MenuItemAction } from './MenuItemAction';
14import { canUseDOM } from '../../helpers/util';
15import { useIsomorphicLayoutEffect } from '../../helpers/useIsomorphicLayout';
16const FlyoutContext = React.createContext({
17 direction: 'right'
18});
19const MenuItemBase = (_a) => {
20 var { children, className, itemId = null, to, isActive = null, isFavorited = null, isLoadButton = false, isLoading = false, flyoutMenu, direction, description = null, onClick = () => { }, component = 'button', isDisabled = false, isExternalLink = false, isSelected = null, icon, actions, onShowFlyout, drilldownMenu, isOnPath, innerRef } = _a, props = __rest(_a, ["children", "className", "itemId", "to", "isActive", "isFavorited", "isLoadButton", "isLoading", "flyoutMenu", "direction", "description", "onClick", "component", "isDisabled", "isExternalLink", "isSelected", "icon", "actions", "onShowFlyout", "drilldownMenu", "isOnPath", "innerRef"]);
21 const { menuId, parentMenu, onSelect, onActionClick, activeItemId, selected, drilldownItemPath, onDrillIn, onDrillOut, flyoutRef, setFlyoutRef, disableHover } = React.useContext(MenuContext);
22 const Component = (to ? 'a' : component);
23 const [flyoutTarget, setFlyoutTarget] = React.useState(null);
24 const flyoutContext = React.useContext(FlyoutContext);
25 const [flyoutXDirection, setFlyoutXDirection] = React.useState(flyoutContext.direction);
26 const ref = React.useRef();
27 const flyoutVisible = ref === flyoutRef;
28 const hasFlyout = flyoutMenu !== undefined;
29 const showFlyout = (show) => {
30 if (!flyoutVisible && show) {
31 setFlyoutRef(ref);
32 }
33 else if (flyoutVisible && !show) {
34 setFlyoutRef(null);
35 }
36 onShowFlyout && show && onShowFlyout();
37 };
38 useIsomorphicLayoutEffect(() => {
39 if (hasFlyout && ref.current && canUseDOM) {
40 const flyoutMenu = ref.current.lastElementChild;
41 if (flyoutMenu && flyoutMenu.classList.contains(styles.menu)) {
42 const origin = ref.current.getClientRects()[0];
43 const rect = flyoutMenu.getClientRects()[0];
44 if (origin && rect) {
45 const spaceLeftLeft = origin.x - rect.width;
46 const spaceLeftRight = window.innerWidth - origin.x - origin.width - rect.width;
47 let xDir = flyoutXDirection;
48 if (spaceLeftRight < 0 && xDir !== 'left') {
49 setFlyoutXDirection('left');
50 xDir = 'left';
51 }
52 else if (spaceLeftLeft < 0 && xDir !== 'right') {
53 setFlyoutXDirection('right');
54 xDir = 'right';
55 }
56 let xOffset = 0;
57 if (spaceLeftLeft < 0 && spaceLeftRight < 0) {
58 xOffset = xDir === 'right' ? -spaceLeftRight : -spaceLeftLeft;
59 }
60 if (xDir === 'left') {
61 flyoutMenu.classList.add(styles.modifiers.left);
62 flyoutMenu.style.setProperty(rightOffset.name, `-${xOffset}px`);
63 }
64 else {
65 flyoutMenu.style.setProperty(leftOffset.name, `-${xOffset}px`);
66 }
67 const spaceLeftBot = window.innerHeight - origin.y - rect.height;
68 const spaceLeftTop = window.innerHeight - rect.height;
69 if (spaceLeftTop < 0 && spaceLeftBot < 0) {
70 // working idea: page can usually scroll down, but not up
71 // TODO: proper scroll buttons
72 }
73 else if (spaceLeftBot < 0) {
74 flyoutMenu.style.setProperty(topOffset.name, `${spaceLeftBot}px`);
75 }
76 }
77 }
78 }
79 }, [flyoutVisible, flyoutMenu]);
80 React.useEffect(() => {
81 setFlyoutXDirection(flyoutContext.direction);
82 }, [flyoutContext]);
83 React.useEffect(() => {
84 if (flyoutTarget) {
85 if (flyoutVisible) {
86 const flyoutMenu = flyoutTarget.nextElementSibling;
87 const flyoutItems = Array.from(flyoutMenu.getElementsByTagName('UL')[0].children).filter(el => !(el.classList.contains('pf-m-disabled') || el.classList.contains('pf-c-divider')));
88 flyoutItems[0].firstChild.focus();
89 }
90 else {
91 flyoutTarget.focus();
92 }
93 }
94 }, [flyoutVisible, flyoutTarget]);
95 const handleFlyout = (event) => {
96 const key = event.key;
97 const target = event.target;
98 if (key === ' ' || key === 'Enter' || key === 'ArrowRight') {
99 event.stopPropagation();
100 if (!flyoutVisible) {
101 showFlyout(true);
102 setFlyoutTarget(target);
103 }
104 }
105 if (key === 'Escape' || key === 'ArrowLeft') {
106 if (flyoutVisible) {
107 event.stopPropagation();
108 showFlyout(false);
109 }
110 }
111 };
112 const onItemSelect = (event, onSelect) => {
113 // Trigger callback for Menu onSelect
114 onSelect && onSelect(event, itemId);
115 // Trigger callback for item onClick
116 onClick && onClick(event);
117 };
118 const _isOnPath = (isOnPath && isOnPath) || (drilldownItemPath && drilldownItemPath.includes(itemId)) || false;
119 let _drill;
120 if (direction) {
121 if (direction === 'down') {
122 _drill = () => onDrillIn &&
123 onDrillIn(menuId, typeof drilldownMenu === 'function'
124 ? drilldownMenu().props.id
125 : drilldownMenu.props.id, itemId);
126 }
127 else {
128 _drill = () => onDrillOut && onDrillOut(parentMenu, itemId);
129 }
130 }
131 let additionalProps = {};
132 if (Component === 'a') {
133 additionalProps = {
134 href: to,
135 'aria-disabled': isDisabled ? true : null,
136 // prevent invalid 'disabled' attribute on <a> tags
137 disabled: null
138 };
139 }
140 else if (Component === 'button') {
141 additionalProps = {
142 type: 'button'
143 };
144 }
145 if (isOnPath) {
146 additionalProps['aria-expanded'] = true;
147 }
148 else if (hasFlyout) {
149 additionalProps['aria-haspopup'] = true;
150 additionalProps['aria-expanded'] = flyoutVisible;
151 }
152 const getAriaCurrent = () => {
153 if (isActive !== null) {
154 if (isActive) {
155 return 'page';
156 }
157 else {
158 return null;
159 }
160 }
161 else if (itemId !== null && activeItemId !== null) {
162 return itemId === activeItemId;
163 }
164 return null;
165 };
166 const getIsSelected = () => {
167 if (isSelected !== null) {
168 return isSelected;
169 }
170 else if (selected !== null && itemId !== null) {
171 return (Array.isArray(selected) && selected.includes(itemId)) || itemId === selected;
172 }
173 return false;
174 };
175 const onMouseOver = () => {
176 if (disableHover) {
177 return;
178 }
179 if (hasFlyout) {
180 showFlyout(true);
181 }
182 else {
183 setFlyoutRef(null);
184 }
185 };
186 return (React.createElement("li", Object.assign({ className: css(styles.menuListItem, isDisabled && styles.modifiers.disabled, _isOnPath && styles.modifiers.currentPath, isLoadButton && styles.modifiers.load, isLoading && styles.modifiers.loading, className), onMouseOver: onMouseOver }, (flyoutMenu && { onKeyDown: handleFlyout }), { ref: ref, role: "none" }, props),
187 React.createElement(Component, Object.assign({ tabIndex: -1, className: css(styles.menuItem, getIsSelected() && styles.modifiers.selected, className), "aria-current": getAriaCurrent(), disabled: isDisabled, role: "menuitem", ref: innerRef, onClick: (event) => {
188 onItemSelect(event, onSelect);
189 _drill && _drill();
190 } }, additionalProps),
191 React.createElement("span", { className: css(styles.menuItemMain) },
192 direction === 'up' && (React.createElement("span", { className: css(styles.menuItemToggleIcon) },
193 React.createElement(AngleLeftIcon, { "aria-hidden": true }))),
194 icon && React.createElement("span", { className: css(styles.menuItemIcon) }, icon),
195 React.createElement("span", { className: css(styles.menuItemText) }, children),
196 isExternalLink && (React.createElement("span", { className: css(styles.menuItemExternalIcon) },
197 React.createElement(ExternalLinkAltIcon, { "aria-hidden": true }))),
198 (flyoutMenu || direction === 'down') && (React.createElement("span", { className: css(styles.menuItemToggleIcon) },
199 React.createElement(AngleRightIcon, { "aria-hidden": true }))),
200 getIsSelected() && (React.createElement("span", { className: css(styles.menuItemSelectIcon) },
201 React.createElement(CheckIcon, { "aria-hidden": true })))),
202 description && direction !== 'up' && (React.createElement("span", { className: css(styles.menuItemDescription) },
203 React.createElement("span", null, description)))),
204 flyoutVisible && (React.createElement(MenuContext.Provider, { value: { disableHover } },
205 React.createElement(FlyoutContext.Provider, { value: { direction: flyoutXDirection } }, flyoutMenu))),
206 typeof drilldownMenu === 'function' ? drilldownMenu() : drilldownMenu,
207 React.createElement(MenuItemContext.Provider, { value: { itemId, isDisabled } },
208 actions,
209 isFavorited !== null && (React.createElement(MenuItemAction, { icon: "favorites", isFavorited: isFavorited, "aria-label": isFavorited ? 'starred' : 'not starred', onClick: event => onActionClick(event, itemId), tabIndex: -1, actionId: "fav" })))));
210};
211export const MenuItem = React.forwardRef((props, ref) => (React.createElement(MenuItemBase, Object.assign({}, props, { innerRef: ref }))));
212MenuItem.displayName = 'MenuItem';
213//# sourceMappingURL=MenuItem.js.map
\No newline at end of file