diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index eb717d8e2b..9b571e8e9c 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -21,7 +21,8 @@ export function useDialogControl(): DialogOuterProps['control'] { open: () => {}, close: () => {}, }) - const {activeDialogs} = useDialogStateContext() + const {activeDialogs, openDialogs} = useDialogStateContext() + const isOpen = openDialogs.includes(id) React.useEffect(() => { activeDialogs.current.set(id, control) @@ -31,14 +32,18 @@ export function useDialogControl(): DialogOuterProps['control'] { } }, [id, activeDialogs]) - return { - id, - ref: control, - open: () => { - control.current.open() - }, - close: cb => { - control.current.close(cb) - }, - } + return React.useMemo( + () => ({ + id, + ref: control, + isOpen, + open: () => { + control.current.open() + }, + close: cb => { + control.current.close(cb) + }, + }), + [id, control, isOpen], + ) } diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index 78dfedf5a8..fa9398fe05 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -22,6 +22,7 @@ export type DialogControlRefProps = { export type DialogControlProps = DialogControlRefProps & { id: string ref: React.RefObject + isOpen: boolean } export type DialogContextProps = { diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 2d0f0addee..a3aff73c16 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -18,6 +18,10 @@ import { export {useDialogControl as useMenuControl} from '#/components/Dialog' +export function useMemoControlContext() { + return React.useContext(Context) +} + export function Root({ children, control, @@ -157,7 +161,7 @@ export function Group({children, style}: GroupProps) { {React.Children.toArray(children).map((child, i) => { // ignore null children, like Dividers return React.isValidElement(child) && child.props.children ? ( - + {i > 0 ? ( ) : null} diff --git a/src/components/Menu/index.web.tsx b/src/components/Menu/index.web.tsx index dadd890eab..927ca8f4f2 100644 --- a/src/components/Menu/index.web.tsx +++ b/src/components/Menu/index.web.tsx @@ -18,34 +18,60 @@ import { import {Context} from '#/components/Menu/context' export function useMenuControl(): Dialog.DialogControlProps { - return { - id: '', - // @ts-ignore - ref: null, - open: () => { - throw new Error(`Menu controls are only available on native platforms`) - }, - close: () => { - throw new Error(`Menu controls are only available on native platforms`) - }, - } + const id = React.useId() + const [isOpen, setIsOpen] = React.useState(false) + + return React.useMemo( + () => ({ + id, + ref: {current: null}, + isOpen, + open() { + setIsOpen(true) + }, + close() { + setIsOpen(false) + }, + }), + [id, isOpen, setIsOpen], + ) +} + +export function useMemoControlContext() { + return React.useContext(Context) } export function Root({ children, + control, }: React.PropsWithChildren<{ control?: Dialog.DialogOuterProps['control'] }>) { + const defaultControl = useMenuControl() const context = React.useMemo( () => ({ - control: null, + control: control || defaultControl, }), - [], + [control, defaultControl], + ) + const onOpenChange = React.useCallback( + (open: boolean) => { + if (context.control.isOpen && !open) { + context.control.close() + } else if (!context.control.isOpen && open) { + context.control.open() + } + }, + [context.control], ) return ( - {children} + + {children} + ) } @@ -56,6 +82,7 @@ export function Trigger({ }: ViewStyleProp & { children(props: TriggerChildProps): React.ReactNode }) { + const {control} = React.useContext(Context) const { state: hovered, onIn: onMouseEnter, @@ -69,13 +96,16 @@ export function Trigger({ onFocus={onFocus} onBlur={onBlur} style={flatten([style, web({outline: 0})])} + onPointerDown={() => { + control.open() + }} {...web({ onMouseEnter, onMouseLeave, })}> {children({ isNative: false, - control: null, + control, state: { hovered, focused, @@ -94,7 +124,7 @@ export function Outer({children}: React.PropsWithChildren<{}>) { return ( - + {children} @@ -122,6 +152,7 @@ export function Item({children, label, onPress}: ItemProps) { className="radix-dropdown-item" accessibilityHint="" accessibilityLabel={label} + onPress={onPress} onFocus={onFocus} onBlur={onBlur} style={flatten([ @@ -129,7 +160,7 @@ export function Item({children, label, onPress}: ItemProps) { a.align_center, a.gap_sm, a.py_sm, - a.px_md, + a.px_sm, a.rounded_xs, {minHeight: 36}, web({outline: 0}), @@ -181,12 +212,13 @@ export function Divider() { const t = useTheme() return ( ) } diff --git a/src/components/Menu/types.ts b/src/components/Menu/types.ts index 9b38f1d7ad..c233120ed7 100644 --- a/src/components/Menu/types.ts +++ b/src/components/Menu/types.ts @@ -5,7 +5,7 @@ import * as Dialog from '#/components/Dialog' import {TextStyleProp, ViewStyleProp} from '#/alf' export type ContextType = { - control: Dialog.DialogOuterProps['control'] | null + control: Dialog.DialogOuterProps['control'] } export type TriggerChildProps = @@ -30,7 +30,7 @@ export type TriggerChildProps = } | { isNative: false - control: null + control: Dialog.DialogOuterProps['control'] state: { hovered: boolean focused: boolean diff --git a/src/view/screens/Storybook/Menus.tsx b/src/view/screens/Storybook/Menus.tsx index 12efccdf1a..d3ebcf4edb 100644 --- a/src/view/screens/Storybook/Menus.tsx +++ b/src/view/screens/Storybook/Menus.tsx @@ -1,22 +1,38 @@ import React from 'react' import {View} from 'react-native' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' import * as Menu from '#/components/Menu' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' // import {useDialogStateControlContext} from '#/state/dialogs' export function Menus() { + const t = useTheme() + const menuControl = Menu.useMenuControl() // const {closeAllDialogs} = useDialogStateControlContext() return ( - + - {({handlers}) => { - return Open + {({state, handlers}) => { + return ( + + Open + + ) }} @@ -29,7 +45,9 @@ export function Menus() { - {}}> + menuControl.close()}> Another item