@seed-design/lynx-react 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/lib/components/Accordion/Accordion.d.ts.map +1 -1
  2. package/lib/components/Accordion/Accordion.jsx +24 -14
  3. package/lib/components/ActionButton/ActionButton.d.ts +1 -1
  4. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
  5. package/lib/components/ActionButton/ActionButton.jsx +12 -4
  6. package/lib/components/AppBar/AppBar.d.ts.map +1 -1
  7. package/lib/components/AppBar/AppBar.jsx +9 -8
  8. package/lib/components/Badge/Badge.d.ts.map +1 -1
  9. package/lib/components/Badge/Badge.jsx +2 -1
  10. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  11. package/lib/components/BottomSheet/BottomSheet.jsx +9 -9
  12. package/lib/components/Box/Box.d.ts.map +1 -1
  13. package/lib/components/Box/Box.jsx +2 -1
  14. package/lib/components/Callout/Callout.d.ts +2 -2
  15. package/lib/components/Callout/Callout.d.ts.map +1 -1
  16. package/lib/components/Callout/Callout.jsx +20 -10
  17. package/lib/components/Checkbox/Checkbox.d.ts +4 -2
  18. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
  19. package/lib/components/Checkbox/Checkbox.jsx +33 -17
  20. package/lib/components/Chip/Chip.d.ts +1 -1
  21. package/lib/components/Chip/Chip.d.ts.map +1 -1
  22. package/lib/components/Chip/Chip.jsx +36 -16
  23. package/lib/components/Count/Count.d.ts +7 -0
  24. package/lib/components/Count/Count.d.ts.map +1 -0
  25. package/lib/components/Count/Count.jsx +19 -0
  26. package/lib/components/Count/index.d.ts +2 -0
  27. package/lib/components/Count/index.d.ts.map +1 -0
  28. package/lib/components/Divider/Divider.d.ts.map +1 -1
  29. package/lib/components/Divider/Divider.jsx +2 -1
  30. package/lib/components/Field/Field.d.ts.map +1 -1
  31. package/lib/components/Field/Field.jsx +11 -15
  32. package/lib/components/Icon/Icon.d.ts.map +1 -1
  33. package/lib/components/Icon/Icon.jsx +4 -3
  34. package/lib/components/InputButton/InputButton.d.ts +40 -0
  35. package/lib/components/InputButton/InputButton.d.ts.map +1 -0
  36. package/lib/components/InputButton/InputButton.jsx +118 -0
  37. package/lib/components/InputButton/InputButton.namespace.d.ts +2 -0
  38. package/lib/components/InputButton/InputButton.namespace.d.ts.map +1 -0
  39. package/lib/components/InputButton/InputButton.namespace.js +16 -0
  40. package/lib/components/InputButton/index.d.ts +3 -0
  41. package/lib/components/InputButton/index.d.ts.map +1 -0
  42. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts.map +1 -1
  43. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.jsx +12 -8
  44. package/lib/components/List/List.d.ts +51 -0
  45. package/lib/components/List/List.d.ts.map +1 -0
  46. package/lib/components/List/List.jsx +176 -0
  47. package/lib/components/List/List.namespace.d.ts +2 -0
  48. package/lib/components/List/List.namespace.d.ts.map +1 -0
  49. package/lib/components/List/List.namespace.js +18 -0
  50. package/lib/components/List/index.d.ts +3 -0
  51. package/lib/components/List/index.d.ts.map +1 -0
  52. package/lib/components/MannerTemp/MannerTemp.d.ts.map +1 -1
  53. package/lib/components/MannerTemp/MannerTemp.jsx +3 -2
  54. package/lib/components/MannerTempBadge/MannerTempBadge.d.ts.map +1 -1
  55. package/lib/components/MannerTempBadge/MannerTempBadge.jsx +2 -1
  56. package/lib/components/Menu/Menu.d.ts +69 -0
  57. package/lib/components/Menu/Menu.d.ts.map +1 -0
  58. package/lib/components/Menu/Menu.jsx +520 -0
  59. package/lib/components/Menu/Menu.namespace.d.ts +2 -0
  60. package/lib/components/Menu/Menu.namespace.d.ts.map +1 -0
  61. package/lib/components/Menu/Menu.namespace.js +21 -0
  62. package/lib/components/Menu/index.d.ts +3 -0
  63. package/lib/components/Menu/index.d.ts.map +1 -0
  64. package/lib/components/NotificationBadge/NotificationBadge.d.ts.map +1 -1
  65. package/lib/components/NotificationBadge/NotificationBadge.jsx +3 -2
  66. package/lib/components/PageBanner/PageBanner.d.ts +5 -5
  67. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  68. package/lib/components/PageBanner/PageBanner.jsx +25 -14
  69. package/lib/components/ProgressCircle/ProgressCircle.d.ts +2 -2
  70. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
  71. package/lib/components/ProgressCircle/ProgressCircle.jsx +4 -3
  72. package/lib/components/RadioGroup/RadioGroup.d.ts +5 -3
  73. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  74. package/lib/components/RadioGroup/RadioGroup.jsx +22 -9
  75. package/lib/components/ReactionButton/ReactionButton.d.ts +20 -0
  76. package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -0
  77. package/lib/components/ReactionButton/ReactionButton.jsx +88 -0
  78. package/lib/components/ReactionButton/index.d.ts +2 -0
  79. package/lib/components/ReactionButton/index.d.ts.map +1 -0
  80. package/lib/components/ScaleFeedback/ScaleFeedback.d.ts +11 -0
  81. package/lib/components/ScaleFeedback/ScaleFeedback.d.ts.map +1 -0
  82. package/lib/components/ScaleFeedback/ScaleFeedback.jsx +16 -0
  83. package/lib/components/ScaleFeedback/index.d.ts +2 -0
  84. package/lib/components/ScaleFeedback/index.d.ts.map +1 -0
  85. package/lib/components/SegmentedControl/SegmentedControl.d.ts +1 -0
  86. package/lib/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  87. package/lib/components/SegmentedControl/SegmentedControl.jsx +23 -10
  88. package/lib/components/SelectBox/SelectBox.d.ts.map +1 -1
  89. package/lib/components/SelectBox/SelectBox.jsx +23 -13
  90. package/lib/components/Stack/Stack.d.ts.map +1 -1
  91. package/lib/components/Stack/Stack.jsx +2 -1
  92. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts +85 -0
  93. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -0
  94. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +256 -0
  95. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.d.ts +2 -0
  96. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.d.ts.map +1 -0
  97. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.js +24 -0
  98. package/lib/components/SwipeableMenuSheet/index.d.ts +3 -0
  99. package/lib/components/SwipeableMenuSheet/index.d.ts.map +1 -0
  100. package/lib/components/Switch/Switch.d.ts +27 -3
  101. package/lib/components/Switch/Switch.d.ts.map +1 -1
  102. package/lib/components/Switch/Switch.jsx +22 -9
  103. package/lib/components/Tabs/Tabs.d.ts +4 -2
  104. package/lib/components/Tabs/Tabs.d.ts.map +1 -1
  105. package/lib/components/Tabs/Tabs.jsx +37 -22
  106. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
  107. package/lib/components/TagGroup/TagGroup.jsx +4 -3
  108. package/lib/components/Text/Text.d.ts.map +1 -1
  109. package/lib/components/Text/Text.jsx +2 -1
  110. package/lib/components/TextField/TextField.d.ts.map +1 -1
  111. package/lib/components/TextField/TextField.jsx +35 -23
  112. package/lib/components/index.d.ts +7 -0
  113. package/lib/components/index.d.ts.map +1 -1
  114. package/lib/components/private/Positioning.d.ts +69 -0
  115. package/lib/components/private/Positioning.d.ts.map +1 -0
  116. package/lib/components/private/Positioning.js +109 -0
  117. package/lib/components/private/TrackPositioning.d.ts +22 -0
  118. package/lib/components/private/TrackPositioning.d.ts.map +1 -0
  119. package/lib/contexts/ScaleFeedbackContentContext.d.ts +4 -0
  120. package/lib/contexts/ScaleFeedbackContentContext.d.ts.map +1 -0
  121. package/lib/contexts/ScaleFeedbackContentContext.js +6 -0
  122. package/lib/contexts/index.d.ts +2 -0
  123. package/lib/contexts/index.d.ts.map +1 -0
  124. package/lib/hooks/index.d.ts +1 -0
  125. package/lib/hooks/index.d.ts.map +1 -1
  126. package/lib/hooks/useIconColor.d.ts.map +1 -1
  127. package/lib/hooks/useIconColor.js +8 -3
  128. package/lib/hooks/usePressTap.d.ts +4 -3
  129. package/lib/hooks/usePressTap.d.ts.map +1 -1
  130. package/lib/hooks/useScaleFeedback.d.ts +48 -0
  131. package/lib/hooks/useScaleFeedback.d.ts.map +1 -0
  132. package/lib/hooks/useScaleFeedback.js +108 -0
  133. package/lib/index.d.ts +1 -0
  134. package/lib/index.d.ts.map +1 -1
  135. package/lib/index.js +17 -3
  136. package/lib/node_modules/.bun/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +493 -0
  137. package/lib/node_modules/.bun/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +114 -0
  138. package/lib/utils/calculate-scale-feedback.d.ts +5 -0
  139. package/lib/utils/calculate-scale-feedback.d.ts.map +1 -0
  140. package/lib/utils/calculate-scale-feedback.js +13 -0
  141. package/lib/utils/compose-event-handlers.d.ts +5 -0
  142. package/lib/utils/compose-event-handlers.d.ts.map +1 -0
  143. package/lib/utils/compose-event-handlers.js +16 -0
  144. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  145. package/lib/utils/create-slot-recipe-context.jsx +6 -16
  146. package/lib/utils/index.d.ts +1 -0
  147. package/lib/utils/index.d.ts.map +1 -1
  148. package/lib/utils/merge-props.d.ts +15 -0
  149. package/lib/utils/merge-props.d.ts.map +1 -0
  150. package/lib/utils/merge-props.js +36 -0
  151. package/lib/utils/merge-refs.d.ts +8 -0
  152. package/lib/utils/merge-refs.d.ts.map +1 -0
  153. package/lib/utils/merge-refs.js +38 -0
  154. package/package.json +6 -5
  155. package/src/components/Accordion/Accordion.tsx +36 -27
  156. package/src/components/ActionButton/ActionButton.tsx +27 -12
  157. package/src/components/AppBar/AppBar.tsx +17 -18
  158. package/src/components/Badge/Badge.tsx +2 -2
  159. package/src/components/BottomSheet/BottomSheet.tsx +9 -17
  160. package/src/components/Box/Box.tsx +2 -2
  161. package/src/components/Callout/Callout.tsx +39 -22
  162. package/src/components/Checkbox/Checkbox.test.tsx +44 -0
  163. package/src/components/Checkbox/Checkbox.tsx +52 -21
  164. package/src/components/Chip/Chip.test.tsx +50 -1
  165. package/src/components/Chip/Chip.tsx +75 -31
  166. package/src/components/Count/Count.tsx +35 -0
  167. package/src/components/Count/index.ts +1 -0
  168. package/src/components/Divider/Divider.tsx +2 -2
  169. package/src/components/Field/Field.tsx +15 -28
  170. package/src/components/Icon/Icon.test.tsx +4 -6
  171. package/src/components/Icon/Icon.tsx +14 -7
  172. package/src/components/InputButton/InputButton.namespace.ts +20 -0
  173. package/src/components/InputButton/InputButton.test.tsx +82 -0
  174. package/src/components/InputButton/InputButton.tsx +295 -0
  175. package/src/components/InputButton/index.ts +2 -0
  176. package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +16 -21
  177. package/src/components/List/List.namespace.ts +24 -0
  178. package/src/components/List/List.test.tsx +162 -0
  179. package/src/components/List/List.tsx +476 -0
  180. package/src/components/List/index.ts +3 -0
  181. package/src/components/MannerTemp/MannerTemp.tsx +3 -4
  182. package/src/components/MannerTempBadge/MannerTempBadge.tsx +2 -2
  183. package/src/components/Menu/Menu.namespace.ts +26 -0
  184. package/src/components/Menu/Menu.tsx +883 -0
  185. package/src/components/Menu/index.ts +31 -0
  186. package/src/components/NotificationBadge/NotificationBadge.tsx +4 -9
  187. package/src/components/PageBanner/PageBanner.test.tsx +3 -0
  188. package/src/components/PageBanner/PageBanner.tsx +54 -45
  189. package/src/components/ProgressCircle/ProgressCircle.test.tsx +48 -0
  190. package/src/components/ProgressCircle/ProgressCircle.tsx +22 -3
  191. package/src/components/RadioGroup/RadioGroup.test.tsx +87 -0
  192. package/src/components/RadioGroup/RadioGroup.tsx +50 -14
  193. package/src/components/ReactionButton/ReactionButton.test.tsx +122 -0
  194. package/src/components/ReactionButton/ReactionButton.tsx +139 -0
  195. package/src/components/ReactionButton/index.ts +1 -0
  196. package/src/components/ScaleFeedback/ScaleFeedback.test.tsx +41 -0
  197. package/src/components/ScaleFeedback/ScaleFeedback.tsx +32 -0
  198. package/src/components/ScaleFeedback/index.ts +1 -0
  199. package/src/components/SegmentedControl/SegmentedControl.test.tsx +15 -3
  200. package/src/components/SegmentedControl/SegmentedControl.tsx +35 -14
  201. package/src/components/SelectBox/SelectBox.test.tsx +39 -1
  202. package/src/components/SelectBox/SelectBox.tsx +27 -16
  203. package/src/components/Stack/Stack.tsx +2 -2
  204. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.namespace.ts +37 -0
  205. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +258 -0
  206. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +655 -0
  207. package/src/components/SwipeableMenuSheet/index.ts +43 -0
  208. package/src/components/Switch/Switch.test.tsx +57 -0
  209. package/src/components/Switch/Switch.tsx +60 -16
  210. package/src/components/Tabs/Tabs.test.tsx +19 -0
  211. package/src/components/Tabs/Tabs.tsx +72 -40
  212. package/src/components/TagGroup/TagGroup.tsx +4 -6
  213. package/src/components/Text/Text.tsx +2 -2
  214. package/src/components/TextField/TextField.tsx +60 -54
  215. package/src/components/index.ts +7 -0
  216. package/src/components/private/Positioning.test.ts +235 -0
  217. package/src/components/private/Positioning.ts +223 -0
  218. package/src/components/private/TrackPositioning.test.ts +58 -0
  219. package/src/components/private/TrackPositioning.ts +37 -0
  220. package/src/contexts/ScaleFeedbackContentContext.ts +4 -0
  221. package/src/contexts/index.ts +1 -0
  222. package/src/hooks/index.ts +1 -0
  223. package/src/hooks/useIconColor.test.ts +4 -4
  224. package/src/hooks/useIconColor.ts +13 -6
  225. package/src/hooks/usePressTap.ts +4 -3
  226. package/src/hooks/useScaleFeedback.integration.test.tsx +85 -0
  227. package/src/hooks/useScaleFeedback.test.ts +50 -0
  228. package/src/hooks/useScaleFeedback.ts +199 -0
  229. package/src/index.ts +1 -0
  230. package/src/utils/calculate-scale-feedback.ts +12 -0
  231. package/src/utils/compose-event-handlers.ts +16 -0
  232. package/src/utils/create-slot-recipe-context.tsx +6 -7
  233. package/src/utils/index.ts +1 -0
  234. package/src/utils/merge-props.test.tsx +152 -0
  235. package/src/utils/merge-props.ts +62 -0
  236. package/src/utils/merge-refs.ts +44 -0
@@ -0,0 +1,31 @@
1
+ export {
2
+ MenuAnchor,
3
+ MenuContent,
4
+ MenuGroup,
5
+ MenuGroupLabel,
6
+ MenuItem,
7
+ MenuItemBody,
8
+ MenuItemDescription,
9
+ MenuItemLabel,
10
+ MenuPrefixIcon,
11
+ MenuRoot,
12
+ MenuScrollArea,
13
+ MenuSuffixIcon,
14
+ MenuTrigger,
15
+ type MenuAnchorProps,
16
+ type MenuContentProps,
17
+ type MenuGroupLabelProps,
18
+ type MenuGroupProps,
19
+ type MenuItemBodyProps,
20
+ type MenuItemDescriptionProps,
21
+ type MenuItemLabelProps,
22
+ type MenuItemProps,
23
+ type MenuOpenChangeDetails,
24
+ type MenuOpenChangeReason,
25
+ type MenuPlacement,
26
+ type MenuRootProps,
27
+ type MenuScrollAreaProps,
28
+ type MenuTriggerProps,
29
+ } from "./Menu";
30
+
31
+ export * as Menu from "./Menu.namespace";
@@ -9,11 +9,8 @@ import {
9
9
  import clsx from "clsx";
10
10
  import * as React from "@lynx-js/react";
11
11
 
12
- import type {
13
- LynxAccessibilityProps,
14
- LynxStyledElementProps,
15
- LynxViewRef,
16
- } from "../../types";
12
+ import type { LynxAccessibilityProps, LynxStyledElementProps, LynxViewRef } from "../../types";
13
+ import { mergeProps } from "../../utils/merge-props";
17
14
 
18
15
  const NotificationBadgeContext = React.createContext<NotificationBadgeVariantProps | null>(null);
19
16
 
@@ -39,8 +36,7 @@ export const NotificationBadge = React.forwardRef<unknown, NotificationBadgeProp
39
36
 
40
37
  return (
41
38
  <view
42
- {...(ref ? { ref: ref as LynxViewRef } : {})}
43
- {...nativeProps}
39
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
44
40
  className={clsx(classes.root, className)}
45
41
  >
46
42
  <text className={classes.label} text-single-line-vertical-align="center">
@@ -68,8 +64,7 @@ export const NotificationBadgePositioner = React.forwardRef<
68
64
  return (
69
65
  <NotificationBadgeContext.Provider value={contextValue}>
70
66
  <view
71
- {...(ref ? { ref: ref as LynxViewRef } : {})}
72
- {...nativeProps}
67
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
73
68
  className={clsx(notificationBadgePositioner(variantProps), className)}
74
69
  >
75
70
  {children}
@@ -45,6 +45,9 @@ describe("PageBanner", () => {
45
45
  const root = getPageBannerRoot();
46
46
 
47
47
  expect(root).toHaveClass("seed-page-banner__root--tone_neutral-variant_weak");
48
+ const closeButton = root.querySelector(".seed-page-banner__closeButton");
49
+ expect(closeButton?.parentElement).toBe(root);
50
+ expect(closeButton).toHaveAttribute("flatten", "false");
48
51
  expect(root.querySelector(".seed-page-banner__content")?.tagName.toLowerCase()).toBe("view");
49
52
  expect(root.querySelector(".seed-page-banner__body")?.tagName.toLowerCase()).toBe("text");
50
53
  expect(root.querySelector(".seed-page-banner__title")).toHaveClass(
@@ -5,6 +5,7 @@ import clsx from "clsx";
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,
@@ -15,6 +16,8 @@ import type {
15
16
  } from "../../types";
16
17
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
17
18
  import { IconSlotProvider } from "../Icon/Icon";
19
+ import { ScaleFeedback } from "../ScaleFeedback";
20
+ import { mergeProps } from "../../utils/merge-props";
18
21
 
19
22
  const { ClassNamesProvider, PropsProvider, useClassNames, useProps } =
20
23
  createSlotRecipeContext(pageBanner);
@@ -42,12 +45,12 @@ function usePageBannerContext(consumer: string) {
42
45
  /**
43
46
  * @platform Lynx
44
47
  *
45
- * 웹 대비 차이:
46
- * - `asChild`와 DOM 이벤트 대신 Lynx native `<view>`와 `bindtap`을 사용합니다.
47
- * - 웹 focus ring과 동적 scale feedback은 지원하지 않습니다.
48
+ * Differences from React Web:
49
+ * - Uses Lynx native `<view>` and `bindtap` instead of `asChild` and DOM events.
50
+ * - Does not provide a web focus ring.
48
51
  */
49
52
  export interface PageBannerRootProps
50
- extends Omit<PageBannerVariantProps, "pressed" | "closeButtonPressed">,
53
+ extends Omit<PageBannerVariantProps, "pressed" | "closeButtonPressed" | "interactive">,
51
54
  LynxStyledElementProps,
52
55
  LynxPressableProps,
53
56
  LynxAccessibilityProps {
@@ -91,7 +94,11 @@ export const PageBannerRoot = React.forwardRef<unknown, PageBannerRootProps>((pr
91
94
  onTap: bindtap,
92
95
  mainThreadOnTap: mainThreadBindtap,
93
96
  });
94
- const classNames = pageBanner({ ...variantProps, pressed: pressTap.pressed });
97
+ const classNames = pageBanner({
98
+ ...variantProps,
99
+ pressed: pressTap.pressed,
100
+ interactive: isInteractive,
101
+ });
95
102
  const dismiss = useMemoizedFn(() => {
96
103
  if (!open) return;
97
104
 
@@ -124,9 +131,11 @@ export const PageBannerRoot = React.forwardRef<unknown, PageBannerRootProps>((pr
124
131
  <PropsProvider value={variantProps}>
125
132
  <IconSlotProvider value={iconSlotContextValue}>
126
133
  <view
127
- {...(ref ? { ref: ref as LynxViewRef } : {})}
128
- {...nativeProps}
129
- {...(isInteractive ? pressTap : {})}
134
+ {...mergeProps(
135
+ ref ? { ref: ref as LynxViewRef } : {},
136
+ isInteractive ? pressTap : {},
137
+ nativeProps,
138
+ )}
130
139
  accessibility-element={accessibilityElement ?? (isInteractive ? true : undefined)}
131
140
  accessibility-traits={accessibilityTraits ?? (isInteractive ? "button" : undefined)}
132
141
  className={clsx(classNames.root, className)}
@@ -152,8 +161,7 @@ export const PageBannerContent = React.forwardRef<unknown, PageBannerContentProp
152
161
 
153
162
  return (
154
163
  <view
155
- {...(ref ? { ref: ref as LynxViewRef } : {})}
156
- {...nativeProps}
164
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
157
165
  className={clsx(classNames.content, className)}
158
166
  style={style}
159
167
  >
@@ -173,8 +181,7 @@ export const PageBannerBody = React.forwardRef<unknown, PageBannerBodyProps>((pr
173
181
 
174
182
  return (
175
183
  <text
176
- {...(ref ? { ref: ref as LynxTextRef } : {})}
177
- {...nativeProps}
184
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
178
185
  className={clsx(classNames.body, className)}
179
186
  style={style}
180
187
  >
@@ -194,8 +201,7 @@ export const PageBannerTitle = React.forwardRef<unknown, PageBannerTitleProps>((
194
201
 
195
202
  return (
196
203
  <text
197
- {...(ref ? { ref: ref as LynxTextRef } : {})}
198
- {...nativeProps}
204
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
199
205
  className={clsx(classNames.title, className)}
200
206
  style={style}
201
207
  >
@@ -219,8 +225,7 @@ export const PageBannerDescription = React.forwardRef<unknown, PageBannerDescrip
219
225
 
220
226
  return (
221
227
  <text
222
- {...(ref ? { ref: ref as LynxTextRef } : {})}
223
- {...nativeProps}
228
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
224
229
  className={clsx(classNames.description, className)}
225
230
  style={style}
226
231
  >
@@ -250,16 +255,17 @@ export const PageBannerButton = React.forwardRef<unknown, PageBannerButtonProps>
250
255
  const classNames = useClassNames();
251
256
 
252
257
  return (
253
- <text
254
- {...(ref ? { ref: ref as LynxTextRef } : {})}
255
- {...nativeProps}
256
- accessibility-element={accessibilityElement}
257
- accessibility-traits={accessibilityTraits}
258
- className={clsx(classNames.button, className)}
259
- style={style}
260
- >
261
- {children}
262
- </text>
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>
263
269
  );
264
270
  });
265
271
  PageBannerButton.displayName = "PageBannerButton";
@@ -267,7 +273,8 @@ PageBannerButton.displayName = "PageBannerButton";
267
273
  ////////////////////////////////////////////////////////////////////////////////////
268
274
 
269
275
  export interface PageBannerCloseButtonProps
270
- extends LynxStyledElementProps,
276
+ // Keep the scale target's Android View even if shared props later expose flatten.
277
+ extends Omit<LynxStyledElementProps, "flatten">,
271
278
  LynxPressableProps,
272
279
  LynxAccessibilityProps {}
273
280
 
@@ -290,29 +297,26 @@ export const PageBannerCloseButton = React.forwardRef<unknown, PageBannerCloseBu
290
297
  bindtap?.(event);
291
298
  dismiss();
292
299
  });
293
- const pressTap = usePressTap({
294
- onTap: handleTap,
295
- mainThreadOnTap: mainThreadBindtap,
300
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressTapHandlers } =
301
+ usePressTap({
302
+ onTap: handleTap,
303
+ mainThreadOnTap: mainThreadBindtap,
304
+ });
305
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
306
+ onTouchStart: bindtouchstart,
307
+ onTouchEnd: bindtouchend,
308
+ onTouchCancel: bindtouchcancel,
296
309
  });
297
310
  const classNames = pageBanner({
298
311
  ...parentVariantProps,
299
- closeButtonPressed: pressTap.pressed,
312
+ closeButtonPressed: pressed,
300
313
  });
301
314
  const iconSlotContextValue = React.useMemo(
302
315
  () => ({
303
316
  classNames: { suffixIcon: classNames.closeButtonIcon },
304
- deps: [
305
- parentVariantProps.tone ?? "neutral",
306
- parentVariantProps.variant ?? "weak",
307
- pressTap.pressed,
308
- ],
317
+ deps: [parentVariantProps.tone ?? "neutral", parentVariantProps.variant ?? "weak", pressed],
309
318
  }),
310
- [
311
- classNames.closeButtonIcon,
312
- parentVariantProps.tone,
313
- parentVariantProps.variant,
314
- pressTap.pressed,
315
- ],
319
+ [classNames.closeButtonIcon, parentVariantProps.tone, parentVariantProps.variant, pressed],
316
320
  );
317
321
 
318
322
  if (process.env.NODE_ENV !== "production" && accessibilityElement && !accessibilityLabel) {
@@ -322,14 +326,19 @@ export const PageBannerCloseButton = React.forwardRef<unknown, PageBannerCloseBu
322
326
  return (
323
327
  <IconSlotProvider value={iconSlotContextValue}>
324
328
  <view
325
- {...(ref ? { ref: ref as LynxViewRef } : {})}
326
- {...nativeProps}
327
- {...pressTap}
329
+ {...mergeProps(
330
+ ref ? { ref: ref as LynxViewRef } : {},
331
+ pressTapHandlers,
332
+ scaleFeedbackTriggerProps,
333
+ scaleFeedbackTargetProps,
334
+ nativeProps,
335
+ )}
328
336
  accessibility-element={accessibilityElement}
329
337
  accessibility-label={accessibilityLabel}
330
338
  accessibility-traits={accessibilityTraits}
331
339
  className={clsx(classNames.closeButton, className)}
332
340
  style={style}
341
+ flatten={false}
333
342
  >
334
343
  {children}
335
344
  </view>
@@ -23,7 +23,55 @@ function getRenderedRoot() {
23
23
  return root;
24
24
  }
25
25
 
26
+ function getProgressCircleRoot() {
27
+ const progressCircleRoot = getRenderedRoot().firstElementChild;
28
+
29
+ if (!progressCircleRoot) {
30
+ throw new Error("Expected ProgressCircle to render a root element.");
31
+ }
32
+
33
+ return progressCircleRoot as HTMLElement;
34
+ }
35
+
26
36
  describe("ProgressCircle", () => {
37
+ it("provides determinate progress accessibility values without rounding", () => {
38
+ render(<ProgressCircle.Root minValue={-0.25} maxValue={1.75} value={0.3333333333333333} />);
39
+
40
+ const root = getProgressCircleRoot();
41
+
42
+ expect(root).toHaveAttribute("accessibility-element", "true");
43
+ expect(root).toHaveAttribute("accessibility-role-description", "progressbar");
44
+ expect(root).toHaveAttribute(
45
+ "accessibility-value",
46
+ "minimum -0.25, maximum 1.75, current 0.3333333333333333",
47
+ );
48
+ });
49
+
50
+ it("describes indeterminate progress without a numeric value", () => {
51
+ render(<ProgressCircle.Root />);
52
+
53
+ expect(getProgressCircleRoot()).toHaveAttribute("accessibility-value", "indeterminate");
54
+ });
55
+
56
+ it("prefers explicit accessibility props over defaults", () => {
57
+ render(
58
+ <ProgressCircle.Root
59
+ minValue={0}
60
+ maxValue={100}
61
+ value={40}
62
+ accessibility-element={false}
63
+ accessibility-role-description="custom progress"
64
+ accessibility-value="40 percent complete"
65
+ />,
66
+ );
67
+
68
+ const root = getProgressCircleRoot();
69
+
70
+ expect(root).toHaveAttribute("accessibility-element", "false");
71
+ expect(root).toHaveAttribute("accessibility-role-description", "custom progress");
72
+ expect(root).toHaveAttribute("accessibility-value", "40 percent complete");
73
+ });
74
+
27
75
  it("renders a visible indeterminate range before the main-thread animation starts", () => {
28
76
  render(
29
77
  <ProgressCircle.Root>
@@ -12,7 +12,7 @@ import type { MainThread } from "@lynx-js/types";
12
12
  import clsx from "clsx";
13
13
  import { progressCircle } from "@seed-design/lynx-css/recipes/progress-circle";
14
14
  import type { ProgressCircleVariantProps } from "@seed-design/lynx-css/recipes/progress-circle";
15
- import type { LynxStyledElementProps, LynxViewRef } from "../../types";
15
+ import type { LynxAccessibilityProps, LynxStyledElementProps, LynxViewRef } from "../../types";
16
16
 
17
17
  ////////////////////////////////////////////////////////////////////////////////////
18
18
 
@@ -153,7 +153,8 @@ const TRANSITION_DURATION = 300;
153
153
 
154
154
  export interface ProgressCircleRootProps
155
155
  extends ProgressCircleVariantProps,
156
- LynxStyledElementProps {
156
+ LynxStyledElementProps,
157
+ LynxAccessibilityProps {
157
158
  minValue?: number;
158
159
  maxValue?: number;
159
160
  value?: number;
@@ -175,7 +176,18 @@ export type RootProps = ProgressCircleRootProps;
175
176
  */
176
177
  export const ProgressCircleRoot = forwardRef<unknown, ProgressCircleRootProps>((props, ref) => {
177
178
  const [variantProps, otherProps] = progressCircle.splitVariantProps(props);
178
- const { children, className, style, minValue, maxValue, value } = otherProps;
179
+ const {
180
+ children,
181
+ className,
182
+ style,
183
+ minValue,
184
+ maxValue,
185
+ value,
186
+ "accessibility-element": accessibilityElement = true,
187
+ "accessibility-role-description": accessibilityRoleDescription = "progressbar",
188
+ "accessibility-value": accessibilityValue,
189
+ ...nativeProps
190
+ } = otherProps;
179
191
  const size = variantProps.size ?? "40";
180
192
  const tone = variantProps.tone ?? "neutral";
181
193
  const numSize = Number(size);
@@ -184,6 +196,9 @@ export const ProgressCircleRoot = forwardRef<unknown, ProgressCircleRootProps>((
184
196
 
185
197
  const range = (maxValue ?? 1) - (minValue ?? 0);
186
198
  const progress = isDeterminate ? (range === 0 ? 0 : ((value ?? 0) - (minValue ?? 0)) / range) : 0;
199
+ const defaultAccessibilityValue = isDeterminate
200
+ ? `minimum ${minValue}, maximum ${maxValue}, current ${value}`
201
+ : "indeterminate";
187
202
 
188
203
  const classes = progressCircle({ tone, size });
189
204
 
@@ -195,9 +210,13 @@ export const ProgressCircleRoot = forwardRef<unknown, ProgressCircleRootProps>((
195
210
  return (
196
211
  <ProgressCircleContext.Provider value={ctx}>
197
212
  <view
213
+ {...nativeProps}
198
214
  {...(ref ? { ref: ref as LynxViewRef } : {})}
199
215
  className={clsx(classes.root, className)}
200
216
  style={{ ...style, width: `${numSize}px`, height: `${numSize}px` }}
217
+ accessibility-element={accessibilityElement}
218
+ accessibility-role-description={accessibilityRoleDescription}
219
+ accessibility-value={accessibilityValue ?? defaultAccessibilityValue}
201
220
  >
202
221
  {children}
203
222
  </view>
@@ -0,0 +1,87 @@
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
+
5
+ import { RadioGroup } from "./index";
6
+
7
+ function getItems(container: HTMLElement) {
8
+ return container.querySelectorAll<HTMLElement>(".seed-radio__root");
9
+ }
10
+
11
+ describe("RadioGroup", () => {
12
+ it("exposes group and item accessibility defaults while preserving selection", () => {
13
+ const onValueChange = vi.fn();
14
+ const { container } = render(
15
+ <RadioGroup.Root
16
+ accessibility-label="과일"
17
+ defaultValue="apple"
18
+ onValueChange={onValueChange}
19
+ >
20
+ <RadioGroup.Item value="apple" accessibility-label="사과" />
21
+ <RadioGroup.Item value="banana" accessibility-label="바나나" />
22
+ </RadioGroup.Root>,
23
+ );
24
+ const root = container.firstElementChild;
25
+ const [apple, banana] = getItems(container);
26
+
27
+ expect(root).toHaveAttribute("accessibility-element", "true");
28
+ expect(root).toHaveAttribute("accessibility-role-description", "radiogroup");
29
+ expect(root).toHaveAttribute("accessibility-label", "과일");
30
+ expect(apple).toHaveAttribute("accessibility-element", "true");
31
+ expect(apple).toHaveAttribute("accessibility-role-description", "radio");
32
+ expect(apple).toHaveAttribute("accessibility-label", "사과");
33
+ expect(apple).toHaveAttribute("accessibility-value", "selected");
34
+ expect(banana).toHaveAttribute("accessibility-value", "not selected");
35
+
36
+ fireEvent.tap(banana);
37
+
38
+ expect(onValueChange).toHaveBeenCalledWith("banana");
39
+ expect(apple).toHaveAttribute("accessibility-value", "not selected");
40
+ expect(banana).toHaveAttribute("accessibility-value", "selected");
41
+ });
42
+
43
+ it("exposes disabled state and allows accessibility overrides", () => {
44
+ const onValueChange = vi.fn();
45
+ const { container, rerender } = render(
46
+ <RadioGroup.Root disabled onValueChange={onValueChange}>
47
+ <RadioGroup.Item value="apple" />
48
+ </RadioGroup.Root>,
49
+ );
50
+ const root = container.firstElementChild;
51
+ const [disabledItem] = getItems(container);
52
+
53
+ expect(root).toHaveAttribute("accessibility-traits", "disabled");
54
+ expect(disabledItem).toHaveAttribute("accessibility-traits", "disabled");
55
+
56
+ fireEvent.tap(disabledItem);
57
+
58
+ expect(onValueChange).not.toHaveBeenCalled();
59
+
60
+ rerender(
61
+ <RadioGroup.Root
62
+ disabled
63
+ accessibility-element={false}
64
+ accessibility-role-description="custom group"
65
+ accessibility-traits="button"
66
+ >
67
+ <RadioGroup.Item
68
+ value="apple"
69
+ accessibility-element={false}
70
+ accessibility-role-description="custom radio"
71
+ accessibility-traits="button"
72
+ accessibility-value="custom selection"
73
+ />
74
+ </RadioGroup.Root>,
75
+ );
76
+ const overriddenRoot = container.firstElementChild;
77
+ const [overriddenItem] = getItems(container);
78
+
79
+ expect(overriddenRoot).toHaveAttribute("accessibility-element", "false");
80
+ expect(overriddenRoot).toHaveAttribute("accessibility-role-description", "custom group");
81
+ expect(overriddenRoot).toHaveAttribute("accessibility-traits", "button");
82
+ expect(overriddenItem).toHaveAttribute("accessibility-element", "false");
83
+ expect(overriddenItem).toHaveAttribute("accessibility-role-description", "custom radio");
84
+ expect(overriddenItem).toHaveAttribute("accessibility-traits", "button");
85
+ expect(overriddenItem).toHaveAttribute("accessibility-value", "custom selection");
86
+ });
87
+ });
@@ -9,8 +9,11 @@ import type { RadiomarkVariantProps } from "@seed-design/lynx-css/recipes/radiom
9
9
  import { radioGroup } from "@seed-design/lynx-css/recipes/radio-group";
10
10
 
11
11
  import { useControllableState } from "../../hooks/useControllableState";
12
+ import { ScaleFeedbackContentContext } from "../../contexts";
12
13
  import { usePressTap } from "../../hooks/usePressTap";
14
+ import { useScaleFeedback, type ScaleFeedbackTargetProps } from "../../hooks/useScaleFeedback";
13
15
  import type {
16
+ LynxAccessibilityProps,
14
17
  LynxIconElementProps,
15
18
  LynxStyledElementProps,
16
19
  LynxTextRef,
@@ -18,6 +21,7 @@ import type {
18
21
  } from "../../types";
19
22
  import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
20
23
  import { InternalIcon } from "../Icon/Icon";
24
+ import { mergeProps } from "../../utils/merge-props";
21
25
 
22
26
  /**
23
27
  * @platform Lynx
@@ -56,6 +60,7 @@ interface RadioGroupItemContextValue {
56
60
  disabled: boolean;
57
61
  pressed: boolean;
58
62
  select: () => void;
63
+ scaleFeedbackTargetProps: ScaleFeedbackTargetProps;
59
64
  }
60
65
 
61
66
  const RadioGroupItemContext = React.createContext<RadioGroupItemContextValue | null>(null);
@@ -88,7 +93,8 @@ function useRadiomarkControlContext(consumer: string): RadiomarkControlContextVa
88
93
  export interface RadioGroupRootProps
89
94
  extends RadioVariantProps,
90
95
  Omit<RadiomarkVariantProps, "size" | "checked" | "disabled">,
91
- LynxStyledElementProps {
96
+ LynxStyledElementProps,
97
+ LynxAccessibilityProps {
92
98
  value?: string;
93
99
  defaultValue?: string;
94
100
  disabled?: boolean;
@@ -105,8 +111,14 @@ export const RadioGroupRoot = React.forwardRef<unknown, RadioGroupRootProps>((pr
105
111
  onValueChange,
106
112
  ...restProps
107
113
  } = props;
108
- const [{ radio: radioVariantProps, radiomark: radiomarkVariantProps }, nativeProps] =
114
+ const [{ radio: radioVariantProps, radiomark: radiomarkVariantProps }, restNativeProps] =
109
115
  splitMultipleVariantsProps(restProps, { radio, radiomark });
116
+ const {
117
+ "accessibility-element": accessibilityElement = true,
118
+ "accessibility-role-description": accessibilityRoleDescription = "radiogroup",
119
+ "accessibility-traits": accessibilityTraits,
120
+ ...nativeProps
121
+ } = restNativeProps;
110
122
 
111
123
  const handleChange = React.useCallback(
112
124
  (next: string | null) => {
@@ -144,9 +156,11 @@ export const RadioGroupRoot = React.forwardRef<unknown, RadioGroupRootProps>((pr
144
156
  return (
145
157
  <RadioGroupContext.Provider value={contextValue}>
146
158
  <view
147
- {...(ref ? { ref: ref as LynxViewRef } : {})}
159
+ {...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
148
160
  className={clsx(rootClassName, className)}
149
- {...nativeProps}
161
+ accessibility-element={accessibilityElement}
162
+ accessibility-role-description={accessibilityRoleDescription}
163
+ accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : undefined)}
150
164
  >
151
165
  {children}
152
166
  </view>
@@ -157,7 +171,7 @@ RadioGroupRoot.displayName = "RadioGroupRoot";
157
171
 
158
172
  ////////////////////////////////////////////////////////////////////////////////////
159
173
 
160
- export interface RadioGroupItemProps extends LynxStyledElementProps {
174
+ export interface RadioGroupItemProps extends LynxStyledElementProps, LynxAccessibilityProps {
161
175
  value: string;
162
176
  disabled?: boolean;
163
177
  }
@@ -168,6 +182,10 @@ export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>((pr
168
182
  disabled: itemDisabled = false,
169
183
  children,
170
184
  className,
185
+ "accessibility-element": accessibilityElement = true,
186
+ "accessibility-role-description": accessibilityRoleDescription = "radio",
187
+ "accessibility-traits": accessibilityTraits,
188
+ "accessibility-value": accessibilityValue,
171
189
  ...nativeProps
172
190
  } = props;
173
191
  const groupContext = useRadioGroupContext("RadioGroupItem");
@@ -179,10 +197,16 @@ export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>((pr
179
197
  groupContext.setValue(itemValue);
180
198
  }, [checked, groupContext, itemValue]);
181
199
 
182
- const { pressed, ...pressHandlers } = usePressTap({
200
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
183
201
  disabled,
184
202
  onTap: select,
185
203
  });
204
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
205
+ disabled,
206
+ onTouchStart: bindtouchstart,
207
+ onTouchEnd: bindtouchend,
208
+ onTouchCancel: bindtouchcancel,
209
+ });
186
210
 
187
211
  const rootClassName = radio({ ...groupContext.radioVariantProps, disabled }).root;
188
212
 
@@ -193,17 +217,25 @@ export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>((pr
193
217
  disabled,
194
218
  pressed,
195
219
  select,
220
+ scaleFeedbackTargetProps,
196
221
  }),
197
- [itemValue, checked, disabled, pressed, select],
222
+ [itemValue, checked, disabled, pressed, select, scaleFeedbackTargetProps],
198
223
  );
199
224
 
200
225
  return (
201
226
  <RadioGroupItemContext.Provider value={itemContextValue}>
202
227
  <view
203
- {...(ref ? { ref: ref as LynxViewRef } : {})}
228
+ {...mergeProps(
229
+ ref ? { ref: ref as LynxViewRef } : {},
230
+ scaleFeedbackTriggerProps,
231
+ pressHandlers,
232
+ nativeProps,
233
+ )}
204
234
  className={clsx(rootClassName, className)}
205
- {...pressHandlers}
206
- {...nativeProps}
235
+ accessibility-element={accessibilityElement}
236
+ accessibility-role-description={accessibilityRoleDescription}
237
+ accessibility-traits={accessibilityTraits ?? (disabled ? "disabled" : undefined)}
238
+ accessibility-value={accessibilityValue ?? (checked ? "selected" : "not selected")}
207
239
  >
208
240
  {children}
209
241
  </view>
@@ -224,6 +256,7 @@ export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemCon
224
256
  const { children, className, ...nativeProps } = restProps;
225
257
  const groupContext = useRadioGroupContext("RadioGroupItemControl");
226
258
  const itemContext = useRadioGroupItemContext("RadioGroupItemControl");
259
+ const hasScaledContent = React.useContext(ScaleFeedbackContentContext);
227
260
  const radiomarkVariantProps: RadiomarkVariantProps = {
228
261
  ...groupContext.radiomarkVariantProps,
229
262
  ...variantProps,
@@ -239,9 +272,13 @@ export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemCon
239
272
  value={{ iconClassName: classes.icon, radiomarkVariantProps }}
240
273
  >
241
274
  <view
242
- {...(ref ? { ref: ref as LynxViewRef } : {})}
275
+ {...mergeProps(
276
+ ref ? { ref: ref as LynxViewRef } : {},
277
+ !hasScaledContent ? itemContext.scaleFeedbackTargetProps : {},
278
+ nativeProps,
279
+ )}
243
280
  className={clsx(classes.root, controlClassName, className)}
244
- {...nativeProps}
281
+ {...(!hasScaledContent ? { flatten: false } : {})}
245
282
  >
246
283
  {children}
247
284
  </view>
@@ -309,9 +346,8 @@ export const RadioGroupItemLabel = React.forwardRef<unknown, RadioGroupItemLabel
309
346
 
310
347
  return (
311
348
  <text
312
- {...(ref ? { ref: ref as LynxTextRef } : {})}
349
+ {...mergeProps(ref ? { ref: ref as LynxTextRef } : {}, nativeProps)}
313
350
  className={clsx(labelClassName, className)}
314
- {...nativeProps}
315
351
  >
316
352
  {children}
317
353
  </text>