@seed-design/lynx-react 0.2.0-alpha.2 → 0.2.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 (186) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +26 -32
  3. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
  4. package/lib/components/ActionButton/ActionButton.jsx +139 -120
  5. package/lib/components/ActionButton/index.d.ts +1 -1
  6. package/lib/components/AppBar/AppBar.d.ts +35 -0
  7. package/lib/components/AppBar/AppBar.d.ts.map +1 -0
  8. package/lib/components/AppBar/AppBar.jsx +132 -0
  9. package/lib/components/AppBar/AppBar.namespace.d.ts +2 -0
  10. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -0
  11. package/lib/components/AppBar/AppBar.namespace.js +15 -0
  12. package/lib/components/AppBar/context.d.ts +13 -0
  13. package/lib/components/AppBar/context.d.ts.map +1 -0
  14. package/lib/components/AppBar/context.js +12 -0
  15. package/lib/components/AppBar/index.d.ts +3 -0
  16. package/lib/components/AppBar/index.d.ts.map +1 -0
  17. package/lib/components/AppBar/useAppBar.d.ts +15 -0
  18. package/lib/components/AppBar/useAppBar.d.ts.map +1 -0
  19. package/lib/components/AppBar/useAppBar.js +72 -0
  20. package/lib/components/Badge/Badge.d.ts +14 -0
  21. package/lib/components/Badge/Badge.d.ts.map +1 -0
  22. package/lib/components/Badge/Badge.jsx +15 -0
  23. package/lib/components/Badge/index.d.ts +2 -0
  24. package/lib/components/Badge/index.d.ts.map +1 -0
  25. package/lib/components/BottomSheet/BottomSheet.d.ts +22 -27
  26. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/lib/components/BottomSheet/BottomSheet.jsx +127 -142
  28. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +1 -1
  29. package/lib/components/BottomSheet/BottomSheet.namespace.js +18 -1
  30. package/lib/components/BottomSheet/index.d.ts +2 -2
  31. package/lib/components/Box/Box.d.ts +20 -0
  32. package/lib/components/Box/Box.d.ts.map +1 -0
  33. package/lib/components/Box/Box.jsx +14 -0
  34. package/lib/components/Box/index.d.ts +2 -0
  35. package/lib/components/Box/index.d.ts.map +1 -0
  36. package/lib/components/Checkbox/Checkbox.d.ts +14 -30
  37. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
  38. package/lib/components/Checkbox/Checkbox.jsx +107 -98
  39. package/lib/components/Checkbox/Checkbox.namespace.d.ts +1 -1
  40. package/lib/components/Checkbox/Checkbox.namespace.js +12 -1
  41. package/lib/components/Checkbox/index.d.ts +2 -2
  42. package/lib/components/Icon/Icon.d.ts +37 -0
  43. package/lib/components/Icon/Icon.d.ts.map +1 -0
  44. package/lib/components/Icon/Icon.jsx +143 -0
  45. package/lib/components/Icon/index.d.ts +2 -0
  46. package/lib/components/Icon/index.d.ts.map +1 -0
  47. package/lib/components/ProgressCircle/ProgressCircle.d.ts +4 -8
  48. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
  49. package/lib/components/ProgressCircle/ProgressCircle.jsx +255 -260
  50. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts +1 -1
  51. package/lib/components/ProgressCircle/ProgressCircle.namespace.js +9 -1
  52. package/lib/components/ProgressCircle/index.d.ts +2 -2
  53. package/lib/components/RadioGroup/RadioGroup.d.ts +12 -28
  54. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  55. package/lib/components/RadioGroup/RadioGroup.jsx +134 -118
  56. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts +1 -1
  57. package/lib/components/RadioGroup/RadioGroup.namespace.js +12 -1
  58. package/lib/components/RadioGroup/index.d.ts +2 -2
  59. package/lib/components/Stack/Stack.d.ts +33 -0
  60. package/lib/components/Stack/Stack.d.ts.map +1 -0
  61. package/lib/components/Stack/Stack.jsx +39 -0
  62. package/lib/components/Stack/index.d.ts +2 -0
  63. package/lib/components/Stack/index.d.ts.map +1 -0
  64. package/lib/components/Switch/Switch.d.ts +8 -14
  65. package/lib/components/Switch/Switch.d.ts.map +1 -1
  66. package/lib/components/Switch/Switch.jsx +82 -56
  67. package/lib/components/Switch/Switch.namespace.d.ts +1 -1
  68. package/lib/components/Switch/Switch.namespace.js +11 -1
  69. package/lib/components/Switch/index.d.ts +2 -2
  70. package/lib/components/TagGroup/TagGroup.d.ts +8 -14
  71. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
  72. package/lib/components/TagGroup/TagGroup.jsx +42 -39
  73. package/lib/components/TagGroup/TagGroup.namespace.d.ts +1 -1
  74. package/lib/components/TagGroup/TagGroup.namespace.js +10 -1
  75. package/lib/components/TagGroup/index.d.ts +2 -2
  76. package/lib/components/Text/Text.d.ts +7 -0
  77. package/lib/components/Text/Text.d.ts.map +1 -0
  78. package/lib/components/Text/Text.jsx +28 -0
  79. package/lib/components/Text/index.d.ts +2 -0
  80. package/lib/components/Text/index.d.ts.map +1 -0
  81. package/lib/components/index.d.ts +13 -7
  82. package/lib/components/index.d.ts.map +1 -1
  83. package/lib/hooks/index.d.ts +4 -3
  84. package/lib/hooks/index.d.ts.map +1 -1
  85. package/lib/hooks/{use-controllable-state.d.ts → useControllableState.d.ts} +1 -1
  86. package/lib/hooks/useControllableState.d.ts.map +1 -0
  87. package/lib/hooks/useControllableState.js +27 -0
  88. package/lib/hooks/useIconColor.d.ts +19 -0
  89. package/lib/hooks/useIconColor.d.ts.map +1 -0
  90. package/lib/hooks/useIconColor.js +38 -0
  91. package/lib/hooks/{use-press-tap.d.ts → usePressTap.d.ts} +12 -9
  92. package/lib/hooks/usePressTap.d.ts.map +1 -0
  93. package/lib/hooks/usePressTap.js +35 -0
  94. package/lib/hooks/useSafeArea.d.ts +19 -0
  95. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  96. package/lib/hooks/useSafeArea.js +33 -0
  97. package/lib/index.d.ts +4 -3
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js +29 -3
  100. package/lib/types.d.ts +51 -0
  101. package/lib/types.d.ts.map +1 -0
  102. package/lib/utils/children.d.ts +3 -0
  103. package/lib/utils/children.d.ts.map +1 -0
  104. package/lib/utils/children.js +8 -0
  105. package/lib/utils/create-slot-recipe-context.d.ts +13 -12
  106. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  107. package/lib/utils/create-slot-recipe-context.jsx +88 -70
  108. package/lib/utils/get-seed-class-name.d.ts +6 -7
  109. package/lib/utils/get-seed-class-name.d.ts.map +1 -1
  110. package/lib/utils/get-seed-class-name.js +26 -37
  111. package/lib/utils/index.d.ts +4 -4
  112. package/lib/utils/resolve-recipe-token.js +45 -43
  113. package/lib/utils/split-multiple-variants-props.js +32 -32
  114. package/lib/utils/styled.d.ts +83 -0
  115. package/lib/utils/styled.d.ts.map +1 -0
  116. package/lib/utils/styled.js +144 -0
  117. package/package.json +9 -9
  118. package/src/components/ActionButton/ActionButton.test.tsx +167 -0
  119. package/src/components/ActionButton/ActionButton.tsx +210 -168
  120. package/src/components/AppBar/AppBar.namespace.ts +18 -0
  121. package/src/components/AppBar/AppBar.test.tsx +270 -0
  122. package/src/components/AppBar/AppBar.tsx +288 -0
  123. package/src/components/AppBar/context.ts +27 -0
  124. package/src/components/AppBar/index.ts +21 -0
  125. package/src/components/AppBar/useAppBar.ts +100 -0
  126. package/src/components/Badge/Badge.test.tsx +54 -0
  127. package/src/components/Badge/Badge.tsx +32 -0
  128. package/src/components/Badge/index.ts +1 -0
  129. package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
  130. package/src/components/BottomSheet/BottomSheet.tsx +120 -83
  131. package/src/components/Box/Box.test.tsx +85 -0
  132. package/src/components/Box/Box.tsx +39 -0
  133. package/src/components/Box/index.ts +1 -0
  134. package/src/components/Checkbox/Checkbox.tsx +108 -117
  135. package/src/components/Icon/Icon.tsx +265 -0
  136. package/src/components/Icon/index.ts +8 -0
  137. package/src/components/ProgressCircle/ProgressCircle.tsx +39 -25
  138. package/src/components/RadioGroup/RadioGroup.tsx +100 -118
  139. package/src/components/Stack/Stack.tsx +97 -0
  140. package/src/components/Stack/index.ts +1 -0
  141. package/src/components/Switch/Switch.tsx +61 -52
  142. package/src/components/TagGroup/TagGroup.tsx +27 -25
  143. package/src/components/Text/Text.tsx +64 -0
  144. package/src/components/Text/index.ts +1 -0
  145. package/src/components/index.ts +6 -0
  146. package/src/hooks/index.ts +4 -3
  147. package/src/hooks/{__tests__/use-controllable-state.test.ts → useControllableState.test.ts} +1 -1
  148. package/src/hooks/useIconColor.test.ts +13 -0
  149. package/src/hooks/useIconColor.ts +67 -0
  150. package/src/hooks/{__tests__/use-press-tap.test.ts → usePressTap.test.ts} +1 -1
  151. package/src/hooks/{use-press-tap.ts → usePressTap.ts} +14 -10
  152. package/src/hooks/useSafeArea.test.ts +83 -0
  153. package/src/hooks/useSafeArea.ts +56 -0
  154. package/src/index.ts +1 -0
  155. package/src/types.ts +76 -0
  156. package/src/utils/children.ts +13 -0
  157. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  158. package/src/utils/create-slot-recipe-context.tsx +55 -15
  159. package/src/utils/get-seed-class-name.ts +7 -12
  160. package/src/utils/{__tests__/split-multiple-variants-props.test.ts → split-multiple-variants-props.test.ts} +1 -1
  161. package/src/utils/styled.ts +442 -0
  162. package/lib/components/ActionButton/index.js +0 -1
  163. package/lib/components/BottomSheet/index.js +0 -2
  164. package/lib/components/Checkbox/index.js +0 -2
  165. package/lib/components/ProgressCircle/index.js +0 -2
  166. package/lib/components/RadioGroup/index.js +0 -2
  167. package/lib/components/Switch/index.js +0 -2
  168. package/lib/components/TagGroup/index.js +0 -2
  169. package/lib/components/index.js +0 -7
  170. package/lib/hooks/index.js +0 -3
  171. package/lib/hooks/use-controllable-state.d.ts.map +0 -1
  172. package/lib/hooks/use-controllable-state.js +0 -27
  173. package/lib/hooks/use-icon-color.d.ts +0 -15
  174. package/lib/hooks/use-icon-color.d.ts.map +0 -1
  175. package/lib/hooks/use-icon-color.js +0 -37
  176. package/lib/hooks/use-press-tap.d.ts.map +0 -1
  177. package/lib/hooks/use-press-tap.js +0 -35
  178. package/lib/utils/index.js +0 -4
  179. package/src/components/ActionButton/__tests__/ActionButton.test.tsx +0 -35
  180. package/src/components/BottomSheet/__tests__/BottomSheet.test.tsx +0 -119
  181. package/src/components/__tests__/ProgressCircle.test.ts +0 -33
  182. package/src/components/__tests__/exports.test.ts +0 -38
  183. package/src/components/__tests__/switch.test.ts +0 -34
  184. package/src/hooks/use-icon-color.ts +0 -51
  185. package/src/utils/__tests__/create-slot-recipe-context.test.tsx +0 -228
  186. /package/src/hooks/{use-controllable-state.ts → useControllableState.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type * as React from "@lynx-js/react";
2
2
  import {
3
3
  createContext,
4
+ forwardRef,
4
5
  runOnMainThread,
5
6
  useContext,
6
7
  useEffect,
@@ -11,12 +12,11 @@ import type { MainThread } from "@lynx-js/types";
11
12
  import clsx from "clsx";
12
13
  import { progressCircle } from "@seed-design/lynx-css/recipes/progress-circle";
13
14
  import type { ProgressCircleVariantProps } from "@seed-design/lynx-css/recipes/progress-circle";
15
+ import type { LynxStyledElementProps, LynxViewRef } from "../../types";
14
16
 
15
- type Tone = "neutral" | "brand" | "staticWhite" | "inherit";
16
- type Size = "14" | "16" | "18" | "24" | "40";
17
+ ////////////////////////////////////////////////////////////////////////////////////
17
18
 
18
19
  interface ProgressCircleContextValue {
19
- tone: Tone;
20
20
  numSize: number;
21
21
  isDeterminate: boolean;
22
22
  progress: number;
@@ -25,7 +25,7 @@ interface ProgressCircleContextValue {
25
25
 
26
26
  const ProgressCircleContext = createContext<ProgressCircleContextValue | null>(null);
27
27
 
28
- function useProgressCircleCtx() {
28
+ function useProgressCircleContext() {
29
29
  const ctx = useContext(ProgressCircleContext);
30
30
  if (!ctx) {
31
31
  throw new Error("ProgressCircle compound components must be used within ProgressCircle.Root");
@@ -33,6 +33,8 @@ function useProgressCircleCtx() {
33
33
  return ctx;
34
34
  }
35
35
 
36
+ ////////////////////////////////////////////////////////////////////////////////////
37
+
36
38
  // --- Background-thread-only utilities (for initial render) ---
37
39
 
38
40
  function computeRingGeometry(numSize: number) {
@@ -55,6 +57,8 @@ function bgPieClipPath(size: number, angleDeg: number): string | undefined {
55
57
  return `path("M ${c} ${c} L ${c} ${c - r} A ${r} ${r} 0 ${largeArc} 1 ${endX} ${endY} Z")`;
56
58
  }
57
59
 
60
+ ////////////////////////////////////////////////////////////////////////////////////
61
+
58
62
  // --- Main-thread-only utilities (for animations) ---
59
63
  // Compiled into the main thread bundle. Must NOT be called from render code.
60
64
 
@@ -104,23 +108,29 @@ function sampleIndeterminate(t: number) {
104
108
  return { containerDeg, arcLength };
105
109
  }
106
110
 
111
+ ////////////////////////////////////////////////////////////////////////////////////
112
+
107
113
  // --- Animation constants ---
108
114
 
109
115
  const INDETERMINATE_DURATION = 1200;
110
116
  const TRANSITION_DURATION = 300;
111
117
 
118
+ ////////////////////////////////////////////////////////////////////////////////////
119
+
112
120
  // --- Components ---
113
121
 
114
- export interface ProgressCircleRootProps extends ProgressCircleVariantProps {
122
+ export interface ProgressCircleRootProps
123
+ extends ProgressCircleVariantProps,
124
+ LynxStyledElementProps {
115
125
  minValue?: number;
116
126
  maxValue?: number;
117
127
  value?: number;
118
- children?: React.ReactNode;
119
- className?: string;
120
128
  }
121
129
 
122
130
  export type RootProps = ProgressCircleRootProps;
123
131
 
132
+ ////////////////////////////////////////////////////////////////////////////////////
133
+
124
134
  /**
125
135
  * Lynx에서 SVG를 사용할 수 없어 CSS clip-path 기반 pie sector로 구현.
126
136
  *
@@ -131,42 +141,42 @@ export type RootProps = ProgressCircleRootProps;
131
141
  *
132
142
  * Lynx SVG + stroke-dasharray 지원 시 CSS-only 애니메이션으로 전환 예정.
133
143
  */
134
- export const ProgressCircleRoot = (props: ProgressCircleRootProps) => {
135
- const size: Size = (props.size as Size) ?? "40";
136
- const tone: Tone = (props.tone as Tone) ?? "neutral";
144
+ export const ProgressCircleRoot = forwardRef<unknown, ProgressCircleRootProps>((props, ref) => {
145
+ const [variantProps, otherProps] = progressCircle.splitVariantProps(props);
146
+ const { children, className, style, minValue, maxValue, value } = otherProps;
147
+ const size = variantProps.size ?? "40";
148
+ const tone = variantProps.tone ?? "neutral";
137
149
  const numSize = Number(size);
138
150
 
139
- const isDeterminate =
140
- props.minValue !== undefined && props.maxValue !== undefined && props.value !== undefined;
151
+ const isDeterminate = minValue !== undefined && maxValue !== undefined && value !== undefined;
141
152
 
142
- const range = (props.maxValue ?? 1) - (props.minValue ?? 0);
143
- const progress = isDeterminate
144
- ? range === 0
145
- ? 0
146
- : ((props.value ?? 0) - (props.minValue ?? 0)) / range
147
- : 0;
153
+ const range = (maxValue ?? 1) - (minValue ?? 0);
154
+ const progress = isDeterminate ? (range === 0 ? 0 : ((value ?? 0) - (minValue ?? 0)) / range) : 0;
148
155
 
149
156
  const classes = progressCircle({ tone, size });
150
157
 
151
158
  const ctx = useMemo(
152
- () => ({ tone, numSize, isDeterminate, progress, classes }),
153
- [tone, numSize, isDeterminate, progress, classes],
159
+ () => ({ numSize, isDeterminate, progress, classes }),
160
+ [numSize, isDeterminate, progress, classes],
154
161
  );
155
162
 
156
163
  return (
157
164
  <ProgressCircleContext.Provider value={ctx}>
158
165
  <view
159
- className={clsx(classes.root, props.className)}
160
- style={{ width: `${numSize}px`, height: `${numSize}px` }}
166
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
167
+ className={clsx(classes.root, className)}
168
+ style={{ ...style, width: `${numSize}px`, height: `${numSize}px` }}
161
169
  >
162
- {props.children}
170
+ {children}
163
171
  </view>
164
172
  </ProgressCircleContext.Provider>
165
173
  );
166
- };
174
+ });
175
+
176
+ ////////////////////////////////////////////////////////////////////////////////////
167
177
 
168
178
  export const ProgressCircleRange = () => {
169
- const { numSize, isDeterminate, progress, classes } = useProgressCircleCtx();
179
+ const { numSize, isDeterminate, progress, classes } = useProgressCircleContext();
170
180
 
171
181
  if (!isDeterminate) {
172
182
  return <IndeterminateRange numSize={numSize} classes={classes} />;
@@ -177,6 +187,8 @@ export const ProgressCircleRange = () => {
177
187
 
178
188
  type Classes = ReturnType<typeof progressCircle>;
179
189
 
190
+ ////////////////////////////////////////////////////////////////////////////////////
191
+
180
192
  function DeterminateRange({
181
193
  numSize,
182
194
  progress,
@@ -300,6 +312,8 @@ function DeterminateRange({
300
312
  );
301
313
  }
302
314
 
315
+ ////////////////////////////////////////////////////////////////////////////////////
316
+
303
317
  function IndeterminateRange({ numSize, classes }: { numSize: number; classes: Classes }) {
304
318
  const containerRef = useMainThreadRef<MainThread.Element>(null);
305
319
  const rangeRef = useMainThreadRef<MainThread.Element>(null);
@@ -1,20 +1,23 @@
1
- import * as React from "react";
2
- import { cloneElement, isValidElement, type CSSProperties, type ReactElement } from "react";
1
+ import * as React from "@lynx-js/react";
2
+ import { isValidElement, type ReactElement } from "@lynx-js/react";
3
3
  import clsx from "clsx";
4
- import type { MainThread } from "@lynx-js/types";
5
4
 
6
5
  import { radio } from "@seed-design/lynx-css/recipes/radio";
7
6
  import type { RadioVariantProps } from "@seed-design/lynx-css/recipes/radio";
8
7
  import { radiomark } from "@seed-design/lynx-css/recipes/radiomark";
9
8
  import type { RadiomarkVariantProps } from "@seed-design/lynx-css/recipes/radiomark";
10
9
  import { radioGroup } from "@seed-design/lynx-css/recipes/radio-group";
11
- import { vars as radiomarkVars } from "@seed-design/lynx-css/vars/component/radiomark";
12
10
 
13
- import { useControllableState } from "../../hooks/use-controllable-state";
14
- import { useIconColor } from "../../hooks/use-icon-color";
15
- import { usePressTap } from "../../hooks/use-press-tap";
16
- import { capitalize, resolveRecipeToken } from "../../utils/resolve-recipe-token";
11
+ import { useControllableState } from "../../hooks/useControllableState";
12
+ import { usePressTap } from "../../hooks/usePressTap";
13
+ import type {
14
+ LynxIconElementProps,
15
+ LynxStyledElementProps,
16
+ LynxTextRef,
17
+ LynxViewRef,
18
+ } from "../../types";
17
19
  import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
20
+ import { InternalIcon } from "../Icon/Icon";
18
21
 
19
22
  /**
20
23
  * @platform Lynx
@@ -29,17 +32,12 @@ import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-
29
32
  * recipe 의 `color` 토큰을 `tint-color` 로 동기화한다.
30
33
  */
31
34
 
32
- type RadioSize = NonNullable<RadioVariantProps["size"]>;
33
- type RadioWeight = NonNullable<RadioVariantProps["weight"]>;
34
- type RadiomarkTone = NonNullable<RadiomarkVariantProps["tone"]>;
35
-
36
35
  interface RadioGroupContextValue {
37
36
  value: string | null;
38
37
  setValue: (value: string) => void;
39
38
  disabled: boolean;
40
- size: RadioSize;
41
- weight: RadioWeight;
42
- tone: RadiomarkTone;
39
+ radioVariantProps: RadioVariantProps;
40
+ radiomarkVariantProps: RadiomarkVariantProps;
43
41
  }
44
42
 
45
43
  const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(null);
@@ -57,8 +55,7 @@ interface RadioGroupItemContextValue {
57
55
  checked: boolean;
58
56
  disabled: boolean;
59
57
  pressed: boolean;
60
- tone: RadiomarkTone;
61
- size: RadioSize;
58
+ select: () => void;
62
59
  }
63
60
 
64
61
  const RadioGroupItemContext = React.createContext<RadioGroupItemContextValue | null>(null);
@@ -71,10 +68,15 @@ function useRadioGroupItemContext(consumer: string): RadioGroupItemContextValue
71
68
  return ctx;
72
69
  }
73
70
 
74
- const RadiomarkIconClassContext = React.createContext<string | null>(null);
71
+ interface RadiomarkControlContextValue {
72
+ iconClassName: string;
73
+ radiomarkVariantProps: RadiomarkVariantProps;
74
+ }
75
+
76
+ const RadiomarkControlContext = React.createContext<RadiomarkControlContextValue | null>(null);
75
77
 
76
- function useRadiomarkIconClass(consumer: string): string {
77
- const ctx = React.useContext(RadiomarkIconClassContext);
78
+ function useRadiomarkControlContext(consumer: string): RadiomarkControlContextValue {
79
+ const ctx = React.useContext(RadiomarkControlContext);
78
80
  if (!ctx) {
79
81
  throw new Error(`<${consumer}/> must be rendered inside <RadioGroupItemControl/>.`);
80
82
  }
@@ -85,9 +87,8 @@ function useRadiomarkIconClass(consumer: string): string {
85
87
 
86
88
  export interface RadioGroupRootProps
87
89
  extends RadioVariantProps,
88
- Omit<RadiomarkVariantProps, "size" | "checked" | "disabled"> {
89
- children?: React.ReactNode;
90
- className?: string;
90
+ Omit<RadiomarkVariantProps, "size" | "checked" | "disabled">,
91
+ LynxStyledElementProps {
91
92
  value?: string;
92
93
  defaultValue?: string;
93
94
  disabled?: boolean;
@@ -106,9 +107,6 @@ export const RadioGroupRoot = React.forwardRef<unknown, RadioGroupRootProps>((pr
106
107
  } = props;
107
108
  const [{ radio: radioVariantProps, radiomark: radiomarkVariantProps }, nativeProps] =
108
109
  splitMultipleVariantsProps(restProps, { radio, radiomark });
109
- const weight: RadioWeight = radioVariantProps.weight ?? "regular";
110
- const size: RadioSize = radioVariantProps.size ?? "medium";
111
- const tone: RadiomarkTone = radiomarkVariantProps.tone ?? "brand";
112
110
 
113
111
  const handleChange = React.useCallback(
114
112
  (next: string | null) => {
@@ -130,17 +128,23 @@ export const RadioGroupRoot = React.forwardRef<unknown, RadioGroupRootProps>((pr
130
128
  [setValueInternal],
131
129
  );
132
130
 
133
- const rootClassName = radioGroup();
131
+ const rootClassName = radioGroup().root;
134
132
 
135
- const ctx = React.useMemo<RadioGroupContextValue>(
136
- () => ({ value, setValue, disabled, size, weight, tone }),
137
- [value, setValue, disabled, size, weight, tone],
133
+ const contextValue = React.useMemo<RadioGroupContextValue>(
134
+ () => ({
135
+ value,
136
+ setValue,
137
+ disabled,
138
+ radioVariantProps,
139
+ radiomarkVariantProps,
140
+ }),
141
+ [value, setValue, disabled, radioVariantProps, radiomarkVariantProps],
138
142
  );
139
143
 
140
144
  return (
141
- <RadioGroupContext.Provider value={ctx}>
145
+ <RadioGroupContext.Provider value={contextValue}>
142
146
  <view
143
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
147
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
144
148
  className={clsx(rootClassName, className)}
145
149
  {...nativeProps}
146
150
  >
@@ -153,10 +157,8 @@ RadioGroupRoot.displayName = "RadioGroupRoot";
153
157
 
154
158
  ////////////////////////////////////////////////////////////////////////////////////
155
159
 
156
- export interface RadioGroupItemProps {
160
+ export interface RadioGroupItemProps extends LynxStyledElementProps {
157
161
  value: string;
158
- children?: React.ReactNode;
159
- className?: string;
160
162
  disabled?: boolean;
161
163
  }
162
164
 
@@ -168,44 +170,37 @@ export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>((pr
168
170
  className,
169
171
  ...nativeProps
170
172
  } = props;
171
- const {
172
- value: groupValue,
173
- setValue,
174
- disabled: groupDisabled,
175
- size,
176
- weight,
177
- tone,
178
- } = useRadioGroupContext("RadioGroupItem");
173
+ const groupContext = useRadioGroupContext("RadioGroupItem");
179
174
 
180
- const disabled = groupDisabled || itemDisabled;
181
- const checked = groupValue === itemValue;
175
+ const disabled = groupContext.disabled || itemDisabled;
176
+ const checked = groupContext.value === itemValue;
177
+ const select = React.useCallback(() => {
178
+ if (checked) return;
179
+ groupContext.setValue(itemValue);
180
+ }, [checked, groupContext, itemValue]);
182
181
 
183
182
  const { pressed, ...pressHandlers } = usePressTap({
184
183
  disabled,
185
- onTap: () => {
186
- if (checked) return;
187
- setValue(itemValue);
188
- },
184
+ onTap: select,
189
185
  });
190
186
 
191
- const rootClassName = radio({ weight, size, disabled }).root;
187
+ const rootClassName = radio({ ...groupContext.radioVariantProps, disabled }).root;
192
188
 
193
- const itemCtx = React.useMemo<RadioGroupItemContextValue>(
189
+ const itemContextValue = React.useMemo<RadioGroupItemContextValue>(
194
190
  () => ({
195
191
  value: itemValue,
196
192
  checked,
197
193
  disabled,
198
194
  pressed,
199
- tone,
200
- size,
195
+ select,
201
196
  }),
202
- [itemValue, checked, disabled, pressed, tone, size],
197
+ [itemValue, checked, disabled, pressed, select],
203
198
  );
204
199
 
205
200
  return (
206
- <RadioGroupItemContext.Provider value={itemCtx}>
201
+ <RadioGroupItemContext.Provider value={itemContextValue}>
207
202
  <view
208
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
203
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
209
204
  className={clsx(rootClassName, className)}
210
205
  {...pressHandlers}
211
206
  {...nativeProps}
@@ -219,40 +214,37 @@ RadioGroupItem.displayName = "RadioGroupItem";
219
214
 
220
215
  ////////////////////////////////////////////////////////////////////////////////////
221
216
 
222
- export interface RadioGroupItemControlProps extends Pick<RadiomarkVariantProps, "tone"> {
223
- children?: React.ReactNode;
224
- className?: string;
225
- }
217
+ export interface RadioGroupItemControlProps
218
+ extends Pick<RadiomarkVariantProps, "tone">,
219
+ LynxStyledElementProps {}
226
220
 
227
221
  export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemControlProps>(
228
222
  (props, ref) => {
229
223
  const [variantProps, restProps] = radiomark.splitVariantProps(props);
230
224
  const { children, className, ...nativeProps } = restProps;
231
- const {
232
- checked,
233
- disabled,
234
- pressed,
235
- tone: ctxTone,
236
- size,
237
- } = useRadioGroupItemContext("RadioGroupItemControl");
238
-
239
- const tone: RadiomarkTone = variantProps.tone ?? ctxTone;
240
-
241
- const classes = React.useMemo(
242
- () => radiomark({ tone, size, checked, disabled, pressed }),
243
- [tone, size, checked, disabled, pressed],
244
- );
225
+ const groupContext = useRadioGroupContext("RadioGroupItemControl");
226
+ const itemContext = useRadioGroupItemContext("RadioGroupItemControl");
227
+ const radiomarkVariantProps: RadiomarkVariantProps = {
228
+ ...groupContext.radiomarkVariantProps,
229
+ ...variantProps,
230
+ checked: itemContext.checked,
231
+ disabled: itemContext.disabled,
232
+ pressed: itemContext.pressed,
233
+ };
234
+ const classes = radiomark(radiomarkVariantProps);
245
235
 
246
236
  return (
247
- <RadiomarkIconClassContext.Provider value={classes.icon}>
237
+ <RadiomarkControlContext.Provider
238
+ value={{ iconClassName: classes.icon, radiomarkVariantProps }}
239
+ >
248
240
  <view
249
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
241
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
250
242
  className={clsx(classes.root, className)}
251
243
  {...nativeProps}
252
244
  >
253
245
  {children}
254
246
  </view>
255
- </RadiomarkIconClassContext.Provider>
247
+ </RadiomarkControlContext.Provider>
256
248
  );
257
249
  },
258
250
  );
@@ -260,73 +252,63 @@ RadioGroupItemControl.displayName = "RadioGroupItemControl";
260
252
 
261
253
  ////////////////////////////////////////////////////////////////////////////////////
262
254
 
263
- type IconElementProps = {
264
- className?: string;
265
- style?: CSSProperties;
266
- ref?: React.Ref<MainThread.Element>;
267
- };
268
-
269
- export interface RadioGroupItemIndicatorProps {
255
+ export interface RadioGroupItemIndicatorProps
256
+ extends Pick<LynxStyledElementProps, "className" | "style"> {
270
257
  /** Icon rendered when not checked. Optional — falls back to default `<view>` dot when omitted. */
271
- unchecked?: ReactElement<IconElementProps>;
258
+ unchecked?: ReactElement<LynxIconElementProps>;
272
259
  /** Icon rendered when the item is checked. Optional — falls back to default `<view>` dot when omitted. */
273
- checked?: ReactElement<IconElementProps>;
274
- className?: string;
275
- style?: CSSProperties;
260
+ checked?: ReactElement<LynxIconElementProps>;
276
261
  }
277
262
 
278
263
  export function RadioGroupItemIndicator(props: RadioGroupItemIndicatorProps) {
279
264
  const { unchecked, checked: checkedIcon, className, style } = props;
280
- const { checked, disabled, pressed, tone, size } =
281
- useRadioGroupItemContext("RadioGroupItemIndicator");
282
- const iconClassName = useRadiomarkIconClass("RadioGroupItemIndicator");
283
- const { ref: tintRef } = useIconColor([checked, disabled, pressed, tone, size]);
265
+ const itemContext = useRadioGroupItemContext("RadioGroupItemIndicator");
266
+ const { iconClassName, radiomarkVariantProps } =
267
+ useRadiomarkControlContext("RadioGroupItemIndicator");
284
268
 
285
- const icon = checked ? checkedIcon : unchecked;
269
+ const icon = itemContext.checked ? checkedIcon : unchecked;
286
270
 
287
271
  // 사용자가 custom icon 을 주지 않으면 default `<view>` 로 동그란 점을 그린다.
288
272
  // radiomark.icon recipe 가 borderRadius/backgroundColor/width/height 를 적용해
289
273
  // 라디오의 inner dot 모양을 재현. 웹 RadioGroup 의 default `<svg><circle/></svg>` 동작과 일치.
290
- if (!icon || !isValidElement<IconElementProps>(icon)) {
274
+ if (!icon || !isValidElement<LynxIconElementProps>(icon)) {
291
275
  return <view className={clsx(iconClassName, className)} style={style} />;
292
276
  }
293
277
 
294
- // lynx-monochrome-icon 이 `<image style={{ width, height, ...style }}>` 로 자체 default size 를 박으므로
295
- // recipe 토큰에서 꺼낸 사이즈를 inline 으로 주입해 덮는다.
296
- const iconSize = resolveRecipeToken(radiomarkVars, [
297
- `size${capitalize(size)}`,
298
- disabled ? "disabled" : "enabled",
299
- "icon",
300
- "size",
301
- ]);
302
-
303
- return cloneElement(icon, {
304
- className: clsx(iconClassName, icon.props.className, className),
305
- style: iconSize
306
- ? { width: iconSize, height: iconSize, ...icon.props.style, ...style }
307
- : { ...icon.props.style, ...style },
308
- ref: tintRef,
309
- });
278
+ return (
279
+ <InternalIcon
280
+ icon={icon}
281
+ className={clsx(iconClassName, className)}
282
+ style={style}
283
+ deps={[
284
+ itemContext.checked,
285
+ itemContext.disabled,
286
+ itemContext.pressed,
287
+ radiomarkVariantProps.tone,
288
+ radiomarkVariantProps.size,
289
+ ]}
290
+ />
291
+ );
310
292
  }
311
293
  RadioGroupItemIndicator.displayName = "RadioGroupItemIndicator";
312
294
 
313
295
  ////////////////////////////////////////////////////////////////////////////////////
314
296
 
315
- export interface RadioGroupItemLabelProps {
316
- children?: React.ReactNode;
317
- className?: string;
318
- }
297
+ export interface RadioGroupItemLabelProps extends LynxStyledElementProps {}
319
298
 
320
299
  export const RadioGroupItemLabel = React.forwardRef<unknown, RadioGroupItemLabelProps>(
321
300
  (props, ref) => {
322
301
  const { children, className, ...nativeProps } = props;
323
- const { disabled } = useRadioGroupItemContext("RadioGroupItemLabel");
324
- const { size, weight } = useRadioGroupContext("RadioGroupItemLabel");
325
- const labelClassName = radio({ weight, size, disabled }).label;
302
+ const itemContext = useRadioGroupItemContext("RadioGroupItemLabel");
303
+ const groupContext = useRadioGroupContext("RadioGroupItemLabel");
304
+ const labelClassName = radio({
305
+ ...groupContext.radioVariantProps,
306
+ disabled: itemContext.disabled,
307
+ }).label;
326
308
 
327
309
  return (
328
310
  <text
329
- {...(ref ? { ref: ref as React.Ref<SVGTextElement> } : {})}
311
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
330
312
  className={clsx(labelClassName, className)}
331
313
  {...nativeProps}
332
314
  >
@@ -0,0 +1,97 @@
1
+ import clsx from "clsx";
2
+ import * as React from "@lynx-js/react";
3
+
4
+ import type { LynxPressableProps, LynxStyledElementProps, LynxViewRef } from "../../types";
5
+ import { useStyleProps, type StyleProps } from "../../utils/styled";
6
+
7
+ type StackStyleProps =
8
+ | "display"
9
+ | "flexDirection"
10
+ | "alignItems"
11
+ | "justifyContent"
12
+ | "flexWrap"
13
+ | "flexGrow"
14
+ | "flexShrink";
15
+
16
+ interface StackBaseProps extends StyleProps, LynxStyledElementProps, LynxPressableProps {
17
+ bindtouchstart?: () => void;
18
+ bindtouchend?: () => void;
19
+ bindtouchcancel?: () => void;
20
+ }
21
+
22
+ /**
23
+ * @platform Lynx
24
+ *
25
+ * `VStack`과 `HStack`은 native `<view>`를 직접 렌더링하고 stack 방향에 필요한
26
+ * 최소 flex style만 적용합니다. 앱 코드에서 성능에 민감한 반복 레이아웃을 만들
27
+ * 때는 native `<view>`/`<text>`와 Tailwind utility className을 우선 고려하세요.
28
+ * 패키지 컴포넌트 내부에서는 `Box`를 합성하지 않고 native tag와 recipe className을
29
+ * 사용해 primitive 컴포넌트 비용이 누적되지 않도록 합니다.
30
+ */
31
+ export interface StackProps extends Omit<StackBaseProps, StackStyleProps> {
32
+ align?: StackBaseProps["alignItems"];
33
+ justify?: StackBaseProps["justifyContent"];
34
+ wrap?: StackBaseProps["flexWrap"];
35
+ grow?: StackBaseProps["flexGrow"];
36
+ shrink?: StackBaseProps["flexShrink"];
37
+ }
38
+
39
+ export interface VStackProps extends StackProps {}
40
+
41
+ export interface HStackProps extends StackProps {}
42
+
43
+ function getStackProps(props: StackProps) {
44
+ const { align, justify, wrap, grow, shrink, ...restProps } = props;
45
+
46
+ return {
47
+ alignItems: align,
48
+ justifyContent: justify,
49
+ flexWrap: wrap,
50
+ flexGrow: grow,
51
+ flexShrink: shrink,
52
+ ...restProps,
53
+ };
54
+ }
55
+
56
+ function useStackStyleProps(props: StackProps, flexDirection: "column" | "row") {
57
+ return useStyleProps({
58
+ display: "flex",
59
+ flexDirection,
60
+ ...getStackProps(props),
61
+ });
62
+ }
63
+
64
+ function renderStackView(
65
+ stackStyleProps: ReturnType<typeof useStackStyleProps>,
66
+ ref: React.ForwardedRef<unknown>,
67
+ ) {
68
+ const { style, restProps } = stackStyleProps;
69
+ const { children, className, ...nativeProps } = restProps;
70
+
71
+ return (
72
+ <view
73
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
74
+ {...nativeProps}
75
+ className={clsx(className)}
76
+ style={style}
77
+ >
78
+ {children}
79
+ </view>
80
+ );
81
+ }
82
+
83
+ export const VStack = React.forwardRef<unknown, VStackProps>((props, ref) => {
84
+ const stackStyleProps = useStackStyleProps(props, "column");
85
+
86
+ return renderStackView(stackStyleProps, ref);
87
+ });
88
+
89
+ VStack.displayName = "VStack";
90
+
91
+ export const HStack = React.forwardRef<unknown, HStackProps>((props, ref) => {
92
+ const stackStyleProps = useStackStyleProps(props, "row");
93
+
94
+ return renderStackView(stackStyleProps, ref);
95
+ });
96
+
97
+ HStack.displayName = "HStack";
@@ -0,0 +1 @@
1
+ export { HStack, type HStackProps, type StackProps, VStack, type VStackProps } from "./Stack";