@cdx-ui/primitives 0.1.3 → 0.1.4

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.
Files changed (93) hide show
  1. package/lib/commonjs/drawer/DrawerClose.js +32 -0
  2. package/lib/commonjs/drawer/DrawerClose.js.map +1 -0
  3. package/lib/commonjs/drawer/DrawerOverlay.js +48 -0
  4. package/lib/commonjs/drawer/DrawerOverlay.js.map +1 -0
  5. package/lib/commonjs/drawer/DrawerPortal.js +33 -0
  6. package/lib/commonjs/drawer/DrawerPortal.js.map +1 -0
  7. package/lib/commonjs/drawer/DrawerRoot.js +55 -0
  8. package/lib/commonjs/drawer/DrawerRoot.js.map +1 -0
  9. package/lib/commonjs/drawer/context.js +11 -0
  10. package/lib/commonjs/drawer/context.js.map +1 -0
  11. package/lib/commonjs/drawer/index.js +47 -0
  12. package/lib/commonjs/drawer/index.js.map +1 -0
  13. package/lib/commonjs/drawer/types.js +6 -0
  14. package/lib/commonjs/drawer/types.js.map +1 -0
  15. package/lib/commonjs/index.js +12 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/menu/MenuItem.js +15 -2
  18. package/lib/commonjs/menu/MenuItem.js.map +1 -1
  19. package/lib/commonjs/menu/MenuItemAction.js +42 -0
  20. package/lib/commonjs/menu/MenuItemAction.js.map +1 -0
  21. package/lib/commonjs/menu/MenuRoot.js +31 -1
  22. package/lib/commonjs/menu/MenuRoot.js.map +1 -1
  23. package/lib/commonjs/menu/index.js +7 -0
  24. package/lib/commonjs/menu/index.js.map +1 -1
  25. package/lib/commonjs/menu/useMenuNavigation.js +1 -1
  26. package/lib/commonjs/menu/useMenuNavigation.js.map +1 -1
  27. package/lib/module/drawer/DrawerClose.js +28 -0
  28. package/lib/module/drawer/DrawerClose.js.map +1 -0
  29. package/lib/module/drawer/DrawerOverlay.js +44 -0
  30. package/lib/module/drawer/DrawerOverlay.js.map +1 -0
  31. package/lib/module/drawer/DrawerPortal.js +29 -0
  32. package/lib/module/drawer/DrawerPortal.js.map +1 -0
  33. package/lib/module/drawer/DrawerRoot.js +51 -0
  34. package/lib/module/drawer/DrawerRoot.js.map +1 -0
  35. package/lib/module/drawer/context.js +5 -0
  36. package/lib/module/drawer/context.js.map +1 -0
  37. package/lib/module/drawer/index.js +8 -0
  38. package/lib/module/drawer/index.js.map +1 -0
  39. package/lib/module/drawer/types.js +4 -0
  40. package/lib/module/drawer/types.js.map +1 -0
  41. package/lib/module/index.js +1 -0
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/menu/MenuItem.js +15 -2
  44. package/lib/module/menu/MenuItem.js.map +1 -1
  45. package/lib/module/menu/MenuItemAction.js +38 -0
  46. package/lib/module/menu/MenuItemAction.js.map +1 -0
  47. package/lib/module/menu/MenuRoot.js +31 -1
  48. package/lib/module/menu/MenuRoot.js.map +1 -1
  49. package/lib/module/menu/index.js +1 -0
  50. package/lib/module/menu/index.js.map +1 -1
  51. package/lib/module/menu/useMenuNavigation.js +1 -1
  52. package/lib/module/menu/useMenuNavigation.js.map +1 -1
  53. package/lib/typescript/drawer/DrawerClose.d.ts +5 -0
  54. package/lib/typescript/drawer/DrawerClose.d.ts.map +1 -0
  55. package/lib/typescript/drawer/DrawerOverlay.d.ts +5 -0
  56. package/lib/typescript/drawer/DrawerOverlay.d.ts.map +1 -0
  57. package/lib/typescript/drawer/DrawerPortal.d.ts +12 -0
  58. package/lib/typescript/drawer/DrawerPortal.d.ts.map +1 -0
  59. package/lib/typescript/drawer/DrawerRoot.d.ts +7 -0
  60. package/lib/typescript/drawer/DrawerRoot.d.ts.map +1 -0
  61. package/lib/typescript/drawer/context.d.ts +6 -0
  62. package/lib/typescript/drawer/context.d.ts.map +1 -0
  63. package/lib/typescript/drawer/index.d.ts +7 -0
  64. package/lib/typescript/drawer/index.d.ts.map +1 -0
  65. package/lib/typescript/drawer/types.d.ts +32 -0
  66. package/lib/typescript/drawer/types.d.ts.map +1 -0
  67. package/lib/typescript/index.d.ts +1 -0
  68. package/lib/typescript/index.d.ts.map +1 -1
  69. package/lib/typescript/menu/MenuItem.d.ts.map +1 -1
  70. package/lib/typescript/menu/MenuItemAction.d.ts +6 -0
  71. package/lib/typescript/menu/MenuItemAction.d.ts.map +1 -0
  72. package/lib/typescript/menu/MenuRoot.d.ts.map +1 -1
  73. package/lib/typescript/menu/index.d.ts +2 -1
  74. package/lib/typescript/menu/index.d.ts.map +1 -1
  75. package/lib/typescript/menu/types.d.ts +6 -0
  76. package/lib/typescript/menu/types.d.ts.map +1 -1
  77. package/lib/typescript/menu/useMenuNavigation.d.ts +9 -6
  78. package/lib/typescript/menu/useMenuNavigation.d.ts.map +1 -1
  79. package/package.json +3 -3
  80. package/src/drawer/DrawerClose.tsx +26 -0
  81. package/src/drawer/DrawerOverlay.tsx +45 -0
  82. package/src/drawer/DrawerPortal.tsx +25 -0
  83. package/src/drawer/DrawerRoot.tsx +70 -0
  84. package/src/drawer/context.tsx +4 -0
  85. package/src/drawer/index.tsx +12 -0
  86. package/src/drawer/types.ts +36 -0
  87. package/src/index.ts +1 -0
  88. package/src/menu/MenuItem.tsx +15 -0
  89. package/src/menu/MenuItemAction.tsx +42 -0
  90. package/src/menu/MenuRoot.tsx +51 -10
  91. package/src/menu/index.ts +2 -0
  92. package/src/menu/types.ts +7 -0
  93. package/src/menu/useMenuNavigation.ts +11 -3
@@ -1 +1 @@
1
- {"version":3,"file":"MenuItem.d.ts","sourceRoot":"","sources":["../../../src/menu/MenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAQf,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC;IACrE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,QAAQ,uHA0KpB,CAAC"}
1
+ {"version":3,"file":"MenuItem.d.ts","sourceRoot":"","sources":["../../../src/menu/MenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAQf,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC;IACrE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,QAAQ,uHAyLpB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { View } from 'react-native';
2
+ import type { IMenuItemActionProps } from './types';
3
+ export interface MenuItemActionProps extends IMenuItemActionProps {
4
+ }
5
+ export declare const MenuItemAction: import("react").ForwardRefExoticComponent<MenuItemActionProps & import("react").RefAttributes<View>>;
6
+ //# sourceMappingURL=MenuItemAction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItemAction.d.ts","sourceRoot":"","sources":["../../../src/menu/MenuItemAction.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,IAAI,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;CAAG;AAEpE,eAAO,MAAM,cAAc,sGA8B1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"MenuRoot.d.ts","sourceRoot":"","sources":["../../../src/menu/MenuRoot.tsx"],"names":[],"mappings":"AASA,OAAO,EAAY,IAAI,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,SAAS,CAAC;AA8B9D,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,gGAyIpB,CAAC"}
1
+ {"version":3,"file":"MenuRoot.d.ts","sourceRoot":"","sources":["../../../src/menu/MenuRoot.tsx"],"names":[],"mappings":"AASA,OAAO,EAAY,IAAI,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,SAAS,CAAC;AA8B9D,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,gGAyJpB,CAAC"}
@@ -1,4 +1,4 @@
1
- export type { IMenuCheckboxItemProps, IMenuItemProps, IMenuOverlayRootProps, IMenuPopupProps, IMenuProps, IMenuRadioGroupProps, IMenuRadioItemProps, IMenuTriggerProps, MenuCloseReason, MenuContextValue, MenuItemInteractionState, RegisteredMenuItem, } from './types';
1
+ export type { IMenuCheckboxItemProps, IMenuItemProps, IMenuOverlayRootProps, IMenuPopupProps, IMenuProps, IMenuRadioGroupProps, IMenuRadioItemProps, IMenuItemActionProps, IMenuTriggerProps, MenuCloseReason, MenuContextValue, MenuItemInteractionState, RegisteredMenuItem, } from './types';
2
2
  export { useMenuContext, useMenuItemContext, useMenuRadioGroupContext, useOptionalMenuItemContext, } from './context';
3
3
  export { MenuPopupProvider, useMenuOverlayContext, useOptionalMenuPopupContext, useOptionalMenuOverlayContext, MenuOverlayProvider, } from './overlayContext';
4
4
  export { MenuOverlayRoot, type MenuOverlayRootProps } from './MenuOverlayRoot';
@@ -9,4 +9,5 @@ export { MenuItem, type MenuItemProps } from './MenuItem';
9
9
  export { MenuCheckboxItem, type MenuCheckboxItemProps } from './MenuCheckboxItem';
10
10
  export { MenuRadioGroup, type MenuRadioGroupProps } from './MenuRadioGroup';
11
11
  export { MenuRadioItem, type MenuRadioItemProps } from './MenuRadioItem';
12
+ export { MenuItemAction, type MenuItemActionProps } from './MenuItemAction';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/menu/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/menu/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -66,6 +66,12 @@ export interface IMenuItemProps extends PressableProps {
66
66
  export interface MenuItemInteractionState extends InteractionState {
67
67
  highlighted: boolean;
68
68
  checked: boolean;
69
+ itemRole: 'menuitem' | 'menuitemcheckbox' | 'menuitemradio';
70
+ consumePress: () => void;
71
+ }
72
+ export interface IMenuItemActionProps extends ViewProps {
73
+ asChild?: boolean;
74
+ children?: ReactNode;
69
75
  }
70
76
  export interface IMenuCheckboxItemProps extends Omit<IMenuItemProps, 'children' | 'isChecked' | 'itemRole'> {
71
77
  children?: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/menu/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/menu/types.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,19 @@
1
1
  import type { RegisteredMenuItem } from './types';
2
+ export interface MenuNavigationKeyEvent {
3
+ key: string;
4
+ shiftKey?: boolean;
5
+ target: EventTarget | null;
6
+ preventDefault: () => void;
7
+ stopPropagation?: () => void;
8
+ }
2
9
  export interface UseMenuNavigationOptions {
3
10
  getItems: () => [string, RegisteredMenuItem][];
4
11
  focusedItemId: string | undefined;
5
12
  setFocusedItemId: (itemId: string | undefined) => void;
6
13
  onEscape?: () => void;
7
- onTab?: () => void;
14
+ onTab?: (event: MenuNavigationKeyEvent) => void;
8
15
  }
9
16
  export declare function useMenuNavigation({ getItems, focusedItemId, setFocusedItemId, onEscape, onTab, }: UseMenuNavigationOptions): {
10
- handleKeyDown: (e: {
11
- key: string;
12
- preventDefault: () => void;
13
- stopPropagation?: () => void;
14
- }) => void;
17
+ handleKeyDown: (e: MenuNavigationKeyEvent) => void;
15
18
  };
16
19
  //# sourceMappingURL=useMenuNavigation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMenuNavigation.d.ts","sourceRoot":"","sources":["../../../src/menu/useMenuNavigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,CAAC;IAC/C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,KAAK,GACN,EAAE,wBAAwB;uBAoBnB;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,IAAI,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE;EA8DhF"}
1
+ {"version":3,"file":"useMenuNavigation.d.ts","sourceRoot":"","sources":["../../../src/menu/useMenuNavigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,CAAC;IAC/C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACjD;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,KAAK,GACN,EAAE,wBAAwB;uBAoBnB,sBAAsB;EA8D7B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdx-ui/primitives",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -68,8 +68,8 @@
68
68
  "@react-stately/disclosure": "3.1.1",
69
69
  "@react-stately/radio": "3.12.0",
70
70
  "@react-stately/toggle": "3.9.4",
71
- "@cdx-ui/formatters": "0.1.3",
72
- "@cdx-ui/utils": "0.1.3"
71
+ "@cdx-ui/formatters": "0.1.4",
72
+ "@cdx-ui/utils": "0.1.4"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/react": "19.1.17",
@@ -0,0 +1,26 @@
1
+ import { forwardRef, type ComponentRef } from 'react';
2
+ import { Pressable, type PressableProps } from 'react-native';
3
+ import { composeEventHandlers } from '@cdx-ui/utils';
4
+ import { Slot } from '../slot';
5
+ import { useDrawer } from './context';
6
+ import type { IDrawerCloseProps } from './types';
7
+
8
+ export interface DrawerCloseProps extends IDrawerCloseProps {}
9
+
10
+ export const DrawerClose = forwardRef<ComponentRef<typeof Pressable>, DrawerCloseProps>(
11
+ ({ asChild = false, children, onPress, ...props }, ref) => {
12
+ const { onOpenChange } = useDrawer();
13
+
14
+ const handlePress = composeEventHandlers(onPress, () => onOpenChange(false));
15
+
16
+ const Comp = asChild ? Slot : Pressable;
17
+
18
+ return (
19
+ <Comp ref={ref} {...(props as PressableProps)} onPress={handlePress}>
20
+ {children}
21
+ </Comp>
22
+ );
23
+ },
24
+ );
25
+
26
+ DrawerClose.displayName = 'DrawerClose';
@@ -0,0 +1,45 @@
1
+ import { forwardRef, useCallback, type ComponentRef } from 'react';
2
+ import { Pressable, type PressableProps } from 'react-native';
3
+ import { Slot } from '../slot';
4
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
5
+ import { useDrawer } from './context';
6
+ import type { IDrawerOverlayProps } from './types';
7
+
8
+ export interface DrawerOverlayProps extends IDrawerOverlayProps {}
9
+
10
+ export const DrawerOverlay = forwardRef<ComponentRef<typeof Pressable>, DrawerOverlayProps>(
11
+ ({ asChild = false, forceMount = false, onPress, ...props }, ref) => {
12
+ const { open, onOpenChange, closeOnBackdropPress } = useDrawer();
13
+
14
+ const handlePress = useCallback(
15
+ (e: Parameters<NonNullable<PressableProps['onPress']>>[0]) => {
16
+ if (closeOnBackdropPress) {
17
+ onOpenChange(false);
18
+ }
19
+ if (typeof onPress === 'function') {
20
+ onPress(e);
21
+ }
22
+ },
23
+ [closeOnBackdropPress, onOpenChange, onPress],
24
+ );
25
+
26
+ if (!forceMount && !open) {
27
+ return null;
28
+ }
29
+
30
+ const Comp = asChild ? Slot : Pressable;
31
+
32
+ return (
33
+ <Comp
34
+ ref={ref}
35
+ accessible={false}
36
+ importantForAccessibility="no-hide-descendants"
37
+ onPress={handlePress}
38
+ {...(props as PressableProps)}
39
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'drawer-overlay' }))}
40
+ />
41
+ );
42
+ },
43
+ );
44
+
45
+ DrawerOverlay.displayName = 'DrawerOverlay';
@@ -0,0 +1,25 @@
1
+ import { Portal } from '../portal';
2
+ import { DrawerProvider, useDrawer } from './context';
3
+ import type { IDrawerPortalProps } from './types';
4
+
5
+ export interface DrawerPortalProps extends IDrawerPortalProps {}
6
+
7
+ /**
8
+ * Mounts drawer children into the shared PortalHost. Re-provides DrawerContext
9
+ * inside the portal boundary (React context does not cross the portal store).
10
+ */
11
+ export function DrawerPortal({ forceMount = false, hostName, children }: DrawerPortalProps) {
12
+ const ctx = useDrawer();
13
+
14
+ if (!forceMount && !ctx.open) {
15
+ return null;
16
+ }
17
+
18
+ return (
19
+ <Portal name={`${ctx.nativeID}-portal`} hostName={hostName}>
20
+ <DrawerProvider value={ctx}>{children}</DrawerProvider>
21
+ </Portal>
22
+ );
23
+ }
24
+
25
+ DrawerPortal.displayName = 'DrawerPortal';
@@ -0,0 +1,70 @@
1
+ import { forwardRef, useCallback, useId, useMemo, type ComponentRef } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { useControllableState } from '@cdx-ui/utils';
4
+ import { Slot } from '../slot';
5
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
6
+ import { DrawerProvider } from './context';
7
+ import type { IDrawerRootProps } from './types';
8
+
9
+ export interface DrawerRootProps extends Omit<ViewProps, 'children' | 'role'>, IDrawerRootProps {
10
+ asChild?: boolean;
11
+ }
12
+
13
+ export const DrawerRoot = forwardRef<ComponentRef<typeof View>, DrawerRootProps>(
14
+ (
15
+ {
16
+ isOpen: openProp,
17
+ onOpenChange,
18
+ defaultOpen = false,
19
+ closeOnBackdropPress = true,
20
+ closeOnEscKey = true,
21
+ asChild,
22
+ children,
23
+ ...props
24
+ },
25
+ ref,
26
+ ) => {
27
+ const [open, setOpenState] = useControllableState<boolean>({
28
+ prop: openProp,
29
+ defaultProp: defaultOpen,
30
+ onChange: onOpenChange,
31
+ });
32
+
33
+ const setOpen = useCallback(
34
+ (newOpen: boolean) => {
35
+ setOpenState(newOpen);
36
+ },
37
+ [setOpenState],
38
+ );
39
+
40
+ const id = useId();
41
+ const nativeID = `drawer-${id}`;
42
+
43
+ const contextValue = useMemo(
44
+ () => ({
45
+ open: open ?? false,
46
+ onOpenChange: setOpen,
47
+ closeOnBackdropPress,
48
+ closeOnEscKey,
49
+ nativeID,
50
+ }),
51
+ [open, setOpen, closeOnBackdropPress, closeOnEscKey, nativeID],
52
+ );
53
+
54
+ const Comp = asChild ? Slot : View;
55
+
56
+ return (
57
+ <DrawerProvider value={contextValue}>
58
+ <Comp
59
+ ref={ref}
60
+ {...(props as ViewProps)}
61
+ {...mergeDataAttributes(props, dataAttributes({ state: open ? 'open' : 'closed' }))}
62
+ >
63
+ {children}
64
+ </Comp>
65
+ </DrawerProvider>
66
+ );
67
+ },
68
+ );
69
+
70
+ DrawerRoot.displayName = 'DrawerRoot';
@@ -0,0 +1,4 @@
1
+ import { createContext } from '@cdx-ui/utils';
2
+ import type { IDrawerContextType } from './types';
3
+
4
+ export const [DrawerProvider, useDrawer] = createContext<IDrawerContextType>('DrawerContext');
@@ -0,0 +1,12 @@
1
+ export { DrawerProvider, useDrawer } from './context';
2
+ export { DrawerRoot, type DrawerRootProps } from './DrawerRoot';
3
+ export { DrawerPortal, type DrawerPortalProps } from './DrawerPortal';
4
+ export { DrawerOverlay, type DrawerOverlayProps } from './DrawerOverlay';
5
+ export { DrawerClose, type DrawerCloseProps } from './DrawerClose';
6
+ export type {
7
+ IDrawerContextType,
8
+ IDrawerRootProps,
9
+ IDrawerPortalProps,
10
+ IDrawerOverlayProps,
11
+ IDrawerCloseProps,
12
+ } from './types';
@@ -0,0 +1,36 @@
1
+ import type React from 'react';
2
+ import type { PressableProps } from 'react-native';
3
+
4
+ export interface IDrawerContextType {
5
+ readonly open: boolean;
6
+ readonly onOpenChange: (open: boolean) => void;
7
+ readonly closeOnBackdropPress: boolean;
8
+ readonly closeOnEscKey: boolean;
9
+ readonly nativeID: string;
10
+ }
11
+
12
+ export interface IDrawerRootProps {
13
+ /** Controlled open state. Use `defaultOpen` for the uncontrolled variant. */
14
+ readonly isOpen?: boolean;
15
+ readonly onOpenChange?: (open: boolean) => void;
16
+ readonly defaultOpen?: boolean;
17
+ readonly closeOnBackdropPress?: boolean;
18
+ readonly closeOnEscKey?: boolean;
19
+ readonly children: React.ReactNode;
20
+ }
21
+
22
+ export interface IDrawerPortalProps {
23
+ readonly forceMount?: boolean;
24
+ readonly hostName?: string;
25
+ readonly children: React.ReactNode;
26
+ }
27
+
28
+ export interface IDrawerOverlayProps extends PressableProps {
29
+ readonly asChild?: boolean;
30
+ readonly forceMount?: boolean;
31
+ }
32
+
33
+ export interface IDrawerCloseProps extends PressableProps {
34
+ readonly asChild?: boolean;
35
+ readonly accessibilityLabel?: string;
36
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from './button';
4
4
  export * from './chip';
5
5
  export * from './checkbox';
6
6
  export * from './dialog';
7
+ export * from './drawer';
7
8
  export * from './field';
8
9
  export * from './form';
9
10
  export * from './full-window-overlay';
@@ -71,9 +71,20 @@ export const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps
71
71
  const mergedRef = mergeRefs(ref, nodeRef);
72
72
  const onPressRef = useRef(onPress);
73
73
  onPressRef.current = onPress;
74
+ const actionConsumedRef = useRef(false);
75
+ const consumePress = useCallback(() => {
76
+ actionConsumedRef.current = true;
77
+ queueMicrotask(() => {
78
+ actionConsumedRef.current = false;
79
+ });
80
+ }, []);
74
81
 
75
82
  const invokePress = useCallback(
76
83
  (event: GestureResponderEvent) => {
84
+ if (actionConsumedRef.current) {
85
+ actionConsumedRef.current = false;
86
+ return;
87
+ }
77
88
  if (!disabled) {
78
89
  onPressRef.current?.(event);
79
90
  if (closeOnSelect) {
@@ -112,6 +123,8 @@ export const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps
112
123
  focusVisible: !!(isFocusVisibleProp || isFocusVisible),
113
124
  highlighted,
114
125
  checked: isChecked,
126
+ itemRole,
127
+ consumePress,
115
128
  }),
116
129
  [
117
130
  isHoveredProp,
@@ -125,6 +138,8 @@ export const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps
125
138
  isFocusVisible,
126
139
  highlighted,
127
140
  isChecked,
141
+ itemRole,
142
+ consumePress,
128
143
  ],
129
144
  );
130
145
 
@@ -0,0 +1,42 @@
1
+ import { forwardRef, type ComponentRef } from 'react';
2
+ import { Platform, View } from 'react-native';
3
+ import { composeEventHandlers } from '@cdx-ui/utils';
4
+ import { Slot } from '../slot';
5
+ import { useOptionalMenuItemContext } from './context';
6
+ import type { IMenuItemActionProps } from './types';
7
+
8
+ export interface MenuItemActionProps extends IMenuItemActionProps {}
9
+
10
+ export const MenuItemAction = forwardRef<ComponentRef<typeof View>, MenuItemActionProps>(
11
+ ({ asChild, children, onTouchStart, ...props }, ref) => {
12
+ const menuItem = useOptionalMenuItemContext();
13
+ const Comp = asChild ? Slot : View;
14
+ const { onClick, ...rest } = props as typeof props & {
15
+ onClick?: (event: { stopPropagation: () => void }) => void;
16
+ };
17
+
18
+ const isolatePress = () => {
19
+ menuItem?.consumePress();
20
+ };
21
+
22
+ return (
23
+ <Comp
24
+ ref={ref}
25
+ {...rest}
26
+ onTouchStart={composeEventHandlers(onTouchStart, isolatePress)}
27
+ {...(Platform.OS === 'web'
28
+ ? {
29
+ onClick: composeEventHandlers(onClick, (event: { stopPropagation: () => void }) => {
30
+ isolatePress();
31
+ event.stopPropagation();
32
+ }),
33
+ }
34
+ : {})}
35
+ >
36
+ {children}
37
+ </Comp>
38
+ );
39
+ },
40
+ );
41
+
42
+ MenuItemAction.displayName = 'MenuItemAction';
@@ -14,7 +14,7 @@ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
14
14
  import { MenuProvider } from './context';
15
15
  import { useOptionalMenuOverlayContext } from './overlayContext';
16
16
  import type { IMenuProps, RegisteredMenuItem } from './types';
17
- import { useMenuNavigation } from './useMenuNavigation';
17
+ import { useMenuNavigation, type MenuNavigationKeyEvent } from './useMenuNavigation';
18
18
  import { useMenuOverlayOpenFocus } from './useMenuOverlayLifecycle';
19
19
 
20
20
  function getItemsInRenderedOrder(
@@ -100,15 +100,31 @@ export const MenuRoot = forwardRef<ComponentRef<typeof View>, MenuRootProps>(
100
100
  onEscape?.();
101
101
  }, [overlay, onEscape]);
102
102
 
103
- const handleTab = useCallback(() => {
104
- if (!overlay) {
105
- return;
106
- }
107
- if (Platform.OS === 'web') {
108
- (overlay.triggerRef.current as { focus?: () => void } | null)?.focus?.();
109
- }
110
- overlay.closeMenu('tab');
111
- }, [overlay]);
103
+ const handleTab = useCallback(
104
+ (event: MenuNavigationKeyEvent) => {
105
+ if (!overlay) {
106
+ return;
107
+ }
108
+
109
+ if (Platform.OS === 'web') {
110
+ const tabbables = getInMenuTabbables(overlay.contentRef.current);
111
+ const index = resolveTabbableIndex(tabbables, event.target);
112
+ const nextIndex = index + (event.shiftKey ? -1 : 1);
113
+ const next = index === -1 ? undefined : tabbables[nextIndex];
114
+ if (next) {
115
+ event.preventDefault();
116
+ next.focus();
117
+ return;
118
+ }
119
+
120
+ event.preventDefault();
121
+ (overlay.triggerRef.current as { focus?: () => void } | null)?.focus?.();
122
+ }
123
+
124
+ overlay.closeMenu('tab');
125
+ },
126
+ [overlay],
127
+ );
112
128
 
113
129
  const { handleKeyDown } = useMenuNavigation({
114
130
  getItems,
@@ -188,3 +204,28 @@ export const MenuRoot = forwardRef<ComponentRef<typeof View>, MenuRootProps>(
188
204
  );
189
205
 
190
206
  MenuRoot.displayName = 'MenuRoot';
207
+
208
+ function getInMenuTabbables(container: unknown): HTMLElement[] {
209
+ if (typeof HTMLElement === 'undefined' || !(container instanceof HTMLElement)) {
210
+ return [];
211
+ }
212
+
213
+ return Array.from(
214
+ container.querySelectorAll<HTMLElement>('button, [href], input, select, textarea, [tabindex]'),
215
+ ).filter((element) => {
216
+ if (element.tabIndex < 0) {
217
+ return false;
218
+ }
219
+ if ('disabled' in element && (element as HTMLButtonElement).disabled) {
220
+ return false;
221
+ }
222
+ return element.getAttribute('aria-hidden') !== 'true';
223
+ });
224
+ }
225
+
226
+ function resolveTabbableIndex(tabbables: HTMLElement[], target: EventTarget | null): number {
227
+ if (!(target instanceof HTMLElement)) {
228
+ return -1;
229
+ }
230
+ return tabbables.findIndex((element) => element === target || element.contains(target));
231
+ }
package/src/menu/index.ts CHANGED
@@ -6,6 +6,7 @@ export type {
6
6
  IMenuProps,
7
7
  IMenuRadioGroupProps,
8
8
  IMenuRadioItemProps,
9
+ IMenuItemActionProps,
9
10
  IMenuTriggerProps,
10
11
  MenuCloseReason,
11
12
  MenuContextValue,
@@ -34,3 +35,4 @@ export { MenuItem, type MenuItemProps } from './MenuItem';
34
35
  export { MenuCheckboxItem, type MenuCheckboxItemProps } from './MenuCheckboxItem';
35
36
  export { MenuRadioGroup, type MenuRadioGroupProps } from './MenuRadioGroup';
36
37
  export { MenuRadioItem, type MenuRadioItemProps } from './MenuRadioItem';
38
+ export { MenuItemAction, type MenuItemActionProps } from './MenuItemAction';
package/src/menu/types.ts CHANGED
@@ -72,6 +72,13 @@ export interface IMenuItemProps extends PressableProps {
72
72
  export interface MenuItemInteractionState extends InteractionState {
73
73
  highlighted: boolean;
74
74
  checked: boolean;
75
+ itemRole: 'menuitem' | 'menuitemcheckbox' | 'menuitemradio';
76
+ consumePress: () => void;
77
+ }
78
+
79
+ export interface IMenuItemActionProps extends ViewProps {
80
+ asChild?: boolean;
81
+ children?: ReactNode;
75
82
  }
76
83
 
77
84
  export interface IMenuCheckboxItemProps extends Omit<
@@ -3,12 +3,20 @@ import type { RegisteredMenuItem } from './types';
3
3
 
4
4
  // Web-only — native platforms rely on OS-managed sequential focus.
5
5
 
6
+ export interface MenuNavigationKeyEvent {
7
+ key: string;
8
+ shiftKey?: boolean;
9
+ target: EventTarget | null;
10
+ preventDefault: () => void;
11
+ stopPropagation?: () => void;
12
+ }
13
+
6
14
  export interface UseMenuNavigationOptions {
7
15
  getItems: () => [string, RegisteredMenuItem][];
8
16
  focusedItemId: string | undefined;
9
17
  setFocusedItemId: (itemId: string | undefined) => void;
10
18
  onEscape?: () => void;
11
- onTab?: () => void;
19
+ onTab?: (event: MenuNavigationKeyEvent) => void;
12
20
  }
13
21
 
14
22
  export function useMenuNavigation({
@@ -37,7 +45,7 @@ export function useMenuNavigation({
37
45
  );
38
46
 
39
47
  const handleKeyDown = useCallback(
40
- (e: { key: string; preventDefault: () => void; stopPropagation?: () => void }) => {
48
+ (e: MenuNavigationKeyEvent) => {
41
49
  if (e.key === 'Escape') {
42
50
  e.preventDefault();
43
51
  e.stopPropagation?.();
@@ -45,7 +53,7 @@ export function useMenuNavigation({
45
53
  return;
46
54
  }
47
55
  if (e.key === 'Tab') {
48
- onTabRef.current?.();
56
+ onTabRef.current?.(e);
49
57
  return;
50
58
  }
51
59