@cdx-ui/primitives 0.0.1-beta.159 → 0.0.1-beta.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/dialog/DialogContent.js +17 -3
- package/lib/commonjs/dialog/DialogContent.js.map +1 -1
- package/lib/commonjs/dialog/DialogPopup.js +5 -1
- package/lib/commonjs/dialog/DialogPopup.js.map +1 -1
- package/lib/commonjs/index.js +54 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/menu/MenuCheckboxItem.js +42 -0
- package/lib/commonjs/menu/MenuCheckboxItem.js.map +1 -0
- package/lib/commonjs/menu/MenuItem.js +142 -0
- package/lib/commonjs/menu/MenuItem.js.map +1 -0
- package/lib/commonjs/menu/MenuOverlayRoot.js +80 -0
- package/lib/commonjs/menu/MenuOverlayRoot.js.map +1 -0
- package/lib/commonjs/menu/MenuPopup.js +78 -0
- package/lib/commonjs/menu/MenuPopup.js.map +1 -0
- package/lib/commonjs/menu/MenuRadioGroup.js +48 -0
- package/lib/commonjs/menu/MenuRadioGroup.js.map +1 -0
- package/lib/commonjs/menu/MenuRadioItem.js +32 -0
- package/lib/commonjs/menu/MenuRadioItem.js.map +1 -0
- package/lib/commonjs/menu/MenuRoot.js +139 -0
- package/lib/commonjs/menu/MenuRoot.js.map +1 -0
- package/lib/commonjs/menu/MenuTrigger.js +59 -0
- package/lib/commonjs/menu/MenuTrigger.js.map +1 -0
- package/lib/commonjs/menu/context.js +36 -0
- package/lib/commonjs/menu/context.js.map +1 -0
- package/lib/commonjs/menu/index.js +118 -0
- package/lib/commonjs/menu/index.js.map +1 -0
- package/lib/commonjs/menu/overlayContext.js +30 -0
- package/lib/commonjs/menu/overlayContext.js.map +1 -0
- package/lib/commonjs/menu/types.js +6 -0
- package/lib/commonjs/menu/types.js.map +1 -0
- package/lib/commonjs/menu/useMenuNavigation.js +87 -0
- package/lib/commonjs/menu/useMenuNavigation.js.map +1 -0
- package/lib/commonjs/menu/useMenuOverlayLifecycle.js +68 -0
- package/lib/commonjs/menu/useMenuOverlayLifecycle.js.map +1 -0
- package/lib/commonjs/menu/useMenuTrigger.js +115 -0
- package/lib/commonjs/menu/useMenuTrigger.js.map +1 -0
- package/lib/commonjs/overlay/OverlayContainer.js +2 -3
- package/lib/commonjs/overlay/OverlayContainer.js.map +1 -1
- package/lib/commonjs/overlay/OverlayStack.js +125 -0
- package/lib/commonjs/overlay/OverlayStack.js.map +1 -0
- package/lib/commonjs/overlay/constants.js +7 -1
- package/lib/commonjs/overlay/constants.js.map +1 -1
- package/lib/commonjs/overlay/getAnchoredOverlayPosition.js +188 -0
- package/lib/commonjs/overlay/getAnchoredOverlayPosition.js.map +1 -0
- package/lib/commonjs/overlay/index.js +39 -0
- package/lib/commonjs/overlay/index.js.map +1 -1
- package/lib/commonjs/overlay/useAnchoredOverlay.js +127 -0
- package/lib/commonjs/overlay/useAnchoredOverlay.js.map +1 -0
- package/lib/commonjs/overlay/useDismissOverlay.js +8 -1
- package/lib/commonjs/overlay/useDismissOverlay.js.map +1 -1
- package/lib/commonjs/overlay/useDismissOverlay.web.js +11 -3
- package/lib/commonjs/overlay/useDismissOverlay.web.js.map +1 -1
- package/lib/commonjs/overlay/useEscapeKey.js +8 -1
- package/lib/commonjs/overlay/useEscapeKey.js.map +1 -1
- package/lib/commonjs/overlay/useEscapeKey.web.js +11 -3
- package/lib/commonjs/overlay/useEscapeKey.web.js.map +1 -1
- package/lib/commonjs/overlay/useOverlayPosition.js +16 -7
- package/lib/commonjs/overlay/useOverlayPosition.js.map +1 -1
- package/lib/commonjs/portal/portal.js +86 -63
- package/lib/commonjs/portal/portal.js.map +1 -1
- package/lib/commonjs/select/SelectContent.js +23 -11
- package/lib/commonjs/select/SelectContent.js.map +1 -1
- package/lib/commonjs/select/useListboxNavigation.js +10 -11
- package/lib/commonjs/select/useListboxNavigation.js.map +1 -1
- package/lib/commonjs/tooltip/TooltipContent.js +25 -24
- package/lib/commonjs/tooltip/TooltipContent.js.map +1 -1
- package/lib/commonjs/tooltip/getTooltipPosition.js +37 -84
- package/lib/commonjs/tooltip/getTooltipPosition.js.map +1 -1
- package/lib/module/dialog/DialogContent.js +18 -4
- package/lib/module/dialog/DialogContent.js.map +1 -1
- package/lib/module/dialog/DialogPopup.js +5 -1
- package/lib/module/dialog/DialogPopup.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/menu/MenuCheckboxItem.js +38 -0
- package/lib/module/menu/MenuCheckboxItem.js.map +1 -0
- package/lib/module/menu/MenuItem.js +138 -0
- package/lib/module/menu/MenuItem.js.map +1 -0
- package/lib/module/menu/MenuOverlayRoot.js +76 -0
- package/lib/module/menu/MenuOverlayRoot.js.map +1 -0
- package/lib/module/menu/MenuPopup.js +74 -0
- package/lib/module/menu/MenuPopup.js.map +1 -0
- package/lib/module/menu/MenuRadioGroup.js +44 -0
- package/lib/module/menu/MenuRadioGroup.js.map +1 -0
- package/lib/module/menu/MenuRadioItem.js +28 -0
- package/lib/module/menu/MenuRadioItem.js.map +1 -0
- package/lib/module/menu/MenuRoot.js +135 -0
- package/lib/module/menu/MenuRoot.js.map +1 -0
- package/lib/module/menu/MenuTrigger.js +55 -0
- package/lib/module/menu/MenuTrigger.js.map +1 -0
- package/lib/module/menu/context.js +27 -0
- package/lib/module/menu/context.js.map +1 -0
- package/lib/module/menu/index.js +13 -0
- package/lib/module/menu/index.js.map +1 -0
- package/lib/module/menu/overlayContext.js +24 -0
- package/lib/module/menu/overlayContext.js.map +1 -0
- package/lib/module/menu/types.js +4 -0
- package/lib/module/menu/types.js.map +1 -0
- package/lib/module/menu/useMenuNavigation.js +84 -0
- package/lib/module/menu/useMenuNavigation.js.map +1 -0
- package/lib/module/menu/useMenuOverlayLifecycle.js +63 -0
- package/lib/module/menu/useMenuOverlayLifecycle.js.map +1 -0
- package/lib/module/menu/useMenuTrigger.js +111 -0
- package/lib/module/menu/useMenuTrigger.js.map +1 -0
- package/lib/module/overlay/OverlayContainer.js +2 -3
- package/lib/module/overlay/OverlayContainer.js.map +1 -1
- package/lib/module/overlay/OverlayStack.js +119 -0
- package/lib/module/overlay/OverlayStack.js.map +1 -0
- package/lib/module/overlay/constants.js +6 -0
- package/lib/module/overlay/constants.js.map +1 -1
- package/lib/module/overlay/getAnchoredOverlayPosition.js +183 -0
- package/lib/module/overlay/getAnchoredOverlayPosition.js.map +1 -0
- package/lib/module/overlay/index.js +4 -1
- package/lib/module/overlay/index.js.map +1 -1
- package/lib/module/overlay/useAnchoredOverlay.js +123 -0
- package/lib/module/overlay/useAnchoredOverlay.js.map +1 -0
- package/lib/module/overlay/useDismissOverlay.js +9 -1
- package/lib/module/overlay/useDismissOverlay.js.map +1 -1
- package/lib/module/overlay/useDismissOverlay.web.js +11 -3
- package/lib/module/overlay/useDismissOverlay.web.js.map +1 -1
- package/lib/module/overlay/useEscapeKey.js +9 -1
- package/lib/module/overlay/useEscapeKey.js.map +1 -1
- package/lib/module/overlay/useEscapeKey.web.js +11 -3
- package/lib/module/overlay/useEscapeKey.web.js.map +1 -1
- package/lib/module/overlay/useOverlayPosition.js +16 -7
- package/lib/module/overlay/useOverlayPosition.js.map +1 -1
- package/lib/module/portal/portal.js +86 -64
- package/lib/module/portal/portal.js.map +1 -1
- package/lib/module/select/SelectContent.js +25 -13
- package/lib/module/select/SelectContent.js.map +1 -1
- package/lib/module/select/useListboxNavigation.js +10 -11
- package/lib/module/select/useListboxNavigation.js.map +1 -1
- package/lib/module/tooltip/TooltipContent.js +30 -29
- package/lib/module/tooltip/TooltipContent.js.map +1 -1
- package/lib/module/tooltip/getTooltipPosition.js +37 -84
- package/lib/module/tooltip/getTooltipPosition.js.map +1 -1
- package/lib/typescript/dialog/DialogContent.d.ts.map +1 -1
- package/lib/typescript/dialog/DialogPopup.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/menu/MenuCheckboxItem.d.ts +6 -0
- package/lib/typescript/menu/MenuCheckboxItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuItem.d.ts +8 -0
- package/lib/typescript/menu/MenuItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuOverlayRoot.d.ts +8 -0
- package/lib/typescript/menu/MenuOverlayRoot.d.ts.map +1 -0
- package/lib/typescript/menu/MenuPopup.d.ts +8 -0
- package/lib/typescript/menu/MenuPopup.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRadioGroup.d.ts +8 -0
- package/lib/typescript/menu/MenuRadioGroup.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRadioItem.d.ts +6 -0
- package/lib/typescript/menu/MenuRadioItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRoot.d.ts +8 -0
- package/lib/typescript/menu/MenuRoot.d.ts.map +1 -0
- package/lib/typescript/menu/MenuTrigger.d.ts +7 -0
- package/lib/typescript/menu/MenuTrigger.d.ts.map +1 -0
- package/lib/typescript/menu/context.d.ts +15 -0
- package/lib/typescript/menu/context.d.ts.map +1 -0
- package/lib/typescript/menu/index.d.ts +12 -0
- package/lib/typescript/menu/index.d.ts.map +1 -0
- package/lib/typescript/menu/overlayContext.d.ts +35 -0
- package/lib/typescript/menu/overlayContext.d.ts.map +1 -0
- package/lib/typescript/menu/types.d.ts +104 -0
- package/lib/typescript/menu/types.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuNavigation.d.ts +16 -0
- package/lib/typescript/menu/useMenuNavigation.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts +15 -0
- package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuTrigger.d.ts +35 -0
- package/lib/typescript/menu/useMenuTrigger.d.ts.map +1 -0
- package/lib/typescript/overlay/OverlayContainer.d.ts.map +1 -1
- package/lib/typescript/overlay/OverlayStack.d.ts +46 -0
- package/lib/typescript/overlay/OverlayStack.d.ts.map +1 -0
- package/lib/typescript/overlay/constants.d.ts +5 -0
- package/lib/typescript/overlay/constants.d.ts.map +1 -1
- package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts +45 -0
- package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts.map +1 -0
- package/lib/typescript/overlay/index.d.ts +5 -2
- package/lib/typescript/overlay/index.d.ts.map +1 -1
- package/lib/typescript/overlay/useAnchoredOverlay.d.ts +15 -0
- package/lib/typescript/overlay/useAnchoredOverlay.d.ts.map +1 -0
- package/lib/typescript/overlay/useDismissOverlay.d.ts +2 -1
- package/lib/typescript/overlay/useDismissOverlay.d.ts.map +1 -1
- package/lib/typescript/overlay/useDismissOverlay.web.d.ts +2 -1
- package/lib/typescript/overlay/useDismissOverlay.web.d.ts.map +1 -1
- package/lib/typescript/overlay/useEscapeKey.d.ts +2 -1
- package/lib/typescript/overlay/useEscapeKey.d.ts.map +1 -1
- package/lib/typescript/overlay/useEscapeKey.web.d.ts +2 -1
- package/lib/typescript/overlay/useEscapeKey.web.d.ts.map +1 -1
- package/lib/typescript/overlay/useOverlayPosition.d.ts +5 -1
- package/lib/typescript/overlay/useOverlayPosition.d.ts.map +1 -1
- package/lib/typescript/portal/portal.d.ts +3 -0
- package/lib/typescript/portal/portal.d.ts.map +1 -1
- package/lib/typescript/select/SelectContent.d.ts.map +1 -1
- package/lib/typescript/select/useListboxNavigation.d.ts.map +1 -1
- package/lib/typescript/tooltip/TooltipContent.d.ts.map +1 -1
- package/lib/typescript/tooltip/getTooltipPosition.d.ts +6 -8
- package/lib/typescript/tooltip/getTooltipPosition.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/dialog/DialogContent.tsx +16 -4
- package/src/dialog/DialogPopup.tsx +5 -1
- package/src/index.ts +23 -0
- package/src/menu/MenuCheckboxItem.tsx +51 -0
- package/src/menu/MenuItem.tsx +197 -0
- package/src/menu/MenuOverlayRoot.tsx +100 -0
- package/src/menu/MenuPopup.tsx +81 -0
- package/src/menu/MenuRadioGroup.tsx +48 -0
- package/src/menu/MenuRadioItem.tsx +32 -0
- package/src/menu/MenuRoot.tsx +190 -0
- package/src/menu/MenuTrigger.tsx +57 -0
- package/src/menu/context.tsx +38 -0
- package/src/menu/index.ts +36 -0
- package/src/menu/overlayContext.tsx +61 -0
- package/src/menu/types.ts +119 -0
- package/src/menu/useMenuNavigation.ts +102 -0
- package/src/menu/useMenuOverlayLifecycle.ts +77 -0
- package/src/menu/useMenuTrigger.ts +161 -0
- package/src/overlay/OverlayContainer.tsx +3 -1
- package/src/overlay/OverlayStack.tsx +202 -0
- package/src/overlay/constants.ts +6 -0
- package/src/overlay/getAnchoredOverlayPosition.ts +326 -0
- package/src/overlay/index.ts +29 -1
- package/src/overlay/useAnchoredOverlay.ts +153 -0
- package/src/overlay/useDismissOverlay.ts +10 -2
- package/src/overlay/useDismissOverlay.web.ts +9 -2
- package/src/overlay/useEscapeKey.ts +13 -1
- package/src/overlay/useEscapeKey.web.ts +13 -3
- package/src/overlay/useOverlayPosition.ts +35 -8
- package/src/portal/portal.tsx +109 -69
- package/src/select/SelectContent.tsx +29 -17
- package/src/select/useListboxNavigation.ts +12 -9
- package/src/tooltip/TooltipContent.tsx +32 -34
- package/src/tooltip/getTooltipPosition.ts +58 -125
package/src/index.ts
CHANGED
|
@@ -21,17 +21,40 @@ export * from './date-time-list';
|
|
|
21
21
|
export * from './date-time-picker';
|
|
22
22
|
export * from './link';
|
|
23
23
|
export * from './list-item';
|
|
24
|
+
export * from './menu';
|
|
24
25
|
export {
|
|
25
26
|
MODAL_OVERLAY_Z_INDEX,
|
|
27
|
+
NOTIFICATION_OVERLAY_Z_INDEX,
|
|
28
|
+
OVERLAY_PLACEMENTS,
|
|
26
29
|
type EdgeInsets,
|
|
30
|
+
getAnchoredOverlayPosition,
|
|
27
31
|
OverlayContainer,
|
|
28
32
|
OverlayInsetsProvider,
|
|
33
|
+
OverlayStackProvider,
|
|
29
34
|
useAnchorPosition,
|
|
35
|
+
useAnchoredOverlay,
|
|
30
36
|
useDismissOverlay,
|
|
31
37
|
useEscapeKey,
|
|
38
|
+
useOverlayStackRegistration,
|
|
32
39
|
useOverlayPosition,
|
|
33
40
|
type AnchorLayout,
|
|
41
|
+
type AnchoredOverlayPosition,
|
|
42
|
+
type AnchoredOverlayPositionOptions,
|
|
43
|
+
type FloatingLayout,
|
|
44
|
+
type OverlayAlign,
|
|
45
|
+
type OverlayCollisionBehavior,
|
|
46
|
+
type OverlayPlacement,
|
|
34
47
|
type OverlayPosition,
|
|
48
|
+
type OverlaySide,
|
|
49
|
+
type OverlayStackCapability,
|
|
50
|
+
type OverlayStackLayer,
|
|
51
|
+
type OverlayStackMetadata,
|
|
52
|
+
type OverlayStackRegistrationOptions,
|
|
53
|
+
type OverlayStackRegistrationResult,
|
|
54
|
+
type OverlayWidthPolicy,
|
|
55
|
+
type ResolvedOverlayPlacement,
|
|
56
|
+
type UseAnchoredOverlayOptions,
|
|
57
|
+
type UseAnchoredOverlayResult,
|
|
35
58
|
} from './overlay';
|
|
36
59
|
export * from './select';
|
|
37
60
|
export * from './switch';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { forwardRef, type ComponentRef } from 'react';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import { composeEventHandlers, useControllableState } from '@cdx-ui/utils';
|
|
4
|
+
import { MenuItem } from './MenuItem';
|
|
5
|
+
import type { IMenuCheckboxItemProps } from './types';
|
|
6
|
+
|
|
7
|
+
export interface MenuCheckboxItemProps extends IMenuCheckboxItemProps {
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const MenuCheckboxItem = forwardRef<ComponentRef<typeof Pressable>, MenuCheckboxItemProps>(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
asChild,
|
|
15
|
+
children,
|
|
16
|
+
isChecked: checkedProp,
|
|
17
|
+
defaultIsChecked = false,
|
|
18
|
+
onChange,
|
|
19
|
+
onPress,
|
|
20
|
+
closeOnSelect = false,
|
|
21
|
+
...props
|
|
22
|
+
},
|
|
23
|
+
ref,
|
|
24
|
+
) => {
|
|
25
|
+
const [checked = false, setCheckedState] = useControllableState<boolean>({
|
|
26
|
+
prop: checkedProp,
|
|
27
|
+
defaultProp: defaultIsChecked,
|
|
28
|
+
});
|
|
29
|
+
const toggleChecked = () => {
|
|
30
|
+
const nextChecked = !checked;
|
|
31
|
+
setCheckedState(nextChecked);
|
|
32
|
+
onChange?.(nextChecked);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<MenuItem
|
|
37
|
+
ref={ref}
|
|
38
|
+
asChild={asChild}
|
|
39
|
+
itemRole="menuitemcheckbox"
|
|
40
|
+
isChecked={checked}
|
|
41
|
+
closeOnSelect={closeOnSelect}
|
|
42
|
+
onPress={composeEventHandlers(onPress, toggleChecked)}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</MenuItem>
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
MenuCheckboxItem.displayName = 'MenuCheckboxItem';
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
type ComponentRef,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { Platform, Pressable, type GestureResponderEvent, type PressableProps } from 'react-native';
|
|
12
|
+
import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
|
|
13
|
+
import { useFocus, useFocusRing } from '@react-native-aria/focus';
|
|
14
|
+
import { useHover, usePress } from '@react-native-aria/interactions';
|
|
15
|
+
import { Slot } from '../slot';
|
|
16
|
+
import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
|
|
17
|
+
import { MenuItemProvider, useMenuContext } from './context';
|
|
18
|
+
import type { IMenuItemProps } from './types';
|
|
19
|
+
|
|
20
|
+
export interface MenuItemProps extends Omit<IMenuItemProps, 'children'> {
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(
|
|
26
|
+
(
|
|
27
|
+
{
|
|
28
|
+
asChild,
|
|
29
|
+
children,
|
|
30
|
+
itemRole = 'menuitem',
|
|
31
|
+
isChecked = false,
|
|
32
|
+
disabled: disabledProp = false,
|
|
33
|
+
closeOnSelect: closeOnSelectProp,
|
|
34
|
+
isHovered: isHoveredProp,
|
|
35
|
+
isActive: isActiveProp,
|
|
36
|
+
isFocused: isFocusedProp,
|
|
37
|
+
isFocusVisible: isFocusVisibleProp,
|
|
38
|
+
isDisabled: isDisabledProp,
|
|
39
|
+
onPress,
|
|
40
|
+
...props
|
|
41
|
+
},
|
|
42
|
+
ref,
|
|
43
|
+
) => {
|
|
44
|
+
const {
|
|
45
|
+
menuId,
|
|
46
|
+
registerItem,
|
|
47
|
+
unregisterItem,
|
|
48
|
+
focusedItemId,
|
|
49
|
+
setFocusedItemId,
|
|
50
|
+
closeOnSelect: closeOverlayOnSelect,
|
|
51
|
+
dismissOverlay,
|
|
52
|
+
} = useMenuContext();
|
|
53
|
+
|
|
54
|
+
const reactId = useId();
|
|
55
|
+
const itemId = `menu-item-${reactId}`;
|
|
56
|
+
const domId = props.id ?? `${menuId}-item-${reactId}`;
|
|
57
|
+
const disabled = !!disabledProp || !!isDisabledProp;
|
|
58
|
+
const highlighted = focusedItemId === itemId;
|
|
59
|
+
const isSelectable = itemRole === 'menuitemcheckbox' || itemRole === 'menuitemradio';
|
|
60
|
+
const closeOnSelect = closeOnSelectProp ?? closeOverlayOnSelect;
|
|
61
|
+
const resolvedRole: PressableProps['role'] =
|
|
62
|
+
Platform.OS === 'web'
|
|
63
|
+
? (itemRole as PressableProps['role'])
|
|
64
|
+
: itemRole === 'menuitemcheckbox'
|
|
65
|
+
? 'checkbox'
|
|
66
|
+
: itemRole === 'menuitemradio'
|
|
67
|
+
? 'radio'
|
|
68
|
+
: 'menuitem';
|
|
69
|
+
|
|
70
|
+
const nodeRef = useRef<ComponentRef<typeof Pressable> | null>(null);
|
|
71
|
+
const mergedRef = mergeRefs(ref, nodeRef);
|
|
72
|
+
const onPressRef = useRef(onPress);
|
|
73
|
+
onPressRef.current = onPress;
|
|
74
|
+
|
|
75
|
+
const invokePress = useCallback(
|
|
76
|
+
(event: GestureResponderEvent) => {
|
|
77
|
+
if (!disabled) {
|
|
78
|
+
onPressRef.current?.(event);
|
|
79
|
+
if (closeOnSelect) {
|
|
80
|
+
dismissOverlay?.('select');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[disabled, closeOnSelect, dismissOverlay],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
registerItem(itemId, {
|
|
89
|
+
disabled,
|
|
90
|
+
node: nodeRef.current as { focus?: () => void } | null,
|
|
91
|
+
});
|
|
92
|
+
return () => unregisterItem(itemId);
|
|
93
|
+
}, [itemId, disabled, registerItem, unregisterItem]);
|
|
94
|
+
|
|
95
|
+
const { isFocusVisible, focusProps: focusRingProps } = useFocusRing() as {
|
|
96
|
+
isFocusVisible: boolean;
|
|
97
|
+
focusProps: {
|
|
98
|
+
onFocus?: (...args: unknown[]) => unknown;
|
|
99
|
+
onBlur?: (...args: unknown[]) => unknown;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const { pressProps, isPressed: isActive } = usePress({ isDisabled: disabled });
|
|
103
|
+
const { isFocused, focusProps } = useFocus();
|
|
104
|
+
const { isHovered, hoverProps } = useHover();
|
|
105
|
+
|
|
106
|
+
const interactionState = useMemo(
|
|
107
|
+
() => ({
|
|
108
|
+
hover: !!(isHoveredProp || isHovered),
|
|
109
|
+
focus: !!(isFocusedProp || isFocused),
|
|
110
|
+
active: !!(isActiveProp || isActive),
|
|
111
|
+
disabled: !!disabled,
|
|
112
|
+
focusVisible: !!(isFocusVisibleProp || isFocusVisible),
|
|
113
|
+
highlighted,
|
|
114
|
+
checked: isChecked,
|
|
115
|
+
}),
|
|
116
|
+
[
|
|
117
|
+
isHoveredProp,
|
|
118
|
+
isHovered,
|
|
119
|
+
isFocusedProp,
|
|
120
|
+
isFocused,
|
|
121
|
+
isActiveProp,
|
|
122
|
+
isActive,
|
|
123
|
+
disabled,
|
|
124
|
+
isFocusVisibleProp,
|
|
125
|
+
isFocusVisible,
|
|
126
|
+
highlighted,
|
|
127
|
+
isChecked,
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const isRovingTabStop = highlighted;
|
|
132
|
+
const webProps = Platform.OS === 'web' ? { tabIndex: isRovingTabStop ? 0 : -1 } : {};
|
|
133
|
+
|
|
134
|
+
const handlePressWithFocus = useCallback(
|
|
135
|
+
(_: GestureResponderEvent) => {
|
|
136
|
+
if (!disabled) {
|
|
137
|
+
setFocusedItemId(itemId);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
[disabled, itemId, setFocusedItemId],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const Comp = asChild ? Slot : Pressable;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<MenuItemProvider value={interactionState}>
|
|
147
|
+
<Comp
|
|
148
|
+
ref={mergedRef}
|
|
149
|
+
role={resolvedRole}
|
|
150
|
+
id={domId}
|
|
151
|
+
aria-checked={isSelectable ? isChecked : undefined}
|
|
152
|
+
aria-disabled={disabled}
|
|
153
|
+
disabled={disabled}
|
|
154
|
+
accessibilityState={{ disabled, ...(isSelectable ? { checked: isChecked } : {}) }}
|
|
155
|
+
{...webProps}
|
|
156
|
+
{...props}
|
|
157
|
+
{...mergeDataAttributes(
|
|
158
|
+
props,
|
|
159
|
+
dataAttributes({
|
|
160
|
+
hover: interactionState.hover,
|
|
161
|
+
focus: interactionState.focus,
|
|
162
|
+
active: interactionState.active,
|
|
163
|
+
disabled: interactionState.disabled,
|
|
164
|
+
focusVisible: interactionState.focusVisible,
|
|
165
|
+
highlighted: interactionState.highlighted,
|
|
166
|
+
state: isSelectable ? (isChecked ? 'checked' : 'unchecked') : undefined,
|
|
167
|
+
}),
|
|
168
|
+
)}
|
|
169
|
+
onPress={composeEventHandlers(invokePress, handlePressWithFocus)}
|
|
170
|
+
onPressIn={composeEventHandlers(props?.onPressIn, pressProps.onPressIn)}
|
|
171
|
+
onPressOut={composeEventHandlers(props?.onPressOut, pressProps.onPressOut)}
|
|
172
|
+
onHoverIn={composeEventHandlers(props?.onHoverIn, hoverProps.onHoverIn)}
|
|
173
|
+
onHoverOut={composeEventHandlers(props?.onHoverOut, hoverProps.onHoverOut)}
|
|
174
|
+
onFocus={composeEventHandlers(
|
|
175
|
+
composeEventHandlers(
|
|
176
|
+
props?.onFocus as (...args: unknown[]) => unknown,
|
|
177
|
+
focusProps.onFocus,
|
|
178
|
+
),
|
|
179
|
+
focusRingProps.onFocus,
|
|
180
|
+
() => setFocusedItemId(itemId),
|
|
181
|
+
)}
|
|
182
|
+
onBlur={composeEventHandlers(
|
|
183
|
+
composeEventHandlers(
|
|
184
|
+
props?.onBlur as (...args: unknown[]) => unknown,
|
|
185
|
+
focusProps.onBlur,
|
|
186
|
+
),
|
|
187
|
+
focusRingProps.onBlur,
|
|
188
|
+
)}
|
|
189
|
+
>
|
|
190
|
+
{children}
|
|
191
|
+
</Comp>
|
|
192
|
+
</MenuItemProvider>
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
MenuItem.displayName = 'MenuItem';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useId, useMemo, useRef, type ComponentRef } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useControllableState } from '@cdx-ui/utils';
|
|
4
|
+
import { Slot } from '../slot';
|
|
5
|
+
import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
|
|
6
|
+
import { MenuOverlayProvider, type MenuFocusOnOpen } from './overlayContext';
|
|
7
|
+
import type { IMenuOverlayRootProps, MenuCloseReason } from './types';
|
|
8
|
+
|
|
9
|
+
export interface MenuOverlayRootProps extends IMenuOverlayRootProps {
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const MenuOverlayRoot = forwardRef<ComponentRef<typeof View>, MenuOverlayRootProps>(
|
|
15
|
+
(
|
|
16
|
+
{
|
|
17
|
+
asChild,
|
|
18
|
+
children,
|
|
19
|
+
isOpen: openProp,
|
|
20
|
+
defaultOpen = false,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
disabled = false,
|
|
23
|
+
placement = 'bottom-start',
|
|
24
|
+
offset = 4,
|
|
25
|
+
crossOffset = 0,
|
|
26
|
+
...props
|
|
27
|
+
},
|
|
28
|
+
ref,
|
|
29
|
+
) => {
|
|
30
|
+
const [open, setOpenState] = useControllableState<boolean>({
|
|
31
|
+
prop: openProp,
|
|
32
|
+
defaultProp: defaultOpen,
|
|
33
|
+
onChange: onOpenChange,
|
|
34
|
+
});
|
|
35
|
+
const closeReasonRef = useRef<MenuCloseReason>('programmatic');
|
|
36
|
+
|
|
37
|
+
const setOpen = useCallback(
|
|
38
|
+
(nextOpen: boolean) => {
|
|
39
|
+
if (disabled && nextOpen) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (nextOpen) {
|
|
43
|
+
closeReasonRef.current = 'programmatic';
|
|
44
|
+
}
|
|
45
|
+
setOpenState(nextOpen);
|
|
46
|
+
},
|
|
47
|
+
[disabled, setOpenState],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const triggerRef = useRef<ComponentRef<typeof View> | null>(null);
|
|
51
|
+
const contentRef = useRef<ComponentRef<typeof View> | null>(null);
|
|
52
|
+
const focusOnOpenRef = useRef<MenuFocusOnOpen>('menu');
|
|
53
|
+
const reactId = useId();
|
|
54
|
+
const triggerId = `menu-trigger-${reactId}`;
|
|
55
|
+
const menuId = `menu-content-${reactId}`;
|
|
56
|
+
|
|
57
|
+
const closeMenu = useCallback(
|
|
58
|
+
(reason: MenuCloseReason = 'programmatic') => {
|
|
59
|
+
closeReasonRef.current = reason;
|
|
60
|
+
setOpen(false);
|
|
61
|
+
},
|
|
62
|
+
[setOpen],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const contextValue = useMemo(
|
|
66
|
+
() => ({
|
|
67
|
+
open: open ?? false,
|
|
68
|
+
setOpen,
|
|
69
|
+
closeMenu,
|
|
70
|
+
disabled,
|
|
71
|
+
placement,
|
|
72
|
+
offset,
|
|
73
|
+
crossOffset,
|
|
74
|
+
triggerRef,
|
|
75
|
+
triggerId,
|
|
76
|
+
menuId,
|
|
77
|
+
contentRef,
|
|
78
|
+
focusOnOpenRef,
|
|
79
|
+
closeReasonRef,
|
|
80
|
+
}),
|
|
81
|
+
[open, setOpen, closeMenu, disabled, placement, offset, crossOffset, triggerId, menuId],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const Comp = asChild ? Slot : View;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<MenuOverlayProvider value={contextValue}>
|
|
88
|
+
<Comp
|
|
89
|
+
ref={ref}
|
|
90
|
+
{...props}
|
|
91
|
+
{...mergeDataAttributes(props, dataAttributes({ slot: 'menu' }))}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</Comp>
|
|
95
|
+
</MenuOverlayProvider>
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
MenuOverlayRoot.displayName = 'MenuOverlayRoot';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { forwardRef, type ComponentRef } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { FadeIn, FadeOut } from 'react-native-reanimated';
|
|
4
|
+
import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
|
|
5
|
+
import { OverlayContainer, useAnchoredOverlay, useDismissOverlay, useEscapeKey } from '../overlay';
|
|
6
|
+
import { Slot } from '../slot';
|
|
7
|
+
import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
|
|
8
|
+
import { MenuPopupProvider, useMenuOverlayContext } from './overlayContext';
|
|
9
|
+
import type { IMenuPopupProps } from './types';
|
|
10
|
+
import { useMenuOverlayCloseFocus } from './useMenuOverlayLifecycle';
|
|
11
|
+
|
|
12
|
+
export interface MenuPopupProps extends IMenuPopupProps {
|
|
13
|
+
asChild?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MenuPopup = forwardRef<ComponentRef<typeof View>, MenuPopupProps>(
|
|
18
|
+
({ asChild, children, onLayout, style, ...props }, ref) => {
|
|
19
|
+
const overlay = useMenuOverlayContext();
|
|
20
|
+
const { open, closeMenu, triggerRef, placement, offset, crossOffset } = overlay;
|
|
21
|
+
|
|
22
|
+
const dismiss = () => closeMenu('outside');
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
floatingRef,
|
|
26
|
+
onFloatingLayout,
|
|
27
|
+
style: overlayStyle,
|
|
28
|
+
position,
|
|
29
|
+
} = useAnchoredOverlay(triggerRef, open, {
|
|
30
|
+
placement,
|
|
31
|
+
offset,
|
|
32
|
+
crossOffset,
|
|
33
|
+
collisionBehavior: 'flip-shift',
|
|
34
|
+
width: 'content',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
useDismissOverlay(open, floatingRef, [triggerRef], dismiss);
|
|
38
|
+
useEscapeKey(open, () => closeMenu('escape'));
|
|
39
|
+
useMenuOverlayCloseFocus(overlay);
|
|
40
|
+
|
|
41
|
+
if (!open) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Comp = asChild ? Slot : View;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<MenuPopupProvider
|
|
49
|
+
value={{
|
|
50
|
+
availableHeight: position?.availableHeight,
|
|
51
|
+
availableWidth: position?.availableWidth,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<OverlayContainer
|
|
55
|
+
onDismiss={dismiss}
|
|
56
|
+
entering={FadeIn.duration(150)}
|
|
57
|
+
exiting={FadeOut.duration(100)}
|
|
58
|
+
>
|
|
59
|
+
<Comp
|
|
60
|
+
ref={mergeRefs(ref, floatingRef)}
|
|
61
|
+
onLayout={composeEventHandlers(onLayout, onFloatingLayout)}
|
|
62
|
+
style={[style, overlayStyle]}
|
|
63
|
+
{...props}
|
|
64
|
+
{...mergeDataAttributes(
|
|
65
|
+
props,
|
|
66
|
+
dataAttributes({
|
|
67
|
+
slot: 'menu-popup',
|
|
68
|
+
side: position?.resolvedPlacement.side,
|
|
69
|
+
align: position?.resolvedPlacement.align,
|
|
70
|
+
}),
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</Comp>
|
|
75
|
+
</OverlayContainer>
|
|
76
|
+
</MenuPopupProvider>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
MenuPopup.displayName = 'MenuPopup';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useMemo, type ComponentRef } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useControllableState } from '@cdx-ui/utils';
|
|
4
|
+
import { Slot } from '../slot';
|
|
5
|
+
import { MenuRadioGroupProvider } from './context';
|
|
6
|
+
import type { IMenuRadioGroupProps } from './types';
|
|
7
|
+
|
|
8
|
+
export interface MenuRadioGroupProps extends IMenuRadioGroupProps {
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const MenuRadioGroup = forwardRef<ComponentRef<typeof View>, MenuRadioGroupProps>(
|
|
14
|
+
({ asChild, children, value: valueProp, defaultValue, onChange, ...props }, ref) => {
|
|
15
|
+
const [value, setValueState] = useControllableState<string>({
|
|
16
|
+
prop: valueProp,
|
|
17
|
+
defaultProp: defaultValue,
|
|
18
|
+
});
|
|
19
|
+
const setValue = useCallback(
|
|
20
|
+
(nextValue: string) => {
|
|
21
|
+
if (nextValue === value) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setValueState(nextValue);
|
|
25
|
+
onChange?.(nextValue);
|
|
26
|
+
},
|
|
27
|
+
[onChange, setValueState, value],
|
|
28
|
+
);
|
|
29
|
+
const contextValue = useMemo(
|
|
30
|
+
() => ({
|
|
31
|
+
value,
|
|
32
|
+
setValue,
|
|
33
|
+
}),
|
|
34
|
+
[setValue, value],
|
|
35
|
+
);
|
|
36
|
+
const Comp = asChild ? Slot : View;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<MenuRadioGroupProvider value={contextValue}>
|
|
40
|
+
<Comp ref={ref} role="group" {...props}>
|
|
41
|
+
{children}
|
|
42
|
+
</Comp>
|
|
43
|
+
</MenuRadioGroupProvider>
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
MenuRadioGroup.displayName = 'MenuRadioGroup';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { forwardRef, type ComponentRef } from 'react';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import { composeEventHandlers } from '@cdx-ui/utils';
|
|
4
|
+
import { useMenuRadioGroupContext } from './context';
|
|
5
|
+
import { MenuItem } from './MenuItem';
|
|
6
|
+
import type { IMenuRadioItemProps } from './types';
|
|
7
|
+
|
|
8
|
+
export interface MenuRadioItemProps extends IMenuRadioItemProps {
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const MenuRadioItem = forwardRef<ComponentRef<typeof Pressable>, MenuRadioItemProps>(
|
|
13
|
+
({ asChild, children, value, onPress, ...props }, ref) => {
|
|
14
|
+
const radioGroup = useMenuRadioGroupContext();
|
|
15
|
+
const checked = radioGroup.value === value;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<MenuItem
|
|
19
|
+
ref={ref}
|
|
20
|
+
asChild={asChild}
|
|
21
|
+
itemRole="menuitemradio"
|
|
22
|
+
isChecked={checked}
|
|
23
|
+
onPress={composeEventHandlers(onPress, () => radioGroup.setValue(value))}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</MenuItem>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
MenuRadioItem.displayName = 'MenuRadioItem';
|