@pisodev/test-component-library 2.0.2 → 2.1.2
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.
- package/components/theme/ThemeContext.ts +15 -0
- package/components/theme/ThemeProvider.native.tsx +36 -0
- package/components/theme/ThemeProvider.web.tsx +67 -0
- package/components/theme/colors.ts +81 -0
- package/components/theme/colors.types.ts +48 -0
- package/components/theme/index.ts +4 -0
- package/components/theme/index.web.ts +4 -0
- package/components/theme/useTheme.ts +12 -0
- package/dist/theme/ThemeContext.d.ts +7 -0
- package/dist/theme/ThemeProvider.native.d.ts +8 -0
- package/dist/theme/ThemeProvider.web.d.ts +7 -0
- package/dist/theme/colors.d.ts +3 -0
- package/dist/theme/colors.types.d.ts +43 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.web.d.ts +4 -0
- package/dist/theme/useTheme.d.ts +1 -0
- package/package.json +2 -1
- package/components/README.md +0 -195
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { Theme, ThemeMode } from './colors.types';
|
|
3
|
+
import { lightTheme } from './colors';
|
|
4
|
+
|
|
5
|
+
export interface ThemeContextType {
|
|
6
|
+
theme: Theme;
|
|
7
|
+
mode: ThemeMode;
|
|
8
|
+
setMode: (mode: ThemeMode) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ThemeContext = createContext<ThemeContextType>({
|
|
12
|
+
theme: lightTheme,
|
|
13
|
+
mode: 'light',
|
|
14
|
+
setMode: () => {},
|
|
15
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { useState, ReactNode } from 'react';
|
|
2
|
+
import { useColorScheme } from 'react-native';
|
|
3
|
+
import { ThemeContext } from './ThemeContext';
|
|
4
|
+
import { ThemeMode } from './colors.types';
|
|
5
|
+
import { lightTheme, darkTheme } from './colors';
|
|
6
|
+
|
|
7
|
+
export interface ThemeProviderProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
initialMode?: ThemeMode;
|
|
10
|
+
followSystem?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
14
|
+
children,
|
|
15
|
+
initialMode,
|
|
16
|
+
followSystem = true,
|
|
17
|
+
}) => {
|
|
18
|
+
const systemColorScheme = useColorScheme();
|
|
19
|
+
const [manualMode, setManualMode] = useState<ThemeMode | null>(
|
|
20
|
+
initialMode || null
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const mode =
|
|
24
|
+
manualMode || (followSystem && systemColorScheme) || 'light';
|
|
25
|
+
const theme = mode === 'light' ? lightTheme : darkTheme;
|
|
26
|
+
|
|
27
|
+
const setMode = (newMode: ThemeMode) => {
|
|
28
|
+
setManualMode(newMode);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ThemeContext.Provider value={{ theme, mode, setMode }}>
|
|
33
|
+
{children}
|
|
34
|
+
</ThemeContext.Provider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useState, useEffect, ReactNode } from 'react';
|
|
2
|
+
import { ThemeContext } from './ThemeContext';
|
|
3
|
+
import { ThemeMode } from './colors.types';
|
|
4
|
+
import { lightTheme, darkTheme } from './colors';
|
|
5
|
+
|
|
6
|
+
export interface ThemeProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
initialMode?: ThemeMode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
12
|
+
children,
|
|
13
|
+
initialMode = 'light',
|
|
14
|
+
}) => {
|
|
15
|
+
const [mode, setMode] = useState<ThemeMode>(initialMode);
|
|
16
|
+
const theme = mode === 'light' ? lightTheme : darkTheme;
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// HTML 클래스 업데이트
|
|
20
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
21
|
+
document.documentElement.classList.add(mode);
|
|
22
|
+
|
|
23
|
+
// CSS Variables 업데이트
|
|
24
|
+
const root = document.documentElement;
|
|
25
|
+
|
|
26
|
+
// Gray colors
|
|
27
|
+
root.style.setProperty('--gray-50', theme.colors.gray[50]);
|
|
28
|
+
root.style.setProperty('--gray-100', theme.colors.gray[100]);
|
|
29
|
+
root.style.setProperty('--gray-200', theme.colors.gray[200]);
|
|
30
|
+
root.style.setProperty('--gray-400', theme.colors.gray[400]);
|
|
31
|
+
root.style.setProperty('--gray-600', theme.colors.gray[600]);
|
|
32
|
+
root.style.setProperty('--gray-800', theme.colors.gray[800]);
|
|
33
|
+
root.style.setProperty('--gray-900', theme.colors.gray[900]);
|
|
34
|
+
|
|
35
|
+
// Green colors
|
|
36
|
+
root.style.setProperty('--green-400', theme.colors.green[400]);
|
|
37
|
+
root.style.setProperty('--green-500', theme.colors.green[500]);
|
|
38
|
+
root.style.setProperty('--green-600', theme.colors.green[600]);
|
|
39
|
+
|
|
40
|
+
// Blue colors
|
|
41
|
+
root.style.setProperty('--blue-400', theme.colors.blue[400]);
|
|
42
|
+
root.style.setProperty('--blue-500', theme.colors.blue[500]);
|
|
43
|
+
root.style.setProperty('--blue-600', theme.colors.blue[600]);
|
|
44
|
+
|
|
45
|
+
// Red colors
|
|
46
|
+
root.style.setProperty('--red-400', theme.colors.red[400]);
|
|
47
|
+
root.style.setProperty('--red-500', theme.colors.red[500]);
|
|
48
|
+
root.style.setProperty('--red-600', theme.colors.red[600]);
|
|
49
|
+
|
|
50
|
+
// Semantic colors
|
|
51
|
+
root.style.setProperty('--color-info', theme.semantic.info);
|
|
52
|
+
root.style.setProperty('--color-success', theme.semantic.success);
|
|
53
|
+
root.style.setProperty('--color-error', theme.semantic.error);
|
|
54
|
+
root.style.setProperty('--color-warning', theme.semantic.warning);
|
|
55
|
+
|
|
56
|
+
// Paper colors
|
|
57
|
+
root.style.setProperty('--paper-default', theme.paper.default);
|
|
58
|
+
root.style.setProperty('--paper-neutral', theme.paper.neutral);
|
|
59
|
+
root.style.setProperty('--paper-dialog', theme.paper.dialog);
|
|
60
|
+
}, [mode, theme]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ThemeContext.Provider value={{ theme, mode, setMode }}>
|
|
64
|
+
{children}
|
|
65
|
+
</ThemeContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Theme } from './colors.types';
|
|
2
|
+
|
|
3
|
+
export const lightTheme: Theme = {
|
|
4
|
+
colors: {
|
|
5
|
+
gray: {
|
|
6
|
+
50: '#F6F8FA',
|
|
7
|
+
100: '#EEF0F2',
|
|
8
|
+
200: '#E6E8EA',
|
|
9
|
+
400: '#CED0D2',
|
|
10
|
+
600: '#86888A',
|
|
11
|
+
800: '#2C2E30',
|
|
12
|
+
900: '#0C0E10',
|
|
13
|
+
},
|
|
14
|
+
green: {
|
|
15
|
+
400: '#18C792',
|
|
16
|
+
500: '#16B383',
|
|
17
|
+
600: '#12976F',
|
|
18
|
+
},
|
|
19
|
+
blue: {
|
|
20
|
+
400: '#1473FF',
|
|
21
|
+
500: '#1267E6',
|
|
22
|
+
600: '#0F57C2',
|
|
23
|
+
},
|
|
24
|
+
red: {
|
|
25
|
+
400: '#EB4242',
|
|
26
|
+
500: '#D43B3B',
|
|
27
|
+
600: '#B33232',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
semantic: {
|
|
31
|
+
info: '#1473FF',
|
|
32
|
+
success: '#18C792',
|
|
33
|
+
error: '#EB4242',
|
|
34
|
+
warning: '#F0B800',
|
|
35
|
+
},
|
|
36
|
+
paper: {
|
|
37
|
+
default: '#FFFFFF',
|
|
38
|
+
neutral: '#F6F8FA',
|
|
39
|
+
dialog: '#FFFFFF',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const darkTheme: Theme = {
|
|
44
|
+
colors: {
|
|
45
|
+
gray: {
|
|
46
|
+
50: '#1A1C1E',
|
|
47
|
+
100: '#282A2C',
|
|
48
|
+
200: '#36383A',
|
|
49
|
+
400: '#525456',
|
|
50
|
+
600: '#888A8C',
|
|
51
|
+
800: '#CCCED0',
|
|
52
|
+
900: '#FFFFFF',
|
|
53
|
+
},
|
|
54
|
+
green: {
|
|
55
|
+
400: '#1EEEAF',
|
|
56
|
+
500: '#54F2C2',
|
|
57
|
+
600: '#73F4CD',
|
|
58
|
+
},
|
|
59
|
+
blue: {
|
|
60
|
+
400: '#1473FF',
|
|
61
|
+
500: '#4C95FF',
|
|
62
|
+
600: '#6DA8FF',
|
|
63
|
+
},
|
|
64
|
+
red: {
|
|
65
|
+
400: '#FF4848',
|
|
66
|
+
500: '#FF7474',
|
|
67
|
+
600: '#FF8E8E',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
semantic: {
|
|
71
|
+
info: '#1473FF',
|
|
72
|
+
success: '#1EEEAF',
|
|
73
|
+
error: '#FF4848',
|
|
74
|
+
warning: '#FFC400',
|
|
75
|
+
},
|
|
76
|
+
paper: {
|
|
77
|
+
default: '#0C0E10',
|
|
78
|
+
neutral: '#0C0E10',
|
|
79
|
+
dialog: '#1A1C1E',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 간소화된 컬러 타입 (예시용)
|
|
2
|
+
export interface ColorScale {
|
|
3
|
+
gray: {
|
|
4
|
+
50: string;
|
|
5
|
+
100: string;
|
|
6
|
+
200: string;
|
|
7
|
+
400: string;
|
|
8
|
+
600: string;
|
|
9
|
+
800: string;
|
|
10
|
+
900: string;
|
|
11
|
+
};
|
|
12
|
+
green: {
|
|
13
|
+
400: string;
|
|
14
|
+
500: string;
|
|
15
|
+
600: string;
|
|
16
|
+
};
|
|
17
|
+
blue: {
|
|
18
|
+
400: string;
|
|
19
|
+
500: string;
|
|
20
|
+
600: string;
|
|
21
|
+
};
|
|
22
|
+
red: {
|
|
23
|
+
400: string;
|
|
24
|
+
500: string;
|
|
25
|
+
600: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SemanticColors {
|
|
30
|
+
info: string;
|
|
31
|
+
success: string;
|
|
32
|
+
error: string;
|
|
33
|
+
warning: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PaperColors {
|
|
37
|
+
default: string;
|
|
38
|
+
neutral: string;
|
|
39
|
+
dialog: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Theme {
|
|
43
|
+
colors: ColorScale;
|
|
44
|
+
semantic: SemanticColors;
|
|
45
|
+
paper: PaperColors;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ThemeMode = 'light' | 'dark';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { ThemeContext } from './ThemeContext';
|
|
3
|
+
|
|
4
|
+
export const useTheme = () => {
|
|
5
|
+
const context = useContext(ThemeContext);
|
|
6
|
+
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error('useTheme must be used within ThemeProvider');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { ThemeMode } from './colors.types';
|
|
3
|
+
export interface ThemeProviderProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
initialMode?: ThemeMode;
|
|
6
|
+
followSystem?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const ThemeProvider: React.FC<ThemeProviderProps>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface ColorScale {
|
|
2
|
+
gray: {
|
|
3
|
+
50: string;
|
|
4
|
+
100: string;
|
|
5
|
+
200: string;
|
|
6
|
+
400: string;
|
|
7
|
+
600: string;
|
|
8
|
+
800: string;
|
|
9
|
+
900: string;
|
|
10
|
+
};
|
|
11
|
+
green: {
|
|
12
|
+
400: string;
|
|
13
|
+
500: string;
|
|
14
|
+
600: string;
|
|
15
|
+
};
|
|
16
|
+
blue: {
|
|
17
|
+
400: string;
|
|
18
|
+
500: string;
|
|
19
|
+
600: string;
|
|
20
|
+
};
|
|
21
|
+
red: {
|
|
22
|
+
400: string;
|
|
23
|
+
500: string;
|
|
24
|
+
600: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface SemanticColors {
|
|
28
|
+
info: string;
|
|
29
|
+
success: string;
|
|
30
|
+
error: string;
|
|
31
|
+
warning: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PaperColors {
|
|
34
|
+
default: string;
|
|
35
|
+
neutral: string;
|
|
36
|
+
dialog: string;
|
|
37
|
+
}
|
|
38
|
+
export interface Theme {
|
|
39
|
+
colors: ColorScale;
|
|
40
|
+
semantic: SemanticColors;
|
|
41
|
+
paper: PaperColors;
|
|
42
|
+
}
|
|
43
|
+
export type ThemeMode = 'light' | 'dark';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useTheme: () => import("./ThemeContext").ThemeContextType;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pisodev/test-component-library",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "웹과 React Native를 위한 컴포넌트 라이브러리",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"rollup": "^4.54.0",
|
|
52
52
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
53
53
|
"rollup-plugin-postcss": "^4.0.2",
|
|
54
|
+
"postcss": "^8.4.49",
|
|
54
55
|
"sass": "^1.97.1",
|
|
55
56
|
"tslib": "^2.8.1",
|
|
56
57
|
"typescript": "^5.9.3"
|
package/components/README.md
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# 컴포넌트 가이드
|
|
2
|
-
|
|
3
|
-
## 컴포넌트 타입
|
|
4
|
-
|
|
5
|
-
### 1. 크로스 플랫폼 컴포넌트 (웹 + RN 모두 지원)
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
ComponentName/
|
|
9
|
-
├── ComponentName.types.ts # 공유 타입
|
|
10
|
-
├── ComponentName.web.tsx # 웹 구현
|
|
11
|
-
├── ComponentName.native.tsx # RN 구현
|
|
12
|
-
├── index.ts # export
|
|
13
|
-
└── README.md
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
**예시**: Anchor, Button, Card
|
|
17
|
-
|
|
18
|
-
**index.ts**:
|
|
19
|
-
```typescript
|
|
20
|
-
export { ComponentName } from './ComponentName';
|
|
21
|
-
export type { ComponentNameProps } from './ComponentName.types';
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
### 2. 웹 전용 컴포넌트
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
WebOnlyComponent/
|
|
30
|
-
├── WebOnlyComponent.types.ts # 타입
|
|
31
|
-
├── WebOnlyComponent.tsx # 웹 구현 (.web.tsx 아님!)
|
|
32
|
-
├── WebOnlyComponent.module.scss # 스타일
|
|
33
|
-
├── index.ts # export
|
|
34
|
-
└── README.md
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**예시**: Table, Tooltip, Dropdown
|
|
38
|
-
|
|
39
|
-
**index.ts**:
|
|
40
|
-
```typescript
|
|
41
|
-
export { WebOnlyComponent } from './WebOnlyComponent';
|
|
42
|
-
export type { WebOnlyComponentProps } from './WebOnlyComponent.types';
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**중요**: RN에서 import 시 에러 방지를 위한 처리
|
|
46
|
-
|
|
47
|
-
**Option 1 - 타입만 제공**:
|
|
48
|
-
```typescript
|
|
49
|
-
// index.native.ts (별도 파일)
|
|
50
|
-
import { WebOnlyComponentProps } from './WebOnlyComponent.types';
|
|
51
|
-
|
|
52
|
-
export const WebOnlyComponent = () => {
|
|
53
|
-
if (__DEV__) {
|
|
54
|
-
console.warn('WebOnlyComponent is not available on React Native');
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type { WebOnlyComponentProps };
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**Option 2 - 완전히 제외**:
|
|
63
|
-
```typescript
|
|
64
|
-
// components/index.ts에서
|
|
65
|
-
export * from './Anchor';
|
|
66
|
-
export * from './Button';
|
|
67
|
-
|
|
68
|
-
// 조건부 export (빌드 설정에서 처리)
|
|
69
|
-
if (typeof window !== 'undefined') {
|
|
70
|
-
export * from './WebOnlyComponent';
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
### 3. React Native 전용 컴포넌트
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
NativeOnlyComponent/
|
|
80
|
-
├── NativeOnlyComponent.types.ts # 타입
|
|
81
|
-
├── NativeOnlyComponent.tsx # RN 구현 (.native.tsx 아님!)
|
|
82
|
-
├── index.ts # export
|
|
83
|
-
└── README.md
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
**예시**: MapView, Camera, BiometricAuth
|
|
87
|
-
|
|
88
|
-
**index.ts**:
|
|
89
|
-
```typescript
|
|
90
|
-
export { NativeOnlyComponent } from './NativeOnlyComponent';
|
|
91
|
-
export type { NativeOnlyComponentProps } from './NativeOnlyComponent.types';
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**웹에서 import 시 에러 방지**:
|
|
95
|
-
|
|
96
|
-
**index.web.ts (별도 파일)**:
|
|
97
|
-
```typescript
|
|
98
|
-
import { NativeOnlyComponentProps } from './NativeOnlyComponent.types';
|
|
99
|
-
|
|
100
|
-
export const NativeOnlyComponent = () => {
|
|
101
|
-
if (process.env.NODE_ENV === 'development') {
|
|
102
|
-
console.warn('NativeOnlyComponent is only available on React Native');
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export type { NativeOnlyComponentProps };
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## 실전 예시
|
|
113
|
-
|
|
114
|
-
### 예시 1: Tooltip (웹 전용)
|
|
115
|
-
|
|
116
|
-
DOM API를 사용하므로 RN에서 불가능
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
Tooltip/
|
|
120
|
-
├── Tooltip.types.ts
|
|
121
|
-
├── Tooltip.tsx # 웹 구현
|
|
122
|
-
├── Tooltip.module.scss
|
|
123
|
-
├── index.ts # 웹용 export
|
|
124
|
-
├── index.native.ts # RN용 - null 반환 또는 대안
|
|
125
|
-
└── README.md
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**index.native.ts**:
|
|
129
|
-
```typescript
|
|
130
|
-
import { TooltipProps } from './Tooltip.types';
|
|
131
|
-
|
|
132
|
-
// RN에서는 React Native의 Tooltip 사용 권장
|
|
133
|
-
export const Tooltip: React.FC<TooltipProps> = ({ children }) => {
|
|
134
|
-
return <>{children}</>; // 툴팁 없이 children만 렌더
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
export type { TooltipProps };
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### 예시 2: DatePicker (플랫폼별 구현 차이가 큼)
|
|
141
|
-
|
|
142
|
-
웹과 RN에서 완전히 다른 API 사용
|
|
143
|
-
|
|
144
|
-
```
|
|
145
|
-
DatePicker/
|
|
146
|
-
├── DatePicker.types.ts # 공통 props만
|
|
147
|
-
├── DatePicker.web.tsx # HTML input type="date"
|
|
148
|
-
├── DatePicker.native.tsx # @react-native-community/datetimepicker
|
|
149
|
-
├── index.ts
|
|
150
|
-
└── README.md
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 예시 3: MapView (RN 전용)
|
|
154
|
-
|
|
155
|
-
react-native-maps 사용
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
MapView/
|
|
159
|
-
├── MapView.types.ts
|
|
160
|
-
├── MapView.tsx # RN 구현
|
|
161
|
-
├── index.ts # RN export
|
|
162
|
-
├── index.web.ts # 웹에서는 에러 또는 대안
|
|
163
|
-
└── README.md
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**index.web.ts**:
|
|
167
|
-
```typescript
|
|
168
|
-
export const MapView = () => {
|
|
169
|
-
throw new Error(
|
|
170
|
-
'MapView is only available on React Native. Use a web map library like react-leaflet instead.'
|
|
171
|
-
);
|
|
172
|
-
};
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## 권장 사항
|
|
178
|
-
|
|
179
|
-
### 플랫폼 전용 컴포넌트를 만들기 전에
|
|
180
|
-
|
|
181
|
-
1. **정말 플랫폼 전용이 필요한가?**
|
|
182
|
-
- 대부분의 UI 컴포넌트는 크로스 플랫폼 가능
|
|
183
|
-
- 로직만 분리하고 렌더링만 다르게 할 수 있는지 검토
|
|
184
|
-
|
|
185
|
-
2. **대안 제공**
|
|
186
|
-
- 웹 전용: RN에서는 null 반환 + 경고
|
|
187
|
-
- RN 전용: 웹에서는 에러 throw + 대안 라이브러리 제안
|
|
188
|
-
|
|
189
|
-
3. **문서화**
|
|
190
|
-
- README.md에 플랫폼 제한 명시
|
|
191
|
-
- 대안 라이브러리/방법 제시
|
|
192
|
-
|
|
193
|
-
4. **TypeScript 타입은 공유**
|
|
194
|
-
- Props 타입은 양쪽에서 사용 가능하도록
|
|
195
|
-
- 개발자 경험(DX) 향상
|