@seed-design/lynx-react 0.6.2 → 0.7.0

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 (236) hide show
  1. package/lib/components/Accordion/Accordion.d.ts.map +1 -1
  2. package/lib/components/Accordion/Accordion.jsx +24 -14
  3. package/lib/components/ActionButton/ActionButton.d.ts +1 -1
  4. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
  5. package/lib/components/ActionButton/ActionButton.jsx +12 -4
  6. package/lib/components/AppBar/AppBar.d.ts.map +1 -1
  7. package/lib/components/AppBar/AppBar.jsx +9 -8
  8. package/lib/components/Badge/Badge.d.ts.map +1 -1
  9. package/lib/components/Badge/Badge.jsx +2 -1
  10. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  11. package/lib/components/BottomSheet/BottomSheet.jsx +9 -9
  12. package/lib/components/Box/Box.d.ts.map +1 -1
  13. package/lib/components/Box/Box.jsx +2 -1
  14. package/lib/components/Callout/Callout.d.ts +2 -2
  15. package/lib/components/Callout/Callout.d.ts.map +1 -1
  16. package/lib/components/Callout/Callout.jsx +20 -10
  17. package/lib/components/Checkbox/Checkbox.d.ts +4 -2
  18. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
  19. package/lib/components/Checkbox/Checkbox.jsx +33 -17
  20. package/lib/components/Chip/Chip.d.ts +1 -1
  21. package/lib/components/Chip/Chip.d.ts.map +1 -1
  22. package/lib/components/Chip/Chip.jsx +36 -16
  23. package/lib/components/Count/Count.d.ts +7 -0
  24. package/lib/components/Count/Count.d.ts.map +1 -0
  25. package/lib/components/Count/Count.jsx +19 -0
  26. package/lib/components/Count/index.d.ts +2 -0
  27. package/lib/components/Count/index.d.ts.map +1 -0
  28. package/lib/components/Divider/Divider.d.ts.map +1 -1
  29. package/lib/components/Divider/Divider.jsx +2 -1
  30. package/lib/components/Field/Field.d.ts.map +1 -1
  31. package/lib/components/Field/Field.jsx +11 -15
  32. package/lib/components/Icon/Icon.d.ts.map +1 -1
  33. package/lib/components/Icon/Icon.jsx +4 -3
  34. package/lib/components/InputButton/InputButton.d.ts +40 -0
  35. package/lib/components/InputButton/InputButton.d.ts.map +1 -0
  36. package/lib/components/InputButton/InputButton.jsx +118 -0
  37. package/lib/components/InputButton/InputButton.namespace.d.ts +2 -0
  38. package/lib/components/InputButton/InputButton.namespace.d.ts.map +1 -0
  39. package/lib/components/InputButton/InputButton.namespace.js +16 -0
  40. package/lib/components/InputButton/index.d.ts +3 -0
  41. package/lib/components/InputButton/index.d.ts.map +1 -0
  42. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts.map +1 -1
  43. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.jsx +12 -8
  44. package/lib/components/List/List.d.ts +51 -0
  45. package/lib/components/List/List.d.ts.map +1 -0
  46. package/lib/components/List/List.jsx +176 -0
  47. package/lib/components/List/List.namespace.d.ts +2 -0
  48. package/lib/components/List/List.namespace.d.ts.map +1 -0
  49. package/lib/components/List/List.namespace.js +18 -0
  50. package/lib/components/List/index.d.ts +3 -0
  51. package/lib/components/List/index.d.ts.map +1 -0
  52. package/lib/components/MannerTemp/MannerTemp.d.ts.map +1 -1
  53. package/lib/components/MannerTemp/MannerTemp.jsx +3 -2
  54. package/lib/components/MannerTempBadge/MannerTempBadge.d.ts.map +1 -1
  55. package/lib/components/MannerTempBadge/MannerTempBadge.jsx +2 -1
  56. package/lib/components/Menu/Menu.d.ts +69 -0
  57. package/lib/components/Menu/Menu.d.ts.map +1 -0
  58. package/lib/components/Menu/Menu.jsx +520 -0
  59. package/lib/components/Menu/Menu.namespace.d.ts +2 -0
  60. package/lib/components/Menu/Menu.namespace.d.ts.map +1 -0
  61. package/lib/components/Menu/Menu.namespace.js +21 -0
  62. package/lib/components/Menu/index.d.ts +3 -0
  63. package/lib/components/Menu/index.d.ts.map +1 -0
  64. package/lib/components/NotificationBadge/NotificationBadge.d.ts.map +1 -1
  65. package/lib/components/NotificationBadge/NotificationBadge.jsx +3 -2
  66. package/lib/components/PageBanner/PageBanner.d.ts +5 -5
  67. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  68. package/lib/components/PageBanner/PageBanner.jsx +25 -14
  69. package/lib/components/ProgressCircle/ProgressCircle.d.ts +2 -2
  70. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
  71. package/lib/components/ProgressCircle/ProgressCircle.jsx +4 -3
  72. package/lib/components/RadioGroup/RadioGroup.d.ts +5 -3
  73. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  74. package/lib/components/RadioGroup/RadioGroup.jsx +22 -9
  75. package/lib/components/ReactionButton/ReactionButton.d.ts +20 -0
  76. package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -0
  77. package/lib/components/ReactionButton/ReactionButton.jsx +88 -0
  78. package/lib/components/ReactionButton/index.d.ts +2 -0
  79. package/lib/components/ReactionButton/index.d.ts.map +1 -0
  80. package/lib/components/ScaleFeedback/ScaleFeedback.d.ts +11 -0
  81. package/lib/components/ScaleFeedback/ScaleFeedback.d.ts.map +1 -0
  82. package/lib/components/ScaleFeedback/ScaleFeedback.jsx +16 -0
  83. package/lib/components/ScaleFeedback/index.d.ts +2 -0
  84. package/lib/components/ScaleFeedback/index.d.ts.map +1 -0
  85. package/lib/components/SegmentedControl/SegmentedControl.d.ts +1 -0
  86. package/lib/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  87. package/lib/components/SegmentedControl/SegmentedControl.jsx +23 -10
  88. package/lib/components/SelectBox/SelectBox.d.ts.map +1 -1
  89. package/lib/components/SelectBox/SelectBox.jsx +23 -13
  90. package/lib/components/Stack/Stack.d.ts.map +1 -1
  91. package/lib/components/Stack/Stack.jsx +2 -1
  92. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts +85 -0
  93. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -0
  94. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +256 -0
  95. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.d.ts +2 -0
  96. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.d.ts.map +1 -0
  97. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.js +24 -0
  98. package/lib/components/SwipeableMenuSheet/index.d.ts +3 -0
  99. package/lib/components/SwipeableMenuSheet/index.d.ts.map +1 -0
  100. package/lib/components/Switch/Switch.d.ts +27 -3
  101. package/lib/components/Switch/Switch.d.ts.map +1 -1
  102. package/lib/components/Switch/Switch.jsx +22 -9
  103. package/lib/components/Tabs/Tabs.d.ts +4 -2
  104. package/lib/components/Tabs/Tabs.d.ts.map +1 -1
  105. package/lib/components/Tabs/Tabs.jsx +37 -22
  106. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
  107. package/lib/components/TagGroup/TagGroup.jsx +4 -3
  108. package/lib/components/Text/Text.d.ts.map +1 -1
  109. package/lib/components/Text/Text.jsx +2 -1
  110. package/lib/components/TextField/TextField.d.ts.map +1 -1
  111. package/lib/components/TextField/TextField.jsx +35 -23
  112. package/lib/components/index.d.ts +7 -0
  113. package/lib/components/index.d.ts.map +1 -1
  114. package/lib/components/private/Positioning.d.ts +69 -0
  115. package/lib/components/private/Positioning.d.ts.map +1 -0
  116. package/lib/components/private/Positioning.js +109 -0
  117. package/lib/components/private/TrackPositioning.d.ts +22 -0
  118. package/lib/components/private/TrackPositioning.d.ts.map +1 -0
  119. package/lib/contexts/ScaleFeedbackContentContext.d.ts +4 -0
  120. package/lib/contexts/ScaleFeedbackContentContext.d.ts.map +1 -0
  121. package/lib/contexts/ScaleFeedbackContentContext.js +6 -0
  122. package/lib/contexts/index.d.ts +2 -0
  123. package/lib/contexts/index.d.ts.map +1 -0
  124. package/lib/hooks/index.d.ts +1 -0
  125. package/lib/hooks/index.d.ts.map +1 -1
  126. package/lib/hooks/useIconColor.d.ts.map +1 -1
  127. package/lib/hooks/useIconColor.js +8 -3
  128. package/lib/hooks/usePressTap.d.ts +4 -3
  129. package/lib/hooks/usePressTap.d.ts.map +1 -1
  130. package/lib/hooks/useScaleFeedback.d.ts +48 -0
  131. package/lib/hooks/useScaleFeedback.d.ts.map +1 -0
  132. package/lib/hooks/useScaleFeedback.js +108 -0
  133. package/lib/index.d.ts +1 -0
  134. package/lib/index.d.ts.map +1 -1
  135. package/lib/index.js +17 -3
  136. package/lib/node_modules/.bun/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +493 -0
  137. package/lib/node_modules/.bun/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +114 -0
  138. package/lib/utils/calculate-scale-feedback.d.ts +5 -0
  139. package/lib/utils/calculate-scale-feedback.d.ts.map +1 -0
  140. package/lib/utils/calculate-scale-feedback.js +13 -0
  141. package/lib/utils/compose-event-handlers.d.ts +5 -0
  142. package/lib/utils/compose-event-handlers.d.ts.map +1 -0
  143. package/lib/utils/compose-event-handlers.js +16 -0
  144. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  145. package/lib/utils/create-slot-recipe-context.jsx +6 -16
  146. package/lib/utils/index.d.ts +1 -0
  147. package/lib/utils/index.d.ts.map +1 -1
  148. package/lib/utils/merge-props.d.ts +15 -0
  149. package/lib/utils/merge-props.d.ts.map +1 -0
  150. package/lib/utils/merge-props.js +36 -0
  151. package/lib/utils/merge-refs.d.ts +8 -0
  152. package/lib/utils/merge-refs.d.ts.map +1 -0
  153. package/lib/utils/merge-refs.js +38 -0
  154. package/package.json +6 -5
  155. package/src/components/Accordion/Accordion.tsx +36 -27
  156. package/src/components/ActionButton/ActionButton.tsx +27 -12
  157. package/src/components/AppBar/AppBar.tsx +17 -18
  158. package/src/components/Badge/Badge.tsx +2 -2
  159. package/src/components/BottomSheet/BottomSheet.tsx +9 -17
  160. package/src/components/Box/Box.tsx +2 -2
  161. package/src/components/Callout/Callout.tsx +39 -22
  162. package/src/components/Checkbox/Checkbox.test.tsx +44 -0
  163. package/src/components/Checkbox/Checkbox.tsx +52 -21
  164. package/src/components/Chip/Chip.test.tsx +50 -1
  165. package/src/components/Chip/Chip.tsx +75 -31
  166. package/src/components/Count/Count.tsx +35 -0
  167. package/src/components/Count/index.ts +1 -0
  168. package/src/components/Divider/Divider.tsx +2 -2
  169. package/src/components/Field/Field.tsx +15 -28
  170. package/src/components/Icon/Icon.test.tsx +4 -6
  171. package/src/components/Icon/Icon.tsx +14 -7
  172. package/src/components/InputButton/InputButton.namespace.ts +20 -0
  173. package/src/components/InputButton/InputButton.test.tsx +82 -0
  174. package/src/components/InputButton/InputButton.tsx +295 -0
  175. package/src/components/InputButton/index.ts +2 -0
  176. package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +16 -21
  177. package/src/components/List/List.namespace.ts +24 -0
  178. package/src/components/List/List.test.tsx +162 -0
  179. package/src/components/List/List.tsx +476 -0
  180. package/src/components/List/index.ts +3 -0
  181. package/src/components/MannerTemp/MannerTemp.tsx +3 -4
  182. package/src/components/MannerTempBadge/MannerTempBadge.tsx +2 -2
  183. package/src/components/Menu/Menu.namespace.ts +26 -0
  184. package/src/components/Menu/Menu.tsx +883 -0
  185. package/src/components/Menu/index.ts +31 -0
  186. package/src/components/NotificationBadge/NotificationBadge.tsx +4 -9
  187. package/src/components/PageBanner/PageBanner.test.tsx +3 -0
  188. package/src/components/PageBanner/PageBanner.tsx +54 -45
  189. package/src/components/ProgressCircle/ProgressCircle.test.tsx +48 -0
  190. package/src/components/ProgressCircle/ProgressCircle.tsx +22 -3
  191. package/src/components/RadioGroup/RadioGroup.test.tsx +87 -0
  192. package/src/components/RadioGroup/RadioGroup.tsx +50 -14
  193. package/src/components/ReactionButton/ReactionButton.test.tsx +122 -0
  194. package/src/components/ReactionButton/ReactionButton.tsx +139 -0
  195. package/src/components/ReactionButton/index.ts +1 -0
  196. package/src/components/ScaleFeedback/ScaleFeedback.test.tsx +41 -0
  197. package/src/components/ScaleFeedback/ScaleFeedback.tsx +32 -0
  198. package/src/components/ScaleFeedback/index.ts +1 -0
  199. package/src/components/SegmentedControl/SegmentedControl.test.tsx +15 -3
  200. package/src/components/SegmentedControl/SegmentedControl.tsx +35 -14
  201. package/src/components/SelectBox/SelectBox.test.tsx +39 -1
  202. package/src/components/SelectBox/SelectBox.tsx +27 -16
  203. package/src/components/Stack/Stack.tsx +2 -2
  204. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.ts +37 -0
  205. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +258 -0
  206. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +655 -0
  207. package/src/components/SwipeableMenuSheet/index.ts +43 -0
  208. package/src/components/Switch/Switch.test.tsx +57 -0
  209. package/src/components/Switch/Switch.tsx +60 -16
  210. package/src/components/Tabs/Tabs.test.tsx +19 -0
  211. package/src/components/Tabs/Tabs.tsx +72 -40
  212. package/src/components/TagGroup/TagGroup.tsx +4 -6
  213. package/src/components/Text/Text.tsx +2 -2
  214. package/src/components/TextField/TextField.tsx +60 -54
  215. package/src/components/index.ts +7 -0
  216. package/src/components/private/Positioning.test.ts +235 -0
  217. package/src/components/private/Positioning.ts +223 -0
  218. package/src/components/private/TrackPositioning.test.ts +58 -0
  219. package/src/components/private/TrackPositioning.ts +37 -0
  220. package/src/contexts/ScaleFeedbackContentContext.ts +4 -0
  221. package/src/contexts/index.ts +1 -0
  222. package/src/hooks/index.ts +1 -0
  223. package/src/hooks/useIconColor.test.ts +4 -4
  224. package/src/hooks/useIconColor.ts +13 -6
  225. package/src/hooks/usePressTap.ts +4 -3
  226. package/src/hooks/useScaleFeedback.integration.test.tsx +85 -0
  227. package/src/hooks/useScaleFeedback.test.ts +50 -0
  228. package/src/hooks/useScaleFeedback.ts +199 -0
  229. package/src/index.ts +1 -0
  230. package/src/utils/calculate-scale-feedback.ts +12 -0
  231. package/src/utils/compose-event-handlers.ts +16 -0
  232. package/src/utils/create-slot-recipe-context.tsx +6 -7
  233. package/src/utils/index.ts +1 -0
  234. package/src/utils/merge-props.test.tsx +152 -0
  235. package/src/utils/merge-props.ts +62 -0
  236. package/src/utils/merge-refs.ts +44 -0
@@ -0,0 +1,883 @@
1
+ import * as React from "@lynx-js/react";
2
+ import { getRectByRef } from "@lynx-js/lynx-ui-common";
3
+ import type { BaseEvent, NodesRef } from "@lynx-js/types";
4
+ import clsx from "clsx";
5
+
6
+ import { menu, type MenuVariantProps } from "@seed-design/lynx-css/recipes/menu";
7
+ import { menuItem, type MenuItemVariantProps } from "@seed-design/lynx-css/recipes/menu-item";
8
+ import { menu as menuVars } from "@seed-design/lynx-css/vars/component";
9
+
10
+ import { useControllableState } from "../../hooks/useControllableState";
11
+ import { usePressTap } from "../../hooks/usePressTap";
12
+ import type {
13
+ LynxAccessibilityProps,
14
+ LynxPressableProps,
15
+ LynxStyledElementProps,
16
+ LynxTextRef,
17
+ LynxViewProps,
18
+ LynxViewRef,
19
+ } from "../../types";
20
+ import { toArray } from "../../utils/children";
21
+ import { IconSlotProvider, PrefixIcon, SuffixIcon } from "../Icon/Icon";
22
+ import {
23
+ computePosition,
24
+ type Placement as MenuPlacement,
25
+ type Position as MenuPosition,
26
+ type Rect as MenuRect,
27
+ } from "../private/Positioning";
28
+
29
+ type MenuClassNames = {
30
+ positioner: string;
31
+ backdrop: string;
32
+ content: string;
33
+ scrollArea: string;
34
+ scrollContent: string;
35
+ group: string;
36
+ groupLabel: string;
37
+ separator: string;
38
+ };
39
+ type MenuItemClassNames = {
40
+ root: string;
41
+ pressedOverlay: string;
42
+ body: string;
43
+ label: string;
44
+ description: string;
45
+ prefixIcon: string;
46
+ suffixIcon: string;
47
+ };
48
+ type MenuPublicVariantProps = Omit<MenuVariantProps, "open" | "positioned" | "size"> & {
49
+ size?: "small" | "medium" | "responsive";
50
+ };
51
+ type MenuItemPublicVariantProps = Omit<MenuItemVariantProps, "size" | "disabled" | "pressed">;
52
+ type NativeTapHandler = NonNullable<LynxViewProps["bindtap"]>;
53
+ type NativeTransitionHandler = NonNullable<LynxViewProps["bindtransitionend"]>;
54
+ type NativeLayoutHandler = NonNullable<LynxViewProps["bindlayoutchange"]>;
55
+ type MenuTriggerHandlers = Pick<LynxViewProps, "bindtap" | "main-thread:bindtap">;
56
+
57
+ const menuMaxHeight = Number.parseFloat(menuVars.base.enabled.root.maxHeight);
58
+ const menuMinimumHeight = 200;
59
+
60
+ export type MenuOpenChangeReason = "trigger" | "interactOutside" | "itemClick" | "dismiss";
61
+
62
+ export interface MenuOpenChangeDetails {
63
+ reason: MenuOpenChangeReason;
64
+ event: BaseEvent;
65
+ }
66
+
67
+ interface MenuContextValue {
68
+ open: boolean;
69
+ mounted: boolean;
70
+ openEpoch: number;
71
+ disabled: boolean;
72
+ size: "small" | "medium";
73
+ placement: MenuPlacement;
74
+ gutter: number;
75
+ overflowPadding: number;
76
+ matchReferenceWidth: boolean;
77
+ isOpenRef: React.MutableRefObject<boolean>;
78
+ openEpochRef: React.MutableRefObject<number>;
79
+ anchorRef: React.MutableRefObject<NodesRef | null>;
80
+ triggerRef: React.MutableRefObject<NodesRef | null>;
81
+ triggerHandlers: MenuTriggerHandlers;
82
+ setTriggerHandlers: (handlers: MenuTriggerHandlers) => void;
83
+ classes: MenuClassNames;
84
+ positioned: boolean;
85
+ setPositioned: (positioned: boolean) => void;
86
+ requestOpen: (open: boolean, details: MenuOpenChangeDetails) => void;
87
+ finishClose: (immediate?: boolean) => void;
88
+ }
89
+
90
+ const MenuContext = React.createContext<MenuContextValue | null>(null);
91
+ const MenuItemClassNamesContext = React.createContext<MenuItemClassNames | null>(null);
92
+ const MenuGroupPositionContext = React.createContext({ isFirst: true });
93
+
94
+ function useMenuContext(consumer: string): MenuContextValue {
95
+ const context = React.useContext(MenuContext);
96
+ if (!context) throw new Error(`<${consumer}/> must be rendered inside <MenuRoot/>.`);
97
+ return context;
98
+ }
99
+
100
+ function useMenuItemClassNames(consumer: string): MenuItemClassNames {
101
+ const context = React.useContext(MenuItemClassNamesContext);
102
+ if (!context) throw new Error(`<${consumer}/> must be rendered inside <MenuItem/>.`);
103
+ return context;
104
+ }
105
+
106
+ function mergeNodeRef(forwardedRef: React.ForwardedRef<unknown>, node: NodesRef | null) {
107
+ if (typeof forwardedRef === "function") {
108
+ forwardedRef(node);
109
+ } else if (forwardedRef) {
110
+ forwardedRef.current = node;
111
+ }
112
+ }
113
+
114
+ function getScreenRect(): MenuRect | null {
115
+ const systemInfo = typeof SystemInfo === "undefined" ? undefined : SystemInfo;
116
+ const pixelWidth = systemInfo?.pixelWidth;
117
+ const pixelHeight = systemInfo?.pixelHeight;
118
+ const pixelRatio = systemInfo?.pixelRatio;
119
+ if (
120
+ typeof pixelWidth !== "number" ||
121
+ typeof pixelHeight !== "number" ||
122
+ typeof pixelRatio !== "number" ||
123
+ pixelWidth <= 0 ||
124
+ pixelHeight <= 0 ||
125
+ pixelRatio <= 0
126
+ ) {
127
+ return null;
128
+ }
129
+
130
+ const width = pixelWidth / pixelRatio;
131
+ const height = pixelHeight / pixelRatio;
132
+ return { left: 0, top: 0, right: width, bottom: height, width, height };
133
+ }
134
+
135
+ function getRootRect() {
136
+ "background only";
137
+ return getRectByRef({ current: lynx.createSelectorQuery().selectRoot() }, true);
138
+ }
139
+
140
+ function toPixel(value: number) {
141
+ return `${value}px`;
142
+ }
143
+
144
+ function getLayoutSize(
145
+ event: Parameters<NativeLayoutHandler>[0],
146
+ ): { width: number; height: number } | null {
147
+ const width = event.detail?.width ?? event.params?.width;
148
+ const height = event.detail?.height ?? event.params?.height;
149
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
150
+ return { width: Math.max(0, width), height: Math.max(0, height) };
151
+ }
152
+
153
+ function hasExitTransition(event: Parameters<NativeTransitionHandler>[0]): boolean {
154
+ if (event.target.uid !== event.currentTarget.uid) return false;
155
+ return (
156
+ event.params.animation_type === "transition-opacity" ||
157
+ event.params.animation_name === "opacity"
158
+ );
159
+ }
160
+
161
+ ////////////////////////////////////////////////////////////////////////////////////
162
+
163
+ export interface MenuRootProps extends MenuPublicVariantProps, LynxStyledElementProps {
164
+ open?: boolean;
165
+ defaultOpen?: boolean;
166
+ onOpenChange?: (open: boolean, details: MenuOpenChangeDetails) => void;
167
+ disabled?: boolean;
168
+ placement?: MenuPlacement;
169
+ gutter?: number;
170
+ overflowPadding?: number;
171
+ matchReferenceWidth?: boolean;
172
+ }
173
+
174
+ /**
175
+ * @platform Lynx
176
+ *
177
+ * Lynx menu supports native tap and accessibility semantics. It does not expose
178
+ * web DOM focus, keyboard navigation, typeahead, `asChild`, or nested submenus.
179
+ */
180
+ export const MenuRoot = React.forwardRef<unknown, MenuRootProps>((props, ref) => {
181
+ const { size: sizeProp = "medium", open: openProp, ...restProps } = props;
182
+ const [variantProps, otherProps] = menu.splitVariantProps({
183
+ ...restProps,
184
+ size: sizeProp === "responsive" ? undefined : sizeProp,
185
+ });
186
+ const {
187
+ children,
188
+ className,
189
+ defaultOpen = false,
190
+ onOpenChange,
191
+ disabled = false,
192
+ placement = "bottom",
193
+ gutter = 8,
194
+ overflowPadding = 8,
195
+ matchReferenceWidth = false,
196
+ ...nativeProps
197
+ } = otherProps;
198
+ const [open, setOpen] = useControllableState({ value: openProp, defaultValue: defaultOpen });
199
+ const [mounted, setMounted] = React.useState(open);
200
+ const [positionedEpoch, setPositionedEpoch] = React.useState<number | null>(null);
201
+ const isOpenRef = React.useRef(open);
202
+ const openEpochRef = React.useRef(0);
203
+ if (open && !isOpenRef.current) openEpochRef.current++;
204
+ isOpenRef.current = open;
205
+ const openEpoch = openEpochRef.current;
206
+ const positioned = positionedEpoch === openEpoch;
207
+ const setPositioned = React.useCallback(
208
+ (nextPositioned: boolean) => setPositionedEpoch(nextPositioned ? openEpochRef.current : null),
209
+ [],
210
+ );
211
+ const [triggerHandlers, setTriggerHandlers] = React.useState<MenuTriggerHandlers>({});
212
+ const anchorRef = React.useRef<NodesRef | null>(null);
213
+ const triggerRef = React.useRef<NodesRef | null>(null);
214
+
215
+ React.useEffect(() => {
216
+ "background only";
217
+ if (open) {
218
+ setMounted(true);
219
+ return;
220
+ }
221
+ if (!positioned) setMounted(false);
222
+ }, [open, positioned]);
223
+
224
+ const requestOpen = React.useCallback(
225
+ (nextOpen: boolean, details: MenuOpenChangeDetails) => {
226
+ "background only";
227
+ if (disabled && nextOpen) return;
228
+ if (nextOpen === open) return;
229
+ setOpen(nextOpen);
230
+ onOpenChange?.(nextOpen, details);
231
+ },
232
+ [disabled, onOpenChange, open, setOpen],
233
+ );
234
+
235
+ const finishClose = React.useCallback((immediate = false) => {
236
+ "background only";
237
+ if (immediate || !isOpenRef.current) {
238
+ setMounted(false);
239
+ setPositionedEpoch(null);
240
+ }
241
+ }, []);
242
+
243
+ const screenRect = getScreenRect();
244
+ const resolvedSize =
245
+ sizeProp === "responsive" && screenRect?.width != null && screenRect.width >= 1280
246
+ ? "small"
247
+ : (variantProps.size ?? "medium");
248
+ const classes = menu({ size: resolvedSize, open, positioned });
249
+ const contextValue = React.useMemo<MenuContextValue>(
250
+ () => ({
251
+ open,
252
+ mounted,
253
+ openEpoch,
254
+ disabled,
255
+ size: resolvedSize,
256
+ placement,
257
+ gutter,
258
+ overflowPadding,
259
+ matchReferenceWidth,
260
+ isOpenRef,
261
+ openEpochRef,
262
+ anchorRef,
263
+ triggerRef,
264
+ triggerHandlers,
265
+ setTriggerHandlers,
266
+ classes,
267
+ positioned,
268
+ setPositioned,
269
+ requestOpen,
270
+ finishClose,
271
+ }),
272
+ [
273
+ classes,
274
+ disabled,
275
+ finishClose,
276
+ gutter,
277
+ matchReferenceWidth,
278
+ mounted,
279
+ openEpoch,
280
+ setPositioned,
281
+ openEpochRef,
282
+ triggerHandlers,
283
+ resolvedSize,
284
+ open,
285
+ overflowPadding,
286
+ placement,
287
+ positioned,
288
+ requestOpen,
289
+ ],
290
+ );
291
+
292
+ return (
293
+ <MenuContext.Provider value={contextValue}>
294
+ <view {...(ref ? { ref: ref as LynxViewRef } : {})} className={className} {...nativeProps}>
295
+ {children}
296
+ </view>
297
+ </MenuContext.Provider>
298
+ );
299
+ });
300
+ MenuRoot.displayName = "MenuRoot";
301
+
302
+ ////////////////////////////////////////////////////////////////////////////////////
303
+
304
+ export interface MenuAnchorProps extends LynxStyledElementProps {}
305
+
306
+ export const MenuAnchor = React.forwardRef<unknown, MenuAnchorProps>((props, ref) => {
307
+ const { children, className, ...nativeProps } = props;
308
+ const context = useMenuContext("MenuAnchor");
309
+ const handleRef = React.useCallback(
310
+ (node: NodesRef | null) => {
311
+ context.anchorRef.current = node;
312
+ mergeNodeRef(ref, node);
313
+ },
314
+ [context.anchorRef, ref],
315
+ );
316
+
317
+ return (
318
+ <view ref={handleRef as LynxViewRef} className={className} {...nativeProps}>
319
+ {children}
320
+ </view>
321
+ );
322
+ });
323
+ MenuAnchor.displayName = "MenuAnchor";
324
+
325
+ ////////////////////////////////////////////////////////////////////////////////////
326
+
327
+ export interface MenuTriggerProps
328
+ extends LynxStyledElementProps,
329
+ LynxPressableProps,
330
+ LynxAccessibilityProps {
331
+ disabled?: boolean;
332
+ }
333
+
334
+ export const MenuTrigger = React.forwardRef<unknown, MenuTriggerProps>((props, ref) => {
335
+ const {
336
+ children,
337
+ className,
338
+ bindtap,
339
+ "main-thread:bindtap": mainThreadBindtap,
340
+ disabled: disabledProp = false,
341
+ "accessibility-element": accessibilityElement = true,
342
+ "accessibility-label": accessibilityLabel,
343
+ "accessibility-traits": accessibilityTraits,
344
+ ...nativeProps
345
+ } = props;
346
+ const context = useMenuContext("MenuTrigger");
347
+ const disabled = context.disabled || disabledProp;
348
+ const handleTap = React.useCallback<NativeTapHandler>(
349
+ (event, instance) => {
350
+ "background only";
351
+ context.requestOpen(!context.open, { reason: "trigger", event });
352
+ bindtap?.(event, instance);
353
+ },
354
+ [bindtap, context],
355
+ );
356
+ const { bindtap: proxyBindtap, ...pressHandlers } = usePressTap({
357
+ disabled,
358
+ onTap: handleTap,
359
+ mainThreadOnTap: mainThreadBindtap,
360
+ });
361
+ const mainThreadProxyBindtap = pressHandlers["main-thread:bindtap"];
362
+ React.useEffect(() => {
363
+ "background only";
364
+ context.setTriggerHandlers({
365
+ bindtap: proxyBindtap,
366
+ "main-thread:bindtap": mainThreadProxyBindtap,
367
+ });
368
+ return () => context.setTriggerHandlers({});
369
+ }, [context.setTriggerHandlers, mainThreadProxyBindtap, proxyBindtap]);
370
+ const handleRef = React.useCallback(
371
+ (node: NodesRef | null) => {
372
+ context.triggerRef.current = node;
373
+ mergeNodeRef(ref, node);
374
+ },
375
+ [context.triggerRef, ref],
376
+ );
377
+
378
+ return (
379
+ <view
380
+ ref={handleRef as LynxViewRef}
381
+ className={className}
382
+ accessibility-element={accessibilityElement}
383
+ accessibility-label={accessibilityLabel}
384
+ accessibility-role-description="button"
385
+ accessibility-value={context.open ? "expanded" : "collapsed"}
386
+ accessibility-traits={disabled ? "disabled" : (accessibilityTraits ?? "button")}
387
+ {...nativeProps}
388
+ {...pressHandlers}
389
+ bindtap={proxyBindtap}
390
+ >
391
+ {children}
392
+ </view>
393
+ );
394
+ });
395
+ MenuTrigger.displayName = "MenuTrigger";
396
+
397
+ ////////////////////////////////////////////////////////////////////////////////////
398
+
399
+ export interface MenuContentProps extends LynxStyledElementProps {}
400
+
401
+ export const MenuContent = React.forwardRef<unknown, MenuContentProps>((props, ref) => {
402
+ const { children, className, style, ...nativeProps } = props;
403
+ const context = useMenuContext("MenuContent");
404
+ const measurementVersionRef = React.useRef(0);
405
+ const intrinsicWidthRef = React.useRef<number | null>(null);
406
+ const [overlayNode, setOverlayNode] = React.useState<NodesRef | null>(null);
407
+ const measurementConfigRef = React.useRef(0);
408
+ const [intrinsicSize, setIntrinsicSize] = React.useState<{
409
+ width: number;
410
+ height: number;
411
+ epoch: number;
412
+ config: number;
413
+ } | null>(null);
414
+ const [position, setPosition] = React.useState<MenuPosition | null>(null);
415
+ const [widthConstraint, setWidthConstraint] = React.useState<number | null>(null);
416
+ const [overlayRect, setOverlayRect] = React.useState<MenuRect | null>(null);
417
+ const [triggerRect, setTriggerRect] = React.useState<MenuRect | null>(null);
418
+
419
+ const measurePosition = React.useCallback(async () => {
420
+ "background only";
421
+ const referenceNode = context.anchorRef.current ?? context.triggerRef.current;
422
+ const triggerNode = context.triggerRef.current;
423
+ const openEpoch = context.openEpoch;
424
+ if (
425
+ !context.open ||
426
+ !referenceNode ||
427
+ !overlayNode ||
428
+ !intrinsicSize ||
429
+ intrinsicSize.epoch !== openEpoch ||
430
+ intrinsicSize.config !== measurementConfigRef.current
431
+ ) {
432
+ return;
433
+ }
434
+ const version = ++measurementVersionRef.current;
435
+ try {
436
+ const [reference, boundary, overlay, trigger] = await Promise.all([
437
+ getRectByRef({ current: referenceNode }, true),
438
+ getRootRect(),
439
+ getRectByRef({ current: overlayNode }, true),
440
+ triggerNode ? getRectByRef({ current: triggerNode }, true) : Promise.resolve(null),
441
+ ]);
442
+ if (
443
+ version !== measurementVersionRef.current ||
444
+ !context.isOpenRef.current ||
445
+ openEpoch !== context.openEpochRef.current
446
+ ) {
447
+ return;
448
+ }
449
+ const intrinsicWidth = context.matchReferenceWidth
450
+ ? reference.width
451
+ : (intrinsicWidthRef.current ?? intrinsicSize.width);
452
+ const width = widthConstraint ?? intrinsicWidth;
453
+ const nextPosition = await computePosition({
454
+ reference,
455
+ boundary,
456
+ width,
457
+ height: Math.min(intrinsicSize.height, menuMaxHeight),
458
+ placement: context.placement,
459
+ gutter: context.gutter,
460
+ overflowPadding: context.overflowPadding,
461
+ flip: { fallbackStrategy: "bestFit" },
462
+ shift: { crossAxis: true },
463
+ size: { order: "beforeFlip", minimumHeight: menuMinimumHeight },
464
+ });
465
+ if (
466
+ version !== measurementVersionRef.current ||
467
+ !context.isOpenRef.current ||
468
+ openEpoch !== context.openEpochRef.current
469
+ ) {
470
+ return;
471
+ }
472
+ if (nextPosition.availableWidth < width) {
473
+ // 너비를 제한한 뒤 bindlayoutchange에서 줄바꿈된 높이를 다시 측정합니다.
474
+ const constrainedWidth = nextPosition.availableWidth;
475
+ if (widthConstraint !== constrainedWidth) {
476
+ setWidthConstraint(constrainedWidth);
477
+ setIntrinsicSize(null);
478
+ setPosition(null);
479
+ setOverlayRect(null);
480
+ setTriggerRect(null);
481
+ context.setPositioned(false);
482
+ }
483
+ return;
484
+ }
485
+ setPosition((previous) => {
486
+ if (
487
+ previous?.left === nextPosition.left &&
488
+ previous.top === nextPosition.top &&
489
+ previous.width === nextPosition.width &&
490
+ previous.height === nextPosition.height &&
491
+ previous.placement === nextPosition.placement
492
+ ) {
493
+ return previous;
494
+ }
495
+ return nextPosition;
496
+ });
497
+ setOverlayRect(overlay);
498
+ setTriggerRect(trigger);
499
+ context.setPositioned(true);
500
+ } catch {
501
+ // A ref can disappear while the native query is in flight. Keep this close hidden.
502
+ }
503
+ }, [context, intrinsicSize, overlayNode, widthConstraint]);
504
+
505
+ React.useEffect(() => {
506
+ "background only";
507
+ measurementVersionRef.current++;
508
+ setWidthConstraint(null);
509
+ setPosition(null);
510
+ setOverlayRect(null);
511
+ setTriggerRect(null);
512
+ setIntrinsicSize((current) => (current ? { ...current, epoch: context.openEpoch } : current));
513
+ }, [context.openEpoch]);
514
+
515
+ React.useEffect(() => {
516
+ "background only";
517
+ measurementVersionRef.current++;
518
+ intrinsicWidthRef.current = null;
519
+ measurementConfigRef.current++;
520
+ setWidthConstraint(null);
521
+ setIntrinsicSize((current) =>
522
+ current ? { ...current, config: measurementConfigRef.current } : current,
523
+ );
524
+ setPosition(null);
525
+ setOverlayRect(null);
526
+ setTriggerRect(null);
527
+ context.setPositioned(false);
528
+ }, [className, context.setPositioned, context.size, style]);
529
+
530
+ React.useEffect(() => {
531
+ "background only";
532
+ if (context.open) void measurePosition();
533
+ }, [context.open, measurePosition]);
534
+
535
+ const handleShowOverlay = React.useCallback(() => {
536
+ "background only";
537
+ void measurePosition();
538
+ }, [measurePosition]);
539
+
540
+ const handleRef = React.useCallback(
541
+ (node: NodesRef | null) => {
542
+ measurementVersionRef.current++;
543
+ mergeNodeRef(ref, node);
544
+ },
545
+ [ref],
546
+ );
547
+ const handlePositionerRef = React.useCallback((node: NodesRef | null) => {
548
+ setOverlayNode(node);
549
+ }, []);
550
+ const handleIntrinsicLayoutChange = React.useCallback<NativeLayoutHandler>(
551
+ (event) => {
552
+ "background only";
553
+ const nextSize = getLayoutSize(event);
554
+ if (!nextSize || !context.open) return;
555
+ if (intrinsicWidthRef.current === null) intrinsicWidthRef.current = nextSize.width;
556
+ setIntrinsicSize((current) => {
557
+ const nextWidth = intrinsicWidthRef.current ?? nextSize.width;
558
+ if (
559
+ current?.width === nextWidth &&
560
+ current.height === nextSize.height &&
561
+ current.epoch === context.openEpoch &&
562
+ current.config === measurementConfigRef.current
563
+ ) {
564
+ return current;
565
+ }
566
+ return {
567
+ width: nextWidth,
568
+ height: nextSize.height,
569
+ epoch: context.openEpoch,
570
+ config: measurementConfigRef.current,
571
+ };
572
+ });
573
+ },
574
+ [context.open, context.openEpoch],
575
+ );
576
+ const handleTransitionEnd = React.useCallback<NativeTransitionHandler>(
577
+ (event) => {
578
+ "background only";
579
+ if (!context.open && hasExitTransition(event)) context.finishClose();
580
+ },
581
+ [context],
582
+ );
583
+ const handleBackdropTap = React.useCallback<NativeTapHandler>(
584
+ (event) => {
585
+ "background only";
586
+ context.requestOpen(false, { reason: "interactOutside", event });
587
+ },
588
+ [context],
589
+ );
590
+ const handleNativeDismiss = React.useCallback(
591
+ (event: BaseEvent) => {
592
+ "background only";
593
+ context.requestOpen(false, { reason: "dismiss", event });
594
+ context.finishClose(true);
595
+ },
596
+ [context],
597
+ );
598
+ const handleRequestClose = React.useCallback(
599
+ (event: BaseEvent) => {
600
+ "background only";
601
+ context.requestOpen(false, { reason: "dismiss", event });
602
+ },
603
+ [context],
604
+ );
605
+
606
+ if (!context.mounted) return null;
607
+
608
+ const geometryStyle =
609
+ position && overlayRect
610
+ ? {
611
+ left: toPixel(position.left - overlayRect.left),
612
+ top: toPixel(position.top - overlayRect.top),
613
+ width: toPixel(position.width),
614
+ transformOrigin: position.transformOrigin,
615
+ }
616
+ : undefined;
617
+ const scrollStyle = position ? { height: toPixel(position.height) } : undefined;
618
+ const triggerProxyStyle =
619
+ triggerRect && overlayRect
620
+ ? {
621
+ left: toPixel(triggerRect.left - overlayRect.left),
622
+ top: toPixel(triggerRect.top - overlayRect.top),
623
+ width: toPixel(triggerRect.width),
624
+ height: toPixel(triggerRect.height),
625
+ }
626
+ : undefined;
627
+ const childNodes = toArray(children);
628
+
629
+ return (
630
+ <overlay
631
+ visible
632
+ style={{ position: "fixed" }}
633
+ binddismissoverlay={handleNativeDismiss}
634
+ bindshowoverlay={handleShowOverlay}
635
+ bindrequestclose={handleRequestClose}
636
+ >
637
+ <view ref={handlePositionerRef as LynxViewRef} className={context.classes.positioner}>
638
+ <view className={context.classes.backdrop} bindtap={handleBackdropTap} />
639
+ {triggerProxyStyle && (
640
+ <view
641
+ style={{ position: "absolute", ...triggerProxyStyle }}
642
+ bindtap={context.triggerHandlers.bindtap}
643
+ main-thread:bindtap={context.triggerHandlers["main-thread:bindtap"]}
644
+ />
645
+ )}
646
+ <view
647
+ ref={handleRef as LynxViewRef}
648
+ className={clsx(context.classes.content, className)}
649
+ style={{
650
+ ...geometryStyle,
651
+ ...style,
652
+ ...(widthConstraint != null ? { width: toPixel(widthConstraint) } : {}),
653
+ }}
654
+ bindtransitionend={handleTransitionEnd}
655
+ {...nativeProps}
656
+ >
657
+ <scroll-view
658
+ className={context.classes.scrollArea}
659
+ scroll-orientation="vertical"
660
+ style={scrollStyle}
661
+ >
662
+ <view
663
+ className={context.classes.scrollContent}
664
+ bindlayoutchange={handleIntrinsicLayoutChange}
665
+ >
666
+ {childNodes.map((child, index) => (
667
+ <MenuGroupPositionContext.Provider
668
+ key={React.isValidElement(child) ? (child.key ?? index) : index}
669
+ value={{ isFirst: index === 0 }}
670
+ >
671
+ {child}
672
+ </MenuGroupPositionContext.Provider>
673
+ ))}
674
+ </view>
675
+ </scroll-view>
676
+ </view>
677
+ </view>
678
+ </overlay>
679
+ );
680
+ });
681
+ MenuContent.displayName = "MenuContent";
682
+
683
+ ////////////////////////////////////////////////////////////////////////////////////
684
+
685
+ /** The scroll viewport is supplied by `MenuContent`; this slot styles custom scroll content only. */
686
+ export interface MenuScrollAreaProps extends LynxStyledElementProps {}
687
+
688
+ export const MenuScrollArea = React.forwardRef<unknown, MenuScrollAreaProps>((props, ref) => {
689
+ const { children, className, ...nativeProps } = props;
690
+ const classes = useMenuContext("MenuScrollArea").classes;
691
+ return (
692
+ <scroll-view
693
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
694
+ className={clsx(classes.scrollArea, className)}
695
+ scroll-orientation="vertical"
696
+ {...nativeProps}
697
+ >
698
+ <view className={classes.scrollContent}>{children}</view>
699
+ </scroll-view>
700
+ );
701
+ });
702
+ MenuScrollArea.displayName = "MenuScrollArea";
703
+
704
+ ////////////////////////////////////////////////////////////////////////////////////
705
+
706
+ export interface MenuGroupProps extends LynxStyledElementProps {}
707
+
708
+ export const MenuGroup = React.forwardRef<unknown, MenuGroupProps>((props, ref) => {
709
+ const { children, className, ...nativeProps } = props;
710
+ const classes = useMenuContext("MenuGroup").classes;
711
+ const position = React.useContext(MenuGroupPositionContext);
712
+ return (
713
+ <view
714
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
715
+ className={clsx(classes.group, className)}
716
+ {...nativeProps}
717
+ >
718
+ {!position.isFirst ? (
719
+ <view className={classes.separator} accessibility-elements-hidden={true} />
720
+ ) : null}
721
+ {children}
722
+ </view>
723
+ );
724
+ });
725
+ MenuGroup.displayName = "MenuGroup";
726
+
727
+ ////////////////////////////////////////////////////////////////////////////////////
728
+
729
+ export interface MenuGroupLabelProps extends LynxStyledElementProps, LynxAccessibilityProps {}
730
+
731
+ export const MenuGroupLabel = React.forwardRef<unknown, MenuGroupLabelProps>((props, ref) => {
732
+ const {
733
+ children,
734
+ className,
735
+ "accessibility-heading": accessibilityHeading = true,
736
+ ...nativeProps
737
+ } = props;
738
+ const classes = useMenuContext("MenuGroupLabel").classes;
739
+ return (
740
+ <text
741
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
742
+ className={clsx(classes.groupLabel, className)}
743
+ accessibility-heading={accessibilityHeading}
744
+ {...nativeProps}
745
+ >
746
+ {children}
747
+ </text>
748
+ );
749
+ });
750
+ MenuGroupLabel.displayName = "MenuGroupLabel";
751
+
752
+ ////////////////////////////////////////////////////////////////////////////////////
753
+
754
+ export interface MenuItemProps
755
+ extends MenuItemPublicVariantProps,
756
+ LynxStyledElementProps,
757
+ LynxPressableProps,
758
+ LynxAccessibilityProps {
759
+ disabled?: boolean;
760
+ }
761
+
762
+ export const MenuItem = React.forwardRef<unknown, MenuItemProps>((props, ref) => {
763
+ const [variantProps, otherProps] = menuItem.splitVariantProps(props);
764
+ const { disabled: disabledProp = false, tone = "neutral" } = variantProps;
765
+ const {
766
+ children,
767
+ className,
768
+ bindtap,
769
+ "main-thread:bindtap": mainThreadBindtap,
770
+ "accessibility-element": accessibilityElement = true,
771
+ "accessibility-label": accessibilityLabel,
772
+ "accessibility-traits": accessibilityTraits,
773
+ ...nativeProps
774
+ } = otherProps;
775
+ const context = useMenuContext("MenuItem");
776
+ const disabled = context.disabled || disabledProp;
777
+ const handleTap = React.useCallback<NativeTapHandler>(
778
+ (event, instance) => {
779
+ "background only";
780
+ bindtap?.(event, instance);
781
+ context.requestOpen(false, { reason: "itemClick", event });
782
+ },
783
+ [bindtap, context],
784
+ );
785
+ const { pressed, ...pressHandlers } = usePressTap({
786
+ disabled,
787
+ onTap: handleTap,
788
+ mainThreadOnTap: mainThreadBindtap,
789
+ });
790
+ const classes = menuItem({
791
+ ...variantProps,
792
+ size: context.size,
793
+ tone,
794
+ disabled,
795
+ pressed,
796
+ });
797
+ const iconSlots = React.useMemo(
798
+ () => ({
799
+ classNames: { prefixIcon: classes.prefixIcon, suffixIcon: classes.suffixIcon },
800
+ deps: [classes.prefixIcon, classes.suffixIcon],
801
+ }),
802
+ [classes.prefixIcon, classes.suffixIcon],
803
+ );
804
+
805
+ return (
806
+ <MenuItemClassNamesContext.Provider value={classes}>
807
+ <IconSlotProvider value={iconSlots}>
808
+ <view
809
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
810
+ className={clsx(classes.root, className)}
811
+ accessibility-element={accessibilityElement}
812
+ accessibility-label={accessibilityLabel}
813
+ accessibility-role-description="button"
814
+ accessibility-traits={disabled ? "disabled" : (accessibilityTraits ?? "button")}
815
+ {...nativeProps}
816
+ {...pressHandlers}
817
+ >
818
+ <view className={classes.pressedOverlay} accessibility-elements-hidden={true} />
819
+ {children}
820
+ </view>
821
+ </IconSlotProvider>
822
+ </MenuItemClassNamesContext.Provider>
823
+ );
824
+ });
825
+ MenuItem.displayName = "MenuItem";
826
+
827
+ ////////////////////////////////////////////////////////////////////////////////////
828
+
829
+ export interface MenuItemBodyProps extends LynxStyledElementProps {}
830
+
831
+ export const MenuItemBody = React.forwardRef<unknown, MenuItemBodyProps>((props, ref) => {
832
+ const { children, className, ...nativeProps } = props;
833
+ const classes = useMenuItemClassNames("MenuItemBody");
834
+ return (
835
+ <view
836
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
837
+ className={clsx(classes.body, className)}
838
+ {...nativeProps}
839
+ >
840
+ {children}
841
+ </view>
842
+ );
843
+ });
844
+ MenuItemBody.displayName = "MenuItemBody";
845
+
846
+ export interface MenuItemLabelProps extends LynxStyledElementProps {}
847
+
848
+ export const MenuItemLabel = React.forwardRef<unknown, MenuItemLabelProps>((props, ref) => {
849
+ const { children, className, ...nativeProps } = props;
850
+ const classes = useMenuItemClassNames("MenuItemLabel");
851
+ return (
852
+ <text
853
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
854
+ className={clsx(classes.label, className)}
855
+ {...nativeProps}
856
+ >
857
+ {children}
858
+ </text>
859
+ );
860
+ });
861
+ MenuItemLabel.displayName = "MenuItemLabel";
862
+
863
+ export interface MenuItemDescriptionProps extends LynxStyledElementProps {}
864
+
865
+ export const MenuItemDescription = React.forwardRef<unknown, MenuItemDescriptionProps>(
866
+ (props, ref) => {
867
+ const { children, className, ...nativeProps } = props;
868
+ const classes = useMenuItemClassNames("MenuItemDescription");
869
+ return (
870
+ <text
871
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
872
+ className={clsx(classes.description, className)}
873
+ {...nativeProps}
874
+ >
875
+ {children}
876
+ </text>
877
+ );
878
+ },
879
+ );
880
+ MenuItemDescription.displayName = "MenuItemDescription";
881
+
882
+ export { PrefixIcon as MenuPrefixIcon, SuffixIcon as MenuSuffixIcon };
883
+ export type { MenuPlacement };