@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,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 { checkbox } from "@seed-design/lynx-css/recipes/checkbox";
7
6
  import type { CheckboxVariantProps } from "@seed-design/lynx-css/recipes/checkbox";
8
7
  import { checkmark } from "@seed-design/lynx-css/recipes/checkmark";
9
8
  import type { CheckmarkVariantProps } from "@seed-design/lynx-css/recipes/checkmark";
10
9
  import { checkboxGroup } from "@seed-design/lynx-css/recipes/checkbox-group";
11
- import { vars as checkmarkVars } from "@seed-design/lynx-css/vars/component/checkmark";
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
@@ -30,20 +33,14 @@ import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-
30
33
  * recipe 의 `color` 토큰을 `tint-color` 로 동기화한다. raw SVG 주입은 Lynx 범위 밖.
31
34
  */
32
35
 
33
- type CheckboxSize = NonNullable<CheckboxVariantProps["size"]>;
34
- type CheckboxWeight = NonNullable<CheckboxVariantProps["weight"]>;
35
- type CheckmarkTone = NonNullable<CheckmarkVariantProps["tone"]>;
36
- type CheckmarkVariant = NonNullable<CheckmarkVariantProps["variant"]>;
37
-
38
36
  interface CheckboxContextValue {
39
37
  checked: boolean;
40
38
  indeterminate: boolean;
41
39
  disabled: boolean;
42
40
  pressed: boolean;
43
- size: CheckboxSize;
44
- weight: CheckboxWeight;
45
- tone: CheckmarkTone;
46
- variant: CheckmarkVariant;
41
+ checkboxVariantProps: CheckboxVariantProps;
42
+ checkmarkVariantProps: CheckmarkVariantProps;
43
+ toggle: () => void;
47
44
  }
48
45
 
49
46
  const CheckboxContext = React.createContext<CheckboxContextValue | null>(null);
@@ -56,10 +53,15 @@ function useCheckboxContext(consumer: string): CheckboxContextValue {
56
53
  return ctx;
57
54
  }
58
55
 
59
- const CheckmarkIconClassContext = React.createContext<string | null>(null);
56
+ interface CheckmarkControlContextValue {
57
+ iconClassName: string;
58
+ checkmarkVariantProps: CheckmarkVariantProps;
59
+ }
60
+
61
+ const CheckmarkControlContext = React.createContext<CheckmarkControlContextValue | null>(null);
60
62
 
61
- function useCheckmarkIconClass(consumer: string): string {
62
- const ctx = React.useContext(CheckmarkIconClassContext);
63
+ function useCheckmarkControlContext(consumer: string): CheckmarkControlContextValue {
64
+ const ctx = React.useContext(CheckmarkControlContext);
63
65
  if (!ctx) {
64
66
  throw new Error(`<${consumer}/> must be rendered inside <CheckboxControl/>.`);
65
67
  }
@@ -70,9 +72,8 @@ function useCheckmarkIconClass(consumer: string): string {
70
72
 
71
73
  export interface CheckboxRootProps
72
74
  extends CheckboxVariantProps,
73
- Omit<CheckmarkVariantProps, "size" | "checked" | "disabled" | "indeterminate"> {
74
- children?: React.ReactNode;
75
- className?: string;
75
+ Omit<CheckmarkVariantProps, "size" | "checked" | "disabled" | "indeterminate">,
76
+ LynxStyledElementProps {
76
77
  checked?: boolean;
77
78
  defaultChecked?: boolean;
78
79
  indeterminate?: boolean;
@@ -93,10 +94,6 @@ export const CheckboxRoot = React.forwardRef<unknown, CheckboxRootProps>((props,
93
94
  } = props;
94
95
  const [{ checkbox: checkboxVariantProps, checkmark: checkmarkVariantProps }, nativeProps] =
95
96
  splitMultipleVariantsProps(restProps, { checkbox, checkmark });
96
- const weight: CheckboxWeight = checkboxVariantProps.weight ?? "regular";
97
- const size: CheckboxSize = checkboxVariantProps.size ?? "medium";
98
- const tone: CheckmarkTone = checkmarkVariantProps.tone ?? "brand";
99
- const variant: CheckmarkVariant = checkmarkVariantProps.variant ?? "square";
100
97
 
101
98
  const [checked, setChecked] = useControllableState({
102
99
  value: checkedProp,
@@ -104,22 +101,40 @@ export const CheckboxRoot = React.forwardRef<unknown, CheckboxRootProps>((props,
104
101
  onChange: onCheckedChange,
105
102
  });
106
103
 
104
+ const toggle = React.useCallback(() => setChecked(!checked), [checked, setChecked]);
105
+
107
106
  const { pressed, ...pressHandlers } = usePressTap({
108
107
  disabled,
109
- onTap: () => setChecked(!checked),
108
+ onTap: toggle,
110
109
  });
111
110
 
112
- const rootClassName = checkbox({ weight, size, disabled }).root;
113
-
114
- const ctx = React.useMemo<CheckboxContextValue>(
115
- () => ({ checked, indeterminate, disabled, pressed, size, weight, tone, variant }),
116
- [checked, indeterminate, disabled, pressed, size, weight, tone, variant],
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
+ ],
117
132
  );
118
133
 
119
134
  return (
120
- <CheckboxContext.Provider value={ctx}>
135
+ <CheckboxContext.Provider value={contextValue}>
121
136
  <view
122
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
137
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
123
138
  className={clsx(rootClassName, className)}
124
139
  {...pressHandlers}
125
140
  {...nativeProps}
@@ -134,64 +149,49 @@ CheckboxRoot.displayName = "CheckboxRoot";
134
149
  ////////////////////////////////////////////////////////////////////////////////////
135
150
 
136
151
  export interface CheckboxControlProps
137
- extends Pick<CheckmarkVariantProps, "tone" | "variant" | "size"> {
138
- children?: React.ReactNode;
139
- className?: string;
140
- }
152
+ extends Pick<CheckmarkVariantProps, "tone" | "variant" | "size">,
153
+ LynxStyledElementProps {}
141
154
 
142
155
  export const CheckboxControl = React.forwardRef<unknown, CheckboxControlProps>((props, ref) => {
143
156
  const [variantProps, restProps] = checkmark.splitVariantProps(props);
144
157
  const { children, className, ...nativeProps } = restProps;
145
- const {
146
- checked,
147
- indeterminate,
148
- disabled,
149
- pressed,
150
- tone: ctxTone,
151
- variant: ctxVariant,
152
- size: ctxSize,
153
- } = useCheckboxContext("CheckboxControl");
154
-
155
- const tone: CheckmarkTone = variantProps.tone ?? ctxTone;
156
- const variant: CheckmarkVariant = variantProps.variant ?? ctxVariant;
157
- const size: CheckboxSize = (variantProps.size as CheckboxSize | undefined) ?? ctxSize;
158
-
159
- const classes = React.useMemo(
160
- () => checkmark({ variant, tone, size, checked, disabled, indeterminate, pressed }),
161
- [variant, tone, size, checked, disabled, indeterminate, pressed],
162
- );
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);
163
168
 
164
169
  return (
165
- <CheckmarkIconClassContext.Provider value={classes.icon}>
170
+ <CheckmarkControlContext.Provider
171
+ value={{ iconClassName: classes.icon, checkmarkVariantProps }}
172
+ >
166
173
  <view
167
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
174
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
168
175
  className={clsx(classes.root, className)}
169
176
  {...nativeProps}
170
177
  >
171
178
  {children}
172
179
  </view>
173
- </CheckmarkIconClassContext.Provider>
180
+ </CheckmarkControlContext.Provider>
174
181
  );
175
182
  });
176
183
  CheckboxControl.displayName = "CheckboxControl";
177
184
 
178
185
  ////////////////////////////////////////////////////////////////////////////////////
179
186
 
180
- type IconElementProps = {
181
- className?: string;
182
- style?: CSSProperties;
183
- ref?: React.Ref<MainThread.Element>;
184
- };
185
-
186
- export interface CheckboxIndicatorProps {
187
+ export interface CheckboxIndicatorProps
188
+ extends Pick<LynxStyledElementProps, "className" | "style"> {
187
189
  /** Icon rendered when neither checked nor indeterminate. Optional. */
188
- unchecked?: ReactElement<IconElementProps>;
190
+ unchecked?: ReactElement<LynxIconElementProps>;
189
191
  /** Icon rendered when `checked=true` and `indeterminate=false`. */
190
- checked: ReactElement<IconElementProps>;
192
+ checked: ReactElement<LynxIconElementProps>;
191
193
  /** Icon rendered when `indeterminate=true`. */
192
- indeterminate?: ReactElement<IconElementProps>;
193
- className?: string;
194
- style?: CSSProperties;
194
+ indeterminate?: ReactElement<LynxIconElementProps>;
195
195
  }
196
196
 
197
197
  export function CheckboxIndicator(props: CheckboxIndicatorProps) {
@@ -202,62 +202,56 @@ export function CheckboxIndicator(props: CheckboxIndicatorProps) {
202
202
  className,
203
203
  style,
204
204
  } = props;
205
- const { checked, indeterminate, disabled, pressed, tone, variant, size } =
206
- useCheckboxContext("CheckboxIndicator");
207
- const iconClassName = useCheckmarkIconClass("CheckboxIndicator");
208
- const { ref: tintRef } = useIconColor([
209
- checked,
210
- indeterminate,
211
- disabled,
212
- pressed,
213
- tone,
214
- variant,
215
- size,
216
- ]);
205
+ const context = useCheckboxContext("CheckboxIndicator");
206
+ const { iconClassName, checkmarkVariantProps } = useCheckmarkControlContext("CheckboxIndicator");
217
207
 
218
- if (process.env.NODE_ENV !== "production" && indeterminate && !indeterminateIcon) {
208
+ if (process.env.NODE_ENV !== "production" && context.indeterminate && !indeterminateIcon) {
219
209
  console.warn(
220
210
  "[seed-design] CheckboxIndicator: `indeterminate` prop must be provided when the checkbox is in an indeterminate state.",
221
211
  );
222
212
  }
223
213
 
224
- const icon = indeterminate ? indeterminateIcon : checked ? checkedIcon : unchecked;
225
- if (!icon || !isValidElement<IconElementProps>(icon)) return null;
226
-
227
- // lynx-monochrome-icon 이 `<image style={{ width, height, ...style }}>` 로 자체 default size 를 박으므로
228
- // recipe 토큰에서 꺼낸 사이즈를 inline 으로 주입해 덮는다.
229
- const iconSize = resolveRecipeToken(checkmarkVars, [
230
- `variant${capitalize(variant)}Size${capitalize(size)}`,
231
- "enabled",
232
- "icon",
233
- "size",
234
- ]);
235
-
236
- return cloneElement(icon, {
237
- className: clsx(iconClassName, icon.props.className, className),
238
- style: iconSize
239
- ? { width: iconSize, height: iconSize, ...icon.props.style, ...style }
240
- : { ...icon.props.style, ...style },
241
- ref: tintRef,
242
- });
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
+ );
243
237
  }
244
238
  CheckboxIndicator.displayName = "CheckboxIndicator";
245
239
 
246
240
  ////////////////////////////////////////////////////////////////////////////////////
247
241
 
248
- export interface CheckboxLabelProps {
249
- children?: React.ReactNode;
250
- className?: string;
251
- }
242
+ export interface CheckboxLabelProps extends LynxStyledElementProps {}
252
243
 
253
244
  export const CheckboxLabel = React.forwardRef<unknown, CheckboxLabelProps>((props, ref) => {
254
245
  const { children, className, ...nativeProps } = props;
255
- const { disabled, weight, size } = useCheckboxContext("CheckboxLabel");
256
- const labelClassName = checkbox({ weight, size, disabled }).label;
246
+ const context = useCheckboxContext("CheckboxLabel");
247
+ const labelClassName = checkbox({
248
+ ...context.checkboxVariantProps,
249
+ disabled: context.disabled,
250
+ }).label;
257
251
 
258
252
  return (
259
253
  <text
260
- {...(ref ? { ref: ref as React.Ref<SVGTextElement> } : {})}
254
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
261
255
  className={clsx(labelClassName, className)}
262
256
  {...nativeProps}
263
257
  >
@@ -269,10 +263,7 @@ CheckboxLabel.displayName = "CheckboxLabel";
269
263
 
270
264
  ////////////////////////////////////////////////////////////////////////////////////
271
265
 
272
- export interface CheckboxGroupProps {
273
- children?: React.ReactNode;
274
- className?: string;
275
- }
266
+ export interface CheckboxGroupProps extends LynxStyledElementProps {}
276
267
 
277
268
  export const CheckboxGroup = React.forwardRef<unknown, CheckboxGroupProps>((props, ref) => {
278
269
  const { children, className, ...nativeProps } = props;
@@ -280,8 +271,8 @@ export const CheckboxGroup = React.forwardRef<unknown, CheckboxGroupProps>((prop
280
271
 
281
272
  return (
282
273
  <view
283
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
284
- className={clsx(classes, className)}
274
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
275
+ className={clsx(classes.root, className)}
285
276
  {...nativeProps}
286
277
  >
287
278
  {children}
@@ -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";