@0610studio/zs-ui 0.7.1 → 0.7.3
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/build/index.d.ts +5 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -3
- package/build/index.js.map +1 -1
- package/build/model/types.d.ts +4 -10
- package/build/model/types.d.ts.map +1 -1
- package/build/model/types.js.map +1 -1
- package/build/model/useOverlay.d.ts +1 -3
- package/build/model/useOverlay.d.ts.map +1 -1
- package/build/model/useOverlay.js +0 -8
- package/build/model/useOverlay.js.map +1 -1
- package/build/model/useOverlayProvider.d.ts.map +1 -1
- package/build/model/useOverlayProvider.js +11 -38
- package/build/model/useOverlayProvider.js.map +1 -1
- package/build/model/utils.d.ts.map +1 -1
- package/build/model/utils.js.map +1 -1
- package/build/overlay/BottomSheetOverlay/index.d.ts +2 -1
- package/build/overlay/BottomSheetOverlay/index.d.ts.map +1 -1
- package/build/overlay/BottomSheetOverlay/index.js +144 -91
- package/build/overlay/BottomSheetOverlay/index.js.map +1 -1
- package/build/overlay/ZSPortal/index.d.ts +11 -0
- package/build/overlay/ZSPortal/index.d.ts.map +1 -0
- package/build/overlay/ZSPortal/index.js +49 -0
- package/build/overlay/ZSPortal/index.js.map +1 -0
- package/build/overlay/index.d.ts +2 -1
- package/build/overlay/index.d.ts.map +1 -1
- package/build/overlay/index.js +2 -1
- package/build/overlay/index.js.map +1 -1
- package/build/ui/ZSAboveKeyboard/index.d.ts +9 -0
- package/build/ui/ZSAboveKeyboard/index.d.ts.map +1 -0
- package/build/ui/ZSAboveKeyboard/index.js +56 -0
- package/build/ui/ZSAboveKeyboard/index.js.map +1 -0
- package/build/ui/ZSContainer/index.d.ts +16 -14
- package/build/ui/ZSContainer/index.d.ts.map +1 -1
- package/build/ui/ZSContainer/index.js +78 -40
- package/build/ui/ZSContainer/index.js.map +1 -1
- package/build/ui/ZSPressable/index.d.ts +2 -1
- package/build/ui/ZSPressable/index.d.ts.map +1 -1
- package/build/ui/ZSPressable/index.js +40 -16
- package/build/ui/ZSPressable/index.js.map +1 -1
- package/build/ui/ZSTextField/index.d.ts.map +1 -1
- package/build/ui/ZSTextField/index.js +127 -41
- package/build/ui/ZSTextField/index.js.map +1 -1
- package/build/ui/atoms/AnimatedWrapper.d.ts.map +1 -1
- package/build/ui/atoms/AnimatedWrapper.js +58 -16
- package/build/ui/atoms/AnimatedWrapper.js.map +1 -1
- package/package.json +1 -1
- package/build/overlay/AboveKeyboard/index.d.ts +0 -4
- package/build/overlay/AboveKeyboard/index.d.ts.map +0 -1
- package/build/overlay/AboveKeyboard/index.js +0 -41
- package/build/overlay/AboveKeyboard/index.js.map +0 -1
- package/build/ui/ZSContainer/ui/VariantView.d.ts +0 -17
- package/build/ui/ZSContainer/ui/VariantView.d.ts.map +0 -1
- package/build/ui/ZSContainer/ui/VariantView.js +0 -13
- package/build/ui/ZSContainer/ui/VariantView.js.map +0 -1
|
@@ -1,75 +1,113 @@
|
|
|
1
|
-
import React, { useEffect, useImperativeHandle, forwardRef, useRef, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useImperativeHandle, forwardRef, useRef, useState, useCallback, useMemo } from 'react';
|
|
2
2
|
import { StatusBar, StyleSheet, ScrollView, Keyboard, View, Platform } from 'react-native';
|
|
3
3
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
4
|
import { useTheme } from '../../model/useThemeProvider';
|
|
5
|
-
const
|
|
5
|
+
const IS_IOS = Platform.OS === 'ios';
|
|
6
|
+
const KEYBOARD_ANIMATION_DELAY = 50;
|
|
7
|
+
const keyboardEvents = {
|
|
8
|
+
showEvent: IS_IOS ? 'keyboardWillShow' : 'keyboardDidShow',
|
|
9
|
+
hideEvent: IS_IOS ? 'keyboardWillHide' : 'keyboardDidHide',
|
|
10
|
+
};
|
|
11
|
+
const ZSContainer = forwardRef(function ZSContainer({ backgroundColor, statusBarColor, barStyle, edges = ['top', 'bottom'], scrollViewDisabled = false, topComponent, bottomComponent, rightComponent, showsVerticalScrollIndicator = true, keyboardScrollExtraOffset = 30, translucent, scrollEventThrottle = 16, scrollToFocusedInput = true, ...props }, forwardedRef) {
|
|
6
12
|
const { palette, dimensions: { height: windowHeight } } = useTheme();
|
|
7
13
|
const positionRef = useRef(0);
|
|
8
14
|
const scrollViewRef = useRef(null);
|
|
9
15
|
const lastTouchY = useRef(0);
|
|
10
16
|
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
11
17
|
useImperativeHandle(forwardedRef, () => scrollViewRef.current, []);
|
|
18
|
+
const handleKeyboardShow = useCallback((e) => {
|
|
19
|
+
setKeyboardHeight(e.endCoordinates.height);
|
|
20
|
+
if (scrollViewRef.current && scrollToFocusedInput) {
|
|
21
|
+
const keyboardHeight = e.endCoordinates.height;
|
|
22
|
+
const safeAreaBottom = 0;
|
|
23
|
+
const availableScreenHeight = windowHeight - keyboardHeight - safeAreaBottom;
|
|
24
|
+
const currentScrollPosition = positionRef.current || 0;
|
|
25
|
+
const touchPosition = lastTouchY.current || 0;
|
|
26
|
+
// 현재 터치 위치와 스크롤 위치를 기반으로 새로운 스크롤 위치 계산
|
|
27
|
+
const scrollOffset = touchPosition - availableScreenHeight + keyboardScrollExtraOffset;
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
scrollViewRef.current?.scrollTo({
|
|
30
|
+
y: currentScrollPosition + scrollOffset,
|
|
31
|
+
animated: true,
|
|
32
|
+
});
|
|
33
|
+
}, KEYBOARD_ANIMATION_DELAY);
|
|
34
|
+
}
|
|
35
|
+
}, [windowHeight, keyboardScrollExtraOffset, scrollToFocusedInput]);
|
|
36
|
+
// 키보드 숨김 핸들러를 메모이제이션하여 성능 최적화
|
|
37
|
+
const handleKeyboardHide = useCallback(() => {
|
|
38
|
+
setKeyboardHeight(0);
|
|
39
|
+
}, []);
|
|
12
40
|
useEffect(() => {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const keyboardShowSubscription = Keyboard.addListener(showEvent, (e) => {
|
|
16
|
-
setKeyboardHeight(e.endCoordinates.height);
|
|
17
|
-
if (scrollViewRef.current) {
|
|
18
|
-
const screenHeight = windowHeight;
|
|
19
|
-
const keyboardHeight = e.endCoordinates.height;
|
|
20
|
-
const safeAreaBottom = 0;
|
|
21
|
-
const availableScreenHeight = screenHeight - keyboardHeight - safeAreaBottom;
|
|
22
|
-
const currentScrollPosition = positionRef.current || 0;
|
|
23
|
-
const touchPosition = lastTouchY.current || 0;
|
|
24
|
-
// touchPosition이 키보드 높이보다 아래인 경우
|
|
25
|
-
// const isTouchPositionBelowKeyboard = touchPosition > availableScreenHeight;
|
|
26
|
-
// 현재 터치 위치와 스크롤 위치를 기반으로 새로운 스크롤 위치 계산
|
|
27
|
-
const scrollOffset = touchPosition - availableScreenHeight + keyboardScrollExtraOffset;
|
|
28
|
-
setTimeout(() => {
|
|
29
|
-
scrollViewRef.current?.scrollTo({
|
|
30
|
-
y: currentScrollPosition + scrollOffset,
|
|
31
|
-
animated: true,
|
|
32
|
-
});
|
|
33
|
-
}, 100);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {
|
|
37
|
-
setKeyboardHeight(0);
|
|
38
|
-
});
|
|
41
|
+
const keyboardShowSubscription = Keyboard.addListener(keyboardEvents.showEvent, handleKeyboardShow);
|
|
42
|
+
const keyboardHideSubscription = Keyboard.addListener(keyboardEvents.hideEvent, handleKeyboardHide);
|
|
39
43
|
return () => {
|
|
40
44
|
positionRef.current = null;
|
|
41
45
|
lastTouchY.current = null;
|
|
42
46
|
keyboardShowSubscription.remove();
|
|
43
47
|
keyboardHideSubscription.remove();
|
|
44
48
|
};
|
|
45
|
-
}, [
|
|
46
|
-
const handleScroll = (event) => {
|
|
49
|
+
}, [keyboardEvents.showEvent, keyboardEvents.hideEvent, handleKeyboardShow, handleKeyboardHide]);
|
|
50
|
+
const handleScroll = useCallback((event) => {
|
|
47
51
|
if (props.onScroll)
|
|
48
52
|
props.onScroll(event);
|
|
49
53
|
positionRef.current = event.nativeEvent.contentOffset.y;
|
|
50
|
-
};
|
|
51
|
-
const handleTouch = (evt) => {
|
|
54
|
+
}, [props.onScroll]);
|
|
55
|
+
const handleTouch = useCallback((evt) => {
|
|
52
56
|
lastTouchY.current = evt.nativeEvent.pageY;
|
|
53
|
-
};
|
|
54
|
-
|
|
57
|
+
}, []);
|
|
58
|
+
const safeAreaStyle = useMemo(() => [
|
|
59
|
+
{ backgroundColor: backgroundColor || palette.background.base },
|
|
60
|
+
styles.flex1
|
|
61
|
+
], [backgroundColor, palette.background.base]);
|
|
62
|
+
const scrollContentStyle = useMemo(() => [
|
|
63
|
+
styles.scrollContainerStyle,
|
|
64
|
+
{
|
|
65
|
+
paddingBottom: keyboardHeight ? (IS_IOS ? keyboardHeight : keyboardScrollExtraOffset) : 0
|
|
66
|
+
}
|
|
67
|
+
], [keyboardHeight, keyboardScrollExtraOffset]);
|
|
68
|
+
const containerStyle = useMemo(() => [
|
|
69
|
+
styles.flex1,
|
|
70
|
+
props.style
|
|
71
|
+
], [props.style]);
|
|
72
|
+
const shouldShowStatusBar = useMemo(() => Boolean(barStyle || statusBarColor || translucent), [barStyle, statusBarColor, translucent]);
|
|
73
|
+
return (<SafeAreaView style={safeAreaStyle} edges={edges}>
|
|
55
74
|
<View style={styles.flex1}>
|
|
56
|
-
{topComponent
|
|
75
|
+
{topComponent}
|
|
57
76
|
{scrollViewDisabled ? (<View style={styles.flex1}>
|
|
58
77
|
{props.children}
|
|
59
|
-
</View>) : (<ScrollView ref={scrollViewRef} style={styles.flex1} contentContainerStyle={
|
|
60
|
-
<View style={
|
|
78
|
+
</View>) : (<ScrollView ref={scrollViewRef} style={styles.flex1} contentContainerStyle={scrollContentStyle} bounces={false} overScrollMode="never" showsVerticalScrollIndicator={showsVerticalScrollIndicator} keyboardShouldPersistTaps="handled" automaticallyAdjustKeyboardInsets={false} onScroll={handleScroll} onTouchStart={handleTouch} scrollEventThrottle={scrollEventThrottle}>
|
|
79
|
+
<View style={containerStyle}>
|
|
61
80
|
{props.children}
|
|
62
81
|
</View>
|
|
63
82
|
</ScrollView>)}
|
|
64
|
-
{bottomComponent
|
|
83
|
+
{bottomComponent}
|
|
65
84
|
</View>
|
|
66
85
|
|
|
67
|
-
{
|
|
86
|
+
{shouldShowStatusBar && (<StatusBar barStyle={barStyle} backgroundColor={statusBarColor || palette.background.base} translucent={translucent}/>)}
|
|
68
87
|
</SafeAreaView>);
|
|
69
88
|
});
|
|
70
89
|
export const styles = StyleSheet.create({
|
|
71
90
|
flex1: { flex: 1, width: '100%' },
|
|
72
91
|
scrollContainerStyle: { flexGrow: 1, alignItems: 'center', width: '100%' },
|
|
73
92
|
});
|
|
74
|
-
|
|
93
|
+
const arePropsEqual = (prevProps, nextProps) => {
|
|
94
|
+
return (prevProps.backgroundColor === nextProps.backgroundColor &&
|
|
95
|
+
prevProps.statusBarColor === nextProps.statusBarColor &&
|
|
96
|
+
prevProps.barStyle === nextProps.barStyle &&
|
|
97
|
+
prevProps.scrollViewDisabled === nextProps.scrollViewDisabled &&
|
|
98
|
+
prevProps.showsVerticalScrollIndicator === nextProps.showsVerticalScrollIndicator &&
|
|
99
|
+
prevProps.keyboardScrollExtraOffset === nextProps.keyboardScrollExtraOffset &&
|
|
100
|
+
prevProps.translucent === nextProps.translucent &&
|
|
101
|
+
prevProps.scrollEventThrottle === nextProps.scrollEventThrottle &&
|
|
102
|
+
prevProps.scrollToFocusedInput === nextProps.scrollToFocusedInput &&
|
|
103
|
+
prevProps.onScroll === nextProps.onScroll &&
|
|
104
|
+
prevProps.style === nextProps.style &&
|
|
105
|
+
prevProps.children === nextProps.children &&
|
|
106
|
+
prevProps.topComponent === nextProps.topComponent &&
|
|
107
|
+
prevProps.bottomComponent === nextProps.bottomComponent &&
|
|
108
|
+
prevProps.rightComponent === nextProps.rightComponent &&
|
|
109
|
+
// edges 배열 비교
|
|
110
|
+
JSON.stringify(prevProps.edges) === JSON.stringify(nextProps.edges));
|
|
111
|
+
};
|
|
112
|
+
export default React.memo(ZSContainer, arePropsEqual);
|
|
75
113
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { ReactNode, useEffect, useImperativeHandle, forwardRef, useRef, useState } from 'react';\r\nimport { ViewProps, StatusBar, StyleSheet, ScrollView, NativeSyntheticEvent, NativeScrollEvent, Keyboard, View, Platform } from 'react-native';\r\nimport { SafeAreaView } from 'react-native-safe-area-context';\r\nimport { useTheme } from '../../model/useThemeProvider';\r\n\r\nexport type ZSContainerProps = ViewProps & {\r\n backgroundColor?: string;\r\n statusBarColor?: string;\r\n barStyle?: 'light-content' | 'dark-content';\r\n edges?: Array<'top' | 'right' | 'bottom' | 'left'>;\r\n scrollViewDisabled?: boolean;\r\n topComponent?: ReactNode;\r\n bottomComponent?: ReactNode;\r\n rightComponent?: ReactNode;\r\n showsVerticalScrollIndicator?: boolean;\r\n keyboardScrollExtraOffset?: number;\r\n translucent?: boolean;\r\n onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;\r\n scrollEventThrottle?: number;\r\n};\r\n\r\nexport type ZSContainerRef = ScrollView;\r\n\r\nconst ZSContainer = forwardRef<ZSContainerRef, ZSContainerProps>(function ZSContainer(\r\n {\r\n backgroundColor,\r\n statusBarColor,\r\n barStyle,\r\n edges = ['top', 'bottom'],\r\n scrollViewDisabled = false,\r\n topComponent,\r\n bottomComponent,\r\n rightComponent,\r\n showsVerticalScrollIndicator = true,\r\n keyboardScrollExtraOffset = 30,\r\n translucent,\r\n scrollEventThrottle = 16,\r\n ...props\r\n },\r\n forwardedRef\r\n) {\r\n const { palette, dimensions: { height: windowHeight } } = useTheme();\r\n const positionRef = useRef<number | null>(0);\r\n const scrollViewRef = useRef<ScrollView>(null);\r\n const lastTouchY = useRef<number | null>(0);\r\n const [keyboardHeight, setKeyboardHeight] = useState<number | null>(0);\r\n\r\n useImperativeHandle(forwardedRef, () => scrollViewRef.current as ScrollView, []);\r\n\r\n useEffect(() => {\r\n const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';\r\n const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';\r\n\r\n const keyboardShowSubscription = Keyboard.addListener(showEvent, (e) => {\r\n setKeyboardHeight(e.endCoordinates.height);\r\n if (scrollViewRef.current) {\r\n const screenHeight = windowHeight;\r\n const keyboardHeight = e.endCoordinates.height;\r\n const safeAreaBottom = 0;\r\n const availableScreenHeight = screenHeight - keyboardHeight - safeAreaBottom;\r\n const currentScrollPosition = positionRef.current || 0;\r\n const touchPosition = lastTouchY.current || 0;\r\n\r\n // touchPosition이 키보드 높이보다 아래인 경우\r\n // const isTouchPositionBelowKeyboard = touchPosition > availableScreenHeight;\r\n\r\n // 현재 터치 위치와 스크롤 위치를 기반으로 새로운 스크롤 위치 계산\r\n const scrollOffset = touchPosition - availableScreenHeight + keyboardScrollExtraOffset;\r\n\r\n setTimeout(() => {\r\n scrollViewRef.current?.scrollTo({\r\n y: currentScrollPosition + scrollOffset,\r\n animated: true,\r\n });\r\n }, 100);\r\n }\r\n });\r\n\r\n const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {\r\n setKeyboardHeight(0);\r\n });\r\n\r\n return () => {\r\n positionRef.current = null;\r\n lastTouchY.current = null;\r\n keyboardShowSubscription.remove();\r\n keyboardHideSubscription.remove();\r\n };\r\n }, [keyboardScrollExtraOffset]);\r\n\r\n const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {\r\n if (props.onScroll) props.onScroll(event);\r\n positionRef.current = event.nativeEvent.contentOffset.y;\r\n };\r\n\r\n const handleTouch = (evt: any) => {\r\n lastTouchY.current = evt.nativeEvent.pageY;\r\n };\r\n\r\n return (\r\n <SafeAreaView\r\n style={[{ backgroundColor: backgroundColor || palette.background.base }, styles.flex1]}\r\n edges={edges}\r\n >\r\n <View style={styles.flex1}>\r\n {topComponent && topComponent}\r\n {\r\n scrollViewDisabled ? (\r\n <View style={styles.flex1}>\r\n {props.children}\r\n </View>\r\n ) : (\r\n <ScrollView\r\n ref={scrollViewRef}\r\n style={styles.flex1}\r\n contentContainerStyle={[styles.scrollContainerStyle, { paddingBottom: Platform.OS === 'ios' ? keyboardHeight || 0 : 0 }]}\r\n bounces={false}\r\n overScrollMode=\"never\"\r\n showsVerticalScrollIndicator={showsVerticalScrollIndicator}\r\n keyboardShouldPersistTaps=\"handled\"\r\n automaticallyAdjustKeyboardInsets={false}\r\n onScroll={handleScroll}\r\n onTouchStart={handleTouch}\r\n scrollEventThrottle={scrollEventThrottle}\r\n >\r\n <View style={[styles.flex1, props.style]}>\r\n {props.children}\r\n </View>\r\n </ScrollView>\r\n )\r\n }\r\n {bottomComponent && bottomComponent}\r\n </View>\r\n\r\n {\r\n (barStyle || statusBarColor || translucent) && (\r\n <StatusBar\r\n barStyle={barStyle}\r\n backgroundColor={statusBarColor || palette.background.base}\r\n translucent={translucent}\r\n />\r\n )\r\n }\r\n </SafeAreaView>\r\n );\r\n});\r\n\r\nexport const styles = StyleSheet.create({\r\n flex1: { flex: 1, width: '100%' },\r\n scrollContainerStyle: { flexGrow: 1, alignItems: 'center', width: '100%' },\r\n});\r\n\r\nexport default ZSContainer;\r\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { ReactNode, useEffect, useImperativeHandle, forwardRef, useRef, useState, useCallback, useMemo } from 'react';\r\nimport { ViewProps, StatusBar, StyleSheet, ScrollView, NativeSyntheticEvent, NativeScrollEvent, Keyboard, View, Platform } from 'react-native';\r\nimport { SafeAreaView } from 'react-native-safe-area-context';\r\nimport { useTheme } from '../../model/useThemeProvider';\r\n\r\nconst IS_IOS = Platform.OS === 'ios';\r\nconst KEYBOARD_ANIMATION_DELAY = 50;\r\nconst keyboardEvents = {\r\n showEvent: IS_IOS ? 'keyboardWillShow' as const : 'keyboardDidShow' as const,\r\n hideEvent: IS_IOS ? 'keyboardWillHide' as const : 'keyboardDidHide' as const,\r\n};\r\n\r\nexport type ZSContainerProps = ViewProps & {\r\n backgroundColor?: string;\r\n statusBarColor?: string;\r\n barStyle?: 'light-content' | 'dark-content';\r\n edges?: Array<'top' | 'right' | 'bottom' | 'left'>;\r\n scrollViewDisabled?: boolean;\r\n topComponent?: ReactNode;\r\n bottomComponent?: ReactNode;\r\n rightComponent?: ReactNode;\r\n showsVerticalScrollIndicator?: boolean;\r\n keyboardScrollExtraOffset?: number;\r\n translucent?: boolean;\r\n onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;\r\n scrollEventThrottle?: number;\r\n scrollToFocusedInput?: boolean;\r\n};\r\n\r\nexport type ZSContainerRef = ScrollView;\r\n\r\nconst ZSContainer = forwardRef<ZSContainerRef, ZSContainerProps>(function ZSContainer(\r\n {\r\n backgroundColor,\r\n statusBarColor,\r\n barStyle,\r\n edges = ['top', 'bottom'],\r\n scrollViewDisabled = false,\r\n topComponent,\r\n bottomComponent,\r\n rightComponent,\r\n showsVerticalScrollIndicator = true,\r\n keyboardScrollExtraOffset = 30,\r\n translucent,\r\n scrollEventThrottle = 16,\r\n scrollToFocusedInput = true,\r\n ...props\r\n },\r\n forwardedRef\r\n) {\r\n const { palette, dimensions: { height: windowHeight } } = useTheme();\r\n const positionRef = useRef<number | null>(0);\r\n const scrollViewRef = useRef<ScrollView>(null);\r\n const lastTouchY = useRef<number | null>(0);\r\n const [keyboardHeight, setKeyboardHeight] = useState<number | null>(0);\r\n\r\n useImperativeHandle(forwardedRef, () => scrollViewRef.current as ScrollView, []);\r\n\r\n const handleKeyboardShow = useCallback((e: any) => {\r\n setKeyboardHeight(e.endCoordinates.height);\r\n \r\n if (scrollViewRef.current && scrollToFocusedInput) {\r\n const keyboardHeight = e.endCoordinates.height;\r\n const safeAreaBottom = 0;\r\n const availableScreenHeight = windowHeight - keyboardHeight - safeAreaBottom;\r\n const currentScrollPosition = positionRef.current || 0;\r\n const touchPosition = lastTouchY.current || 0;\r\n\r\n // 현재 터치 위치와 스크롤 위치를 기반으로 새로운 스크롤 위치 계산\r\n const scrollOffset = touchPosition - availableScreenHeight + keyboardScrollExtraOffset;\r\n\r\n setTimeout(() => {\r\n scrollViewRef.current?.scrollTo({\r\n y: currentScrollPosition + scrollOffset,\r\n animated: true,\r\n });\r\n }, KEYBOARD_ANIMATION_DELAY);\r\n }\r\n }, [windowHeight, keyboardScrollExtraOffset, scrollToFocusedInput]);\r\n\r\n // 키보드 숨김 핸들러를 메모이제이션하여 성능 최적화\r\n const handleKeyboardHide = useCallback(() => {\r\n setKeyboardHeight(0);\r\n }, []);\r\n\r\n useEffect(() => {\r\n const keyboardShowSubscription = Keyboard.addListener(keyboardEvents.showEvent, handleKeyboardShow);\r\n const keyboardHideSubscription = Keyboard.addListener(keyboardEvents.hideEvent, handleKeyboardHide);\r\n\r\n return () => {\r\n positionRef.current = null;\r\n lastTouchY.current = null;\r\n keyboardShowSubscription.remove();\r\n keyboardHideSubscription.remove();\r\n };\r\n }, [keyboardEvents.showEvent, keyboardEvents.hideEvent, handleKeyboardShow, handleKeyboardHide]);\r\n\r\n const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {\r\n if (props.onScroll) props.onScroll(event);\r\n positionRef.current = event.nativeEvent.contentOffset.y;\r\n }, [props.onScroll]);\r\n\r\n const handleTouch = useCallback((evt: any) => {\r\n lastTouchY.current = evt.nativeEvent.pageY;\r\n }, []);\r\n\r\n const safeAreaStyle = useMemo(() => [\r\n { backgroundColor: backgroundColor || palette.background.base }, \r\n styles.flex1\r\n ], [backgroundColor, palette.background.base]);\r\n\r\n const scrollContentStyle = useMemo(() => [\r\n styles.scrollContainerStyle, \r\n { \r\n paddingBottom: keyboardHeight ? (IS_IOS ? keyboardHeight : keyboardScrollExtraOffset) : 0 \r\n }\r\n ], [keyboardHeight, keyboardScrollExtraOffset]);\r\n\r\n const containerStyle = useMemo(() => [\r\n styles.flex1, \r\n props.style\r\n ], [props.style]);\r\n\r\n const shouldShowStatusBar = useMemo(() => \r\n Boolean(barStyle || statusBarColor || translucent), \r\n [barStyle, statusBarColor, translucent]\r\n );\r\n\r\n return (\r\n <SafeAreaView\r\n style={safeAreaStyle}\r\n edges={edges}\r\n >\r\n <View style={styles.flex1}>\r\n {topComponent}\r\n {\r\n scrollViewDisabled ? (\r\n <View style={styles.flex1}>\r\n {props.children}\r\n </View>\r\n ) : (\r\n <ScrollView\r\n ref={scrollViewRef}\r\n style={styles.flex1}\r\n contentContainerStyle={scrollContentStyle}\r\n bounces={false}\r\n overScrollMode=\"never\"\r\n showsVerticalScrollIndicator={showsVerticalScrollIndicator}\r\n keyboardShouldPersistTaps=\"handled\"\r\n automaticallyAdjustKeyboardInsets={false}\r\n onScroll={handleScroll}\r\n onTouchStart={handleTouch}\r\n scrollEventThrottle={scrollEventThrottle}\r\n >\r\n <View style={containerStyle}>\r\n {props.children}\r\n </View>\r\n </ScrollView>\r\n )\r\n }\r\n {bottomComponent}\r\n </View>\r\n\r\n {shouldShowStatusBar && (\r\n <StatusBar\r\n barStyle={barStyle}\r\n backgroundColor={statusBarColor || palette.background.base}\r\n translucent={translucent}\r\n />\r\n )}\r\n </SafeAreaView>\r\n );\r\n});\r\n\r\nexport const styles = StyleSheet.create({\r\n flex1: { flex: 1, width: '100%' },\r\n scrollContainerStyle: { flexGrow: 1, alignItems: 'center', width: '100%' },\r\n});\r\n\r\nconst arePropsEqual = (\r\n prevProps: ZSContainerProps, \r\n nextProps: ZSContainerProps\r\n): boolean => {\r\n return (\r\n prevProps.backgroundColor === nextProps.backgroundColor &&\r\n prevProps.statusBarColor === nextProps.statusBarColor &&\r\n prevProps.barStyle === nextProps.barStyle &&\r\n prevProps.scrollViewDisabled === nextProps.scrollViewDisabled &&\r\n prevProps.showsVerticalScrollIndicator === nextProps.showsVerticalScrollIndicator &&\r\n prevProps.keyboardScrollExtraOffset === nextProps.keyboardScrollExtraOffset &&\r\n prevProps.translucent === nextProps.translucent &&\r\n prevProps.scrollEventThrottle === nextProps.scrollEventThrottle &&\r\n prevProps.scrollToFocusedInput === nextProps.scrollToFocusedInput &&\r\n prevProps.onScroll === nextProps.onScroll &&\r\n prevProps.style === nextProps.style &&\r\n prevProps.children === nextProps.children &&\r\n prevProps.topComponent === nextProps.topComponent &&\r\n prevProps.bottomComponent === nextProps.bottomComponent &&\r\n prevProps.rightComponent === nextProps.rightComponent &&\r\n // edges 배열 비교\r\n JSON.stringify(prevProps.edges) === JSON.stringify(nextProps.edges)\r\n );\r\n};\r\n\r\nexport default React.memo(ZSContainer, arePropsEqual);\r\n"]}
|
|
@@ -13,5 +13,6 @@ interface ZSPressableProps extends ViewProps {
|
|
|
13
13
|
color?: ViewColorOptions;
|
|
14
14
|
}
|
|
15
15
|
declare function ZSPressable({ onPress, onLongPress, isAnimation, pressedBackgroundColor, pressedBackgroundBorderRadius, elevationLevel, fullWidth, color, ...props }: ZSPressableProps): React.JSX.Element;
|
|
16
|
-
|
|
16
|
+
declare const _default: React.MemoExoticComponent<typeof ZSPressable>;
|
|
17
|
+
export default _default;
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSPressable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSPressable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAmB,SAAS,EAAE,MAAM,cAAc,CAAC;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAKrD,UAAU,gBAAiB,SAAQ,SAAS;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,iBAAS,WAAW,CAAC,EACnB,OAAO,EACP,WAAW,EACX,WAAkB,EAClB,sBAAmD,EACnD,6BAAkC,EAClC,cAAc,EACd,SAAiB,EACjB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,qBAkElB;;AAoBD,wBAAsD"}
|
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
1
|
+
import React, { useCallback, useMemo, useRef } from "react";
|
|
2
2
|
import { Pressable, View } from "react-native";
|
|
3
|
-
import Animated, { interpolate, useAnimatedStyle, useSharedValue, withTiming
|
|
3
|
+
import Animated, { interpolate, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
4
4
|
import AnimatedWrapper from "../atoms/AnimatedWrapper";
|
|
5
5
|
const DEFAULT_DURATION = { duration: 100 };
|
|
6
|
+
const SCALE_VALUES = [1, 0.96];
|
|
6
7
|
function ZSPressable({ onPress, onLongPress, isAnimation = true, pressedBackgroundColor = 'rgba(180, 180, 180, 0.1)', pressedBackgroundBorderRadius = 16, elevationLevel, fullWidth = false, color, ...props }) {
|
|
7
|
-
const isButtonPress = useSharedValue(0);
|
|
8
|
+
const isButtonPress = useRef(useSharedValue(0)).current;
|
|
8
9
|
const boxAnimation = useAnimatedStyle(() => {
|
|
9
|
-
const scale = interpolate(isButtonPress.value, [0, 1],
|
|
10
|
+
const scale = interpolate(isButtonPress.value, [0, 1], SCALE_VALUES, 'clamp');
|
|
10
11
|
return {
|
|
11
|
-
transform: [{ scale
|
|
12
|
+
transform: [{ scale }],
|
|
12
13
|
};
|
|
13
14
|
}, []);
|
|
15
|
+
const handlePressIn = useCallback(() => {
|
|
16
|
+
isButtonPress.value = withTiming(1, DEFAULT_DURATION);
|
|
17
|
+
}, [isButtonPress]);
|
|
18
|
+
const handlePressOut = useCallback(() => {
|
|
19
|
+
isButtonPress.value = withTiming(0, DEFAULT_DURATION);
|
|
20
|
+
}, [isButtonPress]);
|
|
21
|
+
const pressedStyle = useMemo(() => ({
|
|
22
|
+
backgroundColor: pressedBackgroundColor,
|
|
23
|
+
borderRadius: pressedBackgroundBorderRadius,
|
|
24
|
+
}), [pressedBackgroundColor, pressedBackgroundBorderRadius]);
|
|
25
|
+
const unpressedStyle = useMemo(() => ({
|
|
26
|
+
backgroundColor: 'transparent',
|
|
27
|
+
borderRadius: pressedBackgroundBorderRadius,
|
|
28
|
+
}), [pressedBackgroundBorderRadius]);
|
|
14
29
|
const handlePressStyle = useCallback((pressed) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}, [pressedBackgroundColor, pressedBackgroundBorderRadius, fullWidth]);
|
|
23
|
-
return (<View style={{ width: fullWidth ? '100%' : undefined }}>
|
|
24
|
-
<Pressable onPress={onPress} onLongPress={onLongPress} style={({ pressed }) => handlePressStyle(pressed)}>
|
|
30
|
+
return pressed ? pressedStyle : unpressedStyle;
|
|
31
|
+
}, [pressedStyle, unpressedStyle]);
|
|
32
|
+
const containerStyle = useMemo(() => ({
|
|
33
|
+
width: fullWidth ? '100%' : undefined
|
|
34
|
+
}), [fullWidth]);
|
|
35
|
+
return (<View style={containerStyle}>
|
|
36
|
+
<Pressable onPress={onPress} onLongPress={onLongPress} onPressIn={handlePressIn} onPressOut={handlePressOut} style={({ pressed }) => handlePressStyle(pressed)}>
|
|
25
37
|
<Animated.View style={boxAnimation}>
|
|
26
38
|
<AnimatedWrapper color={color} isAnimation={isAnimation} elevationLevel={elevationLevel} style={props.style}>
|
|
27
39
|
{props.children}
|
|
@@ -30,5 +42,17 @@ function ZSPressable({ onPress, onLongPress, isAnimation = true, pressedBackgrou
|
|
|
30
42
|
</Pressable>
|
|
31
43
|
</View>);
|
|
32
44
|
}
|
|
33
|
-
|
|
45
|
+
const arePropsEqual = (prevProps, nextProps) => {
|
|
46
|
+
return (prevProps.onPress === nextProps.onPress &&
|
|
47
|
+
prevProps.onLongPress === nextProps.onLongPress &&
|
|
48
|
+
prevProps.isAnimation === nextProps.isAnimation &&
|
|
49
|
+
prevProps.pressedBackgroundColor === nextProps.pressedBackgroundColor &&
|
|
50
|
+
prevProps.pressedBackgroundBorderRadius === nextProps.pressedBackgroundBorderRadius &&
|
|
51
|
+
prevProps.elevationLevel === nextProps.elevationLevel &&
|
|
52
|
+
prevProps.fullWidth === nextProps.fullWidth &&
|
|
53
|
+
prevProps.color === nextProps.color &&
|
|
54
|
+
prevProps.style === nextProps.style &&
|
|
55
|
+
prevProps.children === nextProps.children);
|
|
56
|
+
};
|
|
57
|
+
export default React.memo(ZSPressable, arePropsEqual);
|
|
34
58
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSPressable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSPressable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,IAAI,EAAa,MAAM,cAAc,CAAC;AAC1D,OAAO,QAAQ,EAAE,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC9G,OAAO,eAAe,MAAM,0BAA0B,CAAC;AAIvD,MAAM,gBAAgB,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAW,CAAC;AACpD,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,IAAI,CAAU,CAAC;AAaxC,SAAS,WAAW,CAAC,EACnB,OAAO,EACP,WAAW,EACX,WAAW,GAAG,IAAI,EAClB,sBAAsB,GAAG,0BAA0B,EACnD,6BAA6B,GAAG,EAAE,EAClC,cAAc,EACd,SAAS,GAAG,KAAK,EACjB,KAAK,EACL,GAAG,KAAK,EACS;IACjB,MAAM,aAAa,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAExD,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,WAAW,CACvB,aAAa,CAAC,KAAK,EACnB,CAAC,CAAC,EAAE,CAAC,CAAC,EACN,YAAY,EACZ,OAAO,CACR,CAAC;QACF,OAAO;YACL,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;SACvB,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,aAAa,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACxD,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,aAAa,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACxD,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAClC,eAAe,EAAE,sBAAsB;QACvC,YAAY,EAAE,6BAA6B;KAC5C,CAAC,EAAE,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAC,CAAC;IAE7D,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpC,eAAe,EAAE,aAAa;QAC9B,YAAY,EAAE,6BAA6B;KAC5C,CAAC,EAAE,CAAC,6BAA6B,CAAC,CAAC,CAAC;IAErC,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,OAAgB,EAAE,EAAE;QACnB,OAAO,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC;IACjD,CAAC,EACD,CAAC,YAAY,EAAE,cAAc,CAAC,CAC/B,CAAC;IAEF,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,SAAS;KAC/C,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEjB,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,CAC1B;MAAA,CAAC,SAAS,CACR,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,SAAS,CAAC,CAAC,aAAa,CAAC,CACzB,UAAU,CAAC,CAAC,cAAc,CAAC,CAC3B,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAElD;QAAA,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CACjC;UAAA,CAAC,eAAe,CACd,KAAK,CAAC,CAAC,KAAK,CAAC,CACb,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,cAAc,CAAC,CAAC,cAAc,CAAC,CAC/B,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAEnB;YAAA,CAAC,KAAK,CAAC,QAAQ,CACjB;UAAA,EAAE,eAAe,CACnB;QAAA,EAAE,QAAQ,CAAC,IAAI,CACjB;MAAA,EAAE,SAAS,CACb;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAAG,CACpB,SAA2B,EAC3B,SAA2B,EAClB,EAAE;IACX,OAAO,CACL,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO;QACvC,SAAS,CAAC,WAAW,KAAK,SAAS,CAAC,WAAW;QAC/C,SAAS,CAAC,WAAW,KAAK,SAAS,CAAC,WAAW;QAC/C,SAAS,CAAC,sBAAsB,KAAK,SAAS,CAAC,sBAAsB;QACrE,SAAS,CAAC,6BAA6B,KAAK,SAAS,CAAC,6BAA6B;QACnF,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,cAAc;QACrD,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS;QAC3C,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,KAAK;QACnC,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,KAAK;QACnC,SAAS,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,CAC1C,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC","sourcesContent":["import React, { useCallback, useMemo, useRef } from \"react\";\nimport { Pressable, View, ViewProps } from \"react-native\";\nimport Animated, { interpolate, useAnimatedStyle, useSharedValue, withTiming } from \"react-native-reanimated\";\nimport AnimatedWrapper from \"../atoms/AnimatedWrapper\";\nimport type { ShadowLevel } from \"../types\";\nimport { ViewColorOptions } from \"../../theme/types\";\n\nconst DEFAULT_DURATION = { duration: 100 } as const;\nconst SCALE_VALUES = [1, 0.96] as const;\n\ninterface ZSPressableProps extends ViewProps {\n onPress?: (value?: any) => void;\n onLongPress?: (value?: any) => void;\n pressedBackgroundColor?: string;\n pressedBackgroundBorderRadius?: number;\n isAnimation?: boolean;\n elevationLevel?: ShadowLevel;\n fullWidth?: boolean;\n color?: ViewColorOptions;\n}\n\nfunction ZSPressable({\n onPress,\n onLongPress,\n isAnimation = true,\n pressedBackgroundColor = 'rgba(180, 180, 180, 0.1)',\n pressedBackgroundBorderRadius = 16,\n elevationLevel,\n fullWidth = false,\n color,\n ...props\n}: ZSPressableProps) {\n const isButtonPress = useRef(useSharedValue(0)).current;\n\n const boxAnimation = useAnimatedStyle(() => {\n const scale = interpolate(\n isButtonPress.value,\n [0, 1],\n SCALE_VALUES,\n 'clamp'\n );\n return {\n transform: [{ scale }],\n };\n }, []);\n \n const handlePressIn = useCallback(() => {\n isButtonPress.value = withTiming(1, DEFAULT_DURATION);\n }, [isButtonPress]);\n \n const handlePressOut = useCallback(() => {\n isButtonPress.value = withTiming(0, DEFAULT_DURATION);\n }, [isButtonPress]);\n\n const pressedStyle = useMemo(() => ({\n backgroundColor: pressedBackgroundColor,\n borderRadius: pressedBackgroundBorderRadius,\n }), [pressedBackgroundColor, pressedBackgroundBorderRadius]);\n \n const unpressedStyle = useMemo(() => ({\n backgroundColor: 'transparent',\n borderRadius: pressedBackgroundBorderRadius,\n }), [pressedBackgroundBorderRadius]);\n \n const handlePressStyle = useCallback(\n (pressed: boolean) => {\n return pressed ? pressedStyle : unpressedStyle;\n },\n [pressedStyle, unpressedStyle]\n );\n\n const containerStyle = useMemo(() => ({\n width: fullWidth ? '100%' as const : undefined\n }), [fullWidth]);\n\n return (\n <View style={containerStyle}>\n <Pressable\n onPress={onPress}\n onLongPress={onLongPress}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n style={({ pressed }) => handlePressStyle(pressed)}\n >\n <Animated.View style={boxAnimation}>\n <AnimatedWrapper\n color={color}\n isAnimation={isAnimation}\n elevationLevel={elevationLevel}\n style={props.style}\n >\n {props.children}\n </AnimatedWrapper>\n </Animated.View>\n </Pressable>\n </View>\n );\n}\n\nconst arePropsEqual = (\n prevProps: ZSPressableProps, \n nextProps: ZSPressableProps\n): boolean => {\n return (\n prevProps.onPress === nextProps.onPress &&\n prevProps.onLongPress === nextProps.onLongPress &&\n prevProps.isAnimation === nextProps.isAnimation &&\n prevProps.pressedBackgroundColor === nextProps.pressedBackgroundColor &&\n prevProps.pressedBackgroundBorderRadius === nextProps.pressedBackgroundBorderRadius &&\n prevProps.elevationLevel === nextProps.elevationLevel &&\n prevProps.fullWidth === nextProps.fullWidth &&\n prevProps.color === nextProps.color &&\n prevProps.style === nextProps.style &&\n prevProps.children === nextProps.children\n );\n};\n\nexport default React.memo(ZSPressable, arePropsEqual);\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSTextField/index.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSTextField/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAqD,cAAc,EAAwB,MAAM,cAAc,CAAC;AAIvH,OAAO,EAAE,WAAW,EAA2B,MAAM,aAAa,CAAC;AAKnE,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAIzD,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,iBAAS,WAAW,CAAC,EACnB,IAAe,EACf,MAAkB,EAClB,KAAK,EACL,YAAY,EACZ,KAAqB,EACrB,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,WAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAiB,EACjB,iBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,QAAoB,EACpB,aAAa,EACb,QAAgB,EAChB,gBAAuB,EACvB,UAAkB,GACnB,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAoN9B;;AAoBD,wBAAsD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useCallback, useState, useEffect } from 'react';
|
|
1
|
+
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { Platform, TextInput } from 'react-native';
|
|
3
3
|
import Animated, { interpolate, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
|
4
4
|
import ButtonClose from './ui/ButtonClose';
|
|
@@ -9,31 +9,43 @@ import ViewAtom from '../atoms/ViewAtom';
|
|
|
9
9
|
const iosOffset = Platform.OS === 'ios' ? 8 : 4;
|
|
10
10
|
function ZSTextField({ typo = 'body.2', status = 'default', value, onChangeText, label = 'Placeholder', labelColor, placeHolderColor, inputBgColor, labelBgColor, borderWidth = 1.2, borderColor, focusColor, errorColor, borderRadius = 14, paddingHorizontal = 15, errorMessage, textInputProps, boxStyle = 'outline', innerBoxStyle, disabled = false, allowFontScaling = true, isTextArea = false, }) {
|
|
11
11
|
const { typography, palette } = useTheme();
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
12
|
+
const typoConfig = useMemo(() => {
|
|
13
|
+
const [primaryStyle, subStyle] = typo.split('.');
|
|
14
|
+
return { primaryStyle, subStyle };
|
|
15
|
+
}, [typo]);
|
|
16
|
+
const fErrorColor = useMemo(() => errorColor || palette.danger.main, [errorColor, palette.danger.main]);
|
|
17
|
+
const fontSize = useMemo(() => {
|
|
18
|
+
return extractStyle(typography[typoConfig.primaryStyle][typoConfig.subStyle], 'fontSize') || 17;
|
|
19
|
+
}, [typography, typoConfig.primaryStyle, typoConfig.subStyle]);
|
|
20
|
+
const fontFamily = useMemo(() => {
|
|
21
|
+
return extractStyle(typography[typoConfig.primaryStyle][typoConfig.subStyle], 'fontFamily') || '';
|
|
22
|
+
}, [typography, typoConfig.primaryStyle, typoConfig.subStyle]);
|
|
18
23
|
const [isFocused, setIsFocused] = useState(false);
|
|
19
|
-
const labelAnimationValue = useSharedValue(0);
|
|
20
|
-
const boxHeightValue = useSharedValue(0);
|
|
24
|
+
const labelAnimationValue = useRef(useSharedValue(0)).current;
|
|
25
|
+
const boxHeightValue = useRef(useSharedValue(0)).current;
|
|
21
26
|
// 포커스 및 값 변경 시 라벨 애니메이션 트리거
|
|
22
27
|
useEffect(() => {
|
|
23
|
-
labelAnimationValue.value = withTiming(value !== '' || isFocused ? 1 : 0, { duration:
|
|
24
|
-
}, [value, isFocused]);
|
|
28
|
+
labelAnimationValue.value = withTiming(value !== '' || isFocused ? 1 : 0, { duration: 100 });
|
|
29
|
+
}, [value, isFocused, labelAnimationValue]);
|
|
30
|
+
// 애니메이션 관련 상수
|
|
31
|
+
const animationConstants = useMemo(() => ({
|
|
32
|
+
baseFontSize: fontSize + (boxStyle === 'inbox' ? 1 : 0),
|
|
33
|
+
targetFontSize: boxStyle === 'inbox' ? 10 : 11,
|
|
34
|
+
baseTop: isTextArea ? 12 : 0,
|
|
35
|
+
targetTopOffset: boxStyle === 'inbox' ? 17 : 2,
|
|
36
|
+
}), [fontSize, boxStyle, isTextArea]);
|
|
25
37
|
// 라벨 애니메이션 스타일
|
|
26
38
|
const animatedLabelStyle = useAnimatedStyle(() => {
|
|
27
|
-
const labelFontSize = interpolate(labelAnimationValue.value, [0, 1], [
|
|
39
|
+
const labelFontSize = interpolate(labelAnimationValue.value, [0, 1], [animationConstants.baseFontSize, animationConstants.targetFontSize], 'clamp');
|
|
28
40
|
const labelTop = interpolate(labelAnimationValue.value, [0, 1], [
|
|
29
|
-
|
|
30
|
-
isTextArea ? -12 : -(boxHeightValue.value / 2) - 1 +
|
|
41
|
+
animationConstants.baseTop,
|
|
42
|
+
isTextArea ? -12 : -(boxHeightValue.value / 2) - 1 + animationConstants.targetTopOffset,
|
|
31
43
|
], 'clamp');
|
|
32
44
|
return {
|
|
33
45
|
top: labelTop,
|
|
34
46
|
fontSize: labelFontSize,
|
|
35
47
|
};
|
|
36
|
-
});
|
|
48
|
+
}, [animationConstants.baseFontSize, animationConstants.targetFontSize, animationConstants.baseTop, animationConstants.targetTopOffset, isTextArea]);
|
|
37
49
|
// 레이아웃 핸들러
|
|
38
50
|
const handleLayout = useCallback((event) => {
|
|
39
51
|
const { height } = event.nativeEvent.layout;
|
|
@@ -43,25 +55,74 @@ function ZSTextField({ typo = 'body.2', status = 'default', value, onChangeText,
|
|
|
43
55
|
// 포커스 및 블러 핸들러
|
|
44
56
|
const handleFocus = useCallback(() => setIsFocused(true), []);
|
|
45
57
|
const handleBlur = useCallback(() => setIsFocused(false), []);
|
|
58
|
+
const colorConfig = useMemo(() => ({
|
|
59
|
+
primaryColor: focusColor || palette.primary.main,
|
|
60
|
+
defaultBorderColor: borderColor || palette.grey[30],
|
|
61
|
+
defaultLabelColor: labelColor || palette.text.secondary,
|
|
62
|
+
placeholderColor: placeHolderColor || palette.grey[40],
|
|
63
|
+
}), [focusColor, palette.primary.main, borderColor, palette.grey, labelColor, palette.text.secondary, placeHolderColor]);
|
|
46
64
|
// 상태에 따른 테두리 색상 설정
|
|
47
|
-
const computedBorderColor = useMemo(() =>
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
const computedBorderColor = useMemo(() => {
|
|
66
|
+
if (status === 'error')
|
|
67
|
+
return fErrorColor;
|
|
68
|
+
if (isFocused)
|
|
69
|
+
return colorConfig.primaryColor;
|
|
70
|
+
return colorConfig.defaultBorderColor;
|
|
71
|
+
}, [status, fErrorColor, isFocused, colorConfig.primaryColor, colorConfig.defaultBorderColor]);
|
|
72
|
+
// 상태에 따른 라벨 색상 설정
|
|
73
|
+
const computedLabelColor = useMemo(() => {
|
|
74
|
+
if (status === 'error')
|
|
75
|
+
return fErrorColor;
|
|
76
|
+
if (isFocused)
|
|
77
|
+
return colorConfig.primaryColor;
|
|
78
|
+
if (value)
|
|
79
|
+
return colorConfig.defaultLabelColor;
|
|
80
|
+
return colorConfig.placeholderColor;
|
|
81
|
+
}, [status, fErrorColor, isFocused, value, colorConfig.primaryColor, colorConfig.defaultLabelColor, colorConfig.placeholderColor]);
|
|
82
|
+
const styleConfig = useMemo(() => {
|
|
83
|
+
const baseStyle = {
|
|
84
|
+
width: '100%',
|
|
85
|
+
justifyContent: isTextArea ? 'flex-start' : 'center',
|
|
86
|
+
borderRadius,
|
|
87
|
+
paddingHorizontal,
|
|
88
|
+
backgroundColor: inputBgColor || palette.background.base,
|
|
89
|
+
paddingTop: boxStyle === 'inbox' ? 13 : 0,
|
|
90
|
+
};
|
|
91
|
+
// 박스 스타일에 따른 테두리 설정
|
|
92
|
+
let borderStyle = {};
|
|
93
|
+
if (boxStyle === 'outline' || boxStyle === 'inbox') {
|
|
94
|
+
borderStyle = { borderWidth };
|
|
95
|
+
}
|
|
96
|
+
else if (boxStyle === 'underline') {
|
|
97
|
+
borderStyle = { borderBottomWidth: borderWidth };
|
|
98
|
+
}
|
|
99
|
+
// innerBoxStyle에 따른 스타일 설정
|
|
100
|
+
let innerStyle = {};
|
|
101
|
+
if (innerBoxStyle === 'top') {
|
|
102
|
+
innerStyle = {
|
|
103
|
+
borderBottomLeftRadius: 0,
|
|
104
|
+
borderBottomRightRadius: 0,
|
|
105
|
+
borderBottomWidth: borderWidth / 2
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else if (innerBoxStyle === 'middle') {
|
|
109
|
+
innerStyle = {
|
|
110
|
+
borderRadius: 0,
|
|
111
|
+
borderTopWidth: borderWidth / 2,
|
|
112
|
+
borderBottomWidth: borderWidth / 2
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
else if (innerBoxStyle === 'bottom') {
|
|
116
|
+
innerStyle = {
|
|
117
|
+
borderTopLeftRadius: 0,
|
|
118
|
+
borderTopRightRadius: 0,
|
|
119
|
+
borderTopWidth: borderWidth / 2
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { ...baseStyle, ...borderStyle, ...innerStyle };
|
|
123
|
+
}, [isTextArea, borderRadius, paddingHorizontal, inputBgColor, borderWidth, boxStyle, innerBoxStyle, palette.background.base]);
|
|
50
124
|
// 컨테이너 스타일 정의
|
|
51
|
-
const containerStyle =
|
|
52
|
-
width: '100%',
|
|
53
|
-
justifyContent: isTextArea ? 'flex-start' : 'center',
|
|
54
|
-
borderRadius,
|
|
55
|
-
paddingHorizontal,
|
|
56
|
-
backgroundColor: inputBgColor || palette.background.base,
|
|
57
|
-
paddingTop: boxStyle === 'inbox' ? 13 : 0,
|
|
58
|
-
...(boxStyle === 'outline' || boxStyle === 'inbox' ? { borderWidth } : {}),
|
|
59
|
-
...(boxStyle === 'underline' ? { borderBottomWidth: borderWidth } : {}),
|
|
60
|
-
...(innerBoxStyle === 'top' ? { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, borderBottomWidth: borderWidth / 2 }
|
|
61
|
-
: innerBoxStyle === 'middle' ? { borderRadius: 0, borderTopWidth: borderWidth / 2, borderBottomWidth: borderWidth / 2 }
|
|
62
|
-
: innerBoxStyle === 'bottom' ? { borderTopLeftRadius: 0, borderTopRightRadius: 0, borderTopWidth: borderWidth / 2 }
|
|
63
|
-
: {}),
|
|
64
|
-
}), [isTextArea, borderRadius, paddingHorizontal, inputBgColor, borderWidth, boxStyle, innerBoxStyle, palette]);
|
|
125
|
+
const containerStyle = styleConfig;
|
|
65
126
|
// 라벨 스타일 정의
|
|
66
127
|
const labelTextStyle = useMemo(() => ({
|
|
67
128
|
fontSize,
|
|
@@ -73,19 +134,29 @@ function ZSTextField({ typo = 'body.2', status = 'default', value, onChangeText,
|
|
|
73
134
|
fontFamily,
|
|
74
135
|
borderRadius: boxStyle === 'outline' ? 5 : 0,
|
|
75
136
|
overflow: 'hidden',
|
|
76
|
-
}), [fontSize, paddingHorizontal, labelBgColor, boxStyle, fontFamily, palette]);
|
|
137
|
+
}), [fontSize, paddingHorizontal, labelBgColor, boxStyle, fontFamily, palette.background.base]);
|
|
77
138
|
// 텍스트 변경 핸들러
|
|
78
139
|
const handleTextChange = useCallback((text) => {
|
|
79
140
|
if (onChangeText)
|
|
80
141
|
onChangeText(text);
|
|
81
142
|
}, [onChangeText]);
|
|
143
|
+
const textInputStyle = useMemo(() => [
|
|
144
|
+
{
|
|
145
|
+
paddingTop: 7 + iosOffset,
|
|
146
|
+
paddingBottom: 5 + iosOffset,
|
|
147
|
+
color: palette.text.base,
|
|
148
|
+
fontSize,
|
|
149
|
+
width: '100%',
|
|
150
|
+
paddingRight: 25,
|
|
151
|
+
fontFamily
|
|
152
|
+
},
|
|
153
|
+
textInputProps?.style,
|
|
154
|
+
], [palette.text.base, fontSize, fontFamily, textInputProps?.style]);
|
|
155
|
+
const shouldShowCloseButton = value && isFocused;
|
|
156
|
+
const shouldShowError = status === 'error' && errorMessage;
|
|
82
157
|
return (<ViewAtom style={{ alignSelf: 'stretch', width: '100%' }}>
|
|
83
158
|
<ViewAtom style={[containerStyle, { borderColor: computedBorderColor }]} onLayout={handleLayout} pointerEvents={disabled ? 'none' : 'auto'}>
|
|
84
|
-
<TextInput {...textInputProps} style={[
|
|
85
|
-
{ paddingTop: 7 + iosOffset, paddingBottom: 5 + iosOffset, color: palette.text.base },
|
|
86
|
-
textInputProps?.style,
|
|
87
|
-
{ fontSize, width: '100%', paddingRight: 25, fontFamily },
|
|
88
|
-
]} value={value} onFocus={handleFocus} onBlur={handleBlur} onChangeText={handleTextChange} allowFontScaling={allowFontScaling} selectionColor={palette.grey[50]} autoCorrect={false} spellCheck={false}/>
|
|
159
|
+
<TextInput {...textInputProps} style={textInputStyle} value={value} onFocus={handleFocus} onBlur={handleBlur} onChangeText={handleTextChange} allowFontScaling={allowFontScaling} selectionColor={palette.grey[50]} autoCorrect={false} spellCheck={false}/>
|
|
89
160
|
|
|
90
161
|
<ViewAtom pointerEvents="none" style={{ position: 'absolute' }}>
|
|
91
162
|
<Animated.Text allowFontScaling={allowFontScaling} style={[animatedLabelStyle, labelTextStyle, { color: computedLabelColor }]}>
|
|
@@ -93,11 +164,26 @@ function ZSTextField({ typo = 'body.2', status = 'default', value, onChangeText,
|
|
|
93
164
|
</Animated.Text>
|
|
94
165
|
</ViewAtom>
|
|
95
166
|
|
|
96
|
-
{
|
|
167
|
+
{shouldShowCloseButton && (<ButtonClose marginTop={isTextArea ? 13 : undefined} onChangeText={onChangeText}/>)}
|
|
97
168
|
</ViewAtom>
|
|
98
169
|
|
|
99
|
-
{
|
|
170
|
+
{shouldShowError && (<ErrorComponent errorMessage={errorMessage} errorColor={fErrorColor}/>)}
|
|
100
171
|
</ViewAtom>);
|
|
101
172
|
}
|
|
102
|
-
|
|
173
|
+
const arePropsEqual = (prevProps, nextProps) => {
|
|
174
|
+
return (prevProps.value === nextProps.value &&
|
|
175
|
+
prevProps.status === nextProps.status &&
|
|
176
|
+
prevProps.disabled === nextProps.disabled &&
|
|
177
|
+
prevProps.errorMessage === nextProps.errorMessage &&
|
|
178
|
+
prevProps.typo === nextProps.typo &&
|
|
179
|
+
prevProps.boxStyle === nextProps.boxStyle &&
|
|
180
|
+
prevProps.innerBoxStyle === nextProps.innerBoxStyle &&
|
|
181
|
+
prevProps.isTextArea === nextProps.isTextArea &&
|
|
182
|
+
prevProps.label === nextProps.label &&
|
|
183
|
+
prevProps.focusColor === nextProps.focusColor &&
|
|
184
|
+
prevProps.borderColor === nextProps.borderColor &&
|
|
185
|
+
prevProps.labelColor === nextProps.labelColor &&
|
|
186
|
+
prevProps.onChangeText === nextProps.onChangeText);
|
|
187
|
+
};
|
|
188
|
+
export default React.memo(ZSTextField, arePropsEqual);
|
|
103
189
|
//# sourceMappingURL=index.js.map
|