diff --git a/src/docs/pages/examples/button-menu/index.tsx b/src/docs/pages/examples/button-menu/index.tsx index c8cfd3c0..375d1160 100644 --- a/src/docs/pages/examples/button-menu/index.tsx +++ b/src/docs/pages/examples/button-menu/index.tsx @@ -1,8 +1,8 @@ import { ButtonMenu, Card, useHashRef } from "ninjakit"; -import { MdMenu } from "react-icons/md"; +import { MdFavorite, MdMenu } from "react-icons/md"; import styles from "../examples.module.css"; -import { InputMenuState, options, useButtonMenuState } from "./state"; +import { InputMenuState, useButtonMenuState } from "./state"; export function ButtonMenuExample() { const hashRef = useHashRef({ id: "button-menu" }); @@ -24,10 +24,26 @@ export function ButtonMenuExample() { id="button-menu-example" label="Label" leadingIcon={leadingIcon ? : undefined} - onChange={({ currentTarget: { value } }) => - console.info("ButtonMenu change:", value) + onClick={({ currentTarget: { value } }) => + console.info("ButtonMenu click:", value) } - options={options} + options={[ + "Item One", + { + leadingIcon: , + onClick: () => console.info("Clicked: Item Two"), + value: "Item Two", + }, + "Item Three", + { separator: true }, + "Item Four", + "Item Five", + { disabled: true, value: "Item Six" }, + "Item Seven", + "Item Eight", + "Item Nine", + "Item Ten", + ]} /> diff --git a/src/docs/pages/examples/button-menu/state.tsx b/src/docs/pages/examples/button-menu/state.tsx index 5a04c8da..8f9d98e8 100644 --- a/src/docs/pages/examples/button-menu/state.tsx +++ b/src/docs/pages/examples/button-menu/state.tsx @@ -61,17 +61,3 @@ export function InputMenuState({ ); } - -export const options = [ - "Item One", - "Item Two", - "Item Three", - { separator: true }, - "Item Four", - "Item Five", - { disabled: true, value: "Item Six" }, - "Item Seven", - "Item Eight", - "Item Nine", - "Item Ten", -]; diff --git a/src/docs/pages/examples/input-menu/index.tsx b/src/docs/pages/examples/input-menu/index.tsx index 1bae7d6f..b904c1d3 100644 --- a/src/docs/pages/examples/input-menu/index.tsx +++ b/src/docs/pages/examples/input-menu/index.tsx @@ -2,7 +2,7 @@ import { Card, InputMenu, useHashRef } from "ninjakit"; import { MdFavorite } from "react-icons/md"; import styles from "../examples.module.css"; -import { InputMenuState, options, useInputMenuState } from "./state"; +import { InputMenuState, useInputMenuState } from "./state"; export function InputMenuExample() { const hashRef = useHashRef({ id: "input-menu" }); @@ -25,7 +25,19 @@ export function InputMenuExample() { onChange={({ currentTarget: { value } }) => console.info("InputMenu change:", value) } - options={options} + options={[ + "Item One", + "Item Two", + "Item Three", + { separator: true }, + "Item Four", + "Item Five", + { disabled: true, value: "Item Six" }, + "Item Seven", + { leadingIcon: , value: "Item Eight" }, + "Item Nine", + "Item Ten", + ]} /> diff --git a/src/docs/pages/examples/input-menu/state.tsx b/src/docs/pages/examples/input-menu/state.tsx index 7e6e2305..21d4f99c 100644 --- a/src/docs/pages/examples/input-menu/state.tsx +++ b/src/docs/pages/examples/input-menu/state.tsx @@ -1,6 +1,5 @@ import { Checkbox, InputMenu } from "ninjakit"; import { Dispatch, SetStateAction, useState } from "react"; -import { MdFavorite } from "react-icons/md"; type Appearance = "filled" | "outlined"; @@ -87,17 +86,3 @@ export function InputMenuState({ ); } - -export const options = [ - "Item One", - "Item Two", - "Item Three", - { separator: true }, - "Item Four", - "Item Five", - { disabled: true, value: "Item Six" }, - "Item Seven", - { leadingIcon: , value: "Item Eight" }, - "Item Nine", - "Item Ten", -]; diff --git a/src/lib/components/menu/button.tsx b/src/lib/components/menu/button.tsx index f9627cb2..6e667350 100644 --- a/src/lib/components/menu/button.tsx +++ b/src/lib/components/menu/button.tsx @@ -1,24 +1,22 @@ import { Button, MenuOptions } from "ninjakit"; -import { useRef } from "react"; import { MdArrowDropDown, MdArrowDropUp } from "react-icons/md"; import type { ButtonProps } from "../button"; import { useMenu } from "."; -import { ButtonChangeHandler, Menu } from "./menu"; +import { Menu } from "./menu"; import styles from "./menu.module.css"; export function ButtonMenu({ className: classNameOverride, container, id, - onChange, + onClick, options, ...props -}: Omit & +}: Omit & ButtonProps & { container?: HTMLElement; id: string; - onChange: ButtonChangeHandler; options: MenuOptions; }) { const { @@ -27,13 +25,12 @@ export function ButtonMenu({ handleClickControl, handleKeyDownControl, menuId, + refControl, refFieldset, refMenu, style, setExpanded, - } = useMenu({ classNameOverride, id }); - - const refControl = useRef(null); + } = useMenu({ classNameOverride, id }); return (
@@ -54,7 +51,7 @@ export function ButtonMenu({ container={container} controlElement={refControl.current} menuId={menuId} - onChange={onChange} + onClick={onClick} options={options} ref={refMenu} setExpanded={setExpanded} diff --git a/src/lib/components/menu/index.ts b/src/lib/components/menu/index.ts index 81767215..28d45115 100644 --- a/src/lib/components/menu/index.ts +++ b/src/lib/components/menu/index.ts @@ -5,7 +5,14 @@ import { useFloating, } from "@floating-ui/react-dom"; import { classNames } from "ninjakit"; -import { KeyboardEventHandler, ReactNode, useEffect, useState } from "react"; +import { + KeyboardEventHandler, + MouseEventHandler, + ReactNode, + useEffect, + useRef, + useState, +} from "react"; import { firstHTMLElementChild } from "../../util"; import styles from "./menu.module.css"; @@ -15,6 +22,7 @@ export type MenuOptions = ( | { disabled?: boolean; leadingIcon?: ReactNode; + onClick?: MouseEventHandler; separator?: boolean; value?: T; } @@ -23,18 +31,17 @@ export type MenuOptions = ( } )[]; -export function useMenu({ +export function useMenu({ classNameOverride, flex, id, - input, }: { flex?: boolean; id: string; - input?: true; classNameOverride?: string; }) { const [expanded, setExpanded] = useState(false); + const refControl = useRef(null); const { x, y, reference, floating, refs, strategy, update } = useFloating({ middleware: [flip(), shift()], placement: "bottom-start", @@ -60,10 +67,6 @@ export function useMenu({ }, [refs.reference, refs.floating, update]); const handleClickControl = () => setExpanded(!expanded); const handleKeyDownControl: KeyboardEventHandler = (event) => { - const element = input - ? event.currentTarget.parentElement?.parentElement || null - : event.currentTarget; - if (expanded) switch (event.key) { case " ": @@ -75,7 +78,6 @@ export function useMenu({ return event.preventDefault(); case "ArrowDown": case "Tab": - if (element === null) return; event.preventDefault(); return firstHTMLElementChild( document.getElementById(menuId) @@ -97,7 +99,8 @@ export function useMenu({ className: classNames({ [styles.fieldset]: true, [styles.flex]: flex, - [input ? styles.input : styles.button]: true, + [refControl instanceof HTMLInputElement ? styles.input : styles.button]: + true, classNameOverride, }), expanded, @@ -109,6 +112,7 @@ export function useMenu({ top: y ?? "", }, menuId, + refControl, refFieldset: reference, refMenu: floating, setExpanded, diff --git a/src/lib/components/menu/input.tsx b/src/lib/components/menu/input.tsx index e83f1891..9f1b9f83 100644 --- a/src/lib/components/menu/input.tsx +++ b/src/lib/components/menu/input.tsx @@ -1,5 +1,5 @@ import { MenuOptions, TextInput } from "ninjakit"; -import { forwardRef, useRef } from "react"; +import { forwardRef } from "react"; import { MdArrowDropDown, MdArrowDropUp } from "react-icons/md"; import type { InputProps } from "../input"; @@ -32,13 +32,12 @@ export const InputMenu = forwardRef< handleClickControl, handleKeyDownControl, menuId, + refControl, refFieldset, refMenu, setExpanded, style, - } = useMenu({ classNameOverride, flex, id, input: true }); - - const refControl = useRef(null); + } = useMenu({ classNameOverride, flex, id }); return (
diff --git a/src/lib/components/menu/menu.tsx b/src/lib/components/menu/menu.tsx index dcc267b1..99b9320b 100644 --- a/src/lib/components/menu/menu.tsx +++ b/src/lib/components/menu/menu.tsx @@ -19,16 +19,16 @@ export const Menu = forwardRef< container?: HTMLElement; controlElement: HTMLInputElement | HTMLButtonElement | null; menuId: string; - onChange?: ButtonChangeHandler; + onClick?: MouseEventHandler; options: MenuOptions; setExpanded: (expanded: boolean) => void; - } & Omit + } & Omit >(function Menu( { container = document.body, controlElement, menuId, - onChange, + onClick, options, setExpanded, ...props @@ -40,15 +40,15 @@ export const Menu = forwardRef< setExpanded(false); }; - const handleClickMenuItem = (value: string) => { - if (onChange) { - return onChange({ currentTarget: { value } }); - } - - if (controlElement) { - const event = new Event("change", { bubbles: true, cancelable: true }); - setNativeValue(controlElement, value); - controlElement.dispatchEvent(event); + const handleClickMenuItem: MouseEventHandler = (event) => { + if (controlElement instanceof HTMLButtonElement && onClick) onClick(event); + if (controlElement instanceof HTMLInputElement) { + const changeEvent = new Event("change", { + bubbles: true, + cancelable: true, + }); + setNativeValue(controlElement, event.currentTarget.value); + controlElement.dispatchEvent(changeEvent); } }; const handleKeyDownMenu: KeyboardEventHandler = ({ key }) => @@ -69,8 +69,6 @@ export const Menu = forwardRef< case "ArrowDown": event.preventDefault(); - console.info("ArrowDown", nextHTMLElementSibling(currentTarget)); - return nextHTMLElementSibling(currentTarget)?.focus(); case "ArrowUp": event.preventDefault(); @@ -96,7 +94,7 @@ export const Menu = forwardRef<