@designbasekorea/ui-native 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 (237) hide show
  1. package/README.md +152 -0
  2. package/dist/components/Alert/Alert.d.ts +29 -0
  3. package/dist/components/Alert/Alert.d.ts.map +1 -0
  4. package/dist/components/Alert/Alert.js +44 -0
  5. package/dist/components/Alert/Alert.js.map +1 -0
  6. package/dist/components/Alert/index.d.ts +3 -0
  7. package/dist/components/Alert/index.d.ts.map +1 -0
  8. package/dist/components/Alert/index.js +2 -0
  9. package/dist/components/Alert/index.js.map +1 -0
  10. package/dist/components/Avatar/Avatar.d.ts +28 -0
  11. package/dist/components/Avatar/Avatar.d.ts.map +1 -0
  12. package/dist/components/Avatar/Avatar.js +73 -0
  13. package/dist/components/Avatar/Avatar.js.map +1 -0
  14. package/dist/components/Avatar/index.d.ts +3 -0
  15. package/dist/components/Avatar/index.d.ts.map +1 -0
  16. package/dist/components/Avatar/index.js +2 -0
  17. package/dist/components/Avatar/index.js.map +1 -0
  18. package/dist/components/Badge/Badge.d.ts +28 -0
  19. package/dist/components/Badge/Badge.d.ts.map +1 -0
  20. package/dist/components/Badge/Badge.js +52 -0
  21. package/dist/components/Badge/Badge.js.map +1 -0
  22. package/dist/components/Badge/index.d.ts +3 -0
  23. package/dist/components/Badge/index.d.ts.map +1 -0
  24. package/dist/components/Badge/index.js +2 -0
  25. package/dist/components/Badge/index.js.map +1 -0
  26. package/dist/components/BottomNavigation/BottomNavigation.d.ts +30 -0
  27. package/dist/components/BottomNavigation/BottomNavigation.d.ts.map +1 -0
  28. package/dist/components/BottomNavigation/BottomNavigation.js +48 -0
  29. package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -0
  30. package/dist/components/BottomNavigation/index.d.ts +3 -0
  31. package/dist/components/BottomNavigation/index.d.ts.map +1 -0
  32. package/dist/components/BottomNavigation/index.js +2 -0
  33. package/dist/components/BottomNavigation/index.js.map +1 -0
  34. package/dist/components/BottomSheet/BottomSheet.d.ts +26 -0
  35. package/dist/components/BottomSheet/BottomSheet.d.ts.map +1 -0
  36. package/dist/components/BottomSheet/BottomSheet.js +75 -0
  37. package/dist/components/BottomSheet/BottomSheet.js.map +1 -0
  38. package/dist/components/BottomSheet/index.d.ts +3 -0
  39. package/dist/components/BottomSheet/index.d.ts.map +1 -0
  40. package/dist/components/BottomSheet/index.js +2 -0
  41. package/dist/components/BottomSheet/index.js.map +1 -0
  42. package/dist/components/Button/Button.d.ts +53 -0
  43. package/dist/components/Button/Button.d.ts.map +1 -0
  44. package/dist/components/Button/Button.js +129 -0
  45. package/dist/components/Button/Button.js.map +1 -0
  46. package/dist/components/Button/index.d.ts +3 -0
  47. package/dist/components/Button/index.d.ts.map +1 -0
  48. package/dist/components/Button/index.js +2 -0
  49. package/dist/components/Button/index.js.map +1 -0
  50. package/dist/components/Card/Card.d.ts +36 -0
  51. package/dist/components/Card/Card.d.ts.map +1 -0
  52. package/dist/components/Card/Card.js +61 -0
  53. package/dist/components/Card/Card.js.map +1 -0
  54. package/dist/components/Card/index.d.ts +3 -0
  55. package/dist/components/Card/index.d.ts.map +1 -0
  56. package/dist/components/Card/index.js +2 -0
  57. package/dist/components/Card/index.js.map +1 -0
  58. package/dist/components/Checkbox/Checkbox.d.ts +26 -0
  59. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
  60. package/dist/components/Checkbox/Checkbox.js +57 -0
  61. package/dist/components/Checkbox/Checkbox.js.map +1 -0
  62. package/dist/components/Checkbox/index.d.ts +3 -0
  63. package/dist/components/Checkbox/index.d.ts.map +1 -0
  64. package/dist/components/Checkbox/index.js +2 -0
  65. package/dist/components/Checkbox/index.js.map +1 -0
  66. package/dist/components/Chip/Chip.d.ts +29 -0
  67. package/dist/components/Chip/Chip.d.ts.map +1 -0
  68. package/dist/components/Chip/Chip.js +54 -0
  69. package/dist/components/Chip/Chip.js.map +1 -0
  70. package/dist/components/Chip/index.d.ts +3 -0
  71. package/dist/components/Chip/index.d.ts.map +1 -0
  72. package/dist/components/Chip/index.js +2 -0
  73. package/dist/components/Chip/index.js.map +1 -0
  74. package/dist/components/Divider/Divider.d.ts +22 -0
  75. package/dist/components/Divider/Divider.d.ts.map +1 -0
  76. package/dist/components/Divider/Divider.js +16 -0
  77. package/dist/components/Divider/Divider.js.map +1 -0
  78. package/dist/components/Divider/index.d.ts +3 -0
  79. package/dist/components/Divider/index.d.ts.map +1 -0
  80. package/dist/components/Divider/index.js +2 -0
  81. package/dist/components/Divider/index.js.map +1 -0
  82. package/dist/components/Input/Input.d.ts +33 -0
  83. package/dist/components/Input/Input.d.ts.map +1 -0
  84. package/dist/components/Input/Input.js +98 -0
  85. package/dist/components/Input/Input.js.map +1 -0
  86. package/dist/components/Input/index.d.ts +3 -0
  87. package/dist/components/Input/index.d.ts.map +1 -0
  88. package/dist/components/Input/index.js +2 -0
  89. package/dist/components/Input/index.js.map +1 -0
  90. package/dist/components/Modal/Modal.d.ts +28 -0
  91. package/dist/components/Modal/Modal.d.ts.map +1 -0
  92. package/dist/components/Modal/Modal.js +46 -0
  93. package/dist/components/Modal/Modal.js.map +1 -0
  94. package/dist/components/Modal/index.d.ts +3 -0
  95. package/dist/components/Modal/index.d.ts.map +1 -0
  96. package/dist/components/Modal/index.js +2 -0
  97. package/dist/components/Modal/index.js.map +1 -0
  98. package/dist/components/Navbar/Navbar.d.ts +50 -0
  99. package/dist/components/Navbar/Navbar.d.ts.map +1 -0
  100. package/dist/components/Navbar/Navbar.js +125 -0
  101. package/dist/components/Navbar/Navbar.js.map +1 -0
  102. package/dist/components/Navbar/index.d.ts +3 -0
  103. package/dist/components/Navbar/index.d.ts.map +1 -0
  104. package/dist/components/Navbar/index.js +2 -0
  105. package/dist/components/Navbar/index.js.map +1 -0
  106. package/dist/components/ProgressBar/ProgressBar.d.ts +27 -0
  107. package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -0
  108. package/dist/components/ProgressBar/ProgressBar.js +55 -0
  109. package/dist/components/ProgressBar/ProgressBar.js.map +1 -0
  110. package/dist/components/ProgressBar/index.d.ts +3 -0
  111. package/dist/components/ProgressBar/index.d.ts.map +1 -0
  112. package/dist/components/ProgressBar/index.js +2 -0
  113. package/dist/components/ProgressBar/index.js.map +1 -0
  114. package/dist/components/Radio/Radio.d.ts +30 -0
  115. package/dist/components/Radio/Radio.d.ts.map +1 -0
  116. package/dist/components/Radio/Radio.js +56 -0
  117. package/dist/components/Radio/Radio.js.map +1 -0
  118. package/dist/components/Radio/index.d.ts +3 -0
  119. package/dist/components/Radio/index.d.ts.map +1 -0
  120. package/dist/components/Radio/index.js +2 -0
  121. package/dist/components/Radio/index.js.map +1 -0
  122. package/dist/components/SearchBar/SearchBar.d.ts +29 -0
  123. package/dist/components/SearchBar/SearchBar.d.ts.map +1 -0
  124. package/dist/components/SearchBar/SearchBar.js +64 -0
  125. package/dist/components/SearchBar/SearchBar.js.map +1 -0
  126. package/dist/components/SearchBar/index.d.ts +3 -0
  127. package/dist/components/SearchBar/index.d.ts.map +1 -0
  128. package/dist/components/SearchBar/index.js +2 -0
  129. package/dist/components/SearchBar/index.js.map +1 -0
  130. package/dist/components/Skeleton/Skeleton.d.ts +22 -0
  131. package/dist/components/Skeleton/Skeleton.d.ts.map +1 -0
  132. package/dist/components/Skeleton/Skeleton.js +46 -0
  133. package/dist/components/Skeleton/Skeleton.js.map +1 -0
  134. package/dist/components/Skeleton/index.d.ts +3 -0
  135. package/dist/components/Skeleton/index.d.ts.map +1 -0
  136. package/dist/components/Skeleton/index.js +2 -0
  137. package/dist/components/Skeleton/index.js.map +1 -0
  138. package/dist/components/Spinner/Spinner.d.ts +23 -0
  139. package/dist/components/Spinner/Spinner.d.ts.map +1 -0
  140. package/dist/components/Spinner/Spinner.js +19 -0
  141. package/dist/components/Spinner/Spinner.js.map +1 -0
  142. package/dist/components/Spinner/index.d.ts +3 -0
  143. package/dist/components/Spinner/index.d.ts.map +1 -0
  144. package/dist/components/Spinner/index.js +2 -0
  145. package/dist/components/Spinner/index.js.map +1 -0
  146. package/dist/components/Tabs/Tabs.d.ts +30 -0
  147. package/dist/components/Tabs/Tabs.d.ts.map +1 -0
  148. package/dist/components/Tabs/Tabs.js +73 -0
  149. package/dist/components/Tabs/Tabs.js.map +1 -0
  150. package/dist/components/Tabs/index.d.ts +3 -0
  151. package/dist/components/Tabs/index.d.ts.map +1 -0
  152. package/dist/components/Tabs/index.js +2 -0
  153. package/dist/components/Tabs/index.js.map +1 -0
  154. package/dist/components/Toast/Toast.d.ts +30 -0
  155. package/dist/components/Toast/Toast.d.ts.map +1 -0
  156. package/dist/components/Toast/Toast.js +84 -0
  157. package/dist/components/Toast/Toast.js.map +1 -0
  158. package/dist/components/Toast/index.d.ts +3 -0
  159. package/dist/components/Toast/index.d.ts.map +1 -0
  160. package/dist/components/Toast/index.js +2 -0
  161. package/dist/components/Toast/index.js.map +1 -0
  162. package/dist/components/Toggle/Toggle.d.ts +26 -0
  163. package/dist/components/Toggle/Toggle.d.ts.map +1 -0
  164. package/dist/components/Toggle/Toggle.js +75 -0
  165. package/dist/components/Toggle/Toggle.js.map +1 -0
  166. package/dist/components/Toggle/index.d.ts +3 -0
  167. package/dist/components/Toggle/index.d.ts.map +1 -0
  168. package/dist/components/Toggle/index.js +2 -0
  169. package/dist/components/Toggle/index.js.map +1 -0
  170. package/dist/index.d.ts +61 -0
  171. package/dist/index.d.ts.map +1 -0
  172. package/dist/index.js +43 -0
  173. package/dist/index.js.map +1 -0
  174. package/dist/theme/ThemeProvider.d.ts +35 -0
  175. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  176. package/dist/theme/ThemeProvider.js +45 -0
  177. package/dist/theme/ThemeProvider.js.map +1 -0
  178. package/dist/theme/index.d.ts +6 -0
  179. package/dist/theme/index.d.ts.map +1 -0
  180. package/dist/theme/index.js +4 -0
  181. package/dist/theme/index.js.map +1 -0
  182. package/dist/theme/tokens.d.ts +55 -0
  183. package/dist/theme/tokens.d.ts.map +1 -0
  184. package/dist/theme/tokens.js +172 -0
  185. package/dist/theme/tokens.js.map +1 -0
  186. package/dist/theme/useTheme.d.ts +25 -0
  187. package/dist/theme/useTheme.d.ts.map +1 -0
  188. package/dist/theme/useTheme.js +33 -0
  189. package/dist/theme/useTheme.js.map +1 -0
  190. package/package.json +58 -0
  191. package/src/components/Alert/Alert.tsx +105 -0
  192. package/src/components/Alert/index.ts +2 -0
  193. package/src/components/Avatar/Avatar.tsx +122 -0
  194. package/src/components/Avatar/index.ts +2 -0
  195. package/src/components/Badge/Badge.tsx +100 -0
  196. package/src/components/Badge/index.ts +2 -0
  197. package/src/components/BottomNavigation/BottomNavigation.tsx +104 -0
  198. package/src/components/BottomNavigation/index.ts +2 -0
  199. package/src/components/BottomSheet/BottomSheet.tsx +127 -0
  200. package/src/components/BottomSheet/index.ts +2 -0
  201. package/src/components/Button/Button.tsx +255 -0
  202. package/src/components/Button/index.ts +2 -0
  203. package/src/components/Card/Card.tsx +147 -0
  204. package/src/components/Card/index.ts +2 -0
  205. package/src/components/Checkbox/Checkbox.tsx +95 -0
  206. package/src/components/Checkbox/index.ts +2 -0
  207. package/src/components/Chip/Chip.tsx +108 -0
  208. package/src/components/Chip/index.ts +2 -0
  209. package/src/components/Divider/Divider.tsx +41 -0
  210. package/src/components/Divider/index.ts +2 -0
  211. package/src/components/Input/Input.tsx +199 -0
  212. package/src/components/Input/index.ts +2 -0
  213. package/src/components/Modal/Modal.tsx +117 -0
  214. package/src/components/Modal/index.ts +2 -0
  215. package/src/components/Navbar/Navbar.tsx +278 -0
  216. package/src/components/Navbar/index.ts +2 -0
  217. package/src/components/ProgressBar/ProgressBar.tsx +99 -0
  218. package/src/components/ProgressBar/index.ts +2 -0
  219. package/src/components/Radio/Radio.tsx +103 -0
  220. package/src/components/Radio/index.ts +2 -0
  221. package/src/components/SearchBar/SearchBar.tsx +115 -0
  222. package/src/components/SearchBar/index.ts +2 -0
  223. package/src/components/Skeleton/Skeleton.tsx +74 -0
  224. package/src/components/Skeleton/index.ts +2 -0
  225. package/src/components/Spinner/Spinner.tsx +58 -0
  226. package/src/components/Spinner/index.ts +2 -0
  227. package/src/components/Tabs/Tabs.tsx +124 -0
  228. package/src/components/Tabs/index.ts +2 -0
  229. package/src/components/Toast/Toast.tsx +128 -0
  230. package/src/components/Toast/index.ts +2 -0
  231. package/src/components/Toggle/Toggle.tsx +109 -0
  232. package/src/components/Toggle/index.ts +2 -0
  233. package/src/index.ts +87 -0
  234. package/src/theme/ThemeProvider.tsx +96 -0
  235. package/src/theme/index.ts +5 -0
  236. package/src/theme/tokens.ts +225 -0
  237. package/src/theme/useTheme.ts +37 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Toast 컴포넌트 (React Native)
3
+ *
4
+ * 스타일링 토큰 경로:
5
+ * - 배경: theme.color.aliases.surface.inverse
6
+ * - 텍스트: theme.color.aliases.text.inverse-primary
7
+ * - 아이콘: theme.color.aliases.feedback.{success|warning|error|info}.fg
8
+ * - 라운드: theme.border.semantic.radius.l
9
+ * - 패딩: theme.spacing.aliases.padding.{s|m}
10
+ * - 그림자: theme.shadow.foundation.l
11
+ */
12
+
13
+ import React, { useEffect, useRef } from 'react';
14
+ import { View, Text, Pressable, Animated, StyleSheet, type ViewStyle } from 'react-native';
15
+ import { useTheme } from '../../theme/useTheme';
16
+
17
+ export type ToastVariant = 'default' | 'success' | 'warning' | 'error' | 'info';
18
+
19
+ export interface ToastProps {
20
+ visible: boolean;
21
+ message: string;
22
+ variant?: ToastVariant;
23
+ duration?: number;
24
+ onDismiss?: () => void;
25
+ action?: { label: string; onPress: () => void };
26
+ position?: 'top' | 'bottom';
27
+ style?: ViewStyle;
28
+ }
29
+
30
+ const VARIANT_ICONS: Record<ToastVariant, string> = {
31
+ default: '', success: '✓', warning: '⚠', error: '✕', info: 'ℹ',
32
+ };
33
+
34
+ export const Toast: React.FC<ToastProps> = ({
35
+ visible, message, variant = 'default', duration = 3000,
36
+ onDismiss, action, position = 'bottom', style,
37
+ }) => {
38
+ const { theme } = useTheme();
39
+ const opacity = useRef(new Animated.Value(0)).current;
40
+ const translateY = useRef(new Animated.Value(position === 'top' ? -20 : 20)).current;
41
+
42
+ const surface = theme.color.aliases?.surface ?? {};
43
+ const textColors = theme.color.aliases?.text ?? {};
44
+ const feedback = theme.color.aliases?.feedback ?? {};
45
+ const padding = theme.spacing.aliases?.padding ?? {};
46
+ const fontSize = theme.typography.foundation?.fontSize ?? {};
47
+ const borderRadius = theme.border.semantic?.radius?.l ?? 12;
48
+ const shadow = theme.shadow.foundation ?? {};
49
+ const gap = theme.spacing.aliases?.gap ?? {};
50
+
51
+ const variantColors: Record<ToastVariant, string> = {
52
+ default: '', success: feedback.success?.fg ?? '#16A34A',
53
+ warning: feedback.warning?.fg ?? '#D1B400', error: feedback.error?.fg ?? '#DC2626',
54
+ info: feedback.info?.fg ?? '#0683FF',
55
+ };
56
+
57
+ const toShadowStyle = (s: any) => s ? {
58
+ shadowColor: s.shadowColor ?? '#000',
59
+ shadowOffset: { width: s.shadowOffsetX ?? 0, height: s.shadowOffsetY ?? 0 },
60
+ shadowOpacity: s.shadowOpacity ?? 0.1,
61
+ shadowRadius: s.shadowRadius ?? 15,
62
+ elevation: s.elevation ?? 8,
63
+ } : {};
64
+
65
+ useEffect(() => {
66
+ if (visible) {
67
+ Animated.parallel([
68
+ Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }),
69
+ Animated.spring(translateY, { toValue: 0, useNativeDriver: true, friction: 8 }),
70
+ ]).start();
71
+ if (duration > 0) {
72
+ const timer = setTimeout(() => onDismiss?.(), duration);
73
+ return () => clearTimeout(timer);
74
+ }
75
+ } else {
76
+ Animated.parallel([
77
+ Animated.timing(opacity, { toValue: 0, duration: 150, useNativeDriver: true }),
78
+ Animated.timing(translateY, { toValue: position === 'top' ? -20 : 20, duration: 150, useNativeDriver: true }),
79
+ ]).start();
80
+ }
81
+ }, [visible, duration, onDismiss, opacity, translateY, position]);
82
+
83
+ if (!visible) return null;
84
+
85
+ return (
86
+ <Animated.View style={[
87
+ styles.container,
88
+ position === 'top' ? styles.top : styles.bottom,
89
+ {
90
+ opacity, transform: [{ translateY }],
91
+ backgroundColor: surface.inverse ?? '#17191A',
92
+ borderRadius, paddingHorizontal: padding.m ?? 12, paddingVertical: padding.s ?? 8,
93
+ ...toShadowStyle(shadow.l),
94
+ },
95
+ style,
96
+ ]} accessibilityRole="alert">
97
+ <View style={[styles.content, { gap: gap.s ?? 8 }]}>
98
+ {variant !== 'default' && (
99
+ <Text style={{ color: variantColors[variant], fontSize: fontSize.base ?? 16, fontWeight: '700' }}>
100
+ {VARIANT_ICONS[variant]}
101
+ </Text>
102
+ )}
103
+ <Text style={[styles.message, { color: textColors['inverse-primary'] ?? '#FFFFFF', fontSize: fontSize.s ?? 14 }]} numberOfLines={2}>
104
+ {message}
105
+ </Text>
106
+ {action && (
107
+ <Pressable onPress={action.onPress}>
108
+ <Text style={{ color: theme.color.aliases?.brand?.primary ?? '#006FFF', fontSize: fontSize.s ?? 14, fontWeight: '600' }}>
109
+ {action.label}
110
+ </Text>
111
+ </Pressable>
112
+ )}
113
+ </View>
114
+ </Animated.View>
115
+ );
116
+ };
117
+
118
+ Toast.displayName = 'Toast';
119
+
120
+ const styles = StyleSheet.create({
121
+ container: { position: 'absolute', left: 16, right: 16, zIndex: 9999 },
122
+ top: { top: 60 },
123
+ bottom: { bottom: 40 },
124
+ content: { flexDirection: 'row', alignItems: 'center' },
125
+ message: { flex: 1 },
126
+ });
127
+
128
+ export default Toast;
@@ -0,0 +1,2 @@
1
+ export { Toast } from './Toast';
2
+ export type { ToastProps, ToastVariant } from './Toast';
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Toggle 컴포넌트 (React Native)
3
+ *
4
+ * 스타일링 토큰 경로:
5
+ * - 색상: theme.color.aliases.{brand.primary|border.base}
6
+ * - 그림자: theme.shadow.foundation.s
7
+ * - 폰트: theme.typography.foundation.fontSize.base
8
+ * - 투명도: theme.opacity.foundation.40
9
+ *
10
+ * 원본: packages/ui/src/components/Toggle/Toggle.tsx
11
+ */
12
+
13
+ import React, { useState, useEffect, useRef } from 'react';
14
+ import { View, Text, Pressable, Animated, StyleSheet, type ViewStyle } from 'react-native';
15
+ import { useTheme } from '../../theme/useTheme';
16
+
17
+ export type ToggleSize = 's' | 'm' | 'l';
18
+
19
+ export interface ToggleProps {
20
+ isSelected?: boolean;
21
+ defaultSelected?: boolean;
22
+ isDisabled?: boolean;
23
+ size?: ToggleSize;
24
+ children?: React.ReactNode;
25
+ onChange?: (isSelected: boolean) => void;
26
+ style?: ViewStyle;
27
+ }
28
+
29
+ export const Toggle: React.FC<ToggleProps> = ({
30
+ isSelected: controlledSelected, defaultSelected = false, isDisabled = false,
31
+ size = 'm', children, onChange, style,
32
+ }) => {
33
+ const { theme } = useTheme();
34
+ const [selected, setSelected] = useState(controlledSelected ?? defaultSelected);
35
+ const animatedValue = useRef(new Animated.Value(selected ? 1 : 0)).current;
36
+
37
+ // ─── 토큰에서 가져오기 ───
38
+ const brandPrimary = theme.color.aliases?.brand?.primary ?? '#006FFF';
39
+ const borderBase = theme.color.aliases?.border?.base ?? '#E8EEF2';
40
+ const textPrimary = theme.color.aliases?.text?.primary ?? '#17191A';
41
+ const baseFontSize = theme.typography.foundation?.fontSize?.base ?? 16;
42
+ const disabledOpacity = theme.opacity.foundation?.[40] ?? 0.4;
43
+ const shadowS = theme.shadow.foundation?.s;
44
+ const thumbShadow = shadowS ? {
45
+ shadowColor: shadowS.shadowColor ?? '#000',
46
+ shadowOffset: { width: shadowS.shadowOffsetX ?? 0, height: shadowS.shadowOffsetY ?? 1 },
47
+ shadowOpacity: shadowS.shadowOpacity ?? 0.05,
48
+ shadowRadius: shadowS.shadowRadius ?? 2,
49
+ elevation: shadowS.elevation ?? 1,
50
+ } : {};
51
+
52
+ const gap = theme.spacing.aliases?.gap?.s ?? 8;
53
+
54
+ useEffect(() => {
55
+ if (controlledSelected !== undefined) setSelected(controlledSelected);
56
+ }, [controlledSelected]);
57
+
58
+ useEffect(() => {
59
+ Animated.spring(animatedValue, { toValue: selected ? 1 : 0, useNativeDriver: false, friction: 8, tension: 60 }).start();
60
+ }, [selected, animatedValue]);
61
+
62
+ const handleToggle = () => {
63
+ if (isDisabled) return;
64
+ const newValue = !selected;
65
+ setSelected(newValue);
66
+ onChange?.(newValue);
67
+ };
68
+
69
+ const sizeConfig = {
70
+ s: { trackW: 36, trackH: 20, thumbSize: 16, thumbOffset: 2 },
71
+ m: { trackW: 44, trackH: 24, thumbSize: 20, thumbOffset: 2 },
72
+ l: { trackW: 52, trackH: 28, thumbSize: 24, thumbOffset: 2 },
73
+ }[size];
74
+
75
+ const trackColor = animatedValue.interpolate({ inputRange: [0, 1], outputRange: [borderBase, brandPrimary] });
76
+ const thumbTranslateX = animatedValue.interpolate({
77
+ inputRange: [0, 1],
78
+ outputRange: [sizeConfig.thumbOffset, sizeConfig.trackW - sizeConfig.thumbSize - sizeConfig.thumbOffset],
79
+ });
80
+
81
+ return (
82
+ <Pressable onPress={handleToggle} disabled={isDisabled}
83
+ accessibilityRole="switch" accessibilityState={{ checked: selected, disabled: isDisabled }}
84
+ style={[styles.container, { gap }, isDisabled && { opacity: disabledOpacity }, style]}>
85
+ <Animated.View style={[styles.track, { width: sizeConfig.trackW, height: sizeConfig.trackH, borderRadius: sizeConfig.trackH / 2, backgroundColor: trackColor }]}>
86
+ <Animated.View style={[styles.thumb, {
87
+ width: sizeConfig.thumbSize, height: sizeConfig.thumbSize,
88
+ borderRadius: sizeConfig.thumbSize / 2,
89
+ transform: [{ translateX: thumbTranslateX }],
90
+ ...thumbShadow,
91
+ }]} />
92
+ </Animated.View>
93
+ {children && (typeof children === 'string'
94
+ ? <Text style={{ color: textPrimary, fontSize: baseFontSize }}>{children}</Text>
95
+ : children
96
+ )}
97
+ </Pressable>
98
+ );
99
+ };
100
+
101
+ Toggle.displayName = 'Toggle';
102
+
103
+ const styles = StyleSheet.create({
104
+ container: { flexDirection: 'row', alignItems: 'center' },
105
+ track: { justifyContent: 'center' },
106
+ thumb: { backgroundColor: '#FFFFFF' },
107
+ });
108
+
109
+ export default Toggle;
@@ -0,0 +1,2 @@
1
+ export { Toggle } from './Toggle';
2
+ export type { ToggleProps, ToggleSize } from './Toggle';
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @designbasekorea/ui-native
3
+ *
4
+ * Designbase React Native UI 컴포넌트 라이브러리
5
+ * 앱 개발에 최적화된 네이티브 컴포넌트 (총 20개)
6
+ *
7
+ * 토큰 파이프라인:
8
+ * packages/tokens/tokens/*.json
9
+ * → Style Dictionary (build:native)
10
+ * → packages/tokens/dist/native/*.json
11
+ * → ui-native/src/theme/tokens.ts (import)
12
+ * → useTheme() 훅 → 컴포넌트 스타일링
13
+ */
14
+
15
+ // ─── 테마 시스템 ───
16
+ export { ThemeProvider } from './theme/ThemeProvider';
17
+ export type { ThemeProviderProps, ThemeMode, ThemeContextValue } from './theme/ThemeProvider';
18
+ export { useTheme } from './theme/useTheme';
19
+ export { lightTheme, darkTheme, getTokenValue } from './theme/tokens';
20
+ export type { DesignbaseThemeTokens, NativeTokens } from './theme/tokens';
21
+
22
+ // ─── 기본 컴포넌트 ───
23
+ export { Button } from './components/Button';
24
+ export type { ButtonProps, ButtonVariant, ButtonSize, ButtonRadius } from './components/Button';
25
+
26
+ export { Input } from './components/Input';
27
+ export type { InputProps, InputSize } from './components/Input';
28
+
29
+ export { Card } from './components/Card';
30
+ export type { CardProps, CardVariant, CardSize } from './components/Card';
31
+
32
+ export { Badge } from './components/Badge';
33
+ export type { BadgeProps, BadgeSize, BadgeVariant, BadgeType } from './components/Badge';
34
+
35
+ export { Avatar } from './components/Avatar';
36
+ export type { AvatarProps, AvatarSize, AvatarStatus } from './components/Avatar';
37
+
38
+ export { Chip } from './components/Chip';
39
+ export type { ChipProps, ChipSize, ChipVariant } from './components/Chip';
40
+
41
+ export { Divider } from './components/Divider';
42
+ export type { DividerProps } from './components/Divider';
43
+
44
+ export { Spinner } from './components/Spinner';
45
+ export type { SpinnerProps, SpinnerSize } from './components/Spinner';
46
+
47
+ export { Toggle } from './components/Toggle';
48
+ export type { ToggleProps, ToggleSize } from './components/Toggle';
49
+
50
+ export { Alert } from './components/Alert';
51
+ export type { AlertProps, AlertVariant } from './components/Alert';
52
+
53
+ // ─── 레이아웃 & 네비게이션 ───
54
+ export { Modal } from './components/Modal';
55
+ export type { ModalProps, ModalSize } from './components/Modal';
56
+
57
+ export { BottomSheet } from './components/BottomSheet';
58
+ export type { BottomSheetProps } from './components/BottomSheet';
59
+
60
+ export { Tabs } from './components/Tabs';
61
+ export type { TabsProps, TabItem } from './components/Tabs';
62
+
63
+ export { BottomNavigation } from './components/BottomNavigation';
64
+ export type { BottomNavigationProps, BottomNavItem } from './components/BottomNavigation';
65
+
66
+ export { Navbar } from './components/Navbar';
67
+ export type { NavbarProps, NavbarAction } from './components/Navbar';
68
+
69
+ // ─── 폼 컨트롤 ───
70
+ export { Checkbox } from './components/Checkbox';
71
+ export type { CheckboxProps, CheckboxSize } from './components/Checkbox';
72
+
73
+ export { Radio } from './components/Radio';
74
+ export type { RadioProps, RadioOption, RadioSize } from './components/Radio';
75
+
76
+ export { SearchBar } from './components/SearchBar';
77
+ export type { SearchBarProps, SearchBarSize } from './components/SearchBar';
78
+
79
+ // ─── 피드백 & 상태 ───
80
+ export { Toast } from './components/Toast';
81
+ export type { ToastProps, ToastVariant } from './components/Toast';
82
+
83
+ export { Skeleton } from './components/Skeleton';
84
+ export type { SkeletonProps, SkeletonVariant } from './components/Skeleton';
85
+
86
+ export { ProgressBar } from './components/ProgressBar';
87
+ export type { ProgressBarProps, ProgressBarVariant, ProgressBarSize } from './components/ProgressBar';
@@ -0,0 +1,96 @@
1
+ /**
2
+ * ThemeProvider 컴포넌트
3
+ *
4
+ * 목적: React Context 기반 테마 제공 (웹의 CSS 변수 + data-theme 대체)
5
+ * 기능: Light/Dark 테마, 시스템 다크모드 자동 감지, 토큰 자동 연결
6
+ * 사용법: <ThemeProvider theme="auto"><App /></ThemeProvider>
7
+ */
8
+
9
+ import React, { createContext, useMemo, useState, useCallback } from 'react';
10
+ import { useColorScheme } from 'react-native';
11
+ import { lightTheme, darkTheme, getTokenValue } from './tokens';
12
+ import type { DesignbaseThemeTokens, NativeTokens } from './tokens';
13
+
14
+ export type ThemeMode = 'light' | 'dark' | 'auto';
15
+
16
+ export interface ThemeContextValue {
17
+ /** 파싱된 테마 토큰 (semantic, aliases, foundation 구조) */
18
+ theme: DesignbaseThemeTokens;
19
+ /** 원본 토큰 트리에서 점(.) 경로로 값 가져오기 */
20
+ token: (path: string) => any;
21
+ /** 현재 테마 모드 */
22
+ mode: ThemeMode;
23
+ /** 실제 적용된 테마 (auto 해석 후) */
24
+ resolvedMode: 'light' | 'dark';
25
+ /** 테마 모드 변경 */
26
+ setMode: (mode: ThemeMode) => void;
27
+ /** 라이트/다크 토글 */
28
+ toggleTheme: () => void;
29
+ /** 다크모드 여부 */
30
+ isDark: boolean;
31
+ }
32
+
33
+ export const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
34
+
35
+ export interface ThemeProviderProps {
36
+ /** 초기 테마 모드 ('light' | 'dark' | 'auto') */
37
+ theme?: ThemeMode;
38
+ /** 자식 컴포넌트 */
39
+ children: React.ReactNode;
40
+ }
41
+
42
+ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
43
+ theme: initialMode = 'auto',
44
+ children,
45
+ }) => {
46
+ const systemColorScheme = useColorScheme();
47
+ const [mode, setMode] = useState<ThemeMode>(initialMode);
48
+
49
+ const resolvedMode: 'light' | 'dark' = useMemo(() => {
50
+ if (mode === 'auto') {
51
+ return systemColorScheme === 'dark' ? 'dark' : 'light';
52
+ }
53
+ return mode;
54
+ }, [mode, systemColorScheme]);
55
+
56
+ const currentTheme = useMemo(
57
+ () => (resolvedMode === 'dark' ? darkTheme : lightTheme),
58
+ [resolvedMode],
59
+ );
60
+
61
+ const toggleTheme = useCallback(() => {
62
+ setMode((prev) => {
63
+ if (prev === 'auto') {
64
+ return systemColorScheme === 'dark' ? 'light' : 'dark';
65
+ }
66
+ return prev === 'light' ? 'dark' : 'light';
67
+ });
68
+ }, [systemColorScheme]);
69
+
70
+ /** 점(.) 경로로 원본 토큰 값을 가져오는 헬퍼 */
71
+ const token = useCallback(
72
+ (path: string) => getTokenValue(currentTheme.raw, path),
73
+ [currentTheme],
74
+ );
75
+
76
+ const value = useMemo<ThemeContextValue>(
77
+ () => ({
78
+ theme: currentTheme,
79
+ token,
80
+ mode,
81
+ resolvedMode,
82
+ setMode,
83
+ toggleTheme,
84
+ isDark: resolvedMode === 'dark',
85
+ }),
86
+ [currentTheme, token, mode, resolvedMode, toggleTheme],
87
+ );
88
+
89
+ return (
90
+ <ThemeContext.Provider value={value}>
91
+ {children}
92
+ </ThemeContext.Provider>
93
+ );
94
+ };
95
+
96
+ ThemeProvider.displayName = 'ThemeProvider';
@@ -0,0 +1,5 @@
1
+ export { ThemeProvider } from './ThemeProvider';
2
+ export type { ThemeProviderProps, ThemeMode, ThemeContextValue } from './ThemeProvider';
3
+ export { useTheme } from './useTheme';
4
+ export { lightTheme, darkTheme, getTokenValue } from './tokens';
5
+ export type { DesignbaseThemeTokens, NativeTokens } from './tokens';
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Designbase Native 디자인 토큰
3
+ *
4
+ * 목적: @designbasekorea/tokens 패키지에서 빌드된 JSON을 RN에 맞게 구조화
5
+ * 파이프라인: tokens/JSON → Style Dictionary → dist/native/*.json → 이 파일에서 import
6
+ *
7
+ * ⚠️ 이 파일은 자동생성된 JSON 기반입니다.
8
+ * 토큰 값을 변경하려면 packages/tokens/tokens/ 의 JSON을 수정한 뒤
9
+ * `cd packages/tokens && npm run build:native` 를 실행하세요.
10
+ */
11
+
12
+ // ─── 자동 빌드된 토큰 JSON import ───
13
+ // build:native 실행 전에는 이 파일이 없을 수 있으므로
14
+ // 하드코딩 폴백 + 빌드된 JSON 머지 방식을 사용합니다
15
+
16
+ // ─── 타입 정의 ───
17
+
18
+ /** 중첩 구조의 RN 토큰 (Style Dictionary nested JSON 출력과 동일) */
19
+ export type NativeTokens = Record<string, any>;
20
+
21
+ export interface DesignbaseThemeTokens {
22
+ /** 원본 토큰 트리 (자동빌드된 전체) */
23
+ raw: NativeTokens;
24
+ /** 색상 (semantic 레벨) */
25
+ color: {
26
+ semantic: NativeTokens;
27
+ aliases: NativeTokens;
28
+ foundation: NativeTokens;
29
+ };
30
+ /** 간격 (semantic 레벨) */
31
+ spacing: {
32
+ semantic: NativeTokens;
33
+ aliases: NativeTokens;
34
+ foundation: NativeTokens;
35
+ };
36
+ /** 보더 (semantic 레벨) */
37
+ border: {
38
+ semantic: NativeTokens;
39
+ aliases: NativeTokens;
40
+ foundation: NativeTokens;
41
+ };
42
+ /** 그림자 */
43
+ shadow: NativeTokens;
44
+ /** 사이즈 (semantic 레벨) */
45
+ size: {
46
+ semantic: NativeTokens;
47
+ aliases: NativeTokens;
48
+ foundation: NativeTokens;
49
+ };
50
+ /** 타이포그래피 */
51
+ typography: NativeTokens;
52
+ /** 투명도 */
53
+ opacity: NativeTokens;
54
+ }
55
+
56
+ /**
57
+ * 빌드된 JSON에서 특정 경로의 값을 안전하게 가져오기
58
+ */
59
+ export function getTokenValue(tokens: NativeTokens, path: string): any {
60
+ return path.split('.').reduce((obj, key) => obj?.[key], tokens);
61
+ }
62
+
63
+ /**
64
+ * 빌드된 JSON 트리를 DesignbaseThemeTokens로 파싱
65
+ */
66
+ function parseTokenTree(raw: NativeTokens): DesignbaseThemeTokens {
67
+ return {
68
+ raw,
69
+ color: {
70
+ semantic: raw?.color?.semantic ?? {},
71
+ aliases: raw?.color?.aliases ?? {},
72
+ foundation: raw?.color?.foundation ?? {},
73
+ },
74
+ spacing: {
75
+ semantic: raw?.spacing?.semantic ?? {},
76
+ aliases: raw?.spacing?.aliases ?? {},
77
+ foundation: raw?.spacing?.foundation ?? {},
78
+ },
79
+ border: {
80
+ semantic: raw?.border?.semantic ?? {},
81
+ aliases: raw?.border?.aliases ?? {},
82
+ foundation: raw?.border?.foundation ?? {},
83
+ },
84
+ shadow: raw?.shadow ?? {},
85
+ size: {
86
+ semantic: raw?.size?.semantic ?? {},
87
+ aliases: raw?.size?.aliases ?? {},
88
+ foundation: raw?.size?.foundation ?? {},
89
+ },
90
+ typography: raw?.typography ?? {},
91
+ opacity: raw?.opacity ?? {},
92
+ };
93
+ }
94
+
95
+ // ─── 폴백용 하드코딩 (빌드 전 개발용) ───
96
+ const FALLBACK_LIGHT: NativeTokens = {
97
+ color: {
98
+ foundation: {
99
+ blue: { 600: '#0683FF', 700: '#006FFF', 800: '#0855C5', 900: '#0D4B9B' },
100
+ neutral: {
101
+ 0: '#FFFFFF', white: '#FFFFFF', 50: '#F8FAFC', 100: '#F2F8FC',
102
+ 200: '#E8EEF2', 300: '#BBC5CC', 400: '#A4ADB2', 500: '#8C9499',
103
+ 600: '#757B80', 700: '#5E6366', 800: '#464A4D', 900: '#2F3133',
104
+ 1000: '#17191A', 1100: '#000000',
105
+ },
106
+ red: { 50: '#FEF2F2', 100: '#FEE2E2', 500: '#EF4444', 600: '#DC2626' },
107
+ green: { 50: '#F0FDF5', 600: '#16A34A' },
108
+ yellow: { 50: '#FFFFE7', 600: '#D1B400' },
109
+ },
110
+ aliases: {
111
+ brand: { primary: '#006FFF' },
112
+ text: {
113
+ primary: '#17191A', secondary: '#464A4D', tertiary: '#757B80',
114
+ disabled: '#A4ADB2', link: '#006FFF', 'inverse-primary': '#FFFFFF',
115
+ },
116
+ surface: {
117
+ base: '#FFFFFF', 'layer-1': '#F2F8FC', 'layer-2': '#E8EEF2',
118
+ 'layer-3': '#BBC5CC', muted: '#E8EEF2',
119
+ },
120
+ border: { base: '#E8EEF2', 'layer-1': '#BBC5CC', muted: '#F2F8FC' },
121
+ feedback: {
122
+ success: { fg: '#16A34A', bg: '#F0FDF5' },
123
+ warning: { fg: '#D1B400', bg: '#FFFFE7' },
124
+ error: { fg: '#DC2626', bg: '#FEF2F2' },
125
+ info: { fg: '#0683FF', bg: '#EDF9FF' },
126
+ },
127
+ },
128
+ semantic: {
129
+ button: {
130
+ primary: {
131
+ 'bg-default': '#006FFF', 'bg-hover': '#0855C5', 'bg-active': '#0D4B9B',
132
+ 'text-default': '#FFFFFF', 'border-default': '#006FFF',
133
+ },
134
+ secondary: {
135
+ 'bg-default': '#F2F8FC', 'bg-hover': '#E8EEF2',
136
+ 'text-default': '#006FFF', 'border-default': '#E8EEF2',
137
+ },
138
+ tertiary: {
139
+ 'bg-default': '#FFFFFF', 'bg-hover': '#F2F8FC',
140
+ 'text-default': '#17191A', 'border-default': '#E8EEF2',
141
+ },
142
+ },
143
+ field: {
144
+ input: {
145
+ 'bg-default': '#FFFFFF', 'bg-disabled': '#E8EEF2',
146
+ 'border-default': '#E8EEF2', 'border-focus': '#006FFF',
147
+ 'border-error': '#DC2626', 'text-default': '#17191A',
148
+ 'text-disabled': '#A4ADB2', placeholder: '#757B80',
149
+ },
150
+ },
151
+ },
152
+ },
153
+ spacing: {
154
+ foundation: { 0: 0, '05': 2, 1: 4, 2: 8, 3: 12, 4: 16, 5: 20, 6: 24, 8: 32, 10: 40, 12: 48 },
155
+ aliases: {
156
+ scale: { xxs: 2, xs: 4, s: 8, m: 12, l: 20, xl: 32, '2xl': 40 },
157
+ padding: { xxs: 2, xs: 4, s: 8, m: 12, l: 20, xl: 32, '2xl': 40 },
158
+ gap: { xxs: 2, xs: 4, s: 8, m: 12, l: 20, xl: 32, '2xl': 40 },
159
+ },
160
+ },
161
+ border: {
162
+ foundation: {
163
+ radius: { 0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 12, 6: 16, 7: 24, full: 9999 },
164
+ width: { 0: 0, 1: 0.5, 2: 1, 3: 2, 4: 4 },
165
+ },
166
+ semantic: {
167
+ radius: {
168
+ s: 4, m: 8, l: 12, xl: 16, '2xl': 24, full: 9999,
169
+ button: { s: 4, m: 8, l: 12, pill: 9999 },
170
+ input: { s: 4, m: 8, l: 12 },
171
+ card: { s: 4, m: 8, l: 12 },
172
+ },
173
+ },
174
+ },
175
+ shadow: {
176
+ foundation: {
177
+ s: { shadowColor: 'rgba(0, 0, 0, 0.05)', shadowOffsetX: 0, shadowOffsetY: 1, shadowOpacity: 1, shadowRadius: 2, elevation: 1 },
178
+ m: { shadowColor: 'rgba(0, 0, 0, 0.1)', shadowOffsetX: 0, shadowOffsetY: 4, shadowOpacity: 1, shadowRadius: 6, elevation: 3 },
179
+ l: { shadowColor: 'rgba(0, 0, 0, 0.1)', shadowOffsetX: 0, shadowOffsetY: 10, shadowOpacity: 1, shadowRadius: 15, elevation: 8 },
180
+ },
181
+ },
182
+ size: {
183
+ semantic: {
184
+ button: { s: 24, m: 32, l: 40 },
185
+ input: { s: 24, m: 32, l: 40 },
186
+ icon: { xs: 12, s: 16, m: 20, l: 24, xl: 32 },
187
+ avatar: { xs: 16, s: 20, m: 24, l: 32, xl: 40, '2xl': 48 },
188
+ },
189
+ },
190
+ typography: {
191
+ foundation: {
192
+ fontSize: { xs: 12, s: 14, base: 16, l: 18, xl: 20, '2xl': 24, '3xl': 30, '4xl': 36 },
193
+ fontWeight: { light: '300', normal: '400', medium: '500', semibold: '600', bold: '700' },
194
+ lineHeight: { tight: 1.25, snug: 1.375, normal: 1.5, relaxed: 1.625 },
195
+ },
196
+ },
197
+ opacity: {
198
+ foundation: { 0: 0, 5: 0.05, 10: 0.1, 25: 0.25, 40: 0.4, 50: 0.5, 75: 0.75, 100: 1 },
199
+ },
200
+ };
201
+
202
+ // ─── 토큰 로드 (빌드된 JSON > 폴백) ───
203
+
204
+ let builtLightTokens: NativeTokens | null = null;
205
+ let builtDarkTokens: NativeTokens | null = null;
206
+
207
+ try {
208
+ // @ts-ignore — JSON 파일, 빌드 전에는 없을 수 있음
209
+ builtLightTokens = require('@designbasekorea/tokens/native/light');
210
+ } catch { /* 폴백 사용 */ }
211
+
212
+ try {
213
+ // @ts-ignore
214
+ builtDarkTokens = require('@designbasekorea/tokens/native/dark');
215
+ } catch { /* 폴백 사용 */ }
216
+
217
+ /** Light 테마 토큰 (빌드된 JSON 우선, 없으면 폴백) */
218
+ export const lightTheme: DesignbaseThemeTokens = parseTokenTree(
219
+ builtLightTokens ?? FALLBACK_LIGHT
220
+ );
221
+
222
+ /** Dark 테마 토큰 (빌드된 JSON 우선, 없으면 폴백) */
223
+ export const darkTheme: DesignbaseThemeTokens = parseTokenTree(
224
+ builtDarkTokens ?? FALLBACK_LIGHT // dark 폴백도 light 사용 (빌드 후 정상)
225
+ );