@seed-design/lynx-react 0.1.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 (168) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +44 -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/BottomSheet/BottomSheet.d.ts +87 -0
  8. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -0
  9. package/lib/components/BottomSheet/BottomSheet.jsx +150 -0
  10. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +2 -0
  11. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts.map +1 -0
  12. package/lib/components/BottomSheet/BottomSheet.namespace.js +18 -0
  13. package/lib/components/BottomSheet/index.d.ts +3 -0
  14. package/lib/components/BottomSheet/index.d.ts.map +1 -0
  15. package/lib/components/Box/Box.d.ts +20 -0
  16. package/lib/components/Box/Box.d.ts.map +1 -0
  17. package/lib/components/Box/Box.jsx +14 -0
  18. package/lib/components/Box/index.d.ts +2 -0
  19. package/lib/components/Box/index.d.ts.map +1 -0
  20. package/lib/components/Checkbox/Checkbox.d.ts +35 -0
  21. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -0
  22. package/lib/components/Checkbox/Checkbox.jsx +130 -0
  23. package/lib/components/Checkbox/Checkbox.namespace.d.ts +2 -0
  24. package/lib/components/Checkbox/Checkbox.namespace.d.ts.map +1 -0
  25. package/lib/components/Checkbox/Checkbox.namespace.js +12 -0
  26. package/lib/components/Checkbox/index.d.ts +3 -0
  27. package/lib/components/Checkbox/index.d.ts.map +1 -0
  28. package/lib/components/Icon/Icon.d.ts +37 -0
  29. package/lib/components/Icon/Icon.d.ts.map +1 -0
  30. package/lib/components/Icon/Icon.jsx +143 -0
  31. package/lib/components/Icon/index.d.ts +2 -0
  32. package/lib/components/Icon/index.d.ts.map +1 -0
  33. package/lib/components/ProgressCircle/ProgressCircle.d.ts +25 -0
  34. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -0
  35. package/lib/components/ProgressCircle/ProgressCircle.jsx +281 -0
  36. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts +2 -0
  37. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts.map +1 -0
  38. package/lib/components/ProgressCircle/ProgressCircle.namespace.js +9 -0
  39. package/lib/components/ProgressCircle/index.d.ts +3 -0
  40. package/lib/components/ProgressCircle/index.d.ts.map +1 -0
  41. package/lib/components/RadioGroup/RadioGroup.d.ts +34 -0
  42. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -0
  43. package/lib/components/RadioGroup/RadioGroup.jsx +160 -0
  44. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts +2 -0
  45. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts.map +1 -0
  46. package/lib/components/RadioGroup/RadioGroup.namespace.js +12 -0
  47. package/lib/components/RadioGroup/index.d.ts +3 -0
  48. package/lib/components/RadioGroup/index.d.ts.map +1 -0
  49. package/lib/components/Stack/Stack.d.ts +33 -0
  50. package/lib/components/Stack/Stack.d.ts.map +1 -0
  51. package/lib/components/Stack/Stack.jsx +39 -0
  52. package/lib/components/Stack/index.d.ts +2 -0
  53. package/lib/components/Stack/index.d.ts.map +1 -0
  54. package/lib/components/Switch/Switch.d.ts +21 -0
  55. package/lib/components/Switch/Switch.d.ts.map +1 -0
  56. package/lib/components/Switch/Switch.jsx +101 -0
  57. package/lib/components/Switch/Switch.namespace.d.ts +2 -0
  58. package/lib/components/Switch/Switch.namespace.d.ts.map +1 -0
  59. package/lib/components/Switch/Switch.namespace.js +11 -0
  60. package/lib/components/Switch/index.d.ts +3 -0
  61. package/lib/components/Switch/index.d.ts.map +1 -0
  62. package/lib/components/TagGroup/TagGroup.d.ts +34 -0
  63. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -0
  64. package/lib/components/TagGroup/TagGroup.jsx +57 -0
  65. package/lib/components/TagGroup/TagGroup.namespace.d.ts +2 -0
  66. package/lib/components/TagGroup/TagGroup.namespace.d.ts.map +1 -0
  67. package/lib/components/TagGroup/TagGroup.namespace.js +10 -0
  68. package/lib/components/TagGroup/index.d.ts +3 -0
  69. package/lib/components/TagGroup/index.d.ts.map +1 -0
  70. package/lib/components/Text/Text.d.ts +7 -0
  71. package/lib/components/Text/Text.d.ts.map +1 -0
  72. package/lib/components/Text/Text.jsx +28 -0
  73. package/lib/components/Text/index.d.ts +2 -0
  74. package/lib/components/Text/index.d.ts.map +1 -0
  75. package/lib/components/index.d.ts +12 -0
  76. package/lib/components/index.d.ts.map +1 -0
  77. package/lib/hooks/index.d.ts +5 -0
  78. package/lib/hooks/index.d.ts.map +1 -0
  79. package/lib/hooks/useControllableState.d.ts +15 -0
  80. package/lib/hooks/useControllableState.d.ts.map +1 -0
  81. package/lib/hooks/useControllableState.js +27 -0
  82. package/lib/hooks/useIconColor.d.ts +19 -0
  83. package/lib/hooks/useIconColor.d.ts.map +1 -0
  84. package/lib/hooks/useIconColor.js +38 -0
  85. package/lib/hooks/usePressTap.d.ts +27 -0
  86. package/lib/hooks/usePressTap.d.ts.map +1 -0
  87. package/lib/hooks/usePressTap.js +35 -0
  88. package/lib/hooks/useSafeArea.d.ts +19 -0
  89. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  90. package/lib/hooks/useSafeArea.js +33 -0
  91. package/lib/index.d.ts +5 -0
  92. package/lib/index.d.ts.map +1 -0
  93. package/lib/index.js +26 -0
  94. package/lib/types.d.ts +33 -0
  95. package/lib/types.d.ts.map +1 -0
  96. package/lib/utils/children.d.ts +3 -0
  97. package/lib/utils/children.d.ts.map +1 -0
  98. package/lib/utils/children.js +8 -0
  99. package/lib/utils/create-slot-recipe-context.d.ts +27 -0
  100. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -0
  101. package/lib/utils/create-slot-recipe-context.jsx +94 -0
  102. package/lib/utils/get-seed-class-name.d.ts +22 -0
  103. package/lib/utils/get-seed-class-name.d.ts.map +1 -0
  104. package/lib/utils/get-seed-class-name.js +28 -0
  105. package/lib/utils/index.d.ts +5 -0
  106. package/lib/utils/index.d.ts.map +1 -0
  107. package/lib/utils/resolve-recipe-token.d.ts +38 -0
  108. package/lib/utils/resolve-recipe-token.d.ts.map +1 -0
  109. package/lib/utils/resolve-recipe-token.js +49 -0
  110. package/lib/utils/split-multiple-variants-props.d.ts +29 -0
  111. package/lib/utils/split-multiple-variants-props.d.ts.map +1 -0
  112. package/lib/utils/split-multiple-variants-props.js +34 -0
  113. package/lib/utils/styled.d.ts +83 -0
  114. package/lib/utils/styled.d.ts.map +1 -0
  115. package/lib/utils/styled.js +144 -0
  116. package/package.json +54 -0
  117. package/src/components/ActionButton/ActionButton.test.tsx +137 -0
  118. package/src/components/ActionButton/ActionButton.tsx +340 -0
  119. package/src/components/ActionButton/index.ts +1 -0
  120. package/src/components/BottomSheet/BottomSheet.namespace.ts +24 -0
  121. package/src/components/BottomSheet/BottomSheet.test.tsx +153 -0
  122. package/src/components/BottomSheet/BottomSheet.tsx +397 -0
  123. package/src/components/BottomSheet/index.ts +27 -0
  124. package/src/components/Box/Box.test.tsx +85 -0
  125. package/src/components/Box/Box.tsx +39 -0
  126. package/src/components/Box/index.ts +1 -0
  127. package/src/components/Checkbox/Checkbox.namespace.ts +12 -0
  128. package/src/components/Checkbox/Checkbox.tsx +282 -0
  129. package/src/components/Checkbox/index.ts +14 -0
  130. package/src/components/Icon/Icon.tsx +265 -0
  131. package/src/components/Icon/index.ts +8 -0
  132. package/src/components/ProgressCircle/ProgressCircle.namespace.ts +5 -0
  133. package/src/components/ProgressCircle/ProgressCircle.tsx +424 -0
  134. package/src/components/ProgressCircle/index.ts +9 -0
  135. package/src/components/RadioGroup/RadioGroup.namespace.ts +12 -0
  136. package/src/components/RadioGroup/RadioGroup.tsx +320 -0
  137. package/src/components/RadioGroup/index.ts +14 -0
  138. package/src/components/Stack/Stack.tsx +97 -0
  139. package/src/components/Stack/index.ts +1 -0
  140. package/src/components/Switch/Switch.namespace.ts +10 -0
  141. package/src/components/Switch/Switch.tsx +200 -0
  142. package/src/components/Switch/index.ts +12 -0
  143. package/src/components/TagGroup/TagGroup.namespace.ts +8 -0
  144. package/src/components/TagGroup/TagGroup.tsx +127 -0
  145. package/src/components/TagGroup/index.ts +10 -0
  146. package/src/components/Text/Text.tsx +64 -0
  147. package/src/components/Text/index.ts +1 -0
  148. package/src/components/index.ts +11 -0
  149. package/src/hooks/index.ts +4 -0
  150. package/src/hooks/useControllableState.test.ts +120 -0
  151. package/src/hooks/useControllableState.ts +38 -0
  152. package/src/hooks/useIconColor.test.ts +13 -0
  153. package/src/hooks/useIconColor.ts +67 -0
  154. package/src/hooks/usePressTap.test.ts +129 -0
  155. package/src/hooks/usePressTap.ts +58 -0
  156. package/src/hooks/useSafeArea.test.ts +83 -0
  157. package/src/hooks/useSafeArea.ts +56 -0
  158. package/src/index.ts +4 -0
  159. package/src/types.ts +45 -0
  160. package/src/utils/children.ts +13 -0
  161. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  162. package/src/utils/create-slot-recipe-context.tsx +187 -0
  163. package/src/utils/get-seed-class-name.ts +47 -0
  164. package/src/utils/index.ts +4 -0
  165. package/src/utils/resolve-recipe-token.ts +47 -0
  166. package/src/utils/split-multiple-variants-props.test.ts +61 -0
  167. package/src/utils/split-multiple-variants-props.ts +59 -0
  168. package/src/utils/styled.ts +442 -0
@@ -0,0 +1,282 @@
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 { checkbox } from "@seed-design/lynx-css/recipes/checkbox";
6
+ import type { CheckboxVariantProps } from "@seed-design/lynx-css/recipes/checkbox";
7
+ import { checkmark } from "@seed-design/lynx-css/recipes/checkmark";
8
+ import type { CheckmarkVariantProps } from "@seed-design/lynx-css/recipes/checkmark";
9
+ import { checkboxGroup } from "@seed-design/lynx-css/recipes/checkbox-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 / value / required / invalid: Lynx에 native form 제출 모델이 없음
27
+ * - focus / focusVisible: Lynx에 키보드 포커스 개념이 없음
28
+ * - onChange (raw DOM event): 의미 없음. 토글 이벤트는 onCheckedChange로만 노출
29
+ * - weight="default" | "stronger" 호환 매핑: Lynx 신규 컴포넌트이므로 처음부터 "regular" | "bold" 만 노출
30
+ *
31
+ * Indicator 는 `@karrotmarket/lynx-monochrome-icon` 의 monochrome icon 컴포넌트를
32
+ * 받는다. 내부에서 `<image tint-color=...>` 로 렌더되므로 `useIconColor` 훅이
33
+ * recipe 의 `color` 토큰을 `tint-color` 로 동기화한다. raw SVG 주입은 Lynx 범위 밖.
34
+ */
35
+
36
+ interface CheckboxContextValue {
37
+ checked: boolean;
38
+ indeterminate: boolean;
39
+ disabled: boolean;
40
+ pressed: boolean;
41
+ checkboxVariantProps: CheckboxVariantProps;
42
+ checkmarkVariantProps: CheckmarkVariantProps;
43
+ toggle: () => void;
44
+ }
45
+
46
+ const CheckboxContext = React.createContext<CheckboxContextValue | null>(null);
47
+
48
+ function useCheckboxContext(consumer: string): CheckboxContextValue {
49
+ const ctx = React.useContext(CheckboxContext);
50
+ if (!ctx) {
51
+ throw new Error(`<${consumer}/> must be rendered inside <CheckboxRoot/>.`);
52
+ }
53
+ return ctx;
54
+ }
55
+
56
+ interface CheckmarkControlContextValue {
57
+ iconClassName: string;
58
+ checkmarkVariantProps: CheckmarkVariantProps;
59
+ }
60
+
61
+ const CheckmarkControlContext = React.createContext<CheckmarkControlContextValue | null>(null);
62
+
63
+ function useCheckmarkControlContext(consumer: string): CheckmarkControlContextValue {
64
+ const ctx = React.useContext(CheckmarkControlContext);
65
+ if (!ctx) {
66
+ throw new Error(`<${consumer}/> must be rendered inside <CheckboxControl/>.`);
67
+ }
68
+ return ctx;
69
+ }
70
+
71
+ ////////////////////////////////////////////////////////////////////////////////////
72
+
73
+ export interface CheckboxRootProps
74
+ extends CheckboxVariantProps,
75
+ Omit<CheckmarkVariantProps, "size" | "checked" | "disabled" | "indeterminate">,
76
+ LynxStyledElementProps {
77
+ checked?: boolean;
78
+ defaultChecked?: boolean;
79
+ indeterminate?: boolean;
80
+ disabled?: boolean;
81
+ onCheckedChange?: (checked: boolean) => void;
82
+ }
83
+
84
+ export const CheckboxRoot = React.forwardRef<unknown, CheckboxRootProps>((props, ref) => {
85
+ const {
86
+ children,
87
+ className,
88
+ checked: checkedProp,
89
+ defaultChecked = false,
90
+ indeterminate = false,
91
+ disabled = false,
92
+ onCheckedChange,
93
+ ...restProps
94
+ } = props;
95
+ const [{ checkbox: checkboxVariantProps, checkmark: checkmarkVariantProps }, nativeProps] =
96
+ splitMultipleVariantsProps(restProps, { checkbox, checkmark });
97
+
98
+ const [checked, setChecked] = useControllableState({
99
+ value: checkedProp,
100
+ defaultValue: defaultChecked,
101
+ onChange: onCheckedChange,
102
+ });
103
+
104
+ const toggle = React.useCallback(() => setChecked(!checked), [checked, setChecked]);
105
+
106
+ const { pressed, ...pressHandlers } = usePressTap({
107
+ disabled,
108
+ onTap: toggle,
109
+ });
110
+
111
+ const rootClassName = checkbox({ ...checkboxVariantProps, disabled }).root;
112
+
113
+ const contextValue = React.useMemo<CheckboxContextValue>(
114
+ () => ({
115
+ checked,
116
+ indeterminate,
117
+ disabled,
118
+ pressed,
119
+ checkboxVariantProps,
120
+ checkmarkVariantProps,
121
+ toggle,
122
+ }),
123
+ [
124
+ checked,
125
+ indeterminate,
126
+ disabled,
127
+ pressed,
128
+ checkboxVariantProps,
129
+ checkmarkVariantProps,
130
+ toggle,
131
+ ],
132
+ );
133
+
134
+ return (
135
+ <CheckboxContext.Provider value={contextValue}>
136
+ <view
137
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
138
+ className={clsx(rootClassName, className)}
139
+ {...pressHandlers}
140
+ {...nativeProps}
141
+ >
142
+ {children}
143
+ </view>
144
+ </CheckboxContext.Provider>
145
+ );
146
+ });
147
+ CheckboxRoot.displayName = "CheckboxRoot";
148
+
149
+ ////////////////////////////////////////////////////////////////////////////////////
150
+
151
+ export interface CheckboxControlProps
152
+ extends Pick<CheckmarkVariantProps, "tone" | "variant" | "size">,
153
+ LynxStyledElementProps {}
154
+
155
+ export const CheckboxControl = React.forwardRef<unknown, CheckboxControlProps>((props, ref) => {
156
+ const [variantProps, restProps] = checkmark.splitVariantProps(props);
157
+ const { children, className, ...nativeProps } = restProps;
158
+ const context = useCheckboxContext("CheckboxControl");
159
+ const checkmarkVariantProps: CheckmarkVariantProps = {
160
+ ...context.checkmarkVariantProps,
161
+ ...variantProps,
162
+ checked: context.checked,
163
+ disabled: context.disabled,
164
+ indeterminate: context.indeterminate,
165
+ pressed: context.pressed,
166
+ };
167
+ const classes = checkmark(checkmarkVariantProps);
168
+
169
+ return (
170
+ <CheckmarkControlContext.Provider
171
+ value={{ iconClassName: classes.icon, checkmarkVariantProps }}
172
+ >
173
+ <view
174
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
175
+ className={clsx(classes.root, className)}
176
+ {...nativeProps}
177
+ >
178
+ {children}
179
+ </view>
180
+ </CheckmarkControlContext.Provider>
181
+ );
182
+ });
183
+ CheckboxControl.displayName = "CheckboxControl";
184
+
185
+ ////////////////////////////////////////////////////////////////////////////////////
186
+
187
+ export interface CheckboxIndicatorProps
188
+ extends Pick<LynxStyledElementProps, "className" | "style"> {
189
+ /** Icon rendered when neither checked nor indeterminate. Optional. */
190
+ unchecked?: ReactElement<LynxIconElementProps>;
191
+ /** Icon rendered when `checked=true` and `indeterminate=false`. */
192
+ checked: ReactElement<LynxIconElementProps>;
193
+ /** Icon rendered when `indeterminate=true`. */
194
+ indeterminate?: ReactElement<LynxIconElementProps>;
195
+ }
196
+
197
+ export function CheckboxIndicator(props: CheckboxIndicatorProps) {
198
+ const {
199
+ unchecked,
200
+ checked: checkedIcon,
201
+ indeterminate: indeterminateIcon,
202
+ className,
203
+ style,
204
+ } = props;
205
+ const context = useCheckboxContext("CheckboxIndicator");
206
+ const { iconClassName, checkmarkVariantProps } = useCheckmarkControlContext("CheckboxIndicator");
207
+
208
+ if (process.env.NODE_ENV !== "production" && context.indeterminate && !indeterminateIcon) {
209
+ console.warn(
210
+ "[seed-design] CheckboxIndicator: `indeterminate` prop must be provided when the checkbox is in an indeterminate state.",
211
+ );
212
+ }
213
+
214
+ const icon = context.indeterminate
215
+ ? indeterminateIcon
216
+ : context.checked
217
+ ? checkedIcon
218
+ : unchecked;
219
+ if (!icon || !isValidElement<LynxIconElementProps>(icon)) return null;
220
+
221
+ return (
222
+ <InternalIcon
223
+ icon={icon}
224
+ className={clsx(iconClassName, className)}
225
+ style={style}
226
+ deps={[
227
+ context.checked,
228
+ context.indeterminate,
229
+ context.disabled,
230
+ context.pressed,
231
+ checkmarkVariantProps.tone,
232
+ checkmarkVariantProps.variant,
233
+ checkmarkVariantProps.size,
234
+ ]}
235
+ />
236
+ );
237
+ }
238
+ CheckboxIndicator.displayName = "CheckboxIndicator";
239
+
240
+ ////////////////////////////////////////////////////////////////////////////////////
241
+
242
+ export interface CheckboxLabelProps extends LynxStyledElementProps {}
243
+
244
+ export const CheckboxLabel = React.forwardRef<unknown, CheckboxLabelProps>((props, ref) => {
245
+ const { children, className, ...nativeProps } = props;
246
+ const context = useCheckboxContext("CheckboxLabel");
247
+ const labelClassName = checkbox({
248
+ ...context.checkboxVariantProps,
249
+ disabled: context.disabled,
250
+ }).label;
251
+
252
+ return (
253
+ <text
254
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
255
+ className={clsx(labelClassName, className)}
256
+ {...nativeProps}
257
+ >
258
+ {children}
259
+ </text>
260
+ );
261
+ });
262
+ CheckboxLabel.displayName = "CheckboxLabel";
263
+
264
+ ////////////////////////////////////////////////////////////////////////////////////
265
+
266
+ export interface CheckboxGroupProps extends LynxStyledElementProps {}
267
+
268
+ export const CheckboxGroup = React.forwardRef<unknown, CheckboxGroupProps>((props, ref) => {
269
+ const { children, className, ...nativeProps } = props;
270
+ const classes = checkboxGroup();
271
+
272
+ return (
273
+ <view
274
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
275
+ className={clsx(classes.root, className)}
276
+ {...nativeProps}
277
+ >
278
+ {children}
279
+ </view>
280
+ );
281
+ });
282
+ CheckboxGroup.displayName = "CheckboxGroup";
@@ -0,0 +1,14 @@
1
+ export {
2
+ CheckboxRoot,
3
+ CheckboxControl,
4
+ CheckboxIndicator,
5
+ CheckboxLabel,
6
+ CheckboxGroup,
7
+ type CheckboxRootProps,
8
+ type CheckboxControlProps,
9
+ type CheckboxIndicatorProps,
10
+ type CheckboxLabelProps,
11
+ type CheckboxGroupProps,
12
+ } from "./Checkbox";
13
+
14
+ export * as Checkbox from "./Checkbox.namespace";
@@ -0,0 +1,265 @@
1
+ import { useMainThreadRef } from "@lynx-js/react";
2
+ import type { CSSProperties, MainThread } from "@lynx-js/types";
3
+ import clsx from "clsx";
4
+ import * as React from "@lynx-js/react";
5
+ import {
6
+ cloneElement,
7
+ isValidElement,
8
+ type DependencyList,
9
+ type ReactElement,
10
+ } from "@lynx-js/react";
11
+
12
+ import { useIconColor } from "../../hooks/useIconColor";
13
+ import type { LynxIconElementProps, LynxStyledElementProps, LynxViewRef } from "../../types";
14
+ import { handleColor, handleDimension, type StyleProps } from "../../utils/styled";
15
+
16
+ export type IconSlotName = "icon" | "prefixIcon" | "suffixIcon";
17
+
18
+ ////////////////////////////////////////////////////////////////////////////////////
19
+
20
+ interface IconSlotContextValue {
21
+ classNames?: Partial<Record<IconSlotName, string>>;
22
+ deps?: DependencyList;
23
+ }
24
+
25
+ const IconSlotContext = React.createContext<IconSlotContextValue | null>(null);
26
+
27
+ interface IconRequiredContextValue {
28
+ register: () => void;
29
+ unregister: () => void;
30
+ }
31
+
32
+ const IconRequiredContext = React.createContext<IconRequiredContextValue | null>(null);
33
+
34
+ export function IconRequired({
35
+ children,
36
+ enabled,
37
+ }: {
38
+ children: React.ReactNode;
39
+ enabled: boolean;
40
+ }) {
41
+ const registeredCountRef = React.useRef(0);
42
+ const parentContext = React.useContext(IconRequiredContext);
43
+
44
+ const register = React.useCallback(() => {
45
+ registeredCountRef.current += 1;
46
+
47
+ if (process.env.NODE_ENV !== "production" && registeredCountRef.current > 1) {
48
+ throw new Error(
49
+ "Icon-only Component must render only one <Icon /> under children. Check if you are rendering multiple <Icon />.",
50
+ );
51
+ }
52
+ }, []);
53
+
54
+ const unregister = React.useCallback(() => {
55
+ registeredCountRef.current = Math.max(0, registeredCountRef.current - 1);
56
+ }, []);
57
+
58
+ React.useLayoutEffect(() => {
59
+ if (!enabled) return;
60
+
61
+ if (process.env.NODE_ENV !== "production") {
62
+ if (parentContext) {
63
+ throw new Error(
64
+ "Icon-only Component must not be nested within another Icon-Only. Check if you are using Icon-Only inside another Icon-Only.",
65
+ );
66
+ }
67
+ if (registeredCountRef.current === 0) {
68
+ throw new Error(
69
+ "Icon-only Component must render <Icon /> as a child. Check if you are using raw icon element instead of <Icon icon={} />.",
70
+ );
71
+ }
72
+ }
73
+ }, [enabled, parentContext]);
74
+
75
+ const value = React.useMemo<IconRequiredContextValue | null>(() => {
76
+ if (!enabled) return parentContext ?? null;
77
+ return { register, unregister };
78
+ }, [enabled, parentContext, register, unregister]);
79
+
80
+ return <IconRequiredContext.Provider value={value}>{children}</IconRequiredContext.Provider>;
81
+ }
82
+
83
+ export function IconSlotProvider({
84
+ value,
85
+ children,
86
+ }: {
87
+ value: IconSlotContextValue;
88
+ children: React.ReactNode;
89
+ }) {
90
+ return <IconSlotContext.Provider value={value}>{children}</IconSlotContext.Provider>;
91
+ }
92
+
93
+ ////////////////////////////////////////////////////////////////////////////////////
94
+
95
+ const iconSlotMarker = Symbol.for("@seed-design/lynx-react/icon-slot");
96
+
97
+ interface IconSlotComponent {
98
+ [iconSlotMarker]?: IconSlotName;
99
+ }
100
+
101
+ export function getIconSlotName(node: React.ReactNode): IconSlotName | null {
102
+ if (!isValidElement(node)) return null;
103
+
104
+ return ((node.type as IconSlotComponent)[iconSlotMarker] ?? null) as IconSlotName | null;
105
+ }
106
+
107
+ ////////////////////////////////////////////////////////////////////////////////////
108
+
109
+ export interface IconProps extends LynxStyledElementProps {
110
+ icon: ReactElement<LynxIconElementProps>;
111
+ size?: StyleProps["height"] | number;
112
+ color?: StyleProps["color"];
113
+ }
114
+
115
+ export interface PrefixIconProps extends IconProps {}
116
+
117
+ export interface SuffixIconProps extends IconProps {}
118
+
119
+ interface IconSlotBaseProps extends IconProps {
120
+ slot: IconSlotName | null;
121
+ baseClassName?: string;
122
+ }
123
+
124
+ function mergeWrapperStyle({
125
+ size,
126
+ color,
127
+ style,
128
+ }: Pick<IconProps, "size" | "color" | "style">): CSSProperties | undefined {
129
+ const dimension = handleDimension(size);
130
+ const resolvedColor = handleColor(color);
131
+
132
+ if (dimension == null && resolvedColor == null) return style;
133
+
134
+ return {
135
+ ...(dimension != null ? { width: dimension, height: dimension } : {}),
136
+ ...(resolvedColor != null ? { color: resolvedColor } : {}),
137
+ ...style,
138
+ };
139
+ }
140
+
141
+ function getStyleColor(style: CSSProperties | undefined): CSSProperties["color"] {
142
+ return style?.color;
143
+ }
144
+
145
+ const IconSlotBase = React.forwardRef<unknown, IconSlotBaseProps>((props, ref) => {
146
+ const {
147
+ icon,
148
+ slot,
149
+ baseClassName,
150
+ className,
151
+ style,
152
+ size,
153
+ color,
154
+ children: _children,
155
+ ...nativeProps
156
+ } = props;
157
+ const context = React.useContext(IconSlotContext);
158
+ const iconRequiredContext = React.useContext(IconRequiredContext);
159
+ const sourceRef = useMainThreadRef<MainThread.Element>(null);
160
+ const slotClassName = slot != null ? context?.classNames?.[slot] : undefined;
161
+ const styleColor = getStyleColor(style);
162
+ const iconColor = useIconColor(
163
+ [baseClassName, slotClassName, className, size, color, styleColor, ...(context?.deps ?? [])],
164
+ { sourceRef },
165
+ );
166
+ const hasValidIcon = isValidElement<LynxIconElementProps>(icon);
167
+
168
+ React.useLayoutEffect(() => {
169
+ if (slot !== "icon" || !hasValidIcon) return;
170
+
171
+ iconRequiredContext?.register();
172
+ return () => {
173
+ iconRequiredContext?.unregister();
174
+ };
175
+ }, [hasValidIcon, iconRequiredContext, slot]);
176
+
177
+ if (!hasValidIcon) return null;
178
+
179
+ return (
180
+ <view
181
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
182
+ {...nativeProps}
183
+ main-thread:ref={sourceRef}
184
+ className={clsx(baseClassName, slotClassName, className)}
185
+ style={mergeWrapperStyle({ size, color, style })}
186
+ >
187
+ {cloneElement(icon, {
188
+ ...iconColor,
189
+ className: icon.props.className,
190
+ style: {
191
+ ...icon.props.style,
192
+ width: "100%",
193
+ height: "100%",
194
+ },
195
+ })}
196
+ </view>
197
+ );
198
+ });
199
+ IconSlotBase.displayName = "IconSlotBase";
200
+
201
+ ////////////////////////////////////////////////////////////////////////////////////
202
+
203
+ function createIconComponent<Props extends IconProps>(
204
+ displayName: string,
205
+ slot: IconSlotName,
206
+ baseClassName: string,
207
+ ) {
208
+ const Component = React.forwardRef<unknown, Props>((props, ref) => {
209
+ return <IconSlotBase ref={ref} slot={slot} baseClassName={baseClassName} {...props} />;
210
+ });
211
+
212
+ Component.displayName = displayName;
213
+ (Component as IconSlotComponent)[iconSlotMarker] = slot;
214
+
215
+ return Component;
216
+ }
217
+
218
+ export const Icon = createIconComponent<IconProps>("Icon", "icon", "seed-icon");
219
+ export const PrefixIcon = createIconComponent<PrefixIconProps>(
220
+ "PrefixIcon",
221
+ "prefixIcon",
222
+ "seed-prefix-icon",
223
+ );
224
+ export const SuffixIcon = createIconComponent<SuffixIconProps>(
225
+ "SuffixIcon",
226
+ "suffixIcon",
227
+ "seed-suffix-icon",
228
+ );
229
+
230
+ ////////////////////////////////////////////////////////////////////////////////////
231
+
232
+ export interface InternalIconProps extends LynxStyledElementProps {
233
+ icon: ReactElement<LynxIconElementProps>;
234
+ deps?: DependencyList;
235
+ }
236
+
237
+ export const InternalIcon = React.forwardRef<unknown, InternalIconProps>((props, ref) => {
238
+ const { icon, deps = [], className, style, children: _children, ...nativeProps } = props;
239
+ const sourceRef = useMainThreadRef<MainThread.Element>(null);
240
+ const styleColor = getStyleColor(style);
241
+ const iconColor = useIconColor([className, styleColor, ...(deps ?? [])], { sourceRef });
242
+
243
+ if (!isValidElement<LynxIconElementProps>(icon)) return null;
244
+
245
+ return (
246
+ <view
247
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
248
+ {...nativeProps}
249
+ main-thread:ref={sourceRef}
250
+ className={className}
251
+ style={style}
252
+ >
253
+ {cloneElement(icon, {
254
+ ...iconColor,
255
+ className: icon.props.className,
256
+ style: {
257
+ ...icon.props.style,
258
+ width: "100%",
259
+ height: "100%",
260
+ },
261
+ })}
262
+ </view>
263
+ );
264
+ });
265
+ InternalIcon.displayName = "InternalIcon";
@@ -0,0 +1,8 @@
1
+ export {
2
+ Icon,
3
+ PrefixIcon,
4
+ SuffixIcon,
5
+ type IconProps,
6
+ type PrefixIconProps,
7
+ type SuffixIconProps,
8
+ } from "./Icon";
@@ -0,0 +1,5 @@
1
+ export {
2
+ ProgressCircleRange as Range,
3
+ ProgressCircleRoot as Root,
4
+ type ProgressCircleRootProps as RootProps,
5
+ } from "./ProgressCircle";