@seed-design/lynx-react 0.0.0-alpha-20260622094810

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 (196) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +49 -0
  3. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -0
  4. package/lib/components/ActionButton/ActionButton.jsx +158 -0
  5. package/lib/components/ActionButton/index.d.ts +2 -0
  6. package/lib/components/ActionButton/index.d.ts.map +1 -0
  7. package/lib/components/AppBar/AppBar.d.ts +35 -0
  8. package/lib/components/AppBar/AppBar.d.ts.map +1 -0
  9. package/lib/components/AppBar/AppBar.jsx +132 -0
  10. package/lib/components/AppBar/AppBar.namespace.d.ts +2 -0
  11. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -0
  12. package/lib/components/AppBar/AppBar.namespace.js +15 -0
  13. package/lib/components/AppBar/context.d.ts +13 -0
  14. package/lib/components/AppBar/context.d.ts.map +1 -0
  15. package/lib/components/AppBar/context.js +12 -0
  16. package/lib/components/AppBar/index.d.ts +3 -0
  17. package/lib/components/AppBar/index.d.ts.map +1 -0
  18. package/lib/components/AppBar/useAppBar.d.ts +15 -0
  19. package/lib/components/AppBar/useAppBar.d.ts.map +1 -0
  20. package/lib/components/AppBar/useAppBar.js +72 -0
  21. package/lib/components/Badge/Badge.d.ts +14 -0
  22. package/lib/components/Badge/Badge.d.ts.map +1 -0
  23. package/lib/components/Badge/Badge.jsx +15 -0
  24. package/lib/components/Badge/index.d.ts +2 -0
  25. package/lib/components/Badge/index.d.ts.map +1 -0
  26. package/lib/components/BottomSheet/BottomSheet.d.ts +87 -0
  27. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -0
  28. package/lib/components/BottomSheet/BottomSheet.jsx +163 -0
  29. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +2 -0
  30. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts.map +1 -0
  31. package/lib/components/BottomSheet/BottomSheet.namespace.js +18 -0
  32. package/lib/components/BottomSheet/index.d.ts +3 -0
  33. package/lib/components/BottomSheet/index.d.ts.map +1 -0
  34. package/lib/components/Box/Box.d.ts +20 -0
  35. package/lib/components/Box/Box.d.ts.map +1 -0
  36. package/lib/components/Box/Box.jsx +14 -0
  37. package/lib/components/Box/index.d.ts +2 -0
  38. package/lib/components/Box/index.d.ts.map +1 -0
  39. package/lib/components/Checkbox/Checkbox.d.ts +35 -0
  40. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -0
  41. package/lib/components/Checkbox/Checkbox.jsx +130 -0
  42. package/lib/components/Checkbox/Checkbox.namespace.d.ts +2 -0
  43. package/lib/components/Checkbox/Checkbox.namespace.d.ts.map +1 -0
  44. package/lib/components/Checkbox/Checkbox.namespace.js +12 -0
  45. package/lib/components/Checkbox/index.d.ts +3 -0
  46. package/lib/components/Checkbox/index.d.ts.map +1 -0
  47. package/lib/components/Icon/Icon.d.ts +37 -0
  48. package/lib/components/Icon/Icon.d.ts.map +1 -0
  49. package/lib/components/Icon/Icon.jsx +143 -0
  50. package/lib/components/Icon/index.d.ts +2 -0
  51. package/lib/components/Icon/index.d.ts.map +1 -0
  52. package/lib/components/ProgressCircle/ProgressCircle.d.ts +25 -0
  53. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -0
  54. package/lib/components/ProgressCircle/ProgressCircle.jsx +281 -0
  55. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts +2 -0
  56. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts.map +1 -0
  57. package/lib/components/ProgressCircle/ProgressCircle.namespace.js +9 -0
  58. package/lib/components/ProgressCircle/index.d.ts +3 -0
  59. package/lib/components/ProgressCircle/index.d.ts.map +1 -0
  60. package/lib/components/RadioGroup/RadioGroup.d.ts +34 -0
  61. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -0
  62. package/lib/components/RadioGroup/RadioGroup.jsx +160 -0
  63. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts +2 -0
  64. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts.map +1 -0
  65. package/lib/components/RadioGroup/RadioGroup.namespace.js +12 -0
  66. package/lib/components/RadioGroup/index.d.ts +3 -0
  67. package/lib/components/RadioGroup/index.d.ts.map +1 -0
  68. package/lib/components/Stack/Stack.d.ts +33 -0
  69. package/lib/components/Stack/Stack.d.ts.map +1 -0
  70. package/lib/components/Stack/Stack.jsx +39 -0
  71. package/lib/components/Stack/index.d.ts +2 -0
  72. package/lib/components/Stack/index.d.ts.map +1 -0
  73. package/lib/components/Switch/Switch.d.ts +21 -0
  74. package/lib/components/Switch/Switch.d.ts.map +1 -0
  75. package/lib/components/Switch/Switch.jsx +101 -0
  76. package/lib/components/Switch/Switch.namespace.d.ts +2 -0
  77. package/lib/components/Switch/Switch.namespace.d.ts.map +1 -0
  78. package/lib/components/Switch/Switch.namespace.js +11 -0
  79. package/lib/components/Switch/index.d.ts +3 -0
  80. package/lib/components/Switch/index.d.ts.map +1 -0
  81. package/lib/components/TagGroup/TagGroup.d.ts +34 -0
  82. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -0
  83. package/lib/components/TagGroup/TagGroup.jsx +57 -0
  84. package/lib/components/TagGroup/TagGroup.namespace.d.ts +2 -0
  85. package/lib/components/TagGroup/TagGroup.namespace.d.ts.map +1 -0
  86. package/lib/components/TagGroup/TagGroup.namespace.js +10 -0
  87. package/lib/components/TagGroup/index.d.ts +3 -0
  88. package/lib/components/TagGroup/index.d.ts.map +1 -0
  89. package/lib/components/Text/Text.d.ts +7 -0
  90. package/lib/components/Text/Text.d.ts.map +1 -0
  91. package/lib/components/Text/Text.jsx +28 -0
  92. package/lib/components/Text/index.d.ts +2 -0
  93. package/lib/components/Text/index.d.ts.map +1 -0
  94. package/lib/components/index.d.ts +14 -0
  95. package/lib/components/index.d.ts.map +1 -0
  96. package/lib/hooks/index.d.ts +5 -0
  97. package/lib/hooks/index.d.ts.map +1 -0
  98. package/lib/hooks/useControllableState.d.ts +15 -0
  99. package/lib/hooks/useControllableState.d.ts.map +1 -0
  100. package/lib/hooks/useControllableState.js +27 -0
  101. package/lib/hooks/useIconColor.d.ts +19 -0
  102. package/lib/hooks/useIconColor.d.ts.map +1 -0
  103. package/lib/hooks/useIconColor.js +38 -0
  104. package/lib/hooks/usePressTap.d.ts +27 -0
  105. package/lib/hooks/usePressTap.d.ts.map +1 -0
  106. package/lib/hooks/usePressTap.js +35 -0
  107. package/lib/hooks/useSafeArea.d.ts +19 -0
  108. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  109. package/lib/hooks/useSafeArea.js +33 -0
  110. package/lib/index.d.ts +5 -0
  111. package/lib/index.d.ts.map +1 -0
  112. package/lib/index.js +29 -0
  113. package/lib/types.d.ts +51 -0
  114. package/lib/types.d.ts.map +1 -0
  115. package/lib/utils/children.d.ts +3 -0
  116. package/lib/utils/children.d.ts.map +1 -0
  117. package/lib/utils/children.js +8 -0
  118. package/lib/utils/create-slot-recipe-context.d.ts +27 -0
  119. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -0
  120. package/lib/utils/create-slot-recipe-context.jsx +94 -0
  121. package/lib/utils/get-seed-class-name.d.ts +22 -0
  122. package/lib/utils/get-seed-class-name.d.ts.map +1 -0
  123. package/lib/utils/get-seed-class-name.js +28 -0
  124. package/lib/utils/index.d.ts +5 -0
  125. package/lib/utils/index.d.ts.map +1 -0
  126. package/lib/utils/resolve-recipe-token.d.ts +38 -0
  127. package/lib/utils/resolve-recipe-token.d.ts.map +1 -0
  128. package/lib/utils/resolve-recipe-token.js +49 -0
  129. package/lib/utils/split-multiple-variants-props.d.ts +29 -0
  130. package/lib/utils/split-multiple-variants-props.d.ts.map +1 -0
  131. package/lib/utils/split-multiple-variants-props.js +34 -0
  132. package/lib/utils/styled.d.ts +83 -0
  133. package/lib/utils/styled.d.ts.map +1 -0
  134. package/lib/utils/styled.js +144 -0
  135. package/package.json +54 -0
  136. package/src/components/ActionButton/ActionButton.test.tsx +167 -0
  137. package/src/components/ActionButton/ActionButton.tsx +363 -0
  138. package/src/components/ActionButton/index.ts +1 -0
  139. package/src/components/AppBar/AppBar.namespace.ts +18 -0
  140. package/src/components/AppBar/AppBar.test.tsx +270 -0
  141. package/src/components/AppBar/AppBar.tsx +288 -0
  142. package/src/components/AppBar/context.ts +27 -0
  143. package/src/components/AppBar/index.ts +21 -0
  144. package/src/components/AppBar/useAppBar.ts +100 -0
  145. package/src/components/Badge/Badge.test.tsx +54 -0
  146. package/src/components/Badge/Badge.tsx +32 -0
  147. package/src/components/Badge/index.ts +1 -0
  148. package/src/components/BottomSheet/BottomSheet.namespace.ts +24 -0
  149. package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
  150. package/src/components/BottomSheet/BottomSheet.tsx +423 -0
  151. package/src/components/BottomSheet/index.ts +27 -0
  152. package/src/components/Box/Box.test.tsx +85 -0
  153. package/src/components/Box/Box.tsx +39 -0
  154. package/src/components/Box/index.ts +1 -0
  155. package/src/components/Checkbox/Checkbox.namespace.ts +12 -0
  156. package/src/components/Checkbox/Checkbox.tsx +282 -0
  157. package/src/components/Checkbox/index.ts +14 -0
  158. package/src/components/Icon/Icon.tsx +265 -0
  159. package/src/components/Icon/index.ts +8 -0
  160. package/src/components/ProgressCircle/ProgressCircle.namespace.ts +5 -0
  161. package/src/components/ProgressCircle/ProgressCircle.tsx +424 -0
  162. package/src/components/ProgressCircle/index.ts +9 -0
  163. package/src/components/RadioGroup/RadioGroup.namespace.ts +12 -0
  164. package/src/components/RadioGroup/RadioGroup.tsx +320 -0
  165. package/src/components/RadioGroup/index.ts +14 -0
  166. package/src/components/Stack/Stack.tsx +97 -0
  167. package/src/components/Stack/index.ts +1 -0
  168. package/src/components/Switch/Switch.namespace.ts +10 -0
  169. package/src/components/Switch/Switch.tsx +200 -0
  170. package/src/components/Switch/index.ts +12 -0
  171. package/src/components/TagGroup/TagGroup.namespace.ts +8 -0
  172. package/src/components/TagGroup/TagGroup.tsx +127 -0
  173. package/src/components/TagGroup/index.ts +10 -0
  174. package/src/components/Text/Text.tsx +64 -0
  175. package/src/components/Text/index.ts +1 -0
  176. package/src/components/index.ts +13 -0
  177. package/src/hooks/index.ts +4 -0
  178. package/src/hooks/useControllableState.test.ts +120 -0
  179. package/src/hooks/useControllableState.ts +38 -0
  180. package/src/hooks/useIconColor.test.ts +13 -0
  181. package/src/hooks/useIconColor.ts +67 -0
  182. package/src/hooks/usePressTap.test.ts +129 -0
  183. package/src/hooks/usePressTap.ts +58 -0
  184. package/src/hooks/useSafeArea.test.ts +83 -0
  185. package/src/hooks/useSafeArea.ts +56 -0
  186. package/src/index.ts +4 -0
  187. package/src/types.ts +76 -0
  188. package/src/utils/children.ts +13 -0
  189. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  190. package/src/utils/create-slot-recipe-context.tsx +187 -0
  191. package/src/utils/get-seed-class-name.ts +47 -0
  192. package/src/utils/index.ts +4 -0
  193. package/src/utils/resolve-recipe-token.ts +47 -0
  194. package/src/utils/split-multiple-variants-props.test.ts +61 -0
  195. package/src/utils/split-multiple-variants-props.ts +59 -0
  196. package/src/utils/styled.ts +442 -0
@@ -0,0 +1,320 @@
1
+ import * as React from "@lynx-js/react";
2
+ import { isValidElement, type ReactElement } from "@lynx-js/react";
3
+ import clsx from "clsx";
4
+
5
+ import { radio } from "@seed-design/lynx-css/recipes/radio";
6
+ import type { RadioVariantProps } from "@seed-design/lynx-css/recipes/radio";
7
+ import { radiomark } from "@seed-design/lynx-css/recipes/radiomark";
8
+ import type { RadiomarkVariantProps } from "@seed-design/lynx-css/recipes/radiomark";
9
+ import { radioGroup } from "@seed-design/lynx-css/recipes/radio-group";
10
+
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";
19
+ import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
20
+ import { InternalIcon } from "../Icon/Icon";
21
+
22
+ /**
23
+ * @platform Lynx
24
+ *
25
+ * 웹 대비 미지원 기능:
26
+ * - HiddenInput / name / required / invalid: Lynx에 native form 제출 모델이 없음
27
+ * - focus / focusVisible: Lynx에 키보드 포커스 개념이 없음
28
+ * - onChange (raw DOM event): 의미 없음. 선택 이벤트는 onValueChange로만 노출
29
+ *
30
+ * Indicator 는 `@karrotmarket/lynx-monochrome-icon` 의 monochrome icon 컴포넌트를
31
+ * 받는다. 내부에서 `<image tint-color=...>` 로 렌더되므로 `useIconColor` 훅이
32
+ * recipe 의 `color` 토큰을 `tint-color` 로 동기화한다.
33
+ */
34
+
35
+ interface RadioGroupContextValue {
36
+ value: string | null;
37
+ setValue: (value: string) => void;
38
+ disabled: boolean;
39
+ radioVariantProps: RadioVariantProps;
40
+ radiomarkVariantProps: RadiomarkVariantProps;
41
+ }
42
+
43
+ const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(null);
44
+
45
+ function useRadioGroupContext(consumer: string): RadioGroupContextValue {
46
+ const ctx = React.useContext(RadioGroupContext);
47
+ if (!ctx) {
48
+ throw new Error(`<${consumer}/> must be rendered inside <RadioGroupRoot/>.`);
49
+ }
50
+ return ctx;
51
+ }
52
+
53
+ interface RadioGroupItemContextValue {
54
+ value: string;
55
+ checked: boolean;
56
+ disabled: boolean;
57
+ pressed: boolean;
58
+ select: () => void;
59
+ }
60
+
61
+ const RadioGroupItemContext = React.createContext<RadioGroupItemContextValue | null>(null);
62
+
63
+ function useRadioGroupItemContext(consumer: string): RadioGroupItemContextValue {
64
+ const ctx = React.useContext(RadioGroupItemContext);
65
+ if (!ctx) {
66
+ throw new Error(`<${consumer}/> must be rendered inside <RadioGroupItem/>.`);
67
+ }
68
+ return ctx;
69
+ }
70
+
71
+ interface RadiomarkControlContextValue {
72
+ iconClassName: string;
73
+ radiomarkVariantProps: RadiomarkVariantProps;
74
+ }
75
+
76
+ const RadiomarkControlContext = React.createContext<RadiomarkControlContextValue | null>(null);
77
+
78
+ function useRadiomarkControlContext(consumer: string): RadiomarkControlContextValue {
79
+ const ctx = React.useContext(RadiomarkControlContext);
80
+ if (!ctx) {
81
+ throw new Error(`<${consumer}/> must be rendered inside <RadioGroupItemControl/>.`);
82
+ }
83
+ return ctx;
84
+ }
85
+
86
+ ////////////////////////////////////////////////////////////////////////////////////
87
+
88
+ export interface RadioGroupRootProps
89
+ extends RadioVariantProps,
90
+ Omit<RadiomarkVariantProps, "size" | "checked" | "disabled">,
91
+ LynxStyledElementProps {
92
+ value?: string;
93
+ defaultValue?: string;
94
+ disabled?: boolean;
95
+ onValueChange?: (value: string) => void;
96
+ }
97
+
98
+ export const RadioGroupRoot = React.forwardRef<unknown, RadioGroupRootProps>((props, ref) => {
99
+ const {
100
+ children,
101
+ className,
102
+ value: valueProp,
103
+ defaultValue,
104
+ disabled = false,
105
+ onValueChange,
106
+ ...restProps
107
+ } = props;
108
+ const [{ radio: radioVariantProps, radiomark: radiomarkVariantProps }, nativeProps] =
109
+ splitMultipleVariantsProps(restProps, { radio, radiomark });
110
+
111
+ const handleChange = React.useCallback(
112
+ (next: string | null) => {
113
+ if (next !== null) onValueChange?.(next);
114
+ },
115
+ [onValueChange],
116
+ );
117
+
118
+ const [value, setValueInternal] = useControllableState<string | null>({
119
+ value: valueProp !== undefined ? valueProp : undefined,
120
+ defaultValue: defaultValue ?? null,
121
+ onChange: handleChange,
122
+ });
123
+
124
+ const setValue = React.useCallback(
125
+ (next: string) => {
126
+ setValueInternal(next);
127
+ },
128
+ [setValueInternal],
129
+ );
130
+
131
+ const rootClassName = radioGroup().root;
132
+
133
+ const contextValue = React.useMemo<RadioGroupContextValue>(
134
+ () => ({
135
+ value,
136
+ setValue,
137
+ disabled,
138
+ radioVariantProps,
139
+ radiomarkVariantProps,
140
+ }),
141
+ [value, setValue, disabled, radioVariantProps, radiomarkVariantProps],
142
+ );
143
+
144
+ return (
145
+ <RadioGroupContext.Provider value={contextValue}>
146
+ <view
147
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
148
+ className={clsx(rootClassName, className)}
149
+ {...nativeProps}
150
+ >
151
+ {children}
152
+ </view>
153
+ </RadioGroupContext.Provider>
154
+ );
155
+ });
156
+ RadioGroupRoot.displayName = "RadioGroupRoot";
157
+
158
+ ////////////////////////////////////////////////////////////////////////////////////
159
+
160
+ export interface RadioGroupItemProps extends LynxStyledElementProps {
161
+ value: string;
162
+ disabled?: boolean;
163
+ }
164
+
165
+ export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>((props, ref) => {
166
+ const {
167
+ value: itemValue,
168
+ disabled: itemDisabled = false,
169
+ children,
170
+ className,
171
+ ...nativeProps
172
+ } = props;
173
+ const groupContext = useRadioGroupContext("RadioGroupItem");
174
+
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]);
181
+
182
+ const { pressed, ...pressHandlers } = usePressTap({
183
+ disabled,
184
+ onTap: select,
185
+ });
186
+
187
+ const rootClassName = radio({ ...groupContext.radioVariantProps, disabled }).root;
188
+
189
+ const itemContextValue = React.useMemo<RadioGroupItemContextValue>(
190
+ () => ({
191
+ value: itemValue,
192
+ checked,
193
+ disabled,
194
+ pressed,
195
+ select,
196
+ }),
197
+ [itemValue, checked, disabled, pressed, select],
198
+ );
199
+
200
+ return (
201
+ <RadioGroupItemContext.Provider value={itemContextValue}>
202
+ <view
203
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
204
+ className={clsx(rootClassName, className)}
205
+ {...pressHandlers}
206
+ {...nativeProps}
207
+ >
208
+ {children}
209
+ </view>
210
+ </RadioGroupItemContext.Provider>
211
+ );
212
+ });
213
+ RadioGroupItem.displayName = "RadioGroupItem";
214
+
215
+ ////////////////////////////////////////////////////////////////////////////////////
216
+
217
+ export interface RadioGroupItemControlProps
218
+ extends Pick<RadiomarkVariantProps, "tone">,
219
+ LynxStyledElementProps {}
220
+
221
+ export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemControlProps>(
222
+ (props, ref) => {
223
+ const [variantProps, restProps] = radiomark.splitVariantProps(props);
224
+ const { children, className, ...nativeProps } = restProps;
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);
235
+
236
+ return (
237
+ <RadiomarkControlContext.Provider
238
+ value={{ iconClassName: classes.icon, radiomarkVariantProps }}
239
+ >
240
+ <view
241
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
242
+ className={clsx(classes.root, className)}
243
+ {...nativeProps}
244
+ >
245
+ {children}
246
+ </view>
247
+ </RadiomarkControlContext.Provider>
248
+ );
249
+ },
250
+ );
251
+ RadioGroupItemControl.displayName = "RadioGroupItemControl";
252
+
253
+ ////////////////////////////////////////////////////////////////////////////////////
254
+
255
+ export interface RadioGroupItemIndicatorProps
256
+ extends Pick<LynxStyledElementProps, "className" | "style"> {
257
+ /** Icon rendered when not checked. Optional — falls back to default `<view>` dot when omitted. */
258
+ unchecked?: ReactElement<LynxIconElementProps>;
259
+ /** Icon rendered when the item is checked. Optional — falls back to default `<view>` dot when omitted. */
260
+ checked?: ReactElement<LynxIconElementProps>;
261
+ }
262
+
263
+ export function RadioGroupItemIndicator(props: RadioGroupItemIndicatorProps) {
264
+ const { unchecked, checked: checkedIcon, className, style } = props;
265
+ const itemContext = useRadioGroupItemContext("RadioGroupItemIndicator");
266
+ const { iconClassName, radiomarkVariantProps } =
267
+ useRadiomarkControlContext("RadioGroupItemIndicator");
268
+
269
+ const icon = itemContext.checked ? checkedIcon : unchecked;
270
+
271
+ // 사용자가 custom icon 을 주지 않으면 default `<view>` 로 동그란 점을 그린다.
272
+ // radiomark.icon recipe 가 borderRadius/backgroundColor/width/height 를 적용해
273
+ // 라디오의 inner dot 모양을 재현. 웹 RadioGroup 의 default `<svg><circle/></svg>` 동작과 일치.
274
+ if (!icon || !isValidElement<LynxIconElementProps>(icon)) {
275
+ return <view className={clsx(iconClassName, className)} style={style} />;
276
+ }
277
+
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
+ );
292
+ }
293
+ RadioGroupItemIndicator.displayName = "RadioGroupItemIndicator";
294
+
295
+ ////////////////////////////////////////////////////////////////////////////////////
296
+
297
+ export interface RadioGroupItemLabelProps extends LynxStyledElementProps {}
298
+
299
+ export const RadioGroupItemLabel = React.forwardRef<unknown, RadioGroupItemLabelProps>(
300
+ (props, ref) => {
301
+ const { children, className, ...nativeProps } = props;
302
+ const itemContext = useRadioGroupItemContext("RadioGroupItemLabel");
303
+ const groupContext = useRadioGroupContext("RadioGroupItemLabel");
304
+ const labelClassName = radio({
305
+ ...groupContext.radioVariantProps,
306
+ disabled: itemContext.disabled,
307
+ }).label;
308
+
309
+ return (
310
+ <text
311
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
312
+ className={clsx(labelClassName, className)}
313
+ {...nativeProps}
314
+ >
315
+ {children}
316
+ </text>
317
+ );
318
+ },
319
+ );
320
+ RadioGroupItemLabel.displayName = "RadioGroupItemLabel";
@@ -0,0 +1,14 @@
1
+ export {
2
+ RadioGroupRoot,
3
+ RadioGroupItem,
4
+ RadioGroupItemControl,
5
+ RadioGroupItemIndicator,
6
+ RadioGroupItemLabel,
7
+ type RadioGroupRootProps,
8
+ type RadioGroupItemProps,
9
+ type RadioGroupItemControlProps,
10
+ type RadioGroupItemIndicatorProps,
11
+ type RadioGroupItemLabelProps,
12
+ } from "./RadioGroup";
13
+
14
+ export * as RadioGroup from "./RadioGroup.namespace";
@@ -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";
@@ -0,0 +1,10 @@
1
+ export {
2
+ SwitchControl as Control,
3
+ SwitchLabel as Label,
4
+ SwitchRoot as Root,
5
+ SwitchThumb as Thumb,
6
+ type SwitchControlProps as ControlProps,
7
+ type SwitchLabelProps as LabelProps,
8
+ type SwitchRootProps as RootProps,
9
+ type SwitchThumbProps as ThumbProps,
10
+ } from "./Switch";
@@ -0,0 +1,200 @@
1
+ import * as React from "@lynx-js/react";
2
+ import clsx from "clsx";
3
+
4
+ import { switchStyle } from "@seed-design/lynx-css/recipes/switch";
5
+ import type { SwitchVariantProps } from "@seed-design/lynx-css/recipes/switch";
6
+ import { switchmark } from "@seed-design/lynx-css/recipes/switchmark";
7
+ import type { SwitchmarkVariantProps } from "@seed-design/lynx-css/recipes/switchmark";
8
+
9
+ import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
10
+ import { useControllableState } from "../../hooks/useControllableState";
11
+ import { usePressTap } from "../../hooks/usePressTap";
12
+ import type { LynxStyledElementProps, LynxTextRef, LynxViewRef } from "../../types";
13
+
14
+ /**
15
+ * @platform Lynx
16
+ *
17
+ * 웹 대비 미지원 기능:
18
+ * - HiddenInput / name / value / required / invalid: Lynx에 native form 제출 모델이 없음
19
+ * - focus / focusVisible: Lynx에 키보드 포커스 개념이 없음
20
+ * - onChange (raw DOM event): 의미 없음. 토글 이벤트는 onCheckedChange로만 노출
21
+ *
22
+ * 추후 rootage 토큰 확장 시 추가 예정:
23
+ * - pressed boolean variant: switchmark rootage spec 에 pressed 상태가 추가되면
24
+ * switchmark recipe 와 Switch 컴포넌트에 boolean variant 로 노출.
25
+ */
26
+
27
+ interface SwitchContextValue {
28
+ checked: boolean;
29
+ disabled: boolean;
30
+ switchVariantProps: SwitchVariantProps;
31
+ switchmarkVariantProps: SwitchmarkVariantProps;
32
+ toggle: () => void;
33
+ }
34
+
35
+ const SwitchContext = React.createContext<SwitchContextValue | null>(null);
36
+
37
+ function useSwitchContext(consumer: string): SwitchContextValue {
38
+ const ctx = React.useContext(SwitchContext);
39
+ if (!ctx) {
40
+ throw new Error(`<${consumer}/> must be rendered inside <SwitchRoot/>.`);
41
+ }
42
+ return ctx;
43
+ }
44
+
45
+ interface SwitchmarkControlContextValue {
46
+ thumbClassName: string;
47
+ switchmarkVariantProps: SwitchmarkVariantProps;
48
+ }
49
+
50
+ const SwitchmarkControlContext = React.createContext<SwitchmarkControlContextValue | null>(null);
51
+
52
+ function useSwitchmarkControlContext(consumer: string): SwitchmarkControlContextValue {
53
+ const ctx = React.useContext(SwitchmarkControlContext);
54
+ if (!ctx) {
55
+ throw new Error(`<${consumer}/> must be rendered inside <SwitchControl/>.`);
56
+ }
57
+ return ctx;
58
+ }
59
+
60
+ ////////////////////////////////////////////////////////////////////////////////////
61
+
62
+ export interface SwitchRootProps
63
+ extends SwitchVariantProps,
64
+ Omit<SwitchmarkVariantProps, "size">,
65
+ LynxStyledElementProps {
66
+ checked?: boolean;
67
+ defaultChecked?: boolean;
68
+ disabled?: boolean;
69
+ onCheckedChange?: (checked: boolean) => void;
70
+ }
71
+
72
+ export const SwitchRoot = React.forwardRef<unknown, SwitchRootProps>((props, ref) => {
73
+ const {
74
+ children,
75
+ className,
76
+ checked: checkedProp,
77
+ defaultChecked = false,
78
+ disabled = false,
79
+ onCheckedChange,
80
+ ...restProps
81
+ } = props;
82
+ const [{ switch: switchVariantProps, switchmark: switchmarkVariantProps }, nativeProps] =
83
+ splitMultipleVariantsProps(restProps, { switch: switchStyle, switchmark });
84
+
85
+ const [checked, setChecked] = useControllableState({
86
+ value: checkedProp,
87
+ defaultValue: defaultChecked,
88
+ onChange: onCheckedChange,
89
+ });
90
+
91
+ const toggle = React.useCallback(() => setChecked(!checked), [checked, setChecked]);
92
+
93
+ const { pressed: _pressed, ...pressHandlers } = usePressTap({
94
+ disabled,
95
+ onTap: toggle,
96
+ });
97
+
98
+ const rootClassName = switchStyle({ ...switchVariantProps, disabled }).root;
99
+
100
+ const contextValue = React.useMemo<SwitchContextValue>(
101
+ () => ({
102
+ checked,
103
+ disabled,
104
+ switchVariantProps,
105
+ switchmarkVariantProps,
106
+ toggle,
107
+ }),
108
+ [checked, disabled, switchVariantProps, switchmarkVariantProps, toggle],
109
+ );
110
+
111
+ return (
112
+ <SwitchContext.Provider value={contextValue}>
113
+ <view
114
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
115
+ className={clsx(rootClassName, className)}
116
+ {...pressHandlers}
117
+ {...nativeProps}
118
+ >
119
+ {children}
120
+ </view>
121
+ </SwitchContext.Provider>
122
+ );
123
+ });
124
+ SwitchRoot.displayName = "SwitchRoot";
125
+
126
+ ////////////////////////////////////////////////////////////////////////////////////
127
+
128
+ export interface SwitchControlProps
129
+ extends Pick<SwitchmarkVariantProps, "tone" | "size">,
130
+ LynxStyledElementProps {}
131
+
132
+ export const SwitchControl = React.forwardRef<unknown, SwitchControlProps>((props, ref) => {
133
+ const [variantProps, restProps] = switchmark.splitVariantProps(props);
134
+ const { children, className, ...nativeProps } = restProps;
135
+ const context = useSwitchContext("SwitchControl");
136
+ const switchmarkVariantProps: SwitchmarkVariantProps = {
137
+ ...context.switchmarkVariantProps,
138
+ ...variantProps,
139
+ checked: context.checked,
140
+ disabled: context.disabled,
141
+ };
142
+ const classes = switchmark(switchmarkVariantProps);
143
+
144
+ return (
145
+ <SwitchmarkControlContext.Provider
146
+ value={{ thumbClassName: classes.thumb, switchmarkVariantProps }}
147
+ >
148
+ <view
149
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
150
+ className={clsx(classes.root, className)}
151
+ {...nativeProps}
152
+ >
153
+ {children}
154
+ </view>
155
+ </SwitchmarkControlContext.Provider>
156
+ );
157
+ });
158
+ SwitchControl.displayName = "SwitchControl";
159
+
160
+ ////////////////////////////////////////////////////////////////////////////////////
161
+
162
+ export interface SwitchThumbProps extends Pick<LynxStyledElementProps, "className" | "style"> {}
163
+
164
+ export const SwitchThumb = React.forwardRef<unknown, SwitchThumbProps>((props, ref) => {
165
+ const { className, ...nativeProps } = props;
166
+ const { thumbClassName } = useSwitchmarkControlContext("SwitchThumb");
167
+
168
+ return (
169
+ <view
170
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
171
+ className={clsx(thumbClassName, className)}
172
+ {...nativeProps}
173
+ />
174
+ );
175
+ });
176
+ SwitchThumb.displayName = "SwitchThumb";
177
+
178
+ ////////////////////////////////////////////////////////////////////////////////////
179
+
180
+ export interface SwitchLabelProps extends LynxStyledElementProps {}
181
+
182
+ export const SwitchLabel = React.forwardRef<unknown, SwitchLabelProps>((props, ref) => {
183
+ const { children, className, ...nativeProps } = props;
184
+ const context = useSwitchContext("SwitchLabel");
185
+ const labelClassName = switchStyle({
186
+ ...context.switchVariantProps,
187
+ disabled: context.disabled,
188
+ }).label;
189
+
190
+ return (
191
+ <text
192
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
193
+ className={clsx(labelClassName, className)}
194
+ {...nativeProps}
195
+ >
196
+ {children}
197
+ </text>
198
+ );
199
+ });
200
+ SwitchLabel.displayName = "SwitchLabel";
@@ -0,0 +1,12 @@
1
+ export {
2
+ SwitchRoot,
3
+ SwitchControl,
4
+ SwitchThumb,
5
+ SwitchLabel,
6
+ type SwitchRootProps,
7
+ type SwitchControlProps,
8
+ type SwitchThumbProps,
9
+ type SwitchLabelProps,
10
+ } from "./Switch";
11
+
12
+ export * as Switch from "./Switch.namespace";
@@ -0,0 +1,8 @@
1
+ export {
2
+ TagGroupItem as Item,
3
+ TagGroupItemLabel as ItemLabel,
4
+ TagGroupRoot as Root,
5
+ type TagGroupItemLabelProps as ItemLabelProps,
6
+ type TagGroupItemProps as ItemProps,
7
+ type TagGroupRootProps as RootProps,
8
+ } from "./TagGroup";