@seed-design/lynx-react 0.2.0-alpha.3 → 0.3.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 (179) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +26 -33
  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 -31
  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 -29
  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 +5 -3
  84. package/lib/hooks/index.d.ts.map +1 -1
  85. package/lib/hooks/useControllableState.js +23 -23
  86. package/lib/hooks/useIconColor.d.ts +6 -3
  87. package/lib/hooks/useIconColor.d.ts.map +1 -1
  88. package/lib/hooks/useIconColor.js +34 -37
  89. package/lib/hooks/usePressTap.d.ts +11 -8
  90. package/lib/hooks/usePressTap.d.ts.map +1 -1
  91. package/lib/hooks/usePressTap.js +31 -31
  92. package/lib/hooks/useSafeArea.d.ts +19 -0
  93. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  94. package/lib/hooks/useSafeArea.js +33 -0
  95. package/lib/hooks/useSeedClassName.d.ts +27 -0
  96. package/lib/hooks/useSeedClassName.d.ts.map +1 -0
  97. package/lib/hooks/useSeedClassName.js +32 -0
  98. package/lib/index.d.ts +4 -3
  99. package/lib/index.d.ts.map +1 -1
  100. package/lib/index.js +30 -3
  101. package/lib/types.d.ts +51 -0
  102. package/lib/types.d.ts.map +1 -0
  103. package/lib/utils/children.d.ts +3 -0
  104. package/lib/utils/children.d.ts.map +1 -0
  105. package/lib/utils/children.js +8 -0
  106. package/lib/utils/create-slot-recipe-context.d.ts +13 -12
  107. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  108. package/lib/utils/create-slot-recipe-context.jsx +88 -70
  109. package/lib/utils/get-seed-class-name.d.ts +9 -7
  110. package/lib/utils/get-seed-class-name.d.ts.map +1 -1
  111. package/lib/utils/get-seed-class-name.js +29 -37
  112. package/lib/utils/index.d.ts +4 -4
  113. package/lib/utils/resolve-recipe-token.js +45 -43
  114. package/lib/utils/split-multiple-variants-props.js +32 -32
  115. package/lib/utils/styled.d.ts +83 -0
  116. package/lib/utils/styled.d.ts.map +1 -0
  117. package/lib/utils/styled.js +144 -0
  118. package/package.json +10 -9
  119. package/src/components/ActionButton/ActionButton.test.tsx +167 -0
  120. package/src/components/ActionButton/ActionButton.tsx +210 -169
  121. package/src/components/AppBar/AppBar.namespace.ts +18 -0
  122. package/src/components/AppBar/AppBar.test.tsx +270 -0
  123. package/src/components/AppBar/AppBar.tsx +288 -0
  124. package/src/components/AppBar/context.ts +27 -0
  125. package/src/components/AppBar/index.ts +21 -0
  126. package/src/components/AppBar/useAppBar.ts +100 -0
  127. package/src/components/Badge/Badge.test.tsx +54 -0
  128. package/src/components/Badge/Badge.tsx +32 -0
  129. package/src/components/Badge/index.ts +1 -0
  130. package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
  131. package/src/components/BottomSheet/BottomSheet.tsx +120 -83
  132. package/src/components/Box/Box.test.tsx +85 -0
  133. package/src/components/Box/Box.tsx +39 -0
  134. package/src/components/Box/index.ts +1 -0
  135. package/src/components/Checkbox/Checkbox.tsx +106 -116
  136. package/src/components/Icon/Icon.tsx +265 -0
  137. package/src/components/Icon/index.ts +8 -0
  138. package/src/components/ProgressCircle/ProgressCircle.tsx +39 -25
  139. package/src/components/RadioGroup/RadioGroup.tsx +98 -117
  140. package/src/components/Stack/Stack.tsx +97 -0
  141. package/src/components/Stack/index.ts +1 -0
  142. package/src/components/Switch/Switch.tsx +59 -50
  143. package/src/components/TagGroup/TagGroup.tsx +27 -25
  144. package/src/components/Text/Text.tsx +64 -0
  145. package/src/components/Text/index.ts +1 -0
  146. package/src/components/index.ts +6 -0
  147. package/src/hooks/index.ts +2 -0
  148. package/src/hooks/{__tests__/useControllableState.test.ts → useControllableState.test.ts} +1 -1
  149. package/src/hooks/{__tests__/useIconColor.test.ts → useIconColor.test.ts} +1 -1
  150. package/src/hooks/useIconColor.ts +22 -12
  151. package/src/hooks/{__tests__/usePressTap.test.ts → usePressTap.test.ts} +1 -1
  152. package/src/hooks/usePressTap.ts +14 -10
  153. package/src/hooks/useSafeArea.test.ts +83 -0
  154. package/src/hooks/useSafeArea.ts +56 -0
  155. package/src/hooks/useSeedClassName.ts +35 -0
  156. package/src/index.ts +1 -0
  157. package/src/types.ts +76 -0
  158. package/src/utils/children.ts +13 -0
  159. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  160. package/src/utils/create-slot-recipe-context.tsx +55 -15
  161. package/src/utils/get-seed-class-name.ts +10 -12
  162. package/src/utils/{__tests__/split-multiple-variants-props.test.ts → split-multiple-variants-props.test.ts} +1 -1
  163. package/src/utils/styled.ts +442 -0
  164. package/lib/components/ActionButton/index.js +0 -1
  165. package/lib/components/BottomSheet/index.js +0 -2
  166. package/lib/components/Checkbox/index.js +0 -2
  167. package/lib/components/ProgressCircle/index.js +0 -2
  168. package/lib/components/RadioGroup/index.js +0 -2
  169. package/lib/components/Switch/index.js +0 -2
  170. package/lib/components/TagGroup/index.js +0 -2
  171. package/lib/components/index.js +0 -7
  172. package/lib/hooks/index.js +0 -3
  173. package/lib/utils/index.js +0 -4
  174. package/src/components/ActionButton/__tests__/ActionButton.test.tsx +0 -35
  175. package/src/components/BottomSheet/__tests__/BottomSheet.test.tsx +0 -119
  176. package/src/components/__tests__/ProgressCircle.test.ts +0 -33
  177. package/src/components/__tests__/exports.test.ts +0 -38
  178. package/src/components/__tests__/switch.test.ts +0 -34
  179. package/src/utils/__tests__/create-slot-recipe-context.test.tsx +0 -228
@@ -1,4 +1,4 @@
1
- import * as React from "react";
1
+ import * as React from "@lynx-js/react";
2
2
  import clsx from "clsx";
3
3
 
4
4
  import { switchStyle } from "@seed-design/lynx-css/recipes/switch";
@@ -9,6 +9,7 @@ import type { SwitchmarkVariantProps } from "@seed-design/lynx-css/recipes/switc
9
9
  import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
10
10
  import { useControllableState } from "../../hooks/useControllableState";
11
11
  import { usePressTap } from "../../hooks/usePressTap";
12
+ import type { LynxStyledElementProps, LynxTextRef, LynxViewRef } from "../../types";
12
13
 
13
14
  /**
14
15
  * @platform Lynx
@@ -23,14 +24,12 @@ import { usePressTap } from "../../hooks/usePressTap";
23
24
  * switchmark recipe 와 Switch 컴포넌트에 boolean variant 로 노출.
24
25
  */
25
26
 
26
- type SwitchSize = NonNullable<SwitchVariantProps["size"]>;
27
- type SwitchTone = NonNullable<SwitchmarkVariantProps["tone"]>;
28
-
29
27
  interface SwitchContextValue {
30
28
  checked: boolean;
31
29
  disabled: boolean;
32
- size: SwitchSize;
33
- tone: SwitchTone;
30
+ switchVariantProps: SwitchVariantProps;
31
+ switchmarkVariantProps: SwitchmarkVariantProps;
32
+ toggle: () => void;
34
33
  }
35
34
 
36
35
  const SwitchContext = React.createContext<SwitchContextValue | null>(null);
@@ -43,10 +42,15 @@ function useSwitchContext(consumer: string): SwitchContextValue {
43
42
  return ctx;
44
43
  }
45
44
 
46
- const SwitchmarkThumbClassContext = React.createContext<string | null>(null);
45
+ interface SwitchmarkControlContextValue {
46
+ thumbClassName: string;
47
+ switchmarkVariantProps: SwitchmarkVariantProps;
48
+ }
49
+
50
+ const SwitchmarkControlContext = React.createContext<SwitchmarkControlContextValue | null>(null);
47
51
 
48
- function useSwitchmarkThumbClass(consumer: string): string {
49
- const ctx = React.useContext(SwitchmarkThumbClassContext);
52
+ function useSwitchmarkControlContext(consumer: string): SwitchmarkControlContextValue {
53
+ const ctx = React.useContext(SwitchmarkControlContext);
50
54
  if (!ctx) {
51
55
  throw new Error(`<${consumer}/> must be rendered inside <SwitchControl/>.`);
52
56
  }
@@ -55,9 +59,10 @@ function useSwitchmarkThumbClass(consumer: string): string {
55
59
 
56
60
  ////////////////////////////////////////////////////////////////////////////////////
57
61
 
58
- export interface SwitchRootProps extends SwitchVariantProps, Omit<SwitchmarkVariantProps, "size"> {
59
- children?: React.ReactNode;
60
- className?: string;
62
+ export interface SwitchRootProps
63
+ extends SwitchVariantProps,
64
+ Omit<SwitchmarkVariantProps, "size">,
65
+ LynxStyledElementProps {
61
66
  checked?: boolean;
62
67
  defaultChecked?: boolean;
63
68
  disabled?: boolean;
@@ -76,8 +81,6 @@ export const SwitchRoot = React.forwardRef<unknown, SwitchRootProps>((props, ref
76
81
  } = props;
77
82
  const [{ switch: switchVariantProps, switchmark: switchmarkVariantProps }, nativeProps] =
78
83
  splitMultipleVariantsProps(restProps, { switch: switchStyle, switchmark });
79
- const size: SwitchSize = switchVariantProps.size ?? "32";
80
- const tone: SwitchTone = switchmarkVariantProps.tone ?? "brand";
81
84
 
82
85
  const [checked, setChecked] = useControllableState({
83
86
  value: checkedProp,
@@ -85,22 +88,30 @@ export const SwitchRoot = React.forwardRef<unknown, SwitchRootProps>((props, ref
85
88
  onChange: onCheckedChange,
86
89
  });
87
90
 
91
+ const toggle = React.useCallback(() => setChecked(!checked), [checked, setChecked]);
92
+
88
93
  const { pressed: _pressed, ...pressHandlers } = usePressTap({
89
94
  disabled,
90
- onTap: () => setChecked(!checked),
95
+ onTap: toggle,
91
96
  });
92
97
 
93
- const rootClassName = switchStyle({ size }).root;
94
-
95
- const ctx = React.useMemo<SwitchContextValue>(
96
- () => ({ checked, disabled, size, tone }),
97
- [checked, disabled, size, tone],
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],
98
109
  );
99
110
 
100
111
  return (
101
- <SwitchContext.Provider value={ctx}>
112
+ <SwitchContext.Provider value={contextValue}>
102
113
  <view
103
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
114
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
104
115
  className={clsx(rootClassName, className)}
105
116
  {...pressHandlers}
106
117
  {...nativeProps}
@@ -114,51 +125,49 @@ SwitchRoot.displayName = "SwitchRoot";
114
125
 
115
126
  ////////////////////////////////////////////////////////////////////////////////////
116
127
 
117
- export interface SwitchControlProps extends Pick<SwitchmarkVariantProps, "tone" | "size"> {
118
- children?: React.ReactNode;
119
- className?: string;
120
- }
128
+ export interface SwitchControlProps
129
+ extends Pick<SwitchmarkVariantProps, "tone" | "size">,
130
+ LynxStyledElementProps {}
121
131
 
122
132
  export const SwitchControl = React.forwardRef<unknown, SwitchControlProps>((props, ref) => {
123
133
  const [variantProps, restProps] = switchmark.splitVariantProps(props);
124
134
  const { children, className, ...nativeProps } = restProps;
125
- const { checked, disabled, tone: ctxTone, size: ctxSize } = useSwitchContext("SwitchControl");
126
-
127
- const tone: SwitchTone = variantProps.tone ?? ctxTone;
128
- const size: SwitchSize = variantProps.size ?? ctxSize;
129
-
130
- const classes = React.useMemo(
131
- () => switchmark({ tone, size, checked, disabled }),
132
- [tone, size, checked, disabled],
133
- );
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);
134
143
 
135
144
  return (
136
- <SwitchmarkThumbClassContext.Provider value={classes.thumb}>
145
+ <SwitchmarkControlContext.Provider
146
+ value={{ thumbClassName: classes.thumb, switchmarkVariantProps }}
147
+ >
137
148
  <view
138
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
149
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
139
150
  className={clsx(classes.root, className)}
140
151
  {...nativeProps}
141
152
  >
142
153
  {children}
143
154
  </view>
144
- </SwitchmarkThumbClassContext.Provider>
155
+ </SwitchmarkControlContext.Provider>
145
156
  );
146
157
  });
147
158
  SwitchControl.displayName = "SwitchControl";
148
159
 
149
160
  ////////////////////////////////////////////////////////////////////////////////////
150
161
 
151
- export interface SwitchThumbProps {
152
- className?: string;
153
- }
162
+ export interface SwitchThumbProps extends Pick<LynxStyledElementProps, "className" | "style"> {}
154
163
 
155
164
  export const SwitchThumb = React.forwardRef<unknown, SwitchThumbProps>((props, ref) => {
156
165
  const { className, ...nativeProps } = props;
157
- const thumbClassName = useSwitchmarkThumbClass("SwitchThumb");
166
+ const { thumbClassName } = useSwitchmarkControlContext("SwitchThumb");
158
167
 
159
168
  return (
160
169
  <view
161
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
170
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
162
171
  className={clsx(thumbClassName, className)}
163
172
  {...nativeProps}
164
173
  />
@@ -168,19 +177,19 @@ SwitchThumb.displayName = "SwitchThumb";
168
177
 
169
178
  ////////////////////////////////////////////////////////////////////////////////////
170
179
 
171
- export interface SwitchLabelProps {
172
- children?: React.ReactNode;
173
- className?: string;
174
- }
180
+ export interface SwitchLabelProps extends LynxStyledElementProps {}
175
181
 
176
182
  export const SwitchLabel = React.forwardRef<unknown, SwitchLabelProps>((props, ref) => {
177
183
  const { children, className, ...nativeProps } = props;
178
- const { disabled, size } = useSwitchContext("SwitchLabel");
179
- const labelClassName = switchStyle({ size, disabled }).label;
184
+ const context = useSwitchContext("SwitchLabel");
185
+ const labelClassName = switchStyle({
186
+ ...context.switchVariantProps,
187
+ disabled: context.disabled,
188
+ }).label;
180
189
 
181
190
  return (
182
191
  <text
183
- {...(ref ? { ref: ref as React.Ref<SVGTextElement> } : {})}
192
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
184
193
  className={clsx(labelClassName, className)}
185
194
  {...nativeProps}
186
195
  >
@@ -1,4 +1,4 @@
1
- import * as React from "react";
1
+ import * as React from "@lynx-js/react";
2
2
  import clsx from "clsx";
3
3
  import { tagGroup, type TagGroupVariantProps } from "@seed-design/lynx-css/recipes/tag-group";
4
4
  import {
@@ -6,12 +6,17 @@ import {
6
6
  type TagGroupItemVariantProps,
7
7
  } from "@seed-design/lynx-css/recipes/tag-group-item";
8
8
 
9
+ import type { LynxStyledElementProps, LynxTextRef, LynxViewRef } from "../../types";
10
+ import { toArray } from "../../utils/children";
9
11
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
10
12
  import { splitMultipleVariantsProps } from "../../utils/split-multiple-variants-props";
13
+ import { useStyleProps, type StyleProps } from "../../utils/styled";
11
14
 
12
15
  const { PropsProvider, useProps, ClassNamesProvider, useClassNames } =
13
16
  createSlotRecipeContext(tagGroupItem);
14
17
 
18
+ ////////////////////////////////////////////////////////////////////////////////////
19
+
15
20
  /**
16
21
  * @platform Lynx
17
22
  *
@@ -19,12 +24,12 @@ const { PropsProvider, useProps, ClassNamesProvider, useClassNames } =
19
24
  * - `truncate` prop: Lynx flex 모델에서는 item 단위 wrap만 가능해 웹 수준의
20
25
  * "한 줄 유지 + label ellipsis" 조합을 재현할 수 없음
21
26
  * - `asChild` prop: Lynx Slot 미지원
22
- * - `flexShrink` prop: CSS variable 동적 주입 제한
23
27
  * - 아이콘 slot: Lynx 3.7 SVG 지원 후 검토 (Tier B)
24
28
  */
25
- export interface TagGroupRootProps extends TagGroupVariantProps, TagGroupItemVariantProps {
26
- children?: React.ReactNode;
27
- className?: string;
29
+ export interface TagGroupRootProps
30
+ extends TagGroupVariantProps,
31
+ TagGroupItemVariantProps,
32
+ LynxStyledElementProps {
28
33
  /**
29
34
  * children 사이에 삽입되는 구분자. 기본값 `" · "`.
30
35
  */
@@ -37,12 +42,12 @@ export const TagGroupRoot = React.forwardRef<unknown, TagGroupRootProps>((props,
37
42
  const { children, className, separator = " · ", ...nativeProps } = otherProps;
38
43
  const classes = tagGroup(tagGroupVariantProps);
39
44
 
40
- const visibleChildren = React.Children.toArray(children);
45
+ const visibleChildren = toArray(children);
41
46
 
42
47
  return (
43
48
  <PropsProvider value={tagGroupItemVariantProps}>
44
49
  <view
45
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
50
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
46
51
  className={clsx(classes.root, className)}
47
52
  {...nativeProps}
48
53
  >
@@ -54,12 +59,7 @@ export const TagGroupRoot = React.forwardRef<unknown, TagGroupRootProps>((props,
54
59
  return (
55
60
  <view
56
61
  key={(child as React.ReactElement).key ?? index}
57
- style={{
58
- display: "flex",
59
- flexDirection: "row",
60
- alignItems: "center",
61
- flexShrink: 0,
62
- }}
62
+ className={classes.separatorWrapper}
63
63
  >
64
64
  <text className={classes.separator}>{separator}</text>
65
65
  {child}
@@ -72,29 +72,32 @@ export const TagGroupRoot = React.forwardRef<unknown, TagGroupRootProps>((props,
72
72
  });
73
73
  TagGroupRoot.displayName = "TagGroupRoot";
74
74
 
75
+ ////////////////////////////////////////////////////////////////////////////////////
76
+
75
77
  /**
76
78
  * @platform Lynx
77
79
  *
78
80
  * 웹 대비 미지원 기능:
79
81
  * - `asChild` prop: Lynx Slot 미지원
80
- * - `flexShrink` prop: CSS variable 동적 주입 제한
81
82
  */
82
- export interface TagGroupItemProps extends TagGroupItemVariantProps {
83
- children?: React.ReactNode;
84
- className?: string;
85
- }
83
+ export interface TagGroupItemProps
84
+ extends TagGroupItemVariantProps,
85
+ Pick<StyleProps, "flexShrink">,
86
+ LynxStyledElementProps {}
86
87
 
87
88
  export const TagGroupItem = React.forwardRef<unknown, TagGroupItemProps>((props, ref) => {
88
89
  const parentVariantProps = useProps();
89
90
  const [localVariantProps, otherProps] = tagGroupItem.splitVariantProps(props);
90
91
  const classes = tagGroupItem({ ...parentVariantProps, ...localVariantProps });
91
- const { children, className, ...nativeProps } = otherProps;
92
+ const { style, restProps } = useStyleProps(otherProps);
93
+ const { children, className, ...nativeProps } = restProps;
92
94
 
93
95
  return (
94
96
  <ClassNamesProvider value={classes}>
95
97
  <view
96
- {...(ref ? { ref: ref as React.Ref<SVGViewElement> } : {})}
98
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
97
99
  className={clsx(classes.root, className)}
100
+ style={style}
98
101
  {...nativeProps}
99
102
  >
100
103
  {children}
@@ -104,17 +107,16 @@ export const TagGroupItem = React.forwardRef<unknown, TagGroupItemProps>((props,
104
107
  });
105
108
  TagGroupItem.displayName = "TagGroupItem";
106
109
 
107
- export interface TagGroupItemLabelProps {
108
- children?: React.ReactNode;
109
- className?: string;
110
- }
110
+ ////////////////////////////////////////////////////////////////////////////////////
111
+
112
+ export interface TagGroupItemLabelProps extends LynxStyledElementProps {}
111
113
 
112
114
  export const TagGroupItemLabel = React.forwardRef<unknown, TagGroupItemLabelProps>((props, ref) => {
113
115
  const classes = useClassNames();
114
116
  const { children, className, ...nativeProps } = props;
115
117
  return (
116
118
  <text
117
- {...(ref ? { ref: ref as React.Ref<SVGTextElement> } : {})}
119
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
118
120
  className={clsx(classes.label, className)}
119
121
  {...nativeProps}
120
122
  >
@@ -0,0 +1,64 @@
1
+ import { typography } from "@seed-design/lynx-css/vars/component";
2
+ import type { CSSProperties } from "@lynx-js/types";
3
+ import clsx from "clsx";
4
+ import * as React from "@lynx-js/react";
5
+
6
+ import {
7
+ handleColor,
8
+ handleFontSize,
9
+ handleFontWeight,
10
+ handleLineHeight,
11
+ type TextStyle,
12
+ type TextStyleProps,
13
+ } from "../../utils/styled";
14
+ import type { LynxStyledElementProps, LynxTextRef } from "../../types";
15
+
16
+ function capitalize<T extends string>(value: T): Capitalize<T> {
17
+ return (value.charAt(0).toUpperCase() + value.slice(1)) as Capitalize<T>;
18
+ }
19
+
20
+ function getTypographyStyle(textStyle: TextStyle | undefined) {
21
+ const key = `textStyle${capitalize(textStyle ?? "t5Regular")}`;
22
+ const value = typography[key as keyof typeof typography];
23
+ return value.enabled.root;
24
+ }
25
+
26
+ export interface TextProps extends TextStyleProps, LynxStyledElementProps {}
27
+
28
+ export const Text = React.forwardRef<unknown, TextProps>((props, ref) => {
29
+ const {
30
+ color,
31
+ textStyle,
32
+ fontSize,
33
+ lineHeight,
34
+ fontWeight,
35
+ align,
36
+ children,
37
+ className,
38
+ style,
39
+ ...nativeProps
40
+ } = props;
41
+ const typographyStyle = getTypographyStyle(textStyle);
42
+
43
+ return (
44
+ <text
45
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
46
+ {...nativeProps}
47
+ className={clsx(className)}
48
+ style={
49
+ {
50
+ color: handleColor(color),
51
+ fontSize: handleFontSize(fontSize) ?? typographyStyle.fontSize,
52
+ lineHeight: handleLineHeight(lineHeight ?? fontSize) ?? typographyStyle.lineHeight,
53
+ fontWeight: handleFontWeight(fontWeight) ?? typographyStyle.fontWeight,
54
+ textAlign: align,
55
+ ...style,
56
+ } as CSSProperties
57
+ }
58
+ >
59
+ {children}
60
+ </text>
61
+ );
62
+ });
63
+
64
+ Text.displayName = "Text";
@@ -0,0 +1 @@
1
+ export { Text, type TextProps } from "./Text";
@@ -1,7 +1,13 @@
1
1
  export * from "./ActionButton";
2
+ export * from "./AppBar";
3
+ export * from "./Badge";
4
+ export * from "./Box";
2
5
  export * from "./BottomSheet";
3
6
  export * from "./Checkbox";
7
+ export * from "./Icon";
4
8
  export * from "./ProgressCircle";
5
9
  export * from "./RadioGroup";
10
+ export * from "./Stack";
6
11
  export * from "./Switch";
7
12
  export * from "./TagGroup";
13
+ export * from "./Text";
@@ -1,3 +1,5 @@
1
1
  export * from "./useControllableState";
2
2
  export * from "./useIconColor";
3
3
  export * from "./usePressTap";
4
+ export * from "./useSafeArea";
5
+ export * from "./useSeedClassName";
@@ -1,6 +1,6 @@
1
1
  import { act, renderHook } from "@lynx-js/react/testing-library";
2
2
  import { describe, expect, it, vi } from "vitest";
3
- import { useControllableState } from "../useControllableState";
3
+ import { useControllableState } from "./useControllableState";
4
4
 
5
5
  describe("useControllableState", () => {
6
6
  describe("uncontrolled mode", () => {
@@ -1,7 +1,7 @@
1
1
  import { renderHook } from "@lynx-js/react/testing-library";
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
- import { useIconColor } from "../useIconColor";
4
+ import { useIconColor } from "./useIconColor";
5
5
 
6
6
  describe("useIconColor", () => {
7
7
  it("returns icon tint sync props", () => {
@@ -1,30 +1,36 @@
1
1
  import { runOnMainThread, useEffect, useMainThreadRef } from "@lynx-js/react";
2
2
  import type { MainThread } from "@lynx-js/types";
3
- import type { DependencyList, RefObject } from "react";
3
+ import type { DependencyList, RefObject } from "@lynx-js/react";
4
4
 
5
5
  type IconElement = MainThread.Element & {
6
6
  getComputedStyleProperty?: (name: string) => string;
7
7
  getComputedCssProperty?: (name: string) => string;
8
8
  };
9
9
 
10
+ export interface UseIconColorOptions {
11
+ sourceRef?: RefObject<MainThread.Element>;
12
+ }
13
+
10
14
  // Lynx `<image>` 의 `tint-color` attribute 에 CSS variable 문자열을 직접 넣는 경로는
11
15
  // 안정적으로 동작하지 않는다. CSS `color` 는 computed style 로 resolved color 를 읽을 수
12
16
  // 있으므로 main-thread 에서 한 번 읽어 `tint-color` 로 mirror 한다.
13
- function syncTintColor(ref: RefObject<IconElement>) {
17
+ function syncTintColor(targetRef: RefObject<IconElement>, sourceRef?: RefObject<IconElement>) {
14
18
  "main thread";
15
19
 
16
- const el = ref.current;
17
- if (!el) return;
20
+ const target = targetRef.current;
21
+ if (!target) return;
22
+
23
+ const source = sourceRef?.current ?? target;
18
24
 
19
25
  let color: string | undefined;
20
- if (typeof el.getComputedStyleProperty === "function") {
21
- color = el.getComputedStyleProperty("color");
22
- } else if (typeof el.getComputedCssProperty === "function") {
23
- color = el.getComputedCssProperty("color");
26
+ if (typeof source.getComputedStyleProperty === "function") {
27
+ color = source.getComputedStyleProperty("color");
28
+ } else if (typeof source.getComputedCssProperty === "function") {
29
+ color = source.getComputedCssProperty("color");
24
30
  }
25
31
 
26
32
  if (color) {
27
- el.setAttribute("tint-color", color);
33
+ target.setAttribute("tint-color", color);
28
34
  }
29
35
  }
30
36
 
@@ -37,20 +43,24 @@ function syncTintColor(ref: RefObject<IconElement>) {
37
43
  * return cloneElement(iconChild, iconColorProps);
38
44
  * ```
39
45
  */
40
- export function useIconColor(deps: DependencyList): {
46
+ export function useIconColor(
47
+ deps: DependencyList,
48
+ options?: UseIconColorOptions,
49
+ ): {
41
50
  ref: RefObject<MainThread.Element>;
42
51
  "main-thread:binduiappear": () => void;
43
52
  } {
44
53
  const ref = useMainThreadRef<IconElement>(null);
54
+ const sourceRef = options?.sourceRef as RefObject<IconElement> | undefined;
45
55
 
46
56
  function syncOnUiAppear() {
47
57
  "main thread";
48
- syncTintColor(ref);
58
+ syncTintColor(ref, sourceRef);
49
59
  }
50
60
 
51
61
  // deps 는 caller 가 책임.
52
62
  useEffect(() => {
53
- runOnMainThread(syncTintColor)(ref);
63
+ runOnMainThread(syncTintColor)(ref, sourceRef);
54
64
  }, deps);
55
65
 
56
66
  return { ref, "main-thread:binduiappear": syncOnUiAppear };
@@ -1,6 +1,6 @@
1
1
  import { act, renderHook } from "@lynx-js/react/testing-library";
2
2
  import { describe, expect, it, vi } from "vitest";
3
- import { usePressTap } from "../usePressTap";
3
+ import { usePressTap } from "./usePressTap";
4
4
 
5
5
  const fakeEvent = {} as Parameters<ReturnType<typeof usePressTap>["bindtap"]>[0];
6
6
 
@@ -1,20 +1,24 @@
1
1
  import { useState } from "@lynx-js/react";
2
- import type { TouchEvent } from "@lynx-js/types";
3
2
  import { useMemoizedFn } from "@lynx-js/lynx-ui-common";
3
+ import type { BaseTouchEvent, EventHandler, Target } from "@lynx-js/types";
4
+
5
+ import type { LynxTouchProps } from "../types";
6
+
7
+ type TouchHandler = EventHandler<BaseTouchEvent<Target>>;
4
8
 
5
9
  export interface UsePressTapOptions {
6
10
  disabled?: boolean;
7
- onTap?: () => void;
8
- mainThreadOnTap?: () => void;
11
+ onTap?: TouchHandler;
12
+ mainThreadOnTap?: LynxTouchProps["main-thread:bindtap"];
9
13
  }
10
14
 
11
15
  export interface UsePressTapReturn {
12
16
  pressed: boolean;
13
- bindtap: (e: TouchEvent) => void;
14
- bindtouchstart: (e: TouchEvent) => void;
15
- bindtouchend: (e: TouchEvent) => void;
16
- bindtouchcancel: (e: TouchEvent) => void;
17
- "main-thread:bindtap"?: () => void;
17
+ bindtap: TouchHandler;
18
+ bindtouchstart: TouchHandler;
19
+ bindtouchend: TouchHandler;
20
+ bindtouchcancel: TouchHandler;
21
+ "main-thread:bindtap"?: LynxTouchProps["main-thread:bindtap"];
18
22
  }
19
23
 
20
24
  /**
@@ -38,9 +42,9 @@ export function usePressTap(options: UsePressTapOptions = {}): UsePressTapReturn
38
42
  setPressed(false);
39
43
  });
40
44
 
41
- const handleTap = useMemoizedFn(() => {
45
+ const handleTap = useMemoizedFn((...args: Parameters<TouchHandler>) => {
42
46
  if (disabled) return;
43
- onTap?.();
47
+ onTap?.(...args);
44
48
  });
45
49
 
46
50
  return {
@@ -0,0 +1,83 @@
1
+ import { renderHook } from "@lynx-js/react/testing-library";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { useSafeArea } from "./useSafeArea";
5
+
6
+ interface TestLynxGlobal {
7
+ lynx?: {
8
+ __globalProps?: {
9
+ safeAreaInsetTop?: number;
10
+ safeAreaInsetBottom?: number;
11
+ };
12
+ };
13
+ lynxTestingEnv?: {
14
+ backgroundThread: {
15
+ globalThis: TestLynxGlobal;
16
+ };
17
+ mainThread: {
18
+ globalThis: TestLynxGlobal;
19
+ };
20
+ };
21
+ }
22
+
23
+ function setGlobalProps(
24
+ globalProps: NonNullable<NonNullable<TestLynxGlobal["lynx"]>["__globalProps"]>,
25
+ ) {
26
+ const lynxTestingEnv = (globalThis as TestLynxGlobal).lynxTestingEnv;
27
+
28
+ if (!lynxTestingEnv) {
29
+ throw new Error("Expected Lynx testing environment globals to be available.");
30
+ }
31
+
32
+ const globals = [
33
+ globalThis as TestLynxGlobal,
34
+ lynxTestingEnv.backgroundThread.globalThis,
35
+ lynxTestingEnv.mainThread.globalThis,
36
+ ];
37
+
38
+ for (const global of globals) {
39
+ global.lynx = {
40
+ ...global.lynx,
41
+ __globalProps: globalProps,
42
+ };
43
+ }
44
+ }
45
+
46
+ describe("useSafeArea", () => {
47
+ it("falls back to Lynx env values when host flat global props are missing", () => {
48
+ const { result } = renderHook(() => useSafeArea());
49
+
50
+ expect(result.current).toEqual({
51
+ safeAreaInsetTop: "env(safe-area-inset-top)",
52
+ safeAreaInsetBottom: "env(safe-area-inset-bottom)",
53
+ });
54
+ });
55
+
56
+ it("uses flat globalProps values before env fallback", () => {
57
+ setGlobalProps({
58
+ safeAreaInsetTop: 48,
59
+ safeAreaInsetBottom: 35,
60
+ });
61
+
62
+ const { result } = renderHook(() => useSafeArea());
63
+
64
+ expect(result.current).toEqual({
65
+ safeAreaInsetTop: "48px",
66
+ safeAreaInsetBottom: "35px",
67
+ });
68
+ });
69
+
70
+ it("falls back to Lynx env values when host flat global props are zero", () => {
71
+ setGlobalProps({
72
+ safeAreaInsetTop: 0,
73
+ safeAreaInsetBottom: 0,
74
+ });
75
+
76
+ const { result } = renderHook(() => useSafeArea());
77
+
78
+ expect(result.current).toEqual({
79
+ safeAreaInsetTop: "env(safe-area-inset-top)",
80
+ safeAreaInsetBottom: "env(safe-area-inset-bottom)",
81
+ });
82
+ });
83
+ });