UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

191 lines (190 loc) • 9.09 kB
"use strict"; 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 }); });