diff --git a/.changeset/dry-years-sleep.md b/.changeset/dry-years-sleep.md new file mode 100644 index 00000000..3aaa7936 --- /dev/null +++ b/.changeset/dry-years-sleep.md @@ -0,0 +1,5 @@ +--- +"@heathmont/moon-core-tw": minor +--- + +fix: open options list on input focus [MDS-1378] diff --git a/docs/app/client/icons/icons/IconWrapper.tsx b/docs/app/client/icons/icons/IconWrapper.tsx index 001d5e77..69bf05ac 100644 --- a/docs/app/client/icons/icons/IconWrapper.tsx +++ b/docs/app/client/icons/icons/IconWrapper.tsx @@ -62,9 +62,7 @@ const IconWrapper = ({ children, name }: IconProps) => { > - Icon {renderIcon( - name, - )} copied for import + Icon {renderIcon(name)} copied for import diff --git a/docs/app/client/icons/search/IconSearch.tsx b/docs/app/client/icons/search/IconSearch.tsx index f34f6ec5..aeb60713 100644 --- a/docs/app/client/icons/search/IconSearch.tsx +++ b/docs/app/client/icons/search/IconSearch.tsx @@ -113,9 +113,7 @@ const IconSearch = () => { > - Icon {renderIcon( - lastClickedIcon, - )} copied for import + Icon {renderIcon(lastClickedIcon)} copied for import diff --git a/packages/core/src/combobox/Combobox.tsx b/packages/core/src/combobox/Combobox.tsx index 5d8e1062..a0a0df8a 100644 --- a/packages/core/src/combobox/Combobox.tsx +++ b/packages/core/src/combobox/Combobox.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useState } from "react"; +import React, { forwardRef, useEffect } from "react"; import { Combobox as HeadlessCombobox, Transition as HeadlessTransition, @@ -52,19 +52,50 @@ const ComboboxRoot = ({ placement: position, }); + const comboboxButtonRef = React.useRef(null); + const blurredRef = React.useRef(false); + const prevSelected = React.useRef({}); + + const handleOnFocus: React.FocusEventHandler = (event) => { + setIsInputFocused(true); + + if (event.relatedTarget?.id?.includes("headlessui-combobox-button")) { + return; + } + + comboboxButtonRef?.current?.click(); + }; + + const handleOnKeyDown: React.KeyboardEventHandler = ( + event, + ) => { + if (event.key === "Tab") { + blurredRef.current = true; + prevSelected.current = value; + console.log("in here oe key down tab"); + } + }; + + const handleOnBlur: React.FocusEventHandler = () => { + setIsInputFocused(false); + if (blurredRef.current) { + onChange(prevSelected.current); + } + }; + const states = { - value: value, - displayValue: displayValue, - isError: isError, - size: size, - disabled: disabled, + value, + displayValue, + isError, + size, + disabled, input: { isFocused: isInputFocused, setIsFocused: setIsInputFocused, }, - multiple: multiple, - onClear: onClear, - onQueryChange: onQueryChange, + multiple, + onClear, + onQueryChange, popper: { forceUpdate, styles, @@ -72,6 +103,10 @@ const ComboboxRoot = ({ setAnchor: setAnchorEl, setPopper: setPopperEl, }, + comboboxButtonRef, + handleOnFocus, + handleOnBlur, + handleOnKeyDown, }; const childArray = @@ -152,8 +187,17 @@ const Input = ({ label, ...rest }: InputProps) => { - const { size, popper, disabled, isError, input, onQueryChange } = - useComboboxContext("Combobox.Input"); + const { + size, + popper, + disabled, + isError, + onQueryChange, + handleOnFocus, + handleOnKeyDown, + handleOnBlur, + } = useComboboxContext("Combobox.Input"); + return ( { @@ -171,8 +215,9 @@ const Input = ({ )} disabled={disabled} error={isError} - onFocus={() => input?.setIsFocused(true)} - onBlur={() => input?.setIsFocused(false)} + onFocus={handleOnFocus} + onKeyDown={handleOnKeyDown} + onBlur={handleOnBlur} aria-label={rest["aria-label"]} {...rest} ref={popper?.setAnchor} @@ -188,8 +233,16 @@ const InsetInput = ({ label, ...rest }: InputProps) => { - const { size, popper, disabled, isError, input, onQueryChange } = - useComboboxContext("Combobox.InsetInput"); + const { + size, + popper, + disabled, + isError, + onQueryChange, + handleOnFocus, + handleOnKeyDown, + handleOnBlur, + } = useComboboxContext("Combobox.InsetInput"); return ( input?.setIsFocused(true)} - onBlur={() => input?.setIsFocused(false)} + onFocus={handleOnFocus} + onKeyDown={handleOnKeyDown} + onBlur={handleOnBlur} aria-label={rest["aria-label"]} {...rest} ref={popper?.setAnchor} @@ -235,8 +289,17 @@ const VisualSelectInput = ({ label, ...rest }: InputProps) => { - const { value, size, popper, disabled, isError, onQueryChange } = - useComboboxContext("Combobox.VisualSelectInput"); + const { + value, + size, + popper, + disabled, + isError, + onQueryChange, + handleOnFocus, + handleOnKeyDown, + handleOnBlur, + } = useComboboxContext("Combobox.VisualSelectInput"); const selected = value as []; return ( @@ -276,6 +339,9 @@ const VisualSelectInput = ({ aria-label={rest["aria-label"]} {...rest} ref={popper?.setAnchor} + onFocus={handleOnFocus} + onKeyDown={handleOnKeyDown} + onBlur={handleOnBlur} /> ); @@ -289,8 +355,10 @@ const Button = ({ ["aria-label"]: ariaLabel, ...rest }: WithChildren) => { - const { size, disabled } = useComboboxContext("Combobox.Button"); + const { size, disabled, comboboxButtonRef, input } = + useComboboxContext("Combobox.Button"); const ariaLabelValue = ariaLabel ? ariaLabel : open ? "Close" : "Open"; + return ( @@ -313,14 +382,16 @@ const Options = ({ children, menuWidth, className, + open, ...rest }: WithChildren) => { const { popper } = useComboboxContext("Combobox.Options"); - return ( + const OptionsComponent = ( ); + + if (open === undefined) { + return OptionsComponent; + } + + return open && OptionsComponent; }; const Option = ({ children, value }: OptionProps) => { diff --git a/packages/core/src/combobox/private/types/ComboboxState.ts b/packages/core/src/combobox/private/types/ComboboxState.ts index 20c64283..303bccba 100644 --- a/packages/core/src/combobox/private/types/ComboboxState.ts +++ b/packages/core/src/combobox/private/types/ComboboxState.ts @@ -23,6 +23,10 @@ type ComboboxState = { >; }; size?: Size; + comboboxButtonRef?: React.MutableRefObject; + handleOnFocus?: React.FocusEventHandler; + handleOnBlur?: React.FocusEventHandler; + handleOnKeyDown?: React.KeyboardEventHandler; }; export default ComboboxState; diff --git a/packages/core/src/combobox/private/types/OptionsProps.ts b/packages/core/src/combobox/private/types/OptionsProps.ts index 746cc694..5e941050 100644 --- a/packages/core/src/combobox/private/types/OptionsProps.ts +++ b/packages/core/src/combobox/private/types/OptionsProps.ts @@ -1,6 +1,7 @@ type OptionsProps = { menuWidth?: string; className?: string; + open?: boolean; }; export default OptionsProps; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0caf4239..215f95e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1952,7 +1952,7 @@ packages: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} engines: {node: '>=10'} dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false /aria-query@5.3.0: @@ -5370,7 +5370,7 @@ packages: '@types/react': 18.2.37 react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) - tslib: 2.6.2 + tslib: 2.7.0 dev: false /react-remove-scroll@2.5.4(@types/react@18.2.37)(react@18.2.0): @@ -5387,7 +5387,7 @@ packages: react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.2.37)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) - tslib: 2.6.2 + tslib: 2.7.0 use-callback-ref: 1.3.0(@types/react@18.2.37)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0) dev: false @@ -5406,7 +5406,7 @@ packages: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /react-syntax-highlighter@15.5.0(react@18.2.0): @@ -6198,7 +6198,6 @@ packages: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} requiresBuild: true dev: false - optional: true /tty-table@4.2.3: resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} @@ -6400,7 +6399,7 @@ packages: dependencies: '@types/react': 18.2.37 react: 18.2.0 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /use-sidecar@1.1.2(@types/react@18.2.37)(react@18.2.0): @@ -6416,7 +6415,7 @@ packages: '@types/react': 18.2.37 detect-node-es: 1.1.0 react: 18.2.0 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /util-deprecate@1.0.2: