@seed-design/lynx-react 0.7.1 → 0.9.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 (265) hide show
  1. package/lib/components/Accordion/Accordion.jsx +2 -2
  2. package/lib/components/ActionButton/ActionButton.jsx +1 -1
  3. package/lib/components/AlertDialog/AlertDialog.d.ts +64 -0
  4. package/lib/components/AlertDialog/AlertDialog.d.ts.map +1 -0
  5. package/lib/components/AlertDialog/AlertDialog.jsx +138 -0
  6. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts +2 -0
  7. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts.map +1 -0
  8. package/lib/components/AlertDialog/AlertDialog.namespace.js +17 -0
  9. package/lib/components/AlertDialog/index.d.ts +3 -0
  10. package/lib/components/AlertDialog/index.d.ts.map +1 -0
  11. package/lib/components/AppBar/AppBar.d.ts +14 -1
  12. package/lib/components/AppBar/AppBar.d.ts.map +1 -1
  13. package/lib/components/AppBar/AppBar.jsx +42 -16
  14. package/lib/components/AppBar/AppBar.namespace.d.ts +1 -1
  15. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -1
  16. package/lib/components/AppBar/context.d.ts +2 -1
  17. package/lib/components/AppBar/context.d.ts.map +1 -1
  18. package/lib/components/AppBar/index.d.ts +1 -1
  19. package/lib/components/AppBar/index.d.ts.map +1 -1
  20. package/lib/components/AppBar/useAppBar.d.ts +2 -1
  21. package/lib/components/AppBar/useAppBar.d.ts.map +1 -1
  22. package/lib/components/AppBar/useAppBar.js +17 -15
  23. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts +182 -0
  24. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts.map +1 -0
  25. package/lib/components/AttachmentDisplay/AttachmentDisplay.jsx +444 -0
  26. package/lib/components/AttachmentDisplay/index.d.ts +2 -0
  27. package/lib/components/AttachmentDisplay/index.d.ts.map +1 -0
  28. package/lib/components/AttachmentInput/AttachmentInput.d.ts +179 -0
  29. package/lib/components/AttachmentInput/AttachmentInput.d.ts.map +1 -0
  30. package/lib/components/AttachmentInput/AttachmentInput.jsx +520 -0
  31. package/lib/components/AttachmentInput/index.d.ts +2 -0
  32. package/lib/components/AttachmentInput/index.d.ts.map +1 -0
  33. package/lib/components/BottomSheet/BottomSheet.d.ts +1 -1
  34. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  35. package/lib/components/BottomSheet/BottomSheet.jsx +1 -7
  36. package/lib/components/Box/Box.d.ts +3 -3
  37. package/lib/components/Box/Box.d.ts.map +1 -1
  38. package/lib/components/Callout/Callout.jsx +2 -2
  39. package/lib/components/Checkbox/Checkbox.jsx +2 -2
  40. package/lib/components/Chip/Chip.jsx +2 -2
  41. package/lib/components/ChipTabs/ChipTabs.d.ts +41 -0
  42. package/lib/components/ChipTabs/ChipTabs.d.ts.map +1 -0
  43. package/lib/components/ChipTabs/ChipTabs.jsx +39 -0
  44. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts +2 -0
  45. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts.map +1 -0
  46. package/lib/components/ChipTabs/ChipTabs.namespace.js +13 -0
  47. package/lib/components/ChipTabs/index.d.ts +3 -0
  48. package/lib/components/ChipTabs/index.d.ts.map +1 -0
  49. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts +18 -0
  50. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts.map +1 -0
  51. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.jsx +113 -0
  52. package/lib/components/ContextualFloatingButton/index.d.ts +2 -0
  53. package/lib/components/ContextualFloatingButton/index.d.ts.map +1 -0
  54. package/lib/components/Dialog/Dialog.d.ts +87 -0
  55. package/lib/components/Dialog/Dialog.d.ts.map +1 -0
  56. package/lib/components/Dialog/Dialog.jsx +147 -0
  57. package/lib/components/Dialog/Dialog.namespace.d.ts +2 -0
  58. package/lib/components/Dialog/Dialog.namespace.d.ts.map +1 -0
  59. package/lib/components/Dialog/Dialog.namespace.js +18 -0
  60. package/lib/components/Dialog/index.d.ts +3 -0
  61. package/lib/components/Dialog/index.d.ts.map +1 -0
  62. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts +23 -0
  63. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts.map +1 -0
  64. package/lib/components/FloatingActionButton/FloatingActionButton.jsx +76 -0
  65. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts +2 -0
  66. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts.map +1 -0
  67. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.js +10 -0
  68. package/lib/components/FloatingActionButton/index.d.ts +3 -0
  69. package/lib/components/FloatingActionButton/index.d.ts.map +1 -0
  70. package/lib/components/HelpBubble/HelpBubble.d.ts +67 -0
  71. package/lib/components/HelpBubble/HelpBubble.d.ts.map +1 -0
  72. package/lib/components/HelpBubble/HelpBubble.jsx +600 -0
  73. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts +2 -0
  74. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts.map +1 -0
  75. package/lib/components/HelpBubble/HelpBubble.namespace.js +18 -0
  76. package/lib/components/HelpBubble/index.d.ts +3 -0
  77. package/lib/components/HelpBubble/index.d.ts.map +1 -0
  78. package/lib/components/Icon/Icon.d.ts +1 -0
  79. package/lib/components/Icon/Icon.d.ts.map +1 -1
  80. package/lib/components/Icon/Icon.jsx +2 -1
  81. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts +28 -0
  82. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts.map +1 -0
  83. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.jsx +37 -0
  84. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts +2 -0
  85. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts.map +1 -0
  86. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.js +9 -0
  87. package/lib/components/IdentityPlaceholder/identity-placeholder-business.js +4 -0
  88. package/lib/components/IdentityPlaceholder/identity-placeholder-person.js +4 -0
  89. package/lib/components/IdentityPlaceholder/index.d.ts +3 -0
  90. package/lib/components/IdentityPlaceholder/index.d.ts.map +1 -0
  91. package/lib/components/InputButton/InputButton.d.ts +2 -0
  92. package/lib/components/InputButton/InputButton.d.ts.map +1 -1
  93. package/lib/components/InputButton/InputButton.jsx +40 -8
  94. package/lib/components/List/List.d.ts +1 -1
  95. package/lib/components/List/List.d.ts.map +1 -1
  96. package/lib/components/List/List.jsx +26 -12
  97. package/lib/components/Menu/Menu.d.ts.map +1 -1
  98. package/lib/components/Menu/Menu.jsx +15 -5
  99. package/lib/components/PageBanner/PageBanner.d.ts +3 -0
  100. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  101. package/lib/components/PageBanner/PageBanner.jsx +49 -11
  102. package/lib/components/QuantityPicker/QuantityPicker.d.ts +53 -0
  103. package/lib/components/QuantityPicker/QuantityPicker.d.ts.map +1 -0
  104. package/lib/components/QuantityPicker/QuantityPicker.jsx +282 -0
  105. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts +2 -0
  106. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts.map +1 -0
  107. package/lib/components/QuantityPicker/QuantityPicker.namespace.js +11 -0
  108. package/lib/components/QuantityPicker/index.d.ts +3 -0
  109. package/lib/components/QuantityPicker/index.d.ts.map +1 -0
  110. package/lib/components/RadioGroup/RadioGroup.jsx +2 -2
  111. package/lib/components/ReactionButton/ReactionButton.d.ts +1 -1
  112. package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -1
  113. package/lib/components/ReactionButton/ReactionButton.jsx +22 -13
  114. package/lib/components/ScrollFog/ScrollFog.d.ts +41 -0
  115. package/lib/components/ScrollFog/ScrollFog.d.ts.map +1 -0
  116. package/lib/components/ScrollFog/ScrollFog.jsx +65 -0
  117. package/lib/components/ScrollFog/index.d.ts +2 -0
  118. package/lib/components/ScrollFog/index.d.ts.map +1 -0
  119. package/lib/components/SegmentedControl/SegmentedControl.jsx +2 -2
  120. package/lib/components/Select/Select.d.ts +112 -0
  121. package/lib/components/Select/Select.d.ts.map +1 -0
  122. package/lib/components/Select/Select.jsx +885 -0
  123. package/lib/components/Select/Select.namespace.d.ts +2 -0
  124. package/lib/components/Select/Select.namespace.d.ts.map +1 -0
  125. package/lib/components/Select/Select.namespace.js +23 -0
  126. package/lib/components/Select/index.d.ts +3 -0
  127. package/lib/components/Select/index.d.ts.map +1 -0
  128. package/lib/components/Skeleton/Skeleton.d.ts +15 -0
  129. package/lib/components/Skeleton/Skeleton.d.ts.map +1 -0
  130. package/lib/components/Skeleton/Skeleton.jsx +18 -0
  131. package/lib/components/Skeleton/index.d.ts +2 -0
  132. package/lib/components/Skeleton/index.d.ts.map +1 -0
  133. package/lib/components/Slider/Slider.d.ts +101 -0
  134. package/lib/components/Slider/Slider.d.ts.map +1 -0
  135. package/lib/components/Slider/Slider.jsx +573 -0
  136. package/lib/components/Slider/Slider.namespace.d.ts +2 -0
  137. package/lib/components/Slider/Slider.namespace.d.ts.map +1 -0
  138. package/lib/components/Slider/Slider.namespace.js +19 -0
  139. package/lib/components/Slider/Slider.utils.d.ts +19 -0
  140. package/lib/components/Slider/Slider.utils.d.ts.map +1 -0
  141. package/lib/components/Slider/Slider.utils.js +106 -0
  142. package/lib/components/Slider/index.d.ts +3 -0
  143. package/lib/components/Slider/index.d.ts.map +1 -0
  144. package/lib/components/Stack/Stack.d.ts +7 -9
  145. package/lib/components/Stack/Stack.d.ts.map +1 -1
  146. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -1
  147. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +22 -8
  148. package/lib/components/Switch/Switch.d.ts.map +1 -1
  149. package/lib/components/Switch/Switch.jsx +5 -3
  150. package/lib/components/Tabs/Tabs.d.ts +34 -2
  151. package/lib/components/Tabs/Tabs.d.ts.map +1 -1
  152. package/lib/components/Tabs/Tabs.jsx +186 -56
  153. package/lib/components/Tabs/Tabs.utils.d.ts +14 -1
  154. package/lib/components/Tabs/Tabs.utils.d.ts.map +1 -1
  155. package/lib/components/Tabs/Tabs.utils.js +24 -3
  156. package/lib/components/Tabs/index.d.ts +1 -1
  157. package/lib/components/Tabs/index.d.ts.map +1 -1
  158. package/lib/components/index.d.ts +14 -0
  159. package/lib/components/index.d.ts.map +1 -1
  160. package/lib/hooks/useControllableState.d.ts +1 -14
  161. package/lib/hooks/useControllableState.d.ts.map +1 -1
  162. package/lib/hooks/usePressTap.d.ts +1 -27
  163. package/lib/hooks/usePressTap.d.ts.map +1 -1
  164. package/lib/hooks/useSafeArea.d.ts +8 -4
  165. package/lib/hooks/useSafeArea.d.ts.map +1 -1
  166. package/lib/hooks/useSafeArea.js +22 -20
  167. package/lib/index.js +29 -6
  168. package/lib/{hooks/useControllableState.js → packages/lynx-react-headless/use-controllable-state/lib/index.js} +2 -3
  169. package/lib/{hooks/usePressTap.js → packages/lynx-react-headless/use-press-tap/lib/index.js} +13 -10
  170. package/lib/types.d.ts +1 -0
  171. package/lib/types.d.ts.map +1 -1
  172. package/lib/utils/styled.d.ts +46 -6
  173. package/lib/utils/styled.d.ts.map +1 -1
  174. package/lib/utils/styled.js +19 -4
  175. package/package.json +9 -5
  176. package/src/components/AlertDialog/AlertDialog.namespace.ts +22 -0
  177. package/src/components/AlertDialog/AlertDialog.test.tsx +226 -0
  178. package/src/components/AlertDialog/AlertDialog.tsx +374 -0
  179. package/src/components/AlertDialog/index.ts +24 -0
  180. package/src/components/AppBar/AppBar.namespace.ts +1 -0
  181. package/src/components/AppBar/AppBar.test.tsx +252 -3
  182. package/src/components/AppBar/AppBar.tsx +108 -25
  183. package/src/components/AppBar/context.ts +3 -1
  184. package/src/components/AppBar/index.ts +1 -0
  185. package/src/components/AppBar/useAppBar.ts +21 -15
  186. package/src/components/AttachmentDisplay/AttachmentDisplay.tsx +886 -0
  187. package/src/components/AttachmentDisplay/index.ts +48 -0
  188. package/src/components/AttachmentInput/AttachmentInput.tsx +1001 -0
  189. package/src/components/AttachmentInput/index.ts +49 -0
  190. package/src/components/BottomSheet/BottomSheet.test.tsx +22 -7
  191. package/src/components/BottomSheet/BottomSheet.tsx +1 -19
  192. package/src/components/Box/Box.test.tsx +104 -0
  193. package/src/components/Box/Box.tsx +9 -6
  194. package/src/components/ChipTabs/ChipTabs.namespace.ts +14 -0
  195. package/src/components/ChipTabs/ChipTabs.tsx +119 -0
  196. package/src/components/ChipTabs/index.ts +16 -0
  197. package/src/components/ContextualFloatingButton/ContextualFloatingButton.test.tsx +63 -0
  198. package/src/components/ContextualFloatingButton/ContextualFloatingButton.tsx +232 -0
  199. package/src/components/ContextualFloatingButton/index.ts +4 -0
  200. package/src/components/Dialog/Dialog.namespace.ts +24 -0
  201. package/src/components/Dialog/Dialog.test.tsx +172 -0
  202. package/src/components/Dialog/Dialog.tsx +373 -0
  203. package/src/components/Dialog/index.ts +26 -0
  204. package/src/components/FloatingActionButton/FloatingActionButton.namespace.ts +8 -0
  205. package/src/components/FloatingActionButton/FloatingActionButton.test.tsx +59 -0
  206. package/src/components/FloatingActionButton/FloatingActionButton.tsx +207 -0
  207. package/src/components/FloatingActionButton/index.ts +10 -0
  208. package/src/components/HelpBubble/HelpBubble.namespace.ts +24 -0
  209. package/src/components/HelpBubble/HelpBubble.tsx +998 -0
  210. package/src/components/HelpBubble/index.ts +26 -0
  211. package/src/components/Icon/Icon.test.tsx +0 -3
  212. package/src/components/Icon/Icon.tsx +11 -1
  213. package/src/components/IdentityPlaceholder/IdentityPlaceholder.namespace.ts +6 -0
  214. package/src/components/IdentityPlaceholder/IdentityPlaceholder.tsx +103 -0
  215. package/src/components/IdentityPlaceholder/assets.d.ts +4 -0
  216. package/src/components/IdentityPlaceholder/identity-placeholder-business.webp +0 -0
  217. package/src/components/IdentityPlaceholder/identity-placeholder-person.webp +0 -0
  218. package/src/components/IdentityPlaceholder/index.ts +8 -0
  219. package/src/components/InputButton/InputButton.test.tsx +128 -1
  220. package/src/components/InputButton/InputButton.tsx +60 -9
  221. package/src/components/List/List.test.tsx +54 -2
  222. package/src/components/List/List.tsx +43 -10
  223. package/src/components/Menu/Menu.tsx +14 -4
  224. package/src/components/Menu/__tests__/Menu.test.tsx +34 -0
  225. package/src/components/PageBanner/PageBanner.test.tsx +110 -3
  226. package/src/components/PageBanner/PageBanner.tsx +83 -19
  227. package/src/components/QuantityPicker/QuantityPicker.namespace.ts +10 -0
  228. package/src/components/QuantityPicker/QuantityPicker.test.tsx +235 -0
  229. package/src/components/QuantityPicker/QuantityPicker.tsx +605 -0
  230. package/src/components/QuantityPicker/index.ts +14 -0
  231. package/src/components/ReactionButton/ReactionButton.feedback.test.tsx +50 -0
  232. package/src/components/ReactionButton/ReactionButton.test.tsx +5 -3
  233. package/src/components/ReactionButton/ReactionButton.tsx +20 -12
  234. package/src/components/ScrollFog/ScrollFog.test.tsx +222 -0
  235. package/src/components/ScrollFog/ScrollFog.tsx +172 -0
  236. package/src/components/ScrollFog/index.ts +1 -0
  237. package/src/components/Select/Select.namespace.ts +34 -0
  238. package/src/components/Select/Select.test.tsx +297 -0
  239. package/src/components/Select/Select.tsx +1483 -0
  240. package/src/components/Select/index.ts +39 -0
  241. package/src/components/Skeleton/Skeleton.test.tsx +64 -0
  242. package/src/components/Skeleton/Skeleton.tsx +45 -0
  243. package/src/components/Skeleton/index.ts +1 -0
  244. package/src/components/Slider/Slider.namespace.ts +26 -0
  245. package/src/components/Slider/Slider.test.tsx +260 -0
  246. package/src/components/Slider/Slider.tsx +897 -0
  247. package/src/components/Slider/Slider.utils.ts +167 -0
  248. package/src/components/Slider/index.ts +30 -0
  249. package/src/components/Stack/Stack.test.tsx +7 -0
  250. package/src/components/Stack/Stack.tsx +11 -10
  251. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +21 -1
  252. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +30 -7
  253. package/src/components/Switch/Switch.tsx +14 -13
  254. package/src/components/Tabs/Tabs.test.tsx +218 -1
  255. package/src/components/Tabs/Tabs.tsx +346 -75
  256. package/src/components/Tabs/Tabs.utils.ts +51 -1
  257. package/src/components/Tabs/index.ts +17 -1
  258. package/src/components/index.ts +14 -0
  259. package/src/hooks/useControllableState.ts +1 -43
  260. package/src/hooks/usePressTap.test.ts +23 -11
  261. package/src/hooks/usePressTap.ts +1 -60
  262. package/src/hooks/useSafeArea.test.ts +13 -1
  263. package/src/hooks/useSafeArea.ts +25 -23
  264. package/src/types.ts +1 -0
  265. package/src/utils/styled.ts +127 -9
@@ -5,6 +5,9 @@ import { list } from "@seed-design/lynx-css/recipes/list";
5
5
  import { listHeader, type ListHeaderVariantProps } from "@seed-design/lynx-css/recipes/list-header";
6
6
  import { listItem, type ListItemVariantProps } from "@seed-design/lynx-css/recipes/list-item";
7
7
 
8
+ import { ScaleFeedbackContentContext } from "../../contexts";
9
+ import { useScaleFeedback, type ScaleFeedbackTargetProps } from "../../hooks/useScaleFeedback";
10
+ import { mergeProps } from "../../utils/merge-props";
8
11
  import { usePressTap } from "../../hooks/usePressTap";
9
12
  import type {
10
13
  LynxAccessibilityProps,
@@ -24,7 +27,10 @@ import {
24
27
  } from "../RadioGroup/RadioGroup";
25
28
  import { SwitchRoot, type SwitchRootProps, useSwitchContext } from "../Switch/Switch";
26
29
 
27
- type PublicListItemVariantProps = Omit<ListItemVariantProps, "pressed" | "disabled">;
30
+ type PublicListItemVariantProps = Omit<
31
+ ListItemVariantProps,
32
+ "pressed" | "disabled" | "interactive"
33
+ >;
28
34
 
29
35
  const { ClassNamesProvider, useClassNames } = createSlotRecipeContext(listItem);
30
36
 
@@ -91,12 +97,26 @@ interface ListItemSurfaceProps
91
97
  LynxTouchProps {
92
98
  disabled?: boolean;
93
99
  pressed?: boolean;
100
+ scaleFeedbackTargetProps?: ScaleFeedbackTargetProps;
94
101
  }
95
102
 
96
103
  const ListItemSurface = React.forwardRef<unknown, ListItemSurfaceProps>((props, ref) => {
97
- const { children, className, style, disabled = false, pressed = false, ...restProps } = props;
104
+ const {
105
+ children,
106
+ className,
107
+ style,
108
+ disabled = false,
109
+ pressed = false,
110
+ scaleFeedbackTargetProps,
111
+ ...restProps
112
+ } = props;
98
113
  const [variantProps, nativeProps] = listItem.splitVariantProps(restProps);
99
- const classes = listItem({ ...variantProps, disabled, pressed });
114
+ const classes = listItem({
115
+ ...variantProps,
116
+ disabled,
117
+ pressed,
118
+ interactive: !!scaleFeedbackTargetProps,
119
+ });
100
120
 
101
121
  return (
102
122
  <ClassNamesProvider value={classes}>
@@ -110,14 +130,17 @@ const ListItemSurface = React.forwardRef<unknown, ListItemSurfaceProps>((props,
110
130
  }}
111
131
  >
112
132
  <view
113
- {...(ref ? { ref: ref as LynxViewRef } : {})}
114
- {...nativeProps}
133
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
115
134
  className={clsx(classes.root, className)}
116
135
  style={style}
117
136
  >
118
137
  <view className={classes.highlightedOverlay} accessibility-elements-hidden={true} />
119
138
  <view className={classes.pressedOverlay} accessibility-elements-hidden={true} />
120
- <view className={classes.layout}>{children}</view>
139
+ <view className={classes.layout} {...scaleFeedbackTargetProps}>
140
+ <ScaleFeedbackContentContext.Provider value={!!scaleFeedbackTargetProps}>
141
+ {children}
142
+ </ScaleFeedbackContentContext.Provider>
143
+ </view>
121
144
  </view>
122
145
  </IconSlotProvider>
123
146
  </ClassNamesProvider>
@@ -157,11 +180,17 @@ export const ListButtonItem = React.forwardRef<unknown, ListButtonItemProps>((pr
157
180
  "accessibility-traits": accessibilityTraits,
158
181
  ...restProps
159
182
  } = props;
160
- const { pressed, ...pressHandlers } = usePressTap({
183
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
161
184
  disabled,
162
185
  onTap: bindtap,
163
186
  mainThreadOnTap,
164
187
  });
188
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
189
+ disabled,
190
+ onTouchStart: bindtouchstart,
191
+ onTouchEnd: bindtouchend,
192
+ onTouchCancel: bindtouchcancel,
193
+ });
165
194
 
166
195
  return (
167
196
  <ListItemSurface
@@ -171,8 +200,8 @@ export const ListButtonItem = React.forwardRef<unknown, ListButtonItemProps>((pr
171
200
  accessibility-element={accessibilityElement}
172
201
  accessibility-role-description={accessibilityRoleDescription}
173
202
  accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : "button")}
174
- {...pressHandlers}
175
- {...restProps}
203
+ scaleFeedbackTargetProps={scaleFeedbackTargetProps}
204
+ {...mergeProps(scaleFeedbackTriggerProps, pressHandlers, restProps)}
176
205
  />
177
206
  );
178
207
  });
@@ -193,6 +222,7 @@ function ListCheckboxItemSurface(props: ListCheckboxItemSurfaceProps) {
193
222
  {...props}
194
223
  disabled={context.disabled}
195
224
  pressed={context.pressed}
225
+ scaleFeedbackTargetProps={context.scaleFeedbackTargetProps}
196
226
  accessibility-element={props["accessibility-element"] ?? true}
197
227
  accessibility-role-description={props["accessibility-role-description"] ?? "checkbox"}
198
228
  accessibility-value={
@@ -254,6 +284,7 @@ function ListRadioItemSurface(props: ListRadioItemSurfaceProps) {
254
284
  {...props}
255
285
  disabled={context.disabled}
256
286
  pressed={context.pressed}
287
+ scaleFeedbackTargetProps={context.scaleFeedbackTargetProps}
257
288
  accessibility-element={props["accessibility-element"] ?? true}
258
289
  accessibility-role-description={props["accessibility-role-description"] ?? "radio"}
259
290
  accessibility-value={
@@ -277,12 +308,13 @@ export interface ListRadioItemProps
277
308
  export const ListRadioItem = React.forwardRef<unknown, ListRadioItemProps>((props, ref) => {
278
309
  const { children, className, style, disabled = false, value, ...restProps } = props;
279
310
  const [variantProps, remainingProps] = listItem.splitVariantProps(restProps);
280
- const [accessibilityProps] = splitAccessibilityProps(remainingProps);
311
+ const [accessibilityProps, radioProps] = splitAccessibilityProps(remainingProps);
281
312
  const interactionRootClassName = listItem().interactionRoot;
282
313
 
283
314
  return (
284
315
  <RadioGroupItem
285
316
  ref={ref}
317
+ {...radioProps}
286
318
  value={value}
287
319
  disabled={disabled}
288
320
  className={interactionRootClassName}
@@ -315,6 +347,7 @@ function ListSwitchItemSurface(props: ListSwitchItemSurfaceProps) {
315
347
  {...props}
316
348
  disabled={context.disabled}
317
349
  pressed={context.pressed}
350
+ scaleFeedbackTargetProps={context.scaleFeedbackTargetProps}
318
351
  accessibility-element={props["accessibility-element"] ?? true}
319
352
  accessibility-role-description={props["accessibility-role-description"] ?? "switch"}
320
353
  accessibility-value={props["accessibility-value"] ?? (context.checked ? "켜짐" : "꺼짐")}
@@ -8,6 +8,8 @@ import { menuItem, type MenuItemVariantProps } from "@seed-design/lynx-css/recip
8
8
  import { menu as menuVars } from "@seed-design/lynx-css/vars/component";
9
9
 
10
10
  import { useControllableState } from "../../hooks/useControllableState";
11
+ import { useScaleFeedback } from "../../hooks/useScaleFeedback";
12
+ import { mergeProps } from "../../utils/merge-props";
11
13
  import { usePressTap } from "../../hooks/usePressTap";
12
14
  import type {
13
15
  LynxAccessibilityProps,
@@ -38,6 +40,7 @@ type MenuClassNames = {
38
40
  };
39
41
  type MenuItemClassNames = {
40
42
  root: string;
43
+ scaleContent: string;
41
44
  pressedOverlay: string;
42
45
  body: string;
43
46
  label: string;
@@ -782,11 +785,17 @@ export const MenuItem = React.forwardRef<unknown, MenuItemProps>((props, ref) =>
782
785
  },
783
786
  [bindtap, context],
784
787
  );
785
- const { pressed, ...pressHandlers } = usePressTap({
788
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
786
789
  disabled,
787
790
  onTap: handleTap,
788
791
  mainThreadOnTap: mainThreadBindtap,
789
792
  });
793
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
794
+ disabled,
795
+ onTouchStart: bindtouchstart,
796
+ onTouchEnd: bindtouchend,
797
+ onTouchCancel: bindtouchcancel,
798
+ });
790
799
  const classes = menuItem({
791
800
  ...variantProps,
792
801
  size: context.size,
@@ -812,11 +821,12 @@ export const MenuItem = React.forwardRef<unknown, MenuItemProps>((props, ref) =>
812
821
  accessibility-label={accessibilityLabel}
813
822
  accessibility-role-description="button"
814
823
  accessibility-traits={disabled ? "disabled" : (accessibilityTraits ?? "button")}
815
- {...nativeProps}
816
- {...pressHandlers}
824
+ {...mergeProps(scaleFeedbackTriggerProps, pressHandlers, nativeProps)}
817
825
  >
818
826
  <view className={classes.pressedOverlay} accessibility-elements-hidden={true} />
819
- {children}
827
+ <view className={classes.scaleContent} {...scaleFeedbackTargetProps}>
828
+ {children}
829
+ </view>
820
830
  </view>
821
831
  </IconSlotProvider>
822
832
  </MenuItemClassNamesContext.Provider>
@@ -0,0 +1,34 @@
1
+ import "@testing-library/jest-dom";
2
+ import { fireEvent, render } from "@lynx-js/react/testing-library";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { MenuRoot, MenuItem, MenuItemLabel } from "../Menu";
5
+
6
+ describe("Menu item feedback", () => {
7
+ it("scales the inner content while preserving item callbacks and disabled gating", () => {
8
+ const onTap = vi.fn();
9
+ const { getByText, rerender } = render(
10
+ <MenuRoot>
11
+ <MenuItem bindtap={onTap}>
12
+ <MenuItemLabel>Choose</MenuItemLabel>
13
+ </MenuItem>
14
+ </MenuRoot>,
15
+ );
16
+ const label = getByText("Choose") as HTMLElement;
17
+ const target = label.parentElement!;
18
+ const root = target.parentElement!;
19
+ expect(target).toHaveClass("seed-menu-item__scaleContent");
20
+ expect(target).toHaveAttribute("flatten", "false");
21
+ expect(target).not.toContainElement(root.querySelector(".seed-menu-item__pressedOverlay"));
22
+ fireEvent.tap(root);
23
+ expect(onTap).toHaveBeenCalledTimes(1);
24
+ rerender(
25
+ <MenuRoot>
26
+ <MenuItem disabled bindtap={onTap}>
27
+ <MenuItemLabel>Choose</MenuItemLabel>
28
+ </MenuItem>
29
+ </MenuRoot>,
30
+ );
31
+ fireEvent.tap((getByText("Choose") as HTMLElement).parentElement!.parentElement!);
32
+ expect(onTap).toHaveBeenCalledTimes(1);
33
+ });
34
+ });
@@ -1,5 +1,7 @@
1
1
  import "@testing-library/jest-dom";
2
- import { fireEvent, render } from "@lynx-js/react/testing-library";
2
+ import * as React from "@lynx-js/react";
3
+ import { runOnBackground } from "@lynx-js/react";
4
+ import { fireEvent, render, waitSchedule } from "@lynx-js/react/testing-library";
3
5
  import { describe, expect, it, vi } from "vitest";
4
6
 
5
7
  import * as PageBanner from "./PageBanner.namespace";
@@ -87,6 +89,111 @@ describe("PageBanner", () => {
87
89
  expect(onTap).toHaveBeenCalledTimes(1);
88
90
  });
89
91
 
92
+ it("scales the actionable content group without its background or independent close button", () => {
93
+ render(
94
+ <PageBanner.Root bindtap={() => {}}>
95
+ <React.Fragment key="fragment-slots">
96
+ <view id="prefix" />
97
+ <PageBanner.Content>
98
+ <PageBanner.Description>Body</PageBanner.Description>
99
+ </PageBanner.Content>
100
+ <view id="suffix" />
101
+ <PageBanner.CloseButton accessibility-label="Close" />
102
+ </React.Fragment>
103
+ </PageBanner.Root>,
104
+ );
105
+ const root = getPageBannerRoot();
106
+ const target = root.querySelector(".seed-page-banner__scaleContent");
107
+ const close = root.querySelector(".seed-page-banner__closeButton");
108
+ expect(target).toHaveAttribute("flatten", "false");
109
+ expect(target?.parentElement).toBe(root);
110
+ expect(target?.querySelector("#prefix")).toBeTruthy();
111
+ expect(target?.querySelector("#suffix")).toBeTruthy();
112
+ expect(target?.contains(close)).toBe(false);
113
+ expect(close?.parentElement).toBe(root);
114
+ });
115
+
116
+ it("measures the action text itself and prevents action taps from reaching the banner", () => {
117
+ const onBanner = vi.fn();
118
+ const onAction = vi.fn();
119
+ render(
120
+ <PageBanner.Root bindtap={onBanner}>
121
+ <PageBanner.Content>
122
+ <PageBanner.Button bindtap={onAction}>Action</PageBanner.Button>
123
+ </PageBanner.Content>
124
+ </PageBanner.Root>,
125
+ );
126
+ const root = getPageBannerRoot();
127
+ const action = root.querySelector(".seed-page-banner__button") as HTMLElement;
128
+ expect(action).toHaveAttribute("flatten", "false");
129
+ expect(action.parentElement).toHaveClass("seed-page-banner__content");
130
+ fireEvent.tap(action, { eventType: "catchEvent" });
131
+ expect(onAction).toHaveBeenCalledTimes(1);
132
+ expect(onBanner).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it("does not activate the banner when its independent close action is tapped", () => {
136
+ const onBanner = vi.fn();
137
+ const onDismiss = vi.fn();
138
+ render(
139
+ <PageBanner.Root open bindtap={onBanner} onDismiss={onDismiss}>
140
+ <PageBanner.Content>
141
+ <PageBanner.Description>Body</PageBanner.Description>
142
+ </PageBanner.Content>
143
+ <PageBanner.CloseButton accessibility-label="Close" />
144
+ </PageBanner.Root>,
145
+ );
146
+ const root = getPageBannerRoot();
147
+ fireEvent.tap(root.querySelector(".seed-page-banner__closeButton") as HTMLElement, {
148
+ eventType: "catchEvent",
149
+ });
150
+ expect(onDismiss).toHaveBeenCalledTimes(1);
151
+ expect(onBanner).not.toHaveBeenCalled();
152
+ });
153
+
154
+ it("bridges banner feedback and preserves independent Main Thread handlers", async () => {
155
+ function Example({ report }: { report: () => void }) {
156
+ function handleTouch() {
157
+ "main thread";
158
+ runOnBackground(report)();
159
+ }
160
+ return (
161
+ <PageBanner.Root open bindtap={() => {}}>
162
+ <PageBanner.Content>
163
+ <PageBanner.Description>Body</PageBanner.Description>
164
+ </PageBanner.Content>
165
+ <PageBanner.CloseButton
166
+ accessibility-label="Close"
167
+ main-thread:bindtouchstart={handleTouch}
168
+ />
169
+ </PageBanner.Root>
170
+ );
171
+ }
172
+ const report = vi.fn();
173
+ const { container } = render(<Example report={report} />, {
174
+ enableMainThread: true,
175
+ enableBackgroundThread: true,
176
+ });
177
+ await waitSchedule();
178
+ const root = container.querySelector(".seed-page-banner__root");
179
+ const close = container.querySelector(".seed-page-banner__closeButton");
180
+ if (!root || !close) throw new Error("Expected banner and close button");
181
+ fireEvent.touchstart(close, { eventType: "catchEvent" });
182
+ await waitSchedule();
183
+ expect(report).toHaveBeenCalledTimes(1);
184
+ expect(root.className).toContain("__root--pressed_false");
185
+ expect(close.className).toContain("closeButtonPressed_true");
186
+ fireEvent.touchend(close, { eventType: "catchEvent" });
187
+ await waitSchedule();
188
+ expect(close.className).not.toContain("closeButtonPressed_true");
189
+ fireEvent.touchstart(root, {});
190
+ await waitSchedule();
191
+ expect(root.className).toContain("__root--pressed_true");
192
+ fireEvent.touchcancel(root, {});
193
+ await waitSchedule();
194
+ expect(root.className).toContain("__root--pressed_false");
195
+ });
196
+
90
197
  it("dismisses an uncontrolled banner and composes the close tap handler", () => {
91
198
  const onTap = vi.fn();
92
199
  const onDismiss = vi.fn();
@@ -100,7 +207,7 @@ describe("PageBanner", () => {
100
207
  const closeButton = getPageBannerRoot().querySelector(".seed-page-banner__closeButton");
101
208
  if (!closeButton) throw new Error("Expected close button to exist.");
102
209
 
103
- fireEvent.tap(closeButton);
210
+ fireEvent.tap(closeButton, { eventType: "catchEvent" });
104
211
 
105
212
  expect(onTap).toHaveBeenCalledTimes(1);
106
213
  expect(onDismiss).toHaveBeenCalledTimes(1);
@@ -119,7 +226,7 @@ describe("PageBanner", () => {
119
226
  const closeButton = getPageBannerRoot().querySelector(".seed-page-banner__closeButton");
120
227
  if (!closeButton) throw new Error("Expected close button to exist.");
121
228
 
122
- fireEvent.tap(closeButton);
229
+ fireEvent.tap(closeButton, { eventType: "catchEvent" });
123
230
 
124
231
  expect(onDismiss).toHaveBeenCalledTimes(1);
125
232
  expect(getPageBannerRoot()).toBeInTheDocument();
@@ -16,7 +16,7 @@ import type {
16
16
  } from "../../types";
17
17
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
18
18
  import { IconSlotProvider } from "../Icon/Icon";
19
- import { ScaleFeedback } from "../ScaleFeedback";
19
+ import { toArray } from "../../utils/children";
20
20
  import { mergeProps } from "../../utils/merge-props";
21
21
 
22
22
  const { ClassNamesProvider, PropsProvider, useClassNames, useProps } =
@@ -48,6 +48,9 @@ function usePageBannerContext(consumer: string) {
48
48
  * Differences from React Web:
49
49
  * - Uses Lynx native `<view>` and `bindtap` instead of `asChild` and DOM events.
50
50
  * - Does not provide a web focus ring.
51
+ * - Actionable banners group their children for Content Scale. Render CloseButton
52
+ * directly under Root (or in a Fragment) to exclude it from that group.
53
+ * A CloseButton hidden inside a custom component remains in the content group.
51
54
  */
52
55
  export interface PageBannerRootProps
53
56
  extends Omit<PageBannerVariantProps, "pressed" | "closeButtonPressed" | "interactive">,
@@ -94,6 +97,15 @@ export const PageBannerRoot = React.forwardRef<unknown, PageBannerRootProps>((pr
94
97
  onTap: bindtap,
95
98
  mainThreadOnTap: mainThreadBindtap,
96
99
  });
100
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
101
+ disabled: !isInteractive,
102
+ onTouchStart: pressTap.bindtouchstart,
103
+ onTouchEnd: pressTap.bindtouchend,
104
+ onTouchCancel: pressTap.bindtouchcancel,
105
+ });
106
+ const childArray = flattenPageBannerChildren(children);
107
+ const closeButtons = childArray.filter(isPageBannerCloseButton);
108
+ const scaleChildren = childArray.filter((child) => !isPageBannerCloseButton(child));
97
109
  const classNames = pageBanner({
98
110
  ...variantProps,
99
111
  pressed: pressTap.pressed,
@@ -134,6 +146,7 @@ export const PageBannerRoot = React.forwardRef<unknown, PageBannerRootProps>((pr
134
146
  {...mergeProps(
135
147
  ref ? { ref: ref as LynxViewRef } : {},
136
148
  isInteractive ? pressTap : {},
149
+ isInteractive ? scaleFeedbackTriggerProps : {},
137
150
  nativeProps,
138
151
  )}
139
152
  accessibility-element={accessibilityElement ?? (isInteractive ? true : undefined)}
@@ -141,7 +154,16 @@ export const PageBannerRoot = React.forwardRef<unknown, PageBannerRootProps>((pr
141
154
  className={clsx(classNames.root, className)}
142
155
  style={style}
143
156
  >
144
- {children}
157
+ {isInteractive ? (
158
+ <>
159
+ <view {...scaleFeedbackTargetProps} className={classNames.scaleContent}>
160
+ {scaleChildren}
161
+ </view>
162
+ {closeButtons}
163
+ </>
164
+ ) : (
165
+ children
166
+ )}
145
167
  </view>
146
168
  </IconSlotProvider>
147
169
  </PropsProvider>
@@ -253,19 +275,26 @@ export const PageBannerButton = React.forwardRef<unknown, PageBannerButtonProps>
253
275
  ...nativeProps
254
276
  } = props;
255
277
  const classNames = useClassNames();
278
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback();
256
279
 
257
280
  return (
258
- <ScaleFeedback>
259
- <text
260
- {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
261
- accessibility-element={accessibilityElement}
262
- accessibility-traits={accessibilityTraits}
263
- className={clsx(classNames.button, className)}
264
- style={style}
265
- >
266
- {children}
267
- </text>
268
- </ScaleFeedback>
281
+ <text
282
+ {...independentActionProps(
283
+ mergeProps(
284
+ ref ? { ref: ref as LynxTextRef } : {},
285
+ scaleFeedbackTriggerProps,
286
+ scaleFeedbackTargetProps,
287
+ nativeProps,
288
+ ),
289
+ )}
290
+ accessibility-element={accessibilityElement}
291
+ accessibility-traits={accessibilityTraits}
292
+ className={clsx(classNames.button, className)}
293
+ style={style}
294
+ flatten={false}
295
+ >
296
+ {children}
297
+ </text>
269
298
  );
270
299
  });
271
300
  PageBannerButton.displayName = "PageBannerButton";
@@ -326,12 +355,14 @@ export const PageBannerCloseButton = React.forwardRef<unknown, PageBannerCloseBu
326
355
  return (
327
356
  <IconSlotProvider value={iconSlotContextValue}>
328
357
  <view
329
- {...mergeProps(
330
- ref ? { ref: ref as LynxViewRef } : {},
331
- pressTapHandlers,
332
- scaleFeedbackTriggerProps,
333
- scaleFeedbackTargetProps,
334
- nativeProps,
358
+ {...independentActionProps(
359
+ mergeProps(
360
+ ref ? { ref: ref as LynxViewRef } : {},
361
+ pressTapHandlers,
362
+ scaleFeedbackTriggerProps,
363
+ scaleFeedbackTargetProps,
364
+ nativeProps,
365
+ ),
335
366
  )}
336
367
  accessibility-element={accessibilityElement}
337
368
  accessibility-label={accessibilityLabel}
@@ -347,3 +378,36 @@ export const PageBannerCloseButton = React.forwardRef<unknown, PageBannerCloseBu
347
378
  },
348
379
  );
349
380
  PageBannerCloseButton.displayName = "PageBannerCloseButton";
381
+
382
+ function isPageBannerCloseButton(node: React.ReactNode) {
383
+ return React.isValidElement(node) && node.type === PageBannerCloseButton;
384
+ }
385
+
386
+ function flattenPageBannerChildren(children: React.ReactNode): React.ReactNode[] {
387
+ return toArray(children).flatMap((child) =>
388
+ React.isValidElement<{ children?: React.ReactNode }>(child) && child.type === React.Fragment
389
+ ? flattenPageBannerChildren(child.props.children)
390
+ : [child],
391
+ );
392
+ }
393
+
394
+ // Native catch handlers keep independent actions from starting the banner's feedback or tap.
395
+ function independentActionProps(props: Record<string, unknown>) {
396
+ const result = { ...props };
397
+ for (const event of ["tap", "touchstart", "touchend", "touchcancel"]) {
398
+ const bind = `bind${event}`;
399
+ const catchKey = `catch${event}`;
400
+ const mainBind = `main-thread:${bind}`;
401
+ const mainCatch = `main-thread:${catchKey}`;
402
+ const caught = mergeProps(
403
+ { [catchKey]: result[bind] ?? (() => {}) },
404
+ { [catchKey]: result[catchKey] },
405
+ { [mainCatch]: result[mainBind] },
406
+ { [mainCatch]: result[mainCatch] },
407
+ );
408
+ delete result[bind];
409
+ delete result[mainBind];
410
+ Object.assign(result, caught);
411
+ }
412
+ return result;
413
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ QuantityPickerRoot as Root,
3
+ QuantityPickerDecrementButton as DecrementButton,
4
+ QuantityPickerValueDisplay as ValueDisplay,
5
+ QuantityPickerIncrementButton as IncrementButton,
6
+ type QuantityPickerRootProps as RootProps,
7
+ type QuantityPickerDecrementButtonProps as DecrementButtonProps,
8
+ type QuantityPickerValueDisplayProps as ValueDisplayProps,
9
+ type QuantityPickerIncrementButtonProps as IncrementButtonProps,
10
+ } from "./QuantityPicker";