import * as React from "react"; import { css, cx } from "emotion"; import { MenuHTMLProps, MenuOptions } from "reakit/Menu/Menu"; import { MenuButtonHTMLProps, MenuButtonOptions } from "reakit/Menu/MenuButton"; import { MenuItemCheckboxHTMLProps, MenuItemCheckboxOptions, } from "reakit/Menu/MenuItemCheckbox"; import { MenuItemRadioHTMLProps, MenuItemRadioOptions, } from "reakit/Menu/MenuItemRadio"; import { MenuGroupHTMLProps, MenuGroupOptions } from "reakit/Menu/MenuGroup"; import { MenuBarHTMLProps, MenuBarOptions } from "reakit/Menu/MenuBar"; import { MenuItemHTMLProps, MenuItemOptions } from "reakit/Menu/MenuItem"; import { useContrast } from "reakit-system-palette/utils/contrast"; import { useDarken } from "reakit-system-palette/utils/darken"; import { usePalette } from "reakit-system-palette/utils/palette"; import { MenuStateReturn } from "reakit/Menu/MenuState"; import { BootstrapRoleOptions } from "./Role"; export type BootstrapMenuBarOptions = BootstrapRoleOptions & MenuBarOptions; export function useMenuBarProps( options: BootstrapMenuBarOptions, htmlProps: MenuBarHTMLProps = {} ): MenuBarHTMLProps { const isHorizontal = options.orientation === "horizontal"; const menuBar = css` position: relative; display: flex; flex-direction: ${isHorizontal ? "row" : "column"}; white-space: nowrap; box-shadow: none !important; &[aria-orientation="vertical"] { padding: 0.25em 0; } &[aria-orientation="horizontal"] { padding: 0; } `; return { ...htmlProps, className: cx(menuBar, htmlProps.className) }; } export type BootstrapMenuItemOptions = BootstrapRoleOptions & MenuItemOptions; export function useMenuItemProps( { unstable_system, ...options }: BootstrapMenuItemOptions, htmlProps: MenuItemHTMLProps = {} ): MenuItemHTMLProps { const foreground = usePalette("foreground") || "black"; const primary = usePalette("primary") || "blue"; const primaryContrast = useContrast(primary); const darkPrimary = useDarken(primary, 0.2); const isHorizontal = options.orientation === "horizontal"; const menuItem = css` &&& { line-height: 1.5; padding: 0 ${isHorizontal ? "0.5em" : "1.5em"}; text-align: left; justify-content: flex-start; border: 0; border-radius: 0; font-size: 100%; background: transparent; color: ${foreground}; margin: 0; user-select: none; cursor: default; text-decoration: none; &:focus, &[aria-expanded="true"] { background-color: ${primary}; color: ${primaryContrast}; box-shadow: none !important; } &:active, &[data-active] { background-color: ${darkPrimary} !important; } } `; return { ...htmlProps, className: cx(menuItem, htmlProps.className) }; } export type BootstrapMenuOptions = BootstrapRoleOptions & MenuOptions; const OrientationContext = React.createContext< "horizontal" | "vertical" | undefined >(undefined); export function useMenuOptions({ unstable_system: { palette = "background", fill = "opaque", ...system } = {}, ...options }: BootstrapMenuOptions): BootstrapMenuOptions { const parentOrientation = React.useContext(OrientationContext); const unstable_system = { palette, fill, ...system }; const transform = options.unstable_popoverStyles?.transform || ""; if (parentOrientation === "vertical" && options.orientation === "vertical") { return { ...options, unstable_system, unstable_popoverStyles: { ...options.unstable_popoverStyles, transform: `${transform} translate3d(0px, -0.3em, 0px)`, }, }; } return { ...options, unstable_system }; } export function useMenuProps( options: BootstrapMenuOptions, { wrapElement: htmlWrapElement, ...htmlProps }: MenuHTMLProps = {} ): MenuHTMLProps { const menu = css` display: flex; border-radius: 0; `; const wrapElement = React.useCallback( (element: React.ReactNode) => { if (htmlWrapElement) { element = htmlWrapElement(element); } return ( {element} ); }, [htmlWrapElement, options.orientation] ); return { ...htmlProps, wrapElement, className: cx(menu, htmlProps.className), }; } export type BootstrapMenuButtonOptions = BootstrapRoleOptions & MenuButtonOptions & Pick, "unstable_originalPlacement">; export function useMenuButtonProps( options: BootstrapMenuButtonOptions, { children, ...htmlProps }: MenuButtonHTMLProps = {} ): MenuButtonHTMLProps { const placement = options.unstable_originalPlacement || options.placement; const dir = placement ? (placement.split("-")[0] as "top" | "bottom" | "right" | "left") : undefined; const svg = dir ? { top: ( ), bottom: ( ), right: ( ), left: ( ), }[dir] : null; const menuButton = css` position: relative; [role="menu"] > & { ${children && dir !== "left" && css` padding-right: 2em !important; `} } svg { fill: currentColor; width: 0.65em; height: 0.65em; [role="menu"] > & { position: absolute; top: 50%; transform: translateY(-50%); ${dir === "left" ? "left" : "right"}: 0.5em; } [role="menubar"] > & { display: none; } ${children && css` margin-${dir === "left" ? "right" : "left"}: 0.5em; `} } `; return { ...htmlProps, children: typeof children === "function" ? ( (props: any) => { const child = children(props); return React.cloneElement(child, { children: ( <> {child.props.children} {svg} ), }); } ) : ( <> {children} {svg} ), className: cx(menuButton, htmlProps.className), }; } export type BootstrapMenuItemCheckboxOptions = BootstrapRoleOptions & MenuItemCheckboxOptions; export function useMenuItemCheckboxProps( _: BootstrapMenuItemCheckboxOptions, htmlProps: MenuItemCheckboxHTMLProps = {} ): MenuItemCheckboxHTMLProps { const menuItemCheckbox = css` position: relative; outline: 0; &[aria-checked="true"] { &:before { content: "✓"; position: absolute; top: 0; left: 0.4em; width: 1em; height: 1em; } } `; return { ...htmlProps, className: cx(menuItemCheckbox, htmlProps.className) }; } export type BootstrapMenuItemRadioOptions = BootstrapRoleOptions & MenuItemRadioOptions; export function useMenuItemRadioProps( _: BootstrapMenuItemRadioOptions, htmlProps: MenuItemRadioHTMLProps = {} ): MenuItemRadioHTMLProps { const menuItemRadio = css` position: relative; outline: 0; &[aria-checked="true"] { &:before { content: "•"; position: absolute; font-size: 1.4em; top: -0.25em; left: 0.35em; width: 0.7142857143em; height: 0.7142857143em; } } `; return { ...htmlProps, className: cx(menuItemRadio, htmlProps.className) }; } export type BootstrapMenuGroupOptions = BootstrapRoleOptions & MenuGroupOptions; export function useMenuGroupProps( _: BootstrapMenuGroupOptions, htmlProps: MenuGroupHTMLProps = {} ): MenuGroupHTMLProps { const menuGroup = css` display: inherit; flex-direction: inherit; `; return { ...htmlProps, className: cx(menuGroup, htmlProps.className) }; }