@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
@@ -10,6 +10,7 @@ import { useKeyboardAvoidanceActions } from "../KeyboardAvoidingScrollView/conte
10
10
  import { InternalIcon, type InternalIconProps } from "../Icon/Icon";
11
11
  import { useFieldContext } from "../Field/context";
12
12
  import { NATIVE_TEXT_MAX_LENGTH_UNLIMITED, TextFieldContext } from "./context";
13
+ import { mergeProps } from "../../utils/merge-props";
13
14
 
14
15
  type LynxSystemInfo = { platform?: string };
15
16
 
@@ -102,17 +103,8 @@ export const TextFieldRoot = React.forwardRef<NodesRef, TextFieldRootProps>(
102
103
  readOnly,
103
104
  });
104
105
 
105
- const mergedRef = React.useCallback(
106
- (node: NodesRef | null) => {
107
- "background only";
108
-
109
- rootRef.current = node;
110
- if (typeof forwardedRef === "function") {
111
- forwardedRef(node);
112
- } else if (forwardedRef) {
113
- forwardedRef.current = node;
114
- }
115
- },
106
+ const mergedRef = React.useMemo(
107
+ () => mergeProps({ ref: rootRef }, { ref: forwardedRef }).ref,
116
108
  [forwardedRef],
117
109
  );
118
110
 
@@ -167,7 +159,10 @@ export const TextFieldRoot = React.forwardRef<NodesRef, TextFieldRootProps>(
167
159
  return (
168
160
  <TextFieldContext.Provider value={contextValue}>
169
161
  <ClassNamesProvider value={classes}>
170
- <view ref={mergedRef} className={clsx(classes.root, className)} {...nativeProps}>
162
+ <view
163
+ {...mergeProps({ ref: mergedRef }, nativeProps)}
164
+ className={clsx(classes.root, className)}
165
+ >
171
166
  <view className={classes.baseStroke} accessibility-elements-hidden={true} />
172
167
  <view className={classes.stroke} accessibility-elements-hidden={true} />
173
168
  {children}
@@ -298,17 +293,8 @@ function useNativeTextControl({
298
293
  }
299
294
  }, []);
300
295
 
301
- const mergedRef = React.useCallback(
302
- (node: NodesRef | null) => {
303
- "background only";
304
-
305
- nativeRef.current = node;
306
- if (typeof forwardedRef === "function") {
307
- forwardedRef(node);
308
- } else if (forwardedRef) {
309
- forwardedRef.current = node;
310
- }
311
- },
296
+ const mergedRef = React.useMemo(
297
+ () => mergeProps({ ref: nativeRef }, { ref: forwardedRef }).ref,
312
298
  [forwardedRef],
313
299
  );
314
300
 
@@ -647,13 +633,18 @@ export const TextFieldInput = React.forwardRef<NodesRef, TextFieldInputProps>((p
647
633
 
648
634
  return (
649
635
  <text
650
- ref={control.mergedRef}
636
+ {...mergeProps(
637
+ {
638
+ ref: control.mergedRef,
639
+ bindlayoutchange: props.bindlayoutchange,
640
+ "main-thread:bindlayoutchange": props["main-thread:bindlayoutchange"],
641
+ },
642
+ getAccessibilityProps(props),
643
+ )}
651
644
  id={props.id}
652
645
  hidden={props.hidden}
653
646
  flatten={props.flatten}
654
647
  focusable={props.focusable}
655
- bindlayoutchange={props.bindlayoutchange}
656
- main-thread:bindlayoutchange={props["main-thread:bindlayoutchange"]}
657
648
  className={clsx(classes.value, className)}
658
649
  style={getReadOnlyTextStyle({
659
650
  style: props.style,
@@ -661,7 +652,6 @@ export const TextFieldInput = React.forwardRef<NodesRef, TextFieldInputProps>((p
661
652
  placeholder: isPlaceholder,
662
653
  multiline: false,
663
654
  })}
664
- {...getAccessibilityProps(props)}
665
655
  >
666
656
  {displayValue}
667
657
  </text>
@@ -670,20 +660,24 @@ export const TextFieldInput = React.forwardRef<NodesRef, TextFieldInputProps>((p
670
660
 
671
661
  return (
672
662
  <input
673
- {...control.defaultValueProps}
674
- ref={control.mergedRef}
663
+ {...mergeProps(
664
+ {
665
+ ref: control.mergedRef,
666
+ bindinput: control.handleInput,
667
+ bindselection: handleSelection,
668
+ bindfocus: control.handleFocus,
669
+ bindblur: control.handleBlur,
670
+ },
671
+ control.defaultValueProps,
672
+ resolvedMaxLength === undefined ? {} : { maxlength: resolvedMaxLength },
673
+ nativeProps,
674
+ )}
675
675
  className={clsx(classes.value, className)}
676
676
  disabled={control.disabled}
677
677
  readonly={control.readOnly}
678
678
  show-soft-input-on-focus={showSoftInputOnFocus ?? true}
679
679
  android-set-soft-input-mode={androidSetSoftInputMode ?? "unspecified"}
680
680
  name={name ?? control.context.name}
681
- {...(resolvedMaxLength === undefined ? {} : { maxlength: resolvedMaxLength })}
682
- bindinput={control.handleInput}
683
- bindselection={handleSelection}
684
- bindfocus={control.handleFocus}
685
- bindblur={control.handleBlur}
686
- {...nativeProps}
687
681
  />
688
682
  );
689
683
  });
@@ -821,13 +815,18 @@ export const TextFieldTextarea = React.forwardRef<NodesRef, TextFieldTextareaPro
821
815
 
822
816
  return (
823
817
  <text
824
- ref={control.mergedRef}
818
+ {...mergeProps(
819
+ {
820
+ ref: control.mergedRef,
821
+ bindlayoutchange: props.bindlayoutchange,
822
+ "main-thread:bindlayoutchange": props["main-thread:bindlayoutchange"],
823
+ },
824
+ getAccessibilityProps(props),
825
+ )}
825
826
  id={props.id}
826
827
  hidden={props.hidden}
827
828
  flatten={props.flatten}
828
829
  focusable={props.focusable}
829
- bindlayoutchange={props.bindlayoutchange}
830
- main-thread:bindlayoutchange={props["main-thread:bindlayoutchange"]}
831
830
  className={clsx(classes.value, classes.textareaValue, classes.textareaFixed, className)}
832
831
  style={getReadOnlyTextStyle({
833
832
  style: props.style,
@@ -835,7 +834,6 @@ export const TextFieldTextarea = React.forwardRef<NodesRef, TextFieldTextareaPro
835
834
  placeholder: isPlaceholder,
836
835
  multiline: true,
837
836
  })}
838
- {...getAccessibilityProps(props)}
839
837
  >
840
838
  {isPlaceholder ? props.placeholder : value}
841
839
  </text>
@@ -846,8 +844,19 @@ export const TextFieldTextarea = React.forwardRef<NodesRef, TextFieldTextareaPro
846
844
  // 디자인 padding을 wrapper로 분리해 이때 padding만큼 높이가 중복되지 않게 한다.
847
845
  const textarea = (
848
846
  <textarea
849
- {...control.defaultValueProps}
850
- ref={control.mergedRef}
847
+ {...mergeProps(
848
+ {
849
+ ref: control.mergedRef,
850
+ bindinput: control.handleInput,
851
+ bindselection: handleSelection,
852
+ bindfocus: control.handleFocus,
853
+ bindblur: control.handleBlur,
854
+ bindlayoutchange: usesIOSAutoresizeWrapper ? bindlayoutchange : handleLayoutChange,
855
+ },
856
+ control.defaultValueProps,
857
+ resolvedMaxLength === undefined ? {} : { maxlength: resolvedMaxLength },
858
+ nativeProps,
859
+ )}
851
860
  className={clsx(
852
861
  classes.value,
853
862
  classes.textareaValue,
@@ -864,13 +873,6 @@ export const TextFieldTextarea = React.forwardRef<NodesRef, TextFieldTextareaPro
864
873
  android-fullscreen-mode={androidFullscreenMode ?? false}
865
874
  android-set-soft-input-mode={androidSetSoftInputMode ?? "unspecified"}
866
875
  name={name ?? control.context.name}
867
- {...(resolvedMaxLength === undefined ? {} : { maxlength: resolvedMaxLength })}
868
- bindinput={control.handleInput}
869
- bindselection={handleSelection}
870
- bindfocus={control.handleFocus}
871
- bindblur={control.handleBlur}
872
- bindlayoutchange={usesIOSAutoresizeWrapper ? bindlayoutchange : handleLayoutChange}
873
- {...nativeProps}
874
876
  />
875
877
  );
876
878
 
@@ -900,7 +902,10 @@ export const TextFieldPrefixIcon = React.forwardRef<unknown, TextFieldPrefixIcon
900
902
  const { className, ...otherProps } = props;
901
903
 
902
904
  return (
903
- <InternalIcon ref={ref} className={clsx(classes.prefixIcon, className)} {...otherProps} />
905
+ <InternalIcon
906
+ {...mergeProps({ ref }, otherProps)}
907
+ className={clsx(classes.prefixIcon, className)}
908
+ />
904
909
  );
905
910
  },
906
911
  );
@@ -915,9 +920,8 @@ export const TextFieldPrefixText = React.forwardRef<unknown, TextFieldPrefixText
915
920
 
916
921
  return (
917
922
  <text
918
- {...(ref ? { ref: ref as LynxTextRef } : {})}
923
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
919
924
  className={clsx(classes.prefixText, className)}
920
- {...nativeProps}
921
925
  >
922
926
  {children}
923
927
  </text>
@@ -934,7 +938,10 @@ export const TextFieldSuffixIcon = React.forwardRef<unknown, TextFieldSuffixIcon
934
938
  const { className, ...otherProps } = props;
935
939
 
936
940
  return (
937
- <InternalIcon ref={ref} className={clsx(classes.suffixIcon, className)} {...otherProps} />
941
+ <InternalIcon
942
+ {...mergeProps({ ref }, otherProps)}
943
+ className={clsx(classes.suffixIcon, className)}
944
+ />
938
945
  );
939
946
  },
940
947
  );
@@ -949,9 +956,8 @@ export const TextFieldSuffixText = React.forwardRef<unknown, TextFieldSuffixText
949
956
 
950
957
  return (
951
958
  <text
952
- {...(ref ? { ref: ref as LynxTextRef } : {})}
959
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
953
960
  className={clsx(classes.suffixText, className)}
954
- {...nativeProps}
955
961
  >
956
962
  {children}
957
963
  </text>
@@ -7,20 +7,27 @@ export * from "./BottomSheet";
7
7
  export * from "./Callout";
8
8
  export * from "./Checkbox";
9
9
  export * from "./Chip";
10
+ export * from "./Count";
10
11
  export * from "./Divider";
11
12
  export * from "./Field";
12
13
  export * from "./Icon";
14
+ export * from "./InputButton";
13
15
  export * from "./KeyboardAvoidingScrollView";
14
16
  export * from "./MannerTemp";
17
+ export * from "./Menu";
15
18
  export * from "./MannerTempBadge";
16
19
  export * from "./NotificationBadge";
17
20
  export * from "./PageBanner";
21
+ export * from "./List";
18
22
  export * from "./ProgressCircle";
19
23
  export * from "./RadioGroup";
24
+ export * from "./ReactionButton";
25
+ export * from "./ScaleFeedback";
20
26
  export * from "./SegmentedControl";
21
27
  export * from "./SelectBox";
22
28
  export * from "./Stack";
23
29
  export * from "./Switch";
30
+ export * from "./SwipeableMenuSheet";
24
31
  export * from "./Tabs";
25
32
  export * from "./TagGroup";
26
33
  export * from "./Text";
@@ -0,0 +1,235 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { computePosition, type PositioningOptions, type Rect } from "./Positioning";
4
+
5
+ const boundary: Rect = {
6
+ left: 0,
7
+ top: 0,
8
+ right: 320,
9
+ bottom: 600,
10
+ width: 320,
11
+ height: 600,
12
+ };
13
+
14
+ const menuOptions = {
15
+ flip: { fallbackStrategy: "bestFit" },
16
+ shift: { crossAxis: true },
17
+ size: { order: "beforeFlip", minimumHeight: 200 },
18
+ } satisfies Partial<PositioningOptions>;
19
+
20
+ describe("computePosition", () => {
21
+ it("keeps the requested placement when it fits", async () => {
22
+ await expect(
23
+ computePosition({
24
+ ...menuOptions,
25
+ reference: { left: 140, top: 100, right: 180, bottom: 140, width: 40, height: 40 },
26
+ boundary,
27
+ width: 120,
28
+ height: 100,
29
+ placement: "bottom",
30
+ gutter: 8,
31
+ overflowPadding: 8,
32
+ }),
33
+ ).resolves.toMatchObject({
34
+ left: 100,
35
+ top: 148,
36
+ width: 120,
37
+ height: 100,
38
+ placement: "bottom",
39
+ transformOrigin: "50% 0%",
40
+ });
41
+ });
42
+
43
+ it("flips alignment and shifts the floating element away from a viewport edge", async () => {
44
+ const position = await computePosition({
45
+ ...menuOptions,
46
+ reference: { left: 280, top: 100, right: 320, bottom: 140, width: 40, height: 40 },
47
+ boundary,
48
+ width: 120,
49
+ height: 100,
50
+ placement: "bottom-start",
51
+ gutter: 8,
52
+ overflowPadding: 8,
53
+ });
54
+
55
+ expect(position).toMatchObject({
56
+ left: 192,
57
+ top: 148,
58
+ width: 120,
59
+ height: 100,
60
+ placement: "bottom-end",
61
+ transformOrigin: "100% 0%",
62
+ });
63
+ });
64
+
65
+ it("keeps the floating element visible when neither side has enough space", async () => {
66
+ const position = await computePosition({
67
+ ...menuOptions,
68
+ reference: { left: 150, top: 200, right: 350, bottom: 240, width: 200, height: 40 },
69
+ boundary: { ...boundary, right: 390, bottom: 844, width: 390, height: 844 },
70
+ width: 240,
71
+ height: 108,
72
+ placement: "right",
73
+ gutter: 8,
74
+ overflowPadding: 8,
75
+ });
76
+
77
+ expect(position).toMatchObject({
78
+ left: 8,
79
+ top: 166,
80
+ width: 240,
81
+ height: 108,
82
+ placement: "left",
83
+ transformOrigin: "100% 50%",
84
+ });
85
+ });
86
+
87
+ it("keeps the floating element inside an embedded Lynx root", async () => {
88
+ const embeddedRoot = {
89
+ left: 780,
90
+ top: 0,
91
+ right: 1560,
92
+ bottom: 516,
93
+ width: 780,
94
+ height: 516,
95
+ };
96
+ const position = await computePosition({
97
+ ...menuOptions,
98
+ reference: { left: 1480, top: 80, right: 1520, bottom: 120, width: 40, height: 40 },
99
+ boundary: embeddedRoot,
100
+ width: 740,
101
+ height: 280,
102
+ placement: "bottom-start",
103
+ gutter: 8,
104
+ overflowPadding: 8,
105
+ });
106
+
107
+ expect(position.left).toBeGreaterThanOrEqual(embeddedRoot.left + 8);
108
+ expect(position.left + position.width).toBeLessThanOrEqual(embeddedRoot.right - 8);
109
+ });
110
+
111
+ it("flips sides and limits a long floating element to the available height", async () => {
112
+ const position = await computePosition({
113
+ ...menuOptions,
114
+ reference: { left: 140, top: 250, right: 180, bottom: 290, width: 40, height: 40 },
115
+ boundary: { ...boundary, bottom: 400, height: 400 },
116
+ width: 120,
117
+ height: 480,
118
+ placement: "bottom",
119
+ gutter: 8,
120
+ overflowPadding: 8,
121
+ });
122
+
123
+ expect(position).toMatchObject({
124
+ left: 100,
125
+ top: 8,
126
+ width: 120,
127
+ height: 234,
128
+ placement: "top",
129
+ transformOrigin: "50% 100%",
130
+ });
131
+ });
132
+
133
+ it("lets Select flip before shrinking while Menu keeps its initial side", async () => {
134
+ const options = {
135
+ reference: { left: 140, top: 340, right: 180, bottom: 380, width: 40, height: 40 },
136
+ boundary,
137
+ width: 120,
138
+ height: 220,
139
+ placement: "bottom",
140
+ gutter: 8,
141
+ overflowPadding: 8,
142
+ } as const;
143
+
144
+ const menu = await computePosition({ ...options, ...menuOptions });
145
+ const select = await computePosition({
146
+ ...options,
147
+ flip: { fallbackStrategy: "initialPlacement" },
148
+ size: { order: "afterShift", minimumHeight: 200 },
149
+ });
150
+
151
+ expect(menu).toMatchObject({ placement: "bottom", top: 388, height: 204 });
152
+ expect(select).toMatchObject({ placement: "top", top: 112, height: 220 });
153
+ });
154
+
155
+ it("preserves intrinsic size and supplies constraints for a remeasured bubble", async () => {
156
+ const options = {
157
+ reference: { left: 140, top: 100, right: 180, bottom: 140, width: 40, height: 40 },
158
+ boundary,
159
+ width: 400,
160
+ height: 100,
161
+ placement: "bottom",
162
+ gutter: 8,
163
+ overflowPadding: 16,
164
+ } as const;
165
+ const intrinsic = await computePosition(options);
166
+ expect(intrinsic).toMatchObject({ width: 400, height: 100, availableWidth: 288 });
167
+
168
+ // 너비 제한으로 줄바꿈된 후의 측정 높이를 전달합니다.
169
+ const remeasured = await computePosition({
170
+ ...options,
171
+ width: intrinsic.availableWidth,
172
+ height: 180,
173
+ });
174
+ expect(remeasured).toMatchObject({ left: 16, top: 148, width: 288, height: 180 });
175
+ expect(remeasured.left + remeasured.width).toBe(boundary.right - 16);
176
+ });
177
+
178
+ it("keeps corner padding when a shifted bubble cannot point exactly at its anchor", async () => {
179
+ const position = await computePosition({
180
+ reference: { left: 4, top: 100, right: 28, bottom: 124, width: 24, height: 24 },
181
+ boundary,
182
+ width: 120,
183
+ height: 80,
184
+ placement: "bottom",
185
+ gutter: 4,
186
+ overflowPadding: 8,
187
+ flip: false,
188
+ arrow: { size: 16, tipHeight: 6, padding: 14 },
189
+ });
190
+
191
+ expect(position).toMatchObject({
192
+ left: 8,
193
+ top: 134,
194
+ arrow: { left: 14, centerOffset: -14 },
195
+ });
196
+ });
197
+
198
+ it("includes the arrow tip in the gap after flipping to the opposite side", async () => {
199
+ const position = await computePosition({
200
+ reference: { left: 140, top: 560, right: 180, bottom: 600, width: 40, height: 40 },
201
+ boundary,
202
+ width: 120,
203
+ height: 80,
204
+ placement: "bottom",
205
+ gutter: 4,
206
+ overflowPadding: 8,
207
+ arrow: { size: 16, tipHeight: 6, padding: 14 },
208
+ });
209
+
210
+ expect(position).toMatchObject({
211
+ placement: "top",
212
+ top: 470,
213
+ height: 80,
214
+ arrow: { left: 52, centerOffset: 0 },
215
+ });
216
+ expect(position.arrow?.left).toBe(160 - position.left - 8);
217
+ });
218
+
219
+ it("allows an anchored overlay to opt out of collision movement and resizing", async () => {
220
+ const position = await computePosition({
221
+ reference: { left: 0, top: 0, right: 24, bottom: 24, width: 24, height: 24 },
222
+ boundary,
223
+ width: 120,
224
+ height: 80,
225
+ placement: "top",
226
+ gutter: 8,
227
+ overflowPadding: 8,
228
+ flip: false,
229
+ shift: false,
230
+ });
231
+
232
+ expect(position).toMatchObject({ left: -48, top: -88, height: 80, placement: "top" });
233
+ expect(position.availableHeight).toBe(0);
234
+ });
235
+ });