@seed-design/lynx-react 0.6.3 → 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 (234) 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 +1 -1
  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.tsx +14 -7
  171. package/src/components/InputButton/InputButton.namespace.ts +20 -0
  172. package/src/components/InputButton/InputButton.test.tsx +82 -0
  173. package/src/components/InputButton/InputButton.tsx +295 -0
  174. package/src/components/InputButton/index.ts +2 -0
  175. package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +16 -21
  176. package/src/components/List/List.namespace.ts +24 -0
  177. package/src/components/List/List.test.tsx +162 -0
  178. package/src/components/List/List.tsx +476 -0
  179. package/src/components/List/index.ts +3 -0
  180. package/src/components/MannerTemp/MannerTemp.tsx +3 -4
  181. package/src/components/MannerTempBadge/MannerTempBadge.tsx +2 -2
  182. package/src/components/Menu/Menu.namespace.ts +26 -0
  183. package/src/components/Menu/Menu.tsx +883 -0
  184. package/src/components/Menu/index.ts +31 -0
  185. package/src/components/NotificationBadge/NotificationBadge.tsx +4 -9
  186. package/src/components/PageBanner/PageBanner.test.tsx +3 -0
  187. package/src/components/PageBanner/PageBanner.tsx +54 -45
  188. package/src/components/ProgressCircle/ProgressCircle.test.tsx +48 -0
  189. package/src/components/ProgressCircle/ProgressCircle.tsx +22 -3
  190. package/src/components/RadioGroup/RadioGroup.test.tsx +87 -0
  191. package/src/components/RadioGroup/RadioGroup.tsx +50 -14
  192. package/src/components/ReactionButton/ReactionButton.test.tsx +122 -0
  193. package/src/components/ReactionButton/ReactionButton.tsx +139 -0
  194. package/src/components/ReactionButton/index.ts +1 -0
  195. package/src/components/ScaleFeedback/ScaleFeedback.test.tsx +41 -0
  196. package/src/components/ScaleFeedback/ScaleFeedback.tsx +32 -0
  197. package/src/components/ScaleFeedback/index.ts +1 -0
  198. package/src/components/SegmentedControl/SegmentedControl.test.tsx +15 -3
  199. package/src/components/SegmentedControl/SegmentedControl.tsx +35 -14
  200. package/src/components/SelectBox/SelectBox.test.tsx +39 -1
  201. package/src/components/SelectBox/SelectBox.tsx +27 -16
  202. package/src/components/Stack/Stack.tsx +2 -2
  203. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.ts +37 -0
  204. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +258 -0
  205. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +655 -0
  206. package/src/components/SwipeableMenuSheet/index.ts +43 -0
  207. package/src/components/Switch/Switch.test.tsx +57 -0
  208. package/src/components/Switch/Switch.tsx +60 -16
  209. package/src/components/Tabs/Tabs.test.tsx +19 -0
  210. package/src/components/Tabs/Tabs.tsx +72 -40
  211. package/src/components/TagGroup/TagGroup.tsx +4 -6
  212. package/src/components/Text/Text.tsx +2 -2
  213. package/src/components/TextField/TextField.tsx +60 -54
  214. package/src/components/index.ts +7 -0
  215. package/src/components/private/Positioning.test.ts +235 -0
  216. package/src/components/private/Positioning.ts +223 -0
  217. package/src/components/private/TrackPositioning.test.ts +58 -0
  218. package/src/components/private/TrackPositioning.ts +37 -0
  219. package/src/contexts/ScaleFeedbackContentContext.ts +4 -0
  220. package/src/contexts/index.ts +1 -0
  221. package/src/hooks/index.ts +1 -0
  222. package/src/hooks/useIconColor.ts +5 -4
  223. package/src/hooks/usePressTap.ts +4 -3
  224. package/src/hooks/useScaleFeedback.integration.test.tsx +85 -0
  225. package/src/hooks/useScaleFeedback.test.ts +50 -0
  226. package/src/hooks/useScaleFeedback.ts +199 -0
  227. package/src/index.ts +1 -0
  228. package/src/utils/calculate-scale-feedback.ts +12 -0
  229. package/src/utils/compose-event-handlers.ts +16 -0
  230. package/src/utils/create-slot-recipe-context.tsx +6 -7
  231. package/src/utils/index.ts +1 -0
  232. package/src/utils/merge-props.test.tsx +152 -0
  233. package/src/utils/merge-props.ts +62 -0
  234. package/src/utils/merge-refs.ts +44 -0
@@ -5,6 +5,7 @@ import { chip, type ChipVariantProps } from "@seed-design/lynx-css/recipes/chip"
5
5
 
6
6
  import { useControllableState } from "../../hooks/useControllableState";
7
7
  import { usePressTap } from "../../hooks/usePressTap";
8
+ import { useScaleFeedback } from "../../hooks/useScaleFeedback";
8
9
  import type {
9
10
  LynxAccessibilityProps,
10
11
  LynxPressableProps,
@@ -21,6 +22,7 @@ import {
21
22
  type PrefixIconProps,
22
23
  type SuffixIconProps,
23
24
  } from "../Icon/Icon";
25
+ import { mergeProps } from "../../utils/merge-props";
24
26
 
25
27
  const { ClassNamesProvider, useClassNames } = createSlotRecipeContext(chip);
26
28
 
@@ -30,7 +32,9 @@ interface ChipRootViewProps
30
32
  extends ChipVariantProps,
31
33
  LynxStyledElementProps,
32
34
  LynxPressableProps,
33
- LynxAccessibilityProps {}
35
+ LynxAccessibilityProps {
36
+ flatten?: false;
37
+ }
34
38
 
35
39
  const ChipRootView = React.forwardRef<unknown, ChipRootViewProps>((props, ref) => {
36
40
  const [variantProps, otherProps] = chip.splitVariantProps(props);
@@ -70,9 +74,8 @@ const ChipRootView = React.forwardRef<unknown, ChipRootViewProps>((props, ref) =
70
74
  <IconSlotProvider value={iconSlotContextValue}>
71
75
  <IconRequired enabled={variantProps.layout === "iconOnly"}>
72
76
  <view
73
- {...(ref ? { ref: ref as LynxViewRef } : {})}
77
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
74
78
  className={clsx(classes.root, className)}
75
- {...nativeProps}
76
79
  >
77
80
  {children}
78
81
  </view>
@@ -95,7 +98,8 @@ ChipRootView.displayName = "ChipRootView";
95
98
  */
96
99
  export interface ChipButtonProps
97
100
  extends ChipPublicVariantProps,
98
- LynxStyledElementProps,
101
+ // Keep the scale target's Android View even if shared props later expose flatten.
102
+ Omit<LynxStyledElementProps, "flatten">,
99
103
  LynxPressableProps,
100
104
  LynxAccessibilityProps {
101
105
  disabled?: boolean;
@@ -107,25 +111,38 @@ export const ChipButton = React.forwardRef<unknown, ChipButtonProps>((props, ref
107
111
  "main-thread:bindtap": mainThreadBindtap,
108
112
  disabled = false,
109
113
  "accessibility-element": accessibilityElement = true,
114
+ "accessibility-role-description": accessibilityRoleDescription = "button",
110
115
  "accessibility-traits": accessibilityTraits,
111
116
  ...restProps
112
117
  } = props;
113
- const { pressed, ...pressHandlers } = usePressTap({
118
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
114
119
  disabled,
115
120
  onTap: bindtap,
116
121
  mainThreadOnTap: mainThreadBindtap,
117
122
  });
123
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
124
+ disabled,
125
+ onTouchStart: bindtouchstart,
126
+ onTouchEnd: bindtouchend,
127
+ onTouchCancel: bindtouchcancel,
128
+ });
118
129
 
119
130
  return (
120
131
  <ChipRootView
121
- ref={ref}
122
- {...restProps}
132
+ {...mergeProps(
133
+ { ref },
134
+ scaleFeedbackTargetProps,
135
+ scaleFeedbackTriggerProps,
136
+ pressHandlers,
137
+ restProps,
138
+ )}
123
139
  disabled={disabled}
124
140
  selected={false}
125
141
  pressed={pressed}
126
142
  accessibility-element={accessibilityElement}
127
- accessibility-traits={disabled ? "disabled" : (accessibilityTraits ?? "button")}
128
- {...pressHandlers}
143
+ accessibility-role-description={accessibilityRoleDescription}
144
+ accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : undefined)}
145
+ flatten={false}
129
146
  />
130
147
  );
131
148
  });
@@ -135,7 +152,7 @@ ChipButton.displayName = "ChipButton";
135
152
  export interface ChipRootProps extends ChipButtonProps {}
136
153
 
137
154
  export const ChipRoot = React.forwardRef<unknown, ChipRootProps>((props, ref) => (
138
- <ChipButton ref={ref} {...props} />
155
+ <ChipButton {...mergeProps({ ref }, props)} />
139
156
  ));
140
157
  ChipRoot.displayName = "ChipRoot";
141
158
 
@@ -163,7 +180,9 @@ export const ChipToggle = React.forwardRef<unknown, ChipToggleProps>((props, ref
163
180
  "main-thread:bindtap": mainThreadBindtap,
164
181
  disabled = false,
165
182
  "accessibility-element": accessibilityElement = true,
183
+ "accessibility-role-description": accessibilityRoleDescription = "toggle",
166
184
  "accessibility-traits": accessibilityTraits,
185
+ "accessibility-value": accessibilityValue,
167
186
  ...restProps
168
187
  } = props;
169
188
  const [checked, setChecked] = useControllableState({
@@ -178,24 +197,35 @@ export const ChipToggle = React.forwardRef<unknown, ChipToggleProps>((props, ref
178
197
  },
179
198
  [bindtap, checked, setChecked],
180
199
  );
181
- const { pressed, ...pressHandlers } = usePressTap({
200
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
182
201
  disabled,
183
202
  onTap: handleTap,
184
203
  mainThreadOnTap: mainThreadBindtap,
185
204
  });
205
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
206
+ disabled,
207
+ onTouchStart: bindtouchstart,
208
+ onTouchEnd: bindtouchend,
209
+ onTouchCancel: bindtouchcancel,
210
+ });
186
211
 
187
212
  return (
188
213
  <ChipRootView
189
- ref={ref}
190
- {...restProps}
214
+ {...mergeProps(
215
+ { ref },
216
+ scaleFeedbackTargetProps,
217
+ scaleFeedbackTriggerProps,
218
+ pressHandlers,
219
+ restProps,
220
+ )}
191
221
  disabled={disabled}
192
222
  selected={checked}
193
223
  pressed={pressed}
194
224
  accessibility-element={accessibilityElement}
195
- accessibility-traits={
196
- disabled ? "disabled" : checked ? "selected" : (accessibilityTraits ?? "button")
197
- }
198
- {...pressHandlers}
225
+ accessibility-role-description={accessibilityRoleDescription}
226
+ accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : undefined)}
227
+ accessibility-value={accessibilityValue ?? (checked ? "checked" : "not checked")}
228
+ flatten={false}
199
229
  />
200
230
  );
201
231
  });
@@ -258,7 +288,10 @@ export const ChipRadioRoot = React.forwardRef<unknown, ChipRadioRootProps>((prop
258
288
 
259
289
  return (
260
290
  <ChipRadioGroupContext.Provider value={contextValue}>
261
- <view {...(ref ? { ref: ref as LynxViewRef } : {})} className={className} {...nativeProps}>
291
+ <view
292
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
293
+ className={className}
294
+ >
262
295
  {children}
263
296
  </view>
264
297
  </ChipRadioGroupContext.Provider>
@@ -284,7 +317,9 @@ export const ChipRadioItem = React.forwardRef<unknown, ChipRadioItemProps>((prop
284
317
  bindtap,
285
318
  "main-thread:bindtap": mainThreadBindtap,
286
319
  "accessibility-element": accessibilityElement = true,
320
+ "accessibility-role-description": accessibilityRoleDescription = "radio",
287
321
  "accessibility-traits": accessibilityTraits,
322
+ "accessibility-value": accessibilityValue,
288
323
  ...restProps
289
324
  } = props;
290
325
  const group = useChipRadioGroupContext("ChipRadioItem");
@@ -297,24 +332,35 @@ export const ChipRadioItem = React.forwardRef<unknown, ChipRadioItemProps>((prop
297
332
  },
298
333
  [bindtap, group, itemValue, selected],
299
334
  );
300
- const { pressed, ...pressHandlers } = usePressTap({
335
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
301
336
  disabled,
302
337
  onTap: handleTap,
303
338
  mainThreadOnTap: mainThreadBindtap,
304
339
  });
340
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
341
+ disabled,
342
+ onTouchStart: bindtouchstart,
343
+ onTouchEnd: bindtouchend,
344
+ onTouchCancel: bindtouchcancel,
345
+ });
305
346
 
306
347
  return (
307
348
  <ChipRootView
308
- ref={ref}
309
- {...restProps}
349
+ {...mergeProps(
350
+ { ref },
351
+ scaleFeedbackTargetProps,
352
+ scaleFeedbackTriggerProps,
353
+ pressHandlers,
354
+ restProps,
355
+ )}
310
356
  disabled={disabled}
311
357
  selected={selected}
312
358
  pressed={pressed}
313
359
  accessibility-element={accessibilityElement}
314
- accessibility-traits={
315
- disabled ? "disabled" : selected ? "selected" : (accessibilityTraits ?? "button")
316
- }
317
- {...pressHandlers}
360
+ accessibility-role-description={accessibilityRoleDescription}
361
+ accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : undefined)}
362
+ accessibility-value={accessibilityValue ?? (selected ? "selected" : "not selected")}
363
+ flatten={false}
318
364
  />
319
365
  );
320
366
  });
@@ -330,9 +376,8 @@ export const ChipLabel = React.forwardRef<unknown, ChipLabelProps>((props, ref)
330
376
 
331
377
  return (
332
378
  <text
333
- {...(ref ? { ref: ref as LynxTextRef } : {})}
379
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
334
380
  className={clsx(classes.label, className)}
335
- {...nativeProps}
336
381
  >
337
382
  {children}
338
383
  </text>
@@ -343,7 +388,7 @@ ChipLabel.displayName = "ChipLabel";
343
388
  export interface ChipPrefixIconProps extends PrefixIconProps {}
344
389
 
345
390
  export const ChipPrefixIcon = React.forwardRef<unknown, ChipPrefixIconProps>((props, ref) => (
346
- <PrefixIcon ref={ref} {...props} />
391
+ <PrefixIcon {...mergeProps({ ref }, props)} />
347
392
  ));
348
393
  ChipPrefixIcon.displayName = "ChipPrefixIcon";
349
394
 
@@ -355,9 +400,8 @@ export const ChipPrefixAvatar = React.forwardRef<unknown, ChipPrefixAvatarProps>
355
400
 
356
401
  return (
357
402
  <view
358
- {...(ref ? { ref: ref as LynxViewRef } : {})}
403
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
359
404
  className={clsx(classes.prefixAvatar, className)}
360
- {...nativeProps}
361
405
  >
362
406
  {children}
363
407
  </view>
@@ -368,6 +412,6 @@ ChipPrefixAvatar.displayName = "ChipPrefixAvatar";
368
412
  export interface ChipSuffixIconProps extends SuffixIconProps {}
369
413
 
370
414
  export const ChipSuffixIcon = React.forwardRef<unknown, ChipSuffixIconProps>((props, ref) => (
371
- <SuffixIcon ref={ref} {...props} />
415
+ <SuffixIcon {...mergeProps({ ref }, props)} />
372
416
  ));
373
417
  ChipSuffixIcon.displayName = "ChipSuffixIcon";
@@ -0,0 +1,35 @@
1
+ import clsx from "clsx";
2
+ import * as React from "@lynx-js/react";
3
+ import { isValidElement } from "@lynx-js/react";
4
+
5
+ import type { LynxStyledElementProps, LynxTextRef } from "../../types";
6
+
7
+ const countMarker = Symbol.for("@seed-design/lynx-react/count");
8
+
9
+ interface CountComponent {
10
+ [countMarker]?: true;
11
+ }
12
+
13
+ export interface CountProps extends LynxStyledElementProps {}
14
+
15
+ export const Count = React.forwardRef<unknown, CountProps>((props, ref) => {
16
+ const { children, className, ...nativeProps } = props;
17
+
18
+ return (
19
+ <text
20
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
21
+ {...nativeProps}
22
+ className={clsx("seed-count", className)}
23
+ >
24
+ {children}
25
+ </text>
26
+ );
27
+ });
28
+ Count.displayName = "Count";
29
+ (Count as CountComponent)[countMarker] = true;
30
+
31
+ export function isCountElement(node: React.ReactNode): node is React.ReactElement<CountProps> {
32
+ if (!isValidElement(node)) return false;
33
+
34
+ return (node.type as CountComponent)[countMarker] === true;
35
+ }
@@ -0,0 +1 @@
1
+ export { Count, type CountProps } from "./Count";
@@ -3,6 +3,7 @@ import * as React from "@lynx-js/react";
3
3
  import type { LynxAccessibilityProps, LynxStyledElementProps } from "../../types";
4
4
  import type { StyleProps } from "../../utils/styled";
5
5
  import { Box } from "../Box";
6
+ import { mergeProps } from "../../utils/merge-props";
6
7
 
7
8
  export interface DividerProps
8
9
  extends Omit<LynxStyledElementProps, "children">,
@@ -52,8 +53,7 @@ export const Divider = React.forwardRef<unknown, DividerProps>((props, ref) => {
52
53
 
53
54
  return (
54
55
  <Box
55
- {...nativeProps}
56
- ref={ref}
56
+ {...mergeProps({ ref }, nativeProps)}
57
57
  borderColor={color}
58
58
  borderWidth={0}
59
59
  borderBottomWidth={isHorizontal ? thickness : 0}
@@ -7,6 +7,7 @@ import clsx from "clsx";
7
7
  import type { LynxStyledElementProps, LynxTextRef, LynxViewRef } from "../../types";
8
8
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
9
9
  import { FieldContext, useFieldContext } from "./context";
10
+ import { mergeProps } from "../../utils/merge-props";
10
11
 
11
12
  const { ClassNamesProvider: FieldClassNamesProvider, useClassNames: useFieldClassNames } =
12
13
  createSlotRecipeContext(field);
@@ -43,17 +44,8 @@ export const FieldRoot = React.forwardRef<NodesRef, FieldRootProps>((props, forw
43
44
  const rootRef = React.useRef<NodesRef | null>(null);
44
45
  const classes = field({ invalid });
45
46
 
46
- const mergedRef = React.useCallback(
47
- (node: NodesRef | null) => {
48
- "background only";
49
-
50
- rootRef.current = node;
51
- if (typeof forwardedRef === "function") {
52
- forwardedRef(node);
53
- } else if (forwardedRef) {
54
- forwardedRef.current = node;
55
- }
56
- },
47
+ const mergedRef = React.useMemo(
48
+ () => mergeProps({ ref: rootRef }, { ref: forwardedRef }).ref,
57
49
  [forwardedRef],
58
50
  );
59
51
 
@@ -73,7 +65,10 @@ export const FieldRoot = React.forwardRef<NodesRef, FieldRootProps>((props, forw
73
65
  return (
74
66
  <FieldContext.Provider value={contextValue}>
75
67
  <FieldClassNamesProvider value={classes}>
76
- <view ref={mergedRef} className={clsx(classes.root, className)} {...nativeProps}>
68
+ <view
69
+ {...mergeProps({ ref: mergedRef }, nativeProps)}
70
+ className={clsx(classes.root, className)}
71
+ >
77
72
  {children}
78
73
  </view>
79
74
  </FieldClassNamesProvider>
@@ -92,9 +87,8 @@ export const FieldHeader = React.forwardRef<unknown, FieldHeaderProps>((props, r
92
87
 
93
88
  return (
94
89
  <view
95
- {...(ref ? { ref: ref as LynxViewRef } : {})}
90
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
96
91
  className={clsx(classes.header, className)}
97
- {...nativeProps}
98
92
  >
99
93
  {children}
100
94
  </view>
@@ -112,9 +106,8 @@ export const FieldLabel = React.forwardRef<unknown, FieldLabelProps>((props, ref
112
106
  return (
113
107
  <FieldLabelClassNamesProvider value={classes}>
114
108
  <text
115
- {...(ref ? { ref: ref as LynxTextRef } : {})}
109
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
116
110
  className={clsx(classes.root, className)}
117
- {...nativeProps}
118
111
  >
119
112
  {children}
120
113
  </text>
@@ -132,9 +125,8 @@ export const FieldIndicatorText = React.forwardRef<unknown, FieldIndicatorTextPr
132
125
 
133
126
  return (
134
127
  <text
135
- {...(ref ? { ref: ref as LynxTextRef } : {})}
128
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
136
129
  className={clsx(classes.indicatorText, className)}
137
- {...nativeProps}
138
130
  >
139
131
  {"\u00a0"}
140
132
  {children}
@@ -153,10 +145,9 @@ export const FieldRequiredIndicator = React.forwardRef<unknown, FieldRequiredInd
153
145
 
154
146
  return (
155
147
  <text
156
- {...(ref ? { ref: ref as LynxTextRef } : {})}
148
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
157
149
  accessibility-elements-hidden={true}
158
150
  className={clsx(classes.indicatorIcon, className)}
159
- {...nativeProps}
160
151
  >
161
152
  {"\u200a"}
162
153
  {children}
@@ -176,9 +167,8 @@ export const FieldFooter = React.forwardRef<unknown, FieldFooterProps>((props, r
176
167
 
177
168
  return (
178
169
  <view
179
- {...(ref ? { ref: ref as LynxViewRef } : {})}
170
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
180
171
  className={clsx(classes.footer, className)}
181
- {...nativeProps}
182
172
  >
183
173
  {children}
184
174
  </view>
@@ -194,9 +184,8 @@ export const FieldDescription = React.forwardRef<unknown, FieldDescriptionProps>
194
184
 
195
185
  return (
196
186
  <text
197
- {...(ref ? { ref: ref as LynxTextRef } : {})}
187
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
198
188
  className={clsx(classes.description, className)}
199
- {...nativeProps}
200
189
  >
201
190
  {children}
202
191
  </text>
@@ -212,9 +201,8 @@ export const FieldErrorMessage = React.forwardRef<unknown, FieldErrorMessageProp
212
201
 
213
202
  return (
214
203
  <text
215
- {...(ref ? { ref: ref as LynxTextRef } : {})}
204
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
216
205
  className={clsx(classes.errorMessage, className)}
217
- {...nativeProps}
218
206
  >
219
207
  {children}
220
208
  </text>
@@ -239,9 +227,8 @@ export const FieldCharacterCount = React.forwardRef<unknown, FieldCharacterCount
239
227
 
240
228
  return (
241
229
  <view
242
- {...(ref ? { ref: ref as LynxViewRef } : {})}
230
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
243
231
  className={clsx(classes.characterCountArea, className)}
244
- {...nativeProps}
245
232
  >
246
233
  <text className={classes.characterCount}>{current}</text>
247
234
  <text className={classes.maxCharacterCount}>/{max}</text>
@@ -12,6 +12,7 @@ import {
12
12
  import { useIconColor } from "../../hooks/useIconColor";
13
13
  import type { LynxIconElementProps, LynxStyledElementProps, LynxViewRef } from "../../types";
14
14
  import { handleColor, handleDimension, type StyleProps } from "../../utils/styled";
15
+ import { mergeProps } from "../../utils/merge-props";
15
16
 
16
17
  export type IconSlotName = "icon" | "prefixIcon" | "suffixIcon";
17
18
 
@@ -199,9 +200,11 @@ const IconSlotBase = React.forwardRef<unknown, IconSlotBaseProps>((props, ref) =
199
200
 
200
201
  return (
201
202
  <view
202
- {...(ref ? { ref: ref as LynxViewRef } : {})}
203
- {...nativeProps}
204
- main-thread:ref={sourceRef}
203
+ {...mergeProps(
204
+ { "main-thread:ref": sourceRef },
205
+ ref ? { ref: ref as LynxViewRef } : {},
206
+ nativeProps,
207
+ )}
205
208
  className={clsx(baseClassName, slotClassName, className)}
206
209
  style={mergeWrapperStyle({ size, color, style })}
207
210
  >
@@ -227,7 +230,9 @@ function createIconComponent<Props extends IconProps>(
227
230
  baseClassName: string,
228
231
  ) {
229
232
  const Component = React.forwardRef<unknown, Props>((props, ref) => {
230
- return <IconSlotBase ref={ref} slot={slot} baseClassName={baseClassName} {...props} />;
233
+ return (
234
+ <IconSlotBase {...mergeProps({ ref }, props)} slot={slot} baseClassName={baseClassName} />
235
+ );
231
236
  });
232
237
 
233
238
  Component.displayName = displayName;
@@ -265,9 +270,11 @@ export const InternalIcon = React.forwardRef<unknown, InternalIconProps>((props,
265
270
 
266
271
  return (
267
272
  <view
268
- {...(ref ? { ref: ref as LynxViewRef } : {})}
269
- {...nativeProps}
270
- main-thread:ref={sourceRef}
273
+ {...mergeProps(
274
+ { "main-thread:ref": sourceRef },
275
+ ref ? { ref: ref as LynxViewRef } : {},
276
+ nativeProps,
277
+ )}
271
278
  className={className}
272
279
  style={style}
273
280
  >
@@ -0,0 +1,20 @@
1
+ export {
2
+ InputButtonButton as Button,
3
+ InputButtonClearButton as ClearButton,
4
+ InputButtonPlaceholder as Placeholder,
5
+ InputButtonPrefixIcon as PrefixIcon,
6
+ InputButtonPrefixText as PrefixText,
7
+ InputButtonRoot as Root,
8
+ InputButtonSuffixIcon as SuffixIcon,
9
+ InputButtonSuffixText as SuffixText,
10
+ InputButtonValue as Value,
11
+ type InputButtonButtonProps as ButtonProps,
12
+ type InputButtonClearButtonProps as ClearButtonProps,
13
+ type InputButtonPlaceholderProps as PlaceholderProps,
14
+ type InputButtonPrefixIconProps as PrefixIconProps,
15
+ type InputButtonPrefixTextProps as PrefixTextProps,
16
+ type InputButtonRootProps as RootProps,
17
+ type InputButtonSuffixIconProps as SuffixIconProps,
18
+ type InputButtonSuffixTextProps as SuffixTextProps,
19
+ type InputButtonValueProps as ValueProps,
20
+ } from "./InputButton";
@@ -0,0 +1,82 @@
1
+ import "@testing-library/jest-dom";
2
+ import { fireEvent, getQueriesForElement, render } from "@lynx-js/react/testing-library";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ import { InputButton } from "./index";
6
+
7
+ function getRenderedRoot() {
8
+ const root = elementTree.root;
9
+
10
+ if (!root) {
11
+ throw new Error("Expected Lynx render root to exist.");
12
+ }
13
+
14
+ return root;
15
+ }
16
+
17
+ describe("InputButton", () => {
18
+ it("renders slots with size and invalid state classes", () => {
19
+ render(
20
+ <InputButton.Root size="medium" invalid className="custom-input-button">
21
+ <InputButton.Button accessibility-label="지역 선택" />
22
+ <InputButton.PrefixText>지역</InputButton.PrefixText>
23
+ <InputButton.Value>판교동</InputButton.Value>
24
+ <InputButton.SuffixText>선택됨</InputButton.SuffixText>
25
+ </InputButton.Root>,
26
+ );
27
+
28
+ const root = getRenderedRoot();
29
+ const queries = getQueriesForElement(root);
30
+ const inputButtonRoot = root.querySelector(".seed-input-button__root");
31
+ const button = root.querySelector(".seed-input-button__button");
32
+
33
+ expect(inputButtonRoot).toHaveClass("custom-input-button");
34
+ expect(inputButtonRoot).toHaveClass("seed-input-button__root--size_medium");
35
+ expect(button).toHaveAttribute("accessibility-label", "지역 선택");
36
+ expect(button).toHaveAttribute("accessibility-traits", "button");
37
+ expect(root.querySelector(".seed-input-button__stroke")).toHaveClass(
38
+ "seed-input-button__stroke--invalid_true",
39
+ );
40
+ expect(queries.getByText("판교동")).toHaveClass("seed-input-button__value");
41
+ expect(queries.getByText("지역")).toHaveClass("seed-input-button__prefixText");
42
+ expect(queries.getByText("선택됨")).toHaveClass("seed-input-button__suffixText");
43
+ });
44
+
45
+ it("tracks pressed state and blocks taps when disabled", () => {
46
+ const onTap = vi.fn();
47
+ const { rerender } = render(
48
+ <InputButton.Root>
49
+ <InputButton.Button accessibility-label="지역 선택" bindtap={onTap} />
50
+ <InputButton.Placeholder>지역을 선택하세요</InputButton.Placeholder>
51
+ </InputButton.Root>,
52
+ );
53
+
54
+ let button = getRenderedRoot().querySelector(".seed-input-button__button") as HTMLElement;
55
+
56
+ fireEvent.touchstart(button, {});
57
+ expect(button).toHaveClass("seed-input-button__button--pressed_true");
58
+
59
+ fireEvent.tap(button);
60
+ expect(onTap).toHaveBeenCalledTimes(1);
61
+
62
+ rerender(
63
+ <InputButton.Root disabled>
64
+ <InputButton.Button accessibility-label="지역 선택" bindtap={onTap} />
65
+ <InputButton.Placeholder>지역을 선택하세요</InputButton.Placeholder>
66
+ </InputButton.Root>,
67
+ );
68
+
69
+ button = getRenderedRoot().querySelector(".seed-input-button__button") as HTMLElement;
70
+ fireEvent.tap(button);
71
+
72
+ expect(onTap).toHaveBeenCalledTimes(1);
73
+ expect(button).toHaveAttribute("accessibility-traits", "disabled");
74
+ expect(getQueriesForElement(getRenderedRoot()).getByText("지역을 선택하세요")).toHaveClass(
75
+ "seed-input-button__placeholder--disabled_true",
76
+ );
77
+ });
78
+
79
+ it("throws when a slot is rendered outside InputButton.Root", () => {
80
+ expect(() => render(<InputButton.Value>값</InputButton.Value>)).toThrow(/InputButton\.Value/);
81
+ });
82
+ });