@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
191 lines (190 loc) • 9.09 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Submenu = exports.SubmenuTargetItem = exports.useSubmenuTargetItem = exports.SubmenuPopper = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = __importDefault(require("react"));
const canvas_system_icons_web_1 = require("@workday/canvas-system-icons-web");
const common_1 = require("@workday/canvas-kit-react/common");
const useMenuModel_1 = require("./useMenuModel");
const MenuPopper_1 = require("./MenuPopper");
const MenuItem_1 = require("./MenuItem");
const MenuCard_1 = require("./MenuCard");
const MenuList_1 = require("./MenuList");
const MenuDivider_1 = require("./MenuDivider");
const MenuOption_1 = require("./MenuOption");
const MenuGroup_1 = require("./MenuGroup");
const collection_1 = require("@workday/canvas-kit-react/collection");
const popup_1 = require("@workday/canvas-kit-react/popup");
exports.SubmenuPopper = (0, common_1.createSubcomponent)('div')({
modelHook: useMenuModel_1.useMenuModel,
elemPropsHook: MenuPopper_1.useMenuPopper,
})(({ children, ...elemProps }) => {
return ((0, jsx_runtime_1.jsx)(popup_1.Popper, { placement: "right-start", popperOptions: MenuPopper_1.defaultMenuPopperOptions, ...elemProps, children: children }));
});
const useIntentTimer = (fn, waitMs = 0) => {
const timer = react_1.default.useRef();
const start = () => {
timer.current = window.setTimeout(fn, waitMs);
};
const clear = () => {
window.clearTimeout(timer.current);
timer.current = undefined;
};
// be sure to clear our timeout
react_1.default.useEffect(() => {
return () => {
window.clearTimeout(timer.current);
};
}, [timer]);
return {
start,
clear,
};
};
exports.useSubmenuTargetItem = (0, common_1.composeHooks)((0, common_1.subModelHook)(model => model.UNSTABLE_parentModel, MenuItem_1.useMenuItemFocus), (0, common_1.subModelHook)(model => model.UNSTABLE_parentModel, MenuItem_1.useMenuItemArrowReturn), (0, common_1.subModelHook)(model => model.UNSTABLE_parentModel, collection_1.useListItemRovingFocus), (0, common_1.createElemPropsHook)(useMenuModel_1.useMenuModel)((model, ref) => {
const elementRef = (0, common_1.useForkRef)(ref, model.state.targetRef);
return {
ref: elementRef,
};
}), (0, common_1.subModelHook)(model => model.UNSTABLE_parentModel, collection_1.useListItemRegister), (0, common_1.createElemPropsHook)(useMenuModel_1.useMenuModel)(model => {
const currentTargetIdRef = react_1.default.useRef();
const mouseEnterTimer = useIntentTimer(() => {
model.UNSTABLE_parentModel.events.goTo({ id: currentTargetIdRef.current || '' });
model.events.show(event);
}, 300);
return {
id: model.state.id,
role: 'menuitem',
'aria-haspopup': 'true',
'aria-expanded': model.state.visibility === 'visible',
onMouseDown(event) {
model.UNSTABLE_parentModel.events.goTo({ id: event.currentTarget.getAttribute('data-id') });
},
onMouseEnter(event) {
currentTargetIdRef.current = event.currentTarget.getAttribute('data-id');
mouseEnterTimer.start();
},
onMouseLeave() {
mouseEnterTimer.clear();
},
onClick(event) {
// If we're wrapping a target component that doesn't handle ref forwarding, update the
// `state.targetRef` manually. This ensures that custom target components don't need to handle
// ref forwarding since ref forwarding is only really needed to programmatically open popups
// around a target _before_ a user clicks. In that rare case, ref forwarding is required.
if (!(model.state.targetRef.current instanceof Element)) {
model.state.targetRef.current = event.currentTarget;
}
if (model.state.visibility !== 'hidden') {
model.events.hide(event);
}
else {
model.events.show(event);
}
},
'data-has-children': true,
onKeyDown(event) {
if (model.state.orientation === 'vertical') {
// eslint-disable-next-line default-case
switch (event.key) {
case 'ArrowRight':
case 'Enter':
case ' ':
model.events.show(event);
break;
}
}
},
};
}));
exports.SubmenuTargetItem = (0, common_1.createSubcomponent)('button')({
modelHook: useMenuModel_1.useMenuModel,
elemPropsHook: exports.useSubmenuTargetItem,
})(({ children, ...elemProps }, Element) => {
return ((0, jsx_runtime_1.jsxs)(MenuItem_1.StyledMenuItem, { as: Element, ...elemProps, children: [typeof children === 'string' ? (0, jsx_runtime_1.jsx)(MenuItem_1.MenuItem.Text, { children: children }) : children, (0, jsx_runtime_1.jsx)(MenuItem_1.MenuItem.Icon, { icon: canvas_system_icons_web_1.chevronRightSmallIcon })] }));
});
/**
* `Submenu` should be put in place of a `Menu.Item`. It will render a menu item that is the target
* for the submenu card.
*
* ```tsx
* <Menu.Item>First Item</Menu.Item>
* <Menu.Submenu>
* <Menu.Submenu.TargetItem>Second Item</Menu.Submenu.TargetItem>
* <Menu.Submenu.Popper>
* <Menu.Submenu.Card>
* <Menu.Submenu.List>
* <Menu.Submenu.Item data-id="first">First Sub Item</Menu.Submenu.Item>
* <Menu.Submenu.Item data-id="second">Second Sub Item</Menu.Submenu.Item>
* </Menu.Submenu.List>
* </Menu.Submenu.Card>
* </Menu.Submenu.Popper>
* </Menu.Submenu>
* </Menu.Item>Third Item</Menu.Item>
* ```
*/
exports.Submenu = (0, common_1.createSubcomponent)()({
modelHook: useMenuModel_1.useMenuModel,
subComponents: {
/**
* The menu card is a non-semantic element used to give the dropdown menu its distinct visual
* cue that the dropdown menu is floating above other content. A menu card usually contains a
* menu list, but can also contain other elements like a header or footer.
*/
Card: MenuCard_1.MenuCard,
/**
* The menu list follows the Collections API. A list can either contain static items
* or a render prop and `items`. It is recommended that the `items` comes from a nested
* JavaScript object.
*/
List: MenuList_1.MenuList,
/**
* If the static API is used, a `data-id` prop should be used to identify the item. If you're
* using the dynamic API, pass a `getId` and `getTextValue` to the parent `Menu` the model.
*/
Item: MenuItem_1.MenuItem,
/**
* The `Submenu.TargetItem` is similar to the `Menu.Item`, but represents both the target for
* the submenu and the item in the menu list. This should only be used once per `<Menu.Submenu>`
* component.
*/
TargetItem: exports.SubmenuTargetItem,
Group: MenuGroup_1.MenuGroup,
/**
* A `Menu.Option` is similar to the `Menu.Item`, but has a `role=option` and works with
* `aria-activedescendant` and is selectable with a selected checkmark. It adds the
* `aria-selected="true/false"` attribute. `Menu.Option` requires much more accessibility
* behavior composed into the `Menu.Target` and `Menu.List` component. The `Combobox` and
* `Select` components make use of the `Menu.Option`. See those components for a better idea of
* how behavior is composed.
*/
Option: MenuOption_1.MenuOption,
Divider: MenuDivider_1.MenuDivider,
/**
* The "Popper" of a menu. The popper will appear around the {@link MenuTarget Menu.Target}. It
* renders a `div` element that is portalled to the `document.body` which is controlled by the
* {@link PopupStack}. The `PopupStack` is not part of React. This means no extra props given to
* this component will be forwarded to the `div` element, but the `ref` will be forwarded.
*/
Popper: exports.SubmenuPopper,
},
})(({ children, model: _model, ...props }, _Element, parentModel) => {
const model = (0, useMenuModel_1.useMenuModel)(useMenuModel_1.useMenuModel.mergeConfig(props, {
getId: parentModel.getId,
getTextValue: parentModel.getTextValue,
UNSTABLE_parentModel: parentModel,
mode: parentModel.state.mode,
orientation: parentModel.state.orientation,
onSelect(data) {
parentModel.events.select(data);
},
onSelectAll() {
parentModel.events.selectAll();
},
}));
const Context = useMenuModel_1.useMenuModel.Context;
return (0, jsx_runtime_1.jsx)(Context.Provider, { value: model, children: children });
});