@paprika/dropdown-menu
Advanced tools
Comparing version 2.0.0 to 2.0.1
@@ -40,2 +40,5 @@ "use strict"; | ||
/** Callback to be executed when key is pressed */ | ||
onKeyDown: func, | ||
/** Callback to be executed when dropdown needs to be closed */ | ||
@@ -53,2 +56,3 @@ onClose: func, | ||
onClick: function onClick() {}, | ||
onKeyDown: function onKeyDown() {}, | ||
onClose: function onClose() {}, | ||
@@ -71,6 +75,7 @@ onShowConfirmation: function onShowConfirmation() {}, | ||
renderConfirmation = props.renderConfirmation, | ||
moreProps = (0, _objectWithoutProperties2["default"])(props, ["children", "isDestructive", "onClick", "onClose", "onShowConfirmation", "renderConfirmation"]); | ||
onKeyDown = props.onKeyDown, | ||
moreProps = (0, _objectWithoutProperties2["default"])(props, ["children", "isDestructive", "onClick", "onClose", "onShowConfirmation", "renderConfirmation", "onKeyDown"]); | ||
var handleClickItem = function handleClickItem() { | ||
onClick(); | ||
onClick(children); | ||
onClose(); | ||
@@ -82,5 +87,8 @@ }; | ||
role: "menuitem", | ||
"data-pka-anchor": "dropdown.item", | ||
onKeyDown: onKeyDown, | ||
isDestructive: isDestructive | ||
}; | ||
return _react["default"].createElement(_StyledRawButton, (0, _extends2["default"])({ | ||
tabIndex: 0, | ||
hasInsetFocusStyle: true | ||
@@ -87,0 +95,0 @@ }, itemProps, moreProps), children); |
@@ -25,2 +25,4 @@ "use strict"; | ||
var _extends2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/extends")); | ||
var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty")); | ||
@@ -36,2 +38,4 @@ | ||
var _useI18n = _interopRequireDefault(require("@paprika/l10n/lib/useI18n")); | ||
var _Item = _interopRequireDefault(require("../../Item.styles")); | ||
@@ -45,3 +49,4 @@ | ||
node = _propTypes["default"].node, | ||
string = _propTypes["default"].string; | ||
string = _propTypes["default"].string, | ||
func = _propTypes["default"].func; | ||
var propTypes = { | ||
@@ -54,2 +59,5 @@ /** HTML for each LinkItem */ | ||
/** Callback to be executed when key is pressed */ | ||
onKeyDown: func, | ||
/** Should the link open content in a new tab */ | ||
@@ -59,3 +67,4 @@ isExternal: bool | ||
var defaultProps = { | ||
isExternal: false | ||
isExternal: false, | ||
onKeyDown: function onKeyDown() {} | ||
}; | ||
@@ -70,9 +79,13 @@ | ||
var children = props.children, | ||
onKeyDown = props.onKeyDown, | ||
isExternal = props.isExternal, | ||
link = props.link, | ||
moreProps = (0, _objectWithoutProperties2["default"])(props, ["children", "isExternal", "link"]); | ||
moreProps = (0, _objectWithoutProperties2["default"])(props, ["children", "onKeyDown", "isExternal", "link"]); | ||
var I18n = (0, _useI18n["default"])(); | ||
var linkItemProps = _objectSpread({ | ||
role: "menuitem", | ||
href: link | ||
"data-pka-anchor": "dropdown.item", | ||
href: link, | ||
onKeyDown: onKeyDown | ||
}, moreProps); | ||
@@ -85,3 +98,7 @@ | ||
return _react["default"].createElement(_StyledA, linkItemProps, children); | ||
return _react["default"].createElement(_StyledA, (0, _extends2["default"])({ | ||
"aria-label": isExternal ? I18n.t("dropdownMenu.isExternal", { | ||
link: children | ||
}) : "" | ||
}, linkItemProps), children); | ||
}; | ||
@@ -88,0 +105,0 @@ |
@@ -80,3 +80,3 @@ "use strict"; | ||
var DropdownMenu = function DropdownMenu(props) { | ||
function DropdownMenu(props) { | ||
var align = props.align, | ||
@@ -102,2 +102,7 @@ children = props.children, | ||
var _React$useState7 = _react["default"].useState(0), | ||
_React$useState8 = (0, _slicedToArray2["default"])(_React$useState7, 2), | ||
currentFocusIndex = _React$useState8[0], | ||
setFocusIndex = _React$useState8[1]; | ||
var triggerRef = _react["default"].useRef(null); | ||
@@ -109,2 +114,9 @@ | ||
var dropdownListRef = _react["default"].useRef(null); | ||
function focusAndSetIndex(index) { | ||
if (dropdownListRef && dropdownListRef.current && index !== undefined) dropdownListRef.current.querySelectorAll('[data-pka-anchor="dropdown.item"]')[index].focus(); | ||
setFocusIndex(index); | ||
} | ||
var handleCloseMenu = function handleCloseMenu() { | ||
@@ -122,7 +134,33 @@ setIsOpen(false); | ||
var handleOpenMenu = function handleOpenMenu() { | ||
setIsOpen(true); | ||
setIsOpen(true); // https://github.com/acl-services/paprika/issues/316 | ||
// Todo Should focus the first item via an onAfterOpen event callback in popover | ||
setTimeout(function () { | ||
focusAndSetIndex(0); | ||
}, 250); | ||
}; | ||
var extractedChildren = (0, _extractChildren["default"])(children, ["DropdownMenu.Trigger"]); | ||
var dropdownLastItemIndex = _react["default"].Children.toArray(extractedChildren.children.filter(function (child) { | ||
return child.type && (child.type.displayName === "DropdownMenu.Item" || child.type.displayName === "DropdownMenu.LinkItem"); | ||
})).length - 1; | ||
var _onKeyDown = function onKeyDown(event, currentIndex) { | ||
if (event.key === "ArrowDown") { | ||
var indexToFocus = currentIndex === dropdownLastItemIndex ? 0 : currentIndex + 1; | ||
focusAndSetIndex(indexToFocus); | ||
} else if (event.key === "ArrowUp") { | ||
var _indexToFocus = currentIndex === 0 ? dropdownLastItemIndex : currentIndex - 1; | ||
focusAndSetIndex(_indexToFocus); | ||
} else if (event.key === "Home") { | ||
focusAndSetIndex(0); | ||
} else if (event.key === "End") { | ||
focusAndSetIndex(dropdownLastItemIndex); | ||
} else if (event.key === "Enter" || event.key === " ") {// do nothing | ||
} else { | ||
handleCloseMenu(); | ||
} | ||
}; | ||
var handleShowConfirmation = function handleShowConfirmation(renderConfirmation) { | ||
@@ -153,2 +191,11 @@ return function () { | ||
var getClonedChild = function getClonedChild(child, childKey) { | ||
var additionalProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
return _react["default"].cloneElement(child, _objectSpread({ | ||
onKeyDown: function onKeyDown(e) { | ||
return _onKeyDown(e, currentFocusIndex); | ||
} | ||
}, childKey, {}, additionalProps)); | ||
}; | ||
var renderContent = function renderContent() { | ||
@@ -169,20 +216,22 @@ if (isConfirming) { | ||
return _react["default"].createElement(_StyledDiv, null, extractedChildren.children.map(function (child, index) { | ||
return _react["default"].createElement(_StyledDiv, { | ||
ref: dropdownListRef | ||
}, extractedChildren.children.map(function (child, index) { | ||
var childKey = { | ||
key: "DropdownMenuItem".concat(index) | ||
}; | ||
if (child && child.type && child.type.displayName === "DropdownMenu.Item") { | ||
var childKey = { | ||
key: "DropdownMenuItem".concat(index) | ||
}; | ||
if (child.props.renderConfirmation) { | ||
return _react["default"].cloneElement(child, _objectSpread({ | ||
return getClonedChild(child, childKey, { | ||
onShowConfirmation: handleShowConfirmation(child.props.renderConfirmation) | ||
}, childKey)); | ||
}); | ||
} | ||
return _react["default"].cloneElement(child, _objectSpread({ | ||
return getClonedChild(child, childKey, { | ||
onClose: handleCloseMenu | ||
}, childKey)); | ||
}); | ||
} | ||
return child; | ||
return getClonedChild(child, childKey); | ||
})); | ||
@@ -203,3 +252,3 @@ }; | ||
}, isOpen && _react["default"].createElement(_popover["default"].Card, null, renderContent()))); | ||
}; | ||
} | ||
@@ -206,0 +255,0 @@ DropdownMenu.displayName = "DropdownMenu"; |
{ | ||
"name": "@paprika/dropdown-menu", | ||
"version": "2.0.0", | ||
"version": "2.0.1", | ||
"description": "DropdownMenu component", | ||
@@ -17,9 +17,9 @@ "author": "@paprika", | ||
"@babel/runtime-corejs2": "^7.3.1", | ||
"@paprika/button": "^0.2.4", | ||
"@paprika/confirmation": "^0.2.6", | ||
"@paprika/button": "^0.2.5", | ||
"@paprika/confirmation": "^0.2.7", | ||
"@paprika/helpers": "^0.2.5", | ||
"@paprika/popover": "^0.3.5", | ||
"@paprika/raw-button": "^0.2.2", | ||
"@paprika/stylers": "^0.2.2", | ||
"@paprika/tokens": "^0.1.9", | ||
"@paprika/popover": "^0.3.6", | ||
"@paprika/raw-button": "^0.2.3", | ||
"@paprika/stylers": "^0.2.3", | ||
"@paprika/tokens": "^0.1.10", | ||
"prop-types": "^15.7.2", | ||
@@ -34,3 +34,3 @@ "uuid": "^3.3.2" | ||
}, | ||
"gitHead": "011ea581cd21a8ae84e49197a52230b4d5131a5e" | ||
"gitHead": "02878656cf2acd9c62af07d4123dced3ab4455eb" | ||
} |
@@ -18,2 +18,5 @@ import React from "react"; | ||
/** Callback to be executed when key is pressed */ | ||
onKeyDown: func, | ||
/** Callback to be executed when dropdown needs to be closed */ | ||
@@ -32,2 +35,3 @@ onClose: func, | ||
onClick: () => {}, | ||
onKeyDown: () => {}, | ||
onClose: () => {}, | ||
@@ -39,6 +43,15 @@ onShowConfirmation: () => {}, | ||
const Item = props => { | ||
const { children, isDestructive, onClick, onClose, onShowConfirmation, renderConfirmation, ...moreProps } = props; | ||
const { | ||
children, | ||
isDestructive, | ||
onClick, | ||
onClose, | ||
onShowConfirmation, | ||
renderConfirmation, | ||
onKeyDown, | ||
...moreProps | ||
} = props; | ||
const handleClickItem = () => { | ||
onClick(); | ||
onClick(children); | ||
onClose(); | ||
@@ -50,2 +63,4 @@ }; | ||
role: "menuitem", | ||
"data-pka-anchor": "dropdown.item", | ||
onKeyDown, | ||
isDestructive, | ||
@@ -55,3 +70,3 @@ }; | ||
return ( | ||
<RawButton hasInsetFocusStyle css={ItemStyles} {...itemProps} {...moreProps}> | ||
<RawButton tabIndex={0} hasInsetFocusStyle css={ItemStyles} {...itemProps} {...moreProps}> | ||
{children} | ||
@@ -58,0 +73,0 @@ </RawButton> |
import React from "react"; | ||
import PropTypes from "prop-types"; | ||
import useI18n from "@paprika/l10n/lib/useI18n"; | ||
import linkItemStyles from "../../Item.styles"; | ||
const { bool, node, string } = PropTypes; | ||
const { bool, node, string, func } = PropTypes; | ||
@@ -12,2 +13,4 @@ const propTypes = { | ||
link: string.isRequired, | ||
/** Callback to be executed when key is pressed */ | ||
onKeyDown: func, | ||
/** Should the link open content in a new tab */ | ||
@@ -19,10 +22,14 @@ isExternal: bool, | ||
isExternal: false, | ||
onKeyDown: () => {}, | ||
}; | ||
const LinkItem = props => { | ||
const { children, isExternal, link, ...moreProps } = props; | ||
const { children, onKeyDown, isExternal, link, ...moreProps } = props; | ||
const I18n = useI18n(); | ||
const linkItemProps = { | ||
role: "menuitem", | ||
"data-pka-anchor": "dropdown.item", | ||
href: link, | ||
onKeyDown, | ||
...moreProps, | ||
@@ -37,3 +44,7 @@ }; | ||
return ( | ||
<a css={linkItemStyles} {...linkItemProps}> | ||
<a | ||
aria-label={isExternal ? I18n.t("dropdownMenu.isExternal", { link: children }) : ""} | ||
css={linkItemStyles} | ||
{...linkItemProps} | ||
> | ||
{children} | ||
@@ -40,0 +51,0 @@ </a> |
@@ -30,3 +30,3 @@ import React from "react"; | ||
const DropdownMenu = props => { | ||
function DropdownMenu(props) { | ||
const { align, children, edge, ...moreProps } = props; | ||
@@ -36,2 +36,3 @@ const [isOpen, setIsOpen] = React.useState(false); | ||
const [renderConfirmation, setRenderConfirmation] = React.useState(null); | ||
const [currentFocusIndex, setFocusIndex] = React.useState(0); | ||
const triggerRef = React.useRef(null); | ||
@@ -41,2 +42,10 @@ const menuId = React.useRef(uuid()); | ||
const dropdownListRef = React.useRef(null); | ||
function focusAndSetIndex(index) { | ||
if (dropdownListRef && dropdownListRef.current && index !== undefined) | ||
dropdownListRef.current.querySelectorAll('[data-pka-anchor="dropdown.item"]')[index].focus(); | ||
setFocusIndex(index); | ||
} | ||
const handleCloseMenu = () => { | ||
@@ -55,2 +64,7 @@ setIsOpen(false); | ||
setIsOpen(true); | ||
// https://github.com/acl-services/paprika/issues/316 | ||
// Todo Should focus the first item via an onAfterOpen event callback in popover | ||
setTimeout(() => { | ||
focusAndSetIndex(0); | ||
}, 250); | ||
}; | ||
@@ -60,2 +74,29 @@ | ||
const dropdownLastItemIndex = | ||
React.Children.toArray( | ||
extractedChildren.children.filter( | ||
child => | ||
child.type && | ||
(child.type.displayName === "DropdownMenu.Item" || child.type.displayName === "DropdownMenu.LinkItem") | ||
) | ||
).length - 1; | ||
const onKeyDown = (event, currentIndex) => { | ||
if (event.key === "ArrowDown") { | ||
const indexToFocus = currentIndex === dropdownLastItemIndex ? 0 : currentIndex + 1; | ||
focusAndSetIndex(indexToFocus); | ||
} else if (event.key === "ArrowUp") { | ||
const indexToFocus = currentIndex === 0 ? dropdownLastItemIndex : currentIndex - 1; | ||
focusAndSetIndex(indexToFocus); | ||
} else if (event.key === "Home") { | ||
focusAndSetIndex(0); | ||
} else if (event.key === "End") { | ||
focusAndSetIndex(dropdownLastItemIndex); | ||
} else if (event.key === "Enter" || event.key === " ") { | ||
// do nothing | ||
} else { | ||
handleCloseMenu(); | ||
} | ||
}; | ||
const handleShowConfirmation = renderConfirmation => () => { | ||
@@ -79,2 +120,9 @@ setIsConfirming(prevIsConfirmingState => !prevIsConfirmingState); | ||
const getClonedChild = (child, childKey, additionalProps = {}) => | ||
React.cloneElement(child, { | ||
onKeyDown: e => onKeyDown(e, currentFocusIndex), | ||
...childKey, | ||
...additionalProps, | ||
}); | ||
const renderContent = () => { | ||
@@ -94,18 +142,16 @@ if (isConfirming) { | ||
return ( | ||
<div css={contentStyles}> | ||
<div css={contentStyles} ref={dropdownListRef}> | ||
{extractedChildren.children.map((child, index) => { | ||
const childKey = { key: `DropdownMenuItem${index}` }; | ||
if (child && child.type && child.type.displayName === "DropdownMenu.Item") { | ||
const childKey = { key: `DropdownMenuItem${index}` }; | ||
if (child.props.renderConfirmation) { | ||
return React.cloneElement(child, { | ||
return getClonedChild(child, childKey, { | ||
onShowConfirmation: handleShowConfirmation(child.props.renderConfirmation), | ||
...childKey, | ||
}); | ||
} | ||
return React.cloneElement(child, { | ||
return getClonedChild(child, childKey, { | ||
onClose: handleCloseMenu, | ||
...childKey, | ||
}); | ||
} | ||
return child; | ||
return getClonedChild(child, childKey); | ||
})} | ||
@@ -133,3 +179,3 @@ </div> | ||
); | ||
}; | ||
} | ||
@@ -136,0 +182,0 @@ DropdownMenu.displayName = "DropdownMenu"; |
import React from "react"; | ||
import { Rule, Tagline } from "storybook/assets/styles/common.styles"; | ||
import { select, text } from "@storybook/addon-knobs"; | ||
import { action } from "@storybook/addon-actions"; | ||
import { AlignTypes } from "@paprika/helpers/lib/customPropTypes"; | ||
@@ -33,2 +34,6 @@ import L10n from "@paprika/l10n"; | ||
const handleItemClick = val => { | ||
action("Clicked a item")(val); | ||
}; | ||
const ExampleStory = () => ( | ||
@@ -48,11 +53,11 @@ <DropdownMenuStory> | ||
<DropdownMenu.Trigger>{dropdownComponentProps().triggerContent}</DropdownMenu.Trigger> | ||
<DropdownMenu.Item onClick={() => {}}>Edit</DropdownMenu.Item> | ||
<DropdownMenu.Item onClick={() => {}}>Duplicate</DropdownMenu.Item> | ||
<DropdownMenu.Item isDestructive isDisabled onClick={() => {}}> | ||
<DropdownMenu.Item onClick={handleItemClick}>Edit</DropdownMenu.Item> | ||
<DropdownMenu.Item onClick={handleItemClick}>Duplicate</DropdownMenu.Item> | ||
<DropdownMenu.Item isDestructive isDisabled onClick={handleItemClick}> | ||
Galvanize item | ||
</DropdownMenu.Item> | ||
<DropdownMenu.Item isDisabled onClick={() => {}}> | ||
<DropdownMenu.Item isDisabled onClick={handleItemClick}> | ||
Galvanize item | ||
</DropdownMenu.Item> | ||
<DropdownMenu.LinkItem link="http://www.wegalvanize.com"> | ||
<DropdownMenu.LinkItem isExternal link="http://www.wegalvanize.com"> | ||
{dropdownComponentProps().itemContent} | ||
@@ -63,3 +68,3 @@ </DropdownMenu.LinkItem> | ||
</DropdownMenu.LinkItem> | ||
<DropdownMenu.Item isDisabled onClick={() => {}}> | ||
<DropdownMenu.Item isDisabled onClick={handleItemClick}> | ||
Galvanize | ||
@@ -66,0 +71,0 @@ </DropdownMenu.Item> |
@@ -9,3 +9,3 @@ describe("<DropdownMenu />", () => { | ||
cy.getByTestId("popover.content").should("be.visible"); | ||
cy.wait(100).then(() => { | ||
cy.wait(250).then(() => { | ||
cy.getByTestId("dropdown-menu__trigger").click(); | ||
@@ -18,3 +18,3 @@ cy.getByTestId("popover.content").should("not.be.visible"); | ||
cy.getByTestId("dropdown-menu__trigger").click(); | ||
cy.wait(100).then(() => { | ||
cy.wait(250).then(() => { | ||
cy.getByTestId("dropdown-menu__trigger").click(); | ||
@@ -21,0 +21,0 @@ cy.getByTestId("dropdown-menu__trigger").then(triggerElement => { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
141202
82
1917
Updated@paprika/button@^0.2.5
Updated@paprika/confirmation@^0.2.7
Updated@paprika/popover@^0.3.6
Updated@paprika/raw-button@^0.2.3
Updated@paprika/stylers@^0.2.3
Updated@paprika/tokens@^0.1.10