@0610studio/zs-ui 0.7.0 → 0.7.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.
Files changed (49) hide show
  1. package/build/index.d.ts +5 -4
  2. package/build/index.d.ts.map +1 -1
  3. package/build/index.js +2 -1
  4. package/build/index.js.map +1 -1
  5. package/build/model/types.d.ts +1 -11
  6. package/build/model/types.d.ts.map +1 -1
  7. package/build/model/types.js.map +1 -1
  8. package/build/model/useOverlay.d.ts +1 -3
  9. package/build/model/useOverlay.d.ts.map +1 -1
  10. package/build/model/useOverlay.js +0 -8
  11. package/build/model/useOverlay.js.map +1 -1
  12. package/build/model/useOverlayProvider.d.ts.map +1 -1
  13. package/build/model/useOverlayProvider.js +8 -38
  14. package/build/model/useOverlayProvider.js.map +1 -1
  15. package/build/model/utils.d.ts +1 -0
  16. package/build/model/utils.d.ts.map +1 -1
  17. package/build/model/utils.js +9 -0
  18. package/build/model/utils.js.map +1 -1
  19. package/build/overlay/BottomSheetOverlay/index.d.ts +2 -1
  20. package/build/overlay/BottomSheetOverlay/index.d.ts.map +1 -1
  21. package/build/overlay/BottomSheetOverlay/index.js +144 -91
  22. package/build/overlay/BottomSheetOverlay/index.js.map +1 -1
  23. package/build/ui/ZSAboveKeyboard/index.d.ts +7 -0
  24. package/build/ui/ZSAboveKeyboard/index.d.ts.map +1 -0
  25. package/build/ui/ZSAboveKeyboard/index.js +47 -0
  26. package/build/ui/ZSAboveKeyboard/index.js.map +1 -0
  27. package/build/ui/ZSContainer/index.d.ts +16 -14
  28. package/build/ui/ZSContainer/index.d.ts.map +1 -1
  29. package/build/ui/ZSContainer/index.js +83 -43
  30. package/build/ui/ZSContainer/index.js.map +1 -1
  31. package/build/ui/ZSPressable/index.d.ts +2 -1
  32. package/build/ui/ZSPressable/index.d.ts.map +1 -1
  33. package/build/ui/ZSPressable/index.js +40 -16
  34. package/build/ui/ZSPressable/index.js.map +1 -1
  35. package/build/ui/ZSTextField/index.d.ts.map +1 -1
  36. package/build/ui/ZSTextField/index.js +127 -41
  37. package/build/ui/ZSTextField/index.js.map +1 -1
  38. package/build/ui/atoms/AnimatedWrapper.d.ts.map +1 -1
  39. package/build/ui/atoms/AnimatedWrapper.js +58 -16
  40. package/build/ui/atoms/AnimatedWrapper.js.map +1 -1
  41. package/package.json +1 -1
  42. package/build/overlay/AboveKeyboard/index.d.ts +0 -4
  43. package/build/overlay/AboveKeyboard/index.d.ts.map +0 -1
  44. package/build/overlay/AboveKeyboard/index.js +0 -41
  45. package/build/overlay/AboveKeyboard/index.js.map +0 -1
  46. package/build/ui/ZSContainer/ui/VariantView.d.ts +0 -17
  47. package/build/ui/ZSContainer/ui/VariantView.d.ts.map +0 -1
  48. package/build/ui/ZSContainer/ui/VariantView.js +0 -13
  49. package/build/ui/ZSContainer/ui/VariantView.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, useRef } from 'react';
1
+ import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
2
2
  import { StyleSheet, View, PanResponder, Keyboard, Platform } from 'react-native';
3
3
  import { useBottomSheet } from '../../model/useOverlay';
4
4
  import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
@@ -6,53 +6,82 @@ import ModalBackground from '../ui/ModalBackground';
6
6
  import { useTheme } from '../../model';
7
7
  import { useSafeAreaInsets, initialWindowMetrics } from 'react-native-safe-area-context';
8
8
  import { MAX_OVERLAY_WIDTH, Z_INDEX_VALUE } from '../../model/utils';
9
+ const IS_IOS = Platform.OS === 'ios';
10
+ const keyboardEvents = ({
11
+ showEvent: IS_IOS ? 'keyboardWillShow' : 'keyboardDidShow',
12
+ hideEvent: IS_IOS ? 'keyboardWillHide' : 'keyboardDidHide',
13
+ });
14
+ const ANIMATION_CONFIG = {
15
+ keyboard: {
16
+ show: { duration: IS_IOS ? 250 : 300 },
17
+ hide: { duration: IS_IOS ? 150 : 200 },
18
+ },
19
+ spring: {
20
+ damping: 50,
21
+ stiffness: 300,
22
+ mass: 0.7,
23
+ velocity: 100,
24
+ restDisplacementThreshold: 0.2,
25
+ },
26
+ close: { duration: 150 },
27
+ scale: { duration: 200 },
28
+ scaleRestore: {
29
+ damping: 15,
30
+ stiffness: 300,
31
+ },
32
+ };
33
+ const GESTURE_CONSTANTS = {
34
+ scaleAmount: 0.985,
35
+ horizontalDamping: 18,
36
+ verticalUpDamping: 18,
37
+ verticalDownDamping: 1.5,
38
+ closeVelocityThreshold: 0.5,
39
+ closeDistanceRatio: 1 / 3,
40
+ hideDelay: 200,
41
+ };
9
42
  function BottomSheetOverlay({ headerComponent, component, options = {}, }) {
10
43
  const { isBackgroundTouchClose = true, marginHorizontal = 10, marginBottom = 10, padding = 14, } = options;
11
44
  const { palette, dimensions: { width: windowWidth, height: windowHeight } } = useTheme();
12
45
  const { bottomSheetVisible, setBottomSheetVisible, height } = useBottomSheet();
13
- // 화면의 크기보다 높이가 높으면 화면의 크기로 제한
14
- const maxHeight = Math.min((windowHeight - 30 - (initialWindowMetrics?.insets.bottom || 0) - (initialWindowMetrics?.insets.top || 0)), height);
15
- const translateY = useSharedValue(maxHeight);
16
- const translateX = useSharedValue(0);
17
- const scale = useSharedValue(1);
18
46
  const { bottom } = useSafeAreaInsets();
47
+ // 화면의 크기보다 높이가 높으면 화면의 크기로 제한
48
+ const maxHeight = useMemo(() => Math.min(windowHeight - 30 - (initialWindowMetrics?.insets.bottom || 0) - (initialWindowMetrics?.insets.top || 0), height), [windowHeight, height]);
49
+ const translateY = useRef(useSharedValue(0)).current;
50
+ const translateX = useRef(useSharedValue(0)).current;
51
+ const scale = useRef(useSharedValue(1)).current;
19
52
  const startX = useRef(0);
20
53
  const startY = useRef(0);
21
54
  const [localVisible, setLocalVisible] = useState(false);
22
- // ** 소프트 키보드 핸들링
55
+ const handleKeyboardShow = useCallback((event) => {
56
+ const targetY = IS_IOS ? (-event.endCoordinates.height + bottom) : 0;
57
+ translateY.value = withTiming(targetY, ANIMATION_CONFIG.keyboard.show);
58
+ }, [translateY, bottom]);
59
+ const handleKeyboardHide = useCallback(() => {
60
+ translateY.value = withTiming(0, ANIMATION_CONFIG.keyboard.hide);
61
+ }, [translateY]);
62
+ // 소프트 키보드 핸들링
23
63
  useEffect(() => {
24
- const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
25
- const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
26
- const keyboardShowSubscription = Keyboard.addListener(showEvent, (event) => {
27
- translateY.value = withTiming(Platform.OS === 'ios' ? (-event.endCoordinates.height + bottom) : 0, { duration: 300 });
28
- });
29
- const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {
30
- translateY.value = withTiming(0, { duration: 200 });
31
- });
64
+ const keyboardShowSubscription = Keyboard.addListener(keyboardEvents.showEvent, handleKeyboardShow);
65
+ const keyboardHideSubscription = Keyboard.addListener(keyboardEvents.hideEvent, handleKeyboardHide);
32
66
  return () => {
33
67
  keyboardShowSubscription.remove();
34
68
  keyboardHideSubscription.remove();
35
69
  };
36
- }, []);
70
+ }, [keyboardEvents.showEvent, keyboardEvents.hideEvent, handleKeyboardShow, handleKeyboardHide]);
71
+ // BottomSheet 표시/숨김 애니메이션 처리
37
72
  useEffect(() => {
38
73
  if (bottomSheetVisible) {
39
74
  Keyboard.dismiss();
40
75
  setLocalVisible(true);
41
- translateY.value = withSpring(0, {
42
- damping: 50,
43
- stiffness: 300,
44
- mass: 0.7,
45
- velocity: 100,
46
- restDisplacementThreshold: 0.2,
47
- });
76
+ translateY.value = withSpring(0, ANIMATION_CONFIG.spring);
48
77
  }
49
78
  else {
50
- translateY.value = withTiming(maxHeight + 100, { duration: 150 });
79
+ translateY.value = withTiming(maxHeight + 100, ANIMATION_CONFIG.close);
51
80
  setTimeout(() => {
52
81
  setLocalVisible(false);
53
- }, 200);
82
+ }, GESTURE_CONSTANTS.hideDelay);
54
83
  }
55
- }, [bottomSheetVisible]);
84
+ }, [bottomSheetVisible, translateY, maxHeight]);
56
85
  const animatedStyles = useAnimatedStyle(() => {
57
86
  return {
58
87
  transform: [
@@ -61,75 +90,91 @@ function BottomSheetOverlay({ headerComponent, component, options = {}, }) {
61
90
  { scale: scale.value }
62
91
  ],
63
92
  };
64
- });
65
- const panResponder = useRef(PanResponder.create({
93
+ }, []);
94
+ const handlePanResponderGrant = useCallback(() => {
95
+ Keyboard.dismiss();
96
+ startX.current = translateX.value;
97
+ startY.current = translateY.value;
98
+ scale.value = withTiming(GESTURE_CONSTANTS.scaleAmount, ANIMATION_CONFIG.scale);
99
+ }, [translateX, translateY, scale]);
100
+ const handlePanResponderMove = useCallback((_, gestureState) => {
101
+ const newTranslateX = (startX.current + gestureState.dx) / GESTURE_CONSTANTS.horizontalDamping;
102
+ translateX.value = newTranslateX;
103
+ const newTranslateY = startY.current + gestureState.dy;
104
+ if (newTranslateY < 0) {
105
+ translateY.value = newTranslateY / GESTURE_CONSTANTS.verticalUpDamping;
106
+ }
107
+ else {
108
+ translateY.value = newTranslateY / GESTURE_CONSTANTS.verticalDownDamping;
109
+ }
110
+ }, [translateX, translateY]);
111
+ const handlePanResponderRelease = useCallback((_, gestureState) => {
112
+ translateX.value = withTiming(0, { duration: 100 });
113
+ // 빠른 플리킹 제스처를 했을 때, 혹은 화면의 1/3 이상 내렸을 때, 닫기
114
+ const shouldClose = gestureState.vy > GESTURE_CONSTANTS.closeVelocityThreshold ||
115
+ translateY.value > maxHeight * GESTURE_CONSTANTS.closeDistanceRatio;
116
+ if (shouldClose) {
117
+ translateY.value = withTiming(maxHeight + 100, ANIMATION_CONFIG.close);
118
+ setBottomSheetVisible(false);
119
+ }
120
+ else {
121
+ translateY.value = withTiming(0, ANIMATION_CONFIG.close);
122
+ }
123
+ // 사이즈 원래대로 복귀
124
+ scale.value = withSpring(1, ANIMATION_CONFIG.scaleRestore);
125
+ }, [translateX, translateY, scale, maxHeight, setBottomSheetVisible]);
126
+ const panResponder = useMemo(() => PanResponder.create({
66
127
  onStartShouldSetPanResponder: () => true,
67
128
  onMoveShouldSetPanResponder: () => true,
68
- onPanResponderGrant: () => {
69
- Keyboard.dismiss();
70
- startX.current = translateX.value;
71
- startY.current = translateY.value;
72
- scale.value = withTiming(0.985, { duration: 200 });
129
+ onPanResponderGrant: handlePanResponderGrant,
130
+ onPanResponderMove: handlePanResponderMove,
131
+ onPanResponderRelease: handlePanResponderRelease,
132
+ }), [handlePanResponderGrant, handlePanResponderMove, handlePanResponderRelease]);
133
+ // 배경 터치 핸들러
134
+ const handleBackgroundPress = useCallback(() => {
135
+ if (isBackgroundTouchClose)
136
+ setBottomSheetVisible(false);
137
+ }, [isBackgroundTouchClose, setBottomSheetVisible]);
138
+ const containerStyle = useMemo(() => [
139
+ styles.container,
140
+ {
141
+ width: windowWidth - marginHorizontal * 2,
142
+ height: maxHeight,
143
+ marginHorizontal,
144
+ bottom: marginBottom + bottom,
145
+ backgroundColor: palette.background.base,
73
146
  },
74
- onPanResponderMove: (_, gestureState) => {
75
- const newTranslateX = (startX.current + gestureState.dx) / 18;
76
- translateX.value = newTranslateX;
77
- const newTranslateY = startY.current + gestureState.dy;
78
- if (newTranslateY < 0) {
79
- translateY.value = newTranslateY / 18;
80
- }
81
- else {
82
- translateY.value = newTranslateY / 1.5;
83
- }
84
- },
85
- onPanResponderRelease: (_, gestureState) => {
86
- translateX.value = withTiming(0, { duration: 100 });
87
- // 빠른 플리킹 제스처를 했을 때, 혹은 화면의 1/3 이상 내렸을 때, 닫기
88
- if (gestureState.vy > 0.5 || translateY.value > maxHeight / 3) {
89
- translateY.value = withTiming(maxHeight + 100, { duration: 150 });
90
- setBottomSheetVisible(false);
91
- }
92
- else {
93
- translateY.value = withTiming(0, { duration: 150 });
94
- }
95
- // 사이즈 원래대로 복귀
96
- scale.value = withSpring(1, {
97
- damping: 15,
98
- stiffness: 300
99
- });
100
- },
101
- })).current;
102
- return (!localVisible ? null :
103
- <ModalBackground zIndex={Z_INDEX_VALUE.BOTTOM_SHEET1} key={localVisible ? 'visiblebs' : 'hiddenbs'} modalBgColor={palette.modalBgColor} onPress={() => {
104
- if (isBackgroundTouchClose)
105
- setBottomSheetVisible(false);
106
- }}>
107
- <Animated.View style={[
108
- styles.container,
109
- {
110
- width: windowWidth - marginHorizontal * 2,
111
- height: maxHeight,
112
- marginHorizontal,
113
- bottom: marginBottom + bottom,
114
- backgroundColor: palette.background.base,
115
- },
116
- animatedStyles,
117
- ]}>
118
- <View style={[
119
- styles.pressableView,
120
- { paddingHorizontal: padding, paddingBottom: padding },
121
- ]}>
122
- <View {...panResponder.panHandlers}>
123
- <View style={[styles.gestureBarContainer, { paddingBottom: padding }]}>
124
- <View style={[styles.gestureBar, { backgroundColor: palette.divider }]}/>
125
- </View>
126
- {headerComponent}
147
+ animatedStyles,
148
+ ], [windowWidth, marginHorizontal, maxHeight, marginBottom, bottom, palette.background.base, animatedStyles]);
149
+ const pressableViewStyle = useMemo(() => [
150
+ styles.pressableView,
151
+ { paddingHorizontal: padding, paddingBottom: padding },
152
+ ], [padding]);
153
+ const gestureBarContainerStyle = useMemo(() => [
154
+ styles.gestureBarContainer,
155
+ { paddingBottom: padding }
156
+ ], [padding]);
157
+ const gestureBarStyle = useMemo(() => [
158
+ styles.gestureBar,
159
+ { backgroundColor: palette.divider }
160
+ ], [palette.divider]);
161
+ if (!localVisible) {
162
+ return null;
163
+ }
164
+ return (<ModalBackground zIndex={Z_INDEX_VALUE.BOTTOM_SHEET1} key={localVisible ? 'visiblebs' : 'hiddenbs'} modalBgColor={palette.modalBgColor} onPress={handleBackgroundPress}>
165
+ <Animated.View style={containerStyle}>
166
+ <View style={pressableViewStyle}>
167
+ <View {...panResponder.panHandlers}>
168
+ <View style={gestureBarContainerStyle}>
169
+ <View style={gestureBarStyle}/>
127
170
  </View>
128
-
129
- {component}
171
+ {headerComponent}
130
172
  </View>
131
- </Animated.View>
132
- </ModalBackground>);
173
+
174
+ {component}
175
+ </View>
176
+ </Animated.View>
177
+ </ModalBackground>);
133
178
  }
134
179
  const styles = StyleSheet.create({
135
180
  container: {
@@ -155,5 +200,13 @@ const styles = StyleSheet.create({
155
200
  borderRadius: 2,
156
201
  },
157
202
  });
158
- export default BottomSheetOverlay;
203
+ const arePropsEqual = (prevProps, nextProps) => {
204
+ return (prevProps.headerComponent === nextProps.headerComponent &&
205
+ prevProps.component === nextProps.component &&
206
+ prevProps.options?.isBackgroundTouchClose === nextProps.options?.isBackgroundTouchClose &&
207
+ prevProps.options?.marginHorizontal === nextProps.options?.marginHorizontal &&
208
+ prevProps.options?.marginBottom === nextProps.options?.marginBottom &&
209
+ prevProps.options?.padding === nextProps.options?.padding);
210
+ };
211
+ export default React.memo(BottomSheetOverlay, arePropsEqual);
159
212
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/overlay/BottomSheetOverlay/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useState, useRef } from 'react';\nimport { StyleSheet, View, PanResponder, Keyboard, Platform } from 'react-native';\nimport { useBottomSheet } from '../../model/useOverlay';\nimport Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';\nimport ModalBackground from '../ui/ModalBackground';\nimport { useTheme } from '../../model';\nimport { useSafeAreaInsets, initialWindowMetrics } from 'react-native-safe-area-context';\nimport { ShowBottomSheetProps } from '../../model/types';\nimport { MAX_OVERLAY_WIDTH, Z_INDEX_VALUE } from '../../model/utils';\n\nfunction BottomSheetOverlay({\n headerComponent,\n component,\n options = {},\n}: ShowBottomSheetProps) {\n const {\n isBackgroundTouchClose = true,\n marginHorizontal = 10,\n marginBottom = 10,\n padding = 14,\n } = options;\n const { palette, dimensions: { width: windowWidth, height: windowHeight } } = useTheme();\n const { bottomSheetVisible, setBottomSheetVisible, height } = useBottomSheet();\n // 화면의 크기보다 높이가 높으면 화면의 크기로 제한\n const maxHeight = Math.min((windowHeight - 30 - (initialWindowMetrics?.insets.bottom || 0) - (initialWindowMetrics?.insets.top || 0)), height);\n const translateY = useSharedValue(maxHeight);\n const translateX = useSharedValue(0);\n const scale = useSharedValue(1);\n const { bottom } = useSafeAreaInsets();\n const startX = useRef(0);\n const startY = useRef(0);\n const [localVisible, setLocalVisible] = useState(false);\n\n // ** 소프트 키보드 핸들링\n useEffect(() => {\n const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';\n const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';\n\n const keyboardShowSubscription = Keyboard.addListener(showEvent, (event) => {\n translateY.value = withTiming(Platform.OS === 'ios' ? (-event.endCoordinates.height + bottom) : 0, { duration: 300 });\n });\n\n const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {\n translateY.value = withTiming(0, { duration: 200 });\n });\n\n return () => {\n keyboardShowSubscription.remove();\n keyboardHideSubscription.remove();\n };\n }, []);\n\n useEffect(() => {\n if (bottomSheetVisible) {\n Keyboard.dismiss();\n setLocalVisible(true);\n translateY.value = withSpring(0, {\n damping: 50,\n stiffness: 300,\n mass: 0.7,\n velocity: 100,\n restDisplacementThreshold: 0.2,\n });\n } else {\n translateY.value = withTiming(maxHeight + 100, { duration: 150 });\n setTimeout(() => {\n setLocalVisible(false);\n }, 200);\n }\n }, [bottomSheetVisible]);\n\n const animatedStyles = useAnimatedStyle(() => {\n return {\n transform: [\n { translateY: translateY.value },\n { translateX: translateX.value },\n { scale: scale.value }\n ],\n };\n });\n\n const panResponder = useRef(\n PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n onPanResponderGrant: () => {\n Keyboard.dismiss();\n startX.current = translateX.value;\n startY.current = translateY.value;\n scale.value = withTiming(0.985, { duration: 200 });\n\n },\n onPanResponderMove: (_, gestureState) => {\n const newTranslateX = (startX.current + gestureState.dx) / 18;\n translateX.value = newTranslateX;\n\n const newTranslateY = startY.current + gestureState.dy;\n if (newTranslateY < 0) {\n translateY.value = newTranslateY / 18;\n } else {\n translateY.value = newTranslateY / 1.5;\n }\n },\n onPanResponderRelease: (_, gestureState) => {\n translateX.value = withTiming(0, { duration: 100 });\n\n // 빠른 플리킹 제스처를 했을 때, 혹은 화면의 1/3 이상 내렸을 때, 닫기\n if (gestureState.vy > 0.5 || translateY.value > maxHeight / 3) {\n translateY.value = withTiming(maxHeight + 100, { duration: 150 });\n setBottomSheetVisible(false);\n } else {\n translateY.value = withTiming(0, { duration: 150 });\n }\n\n // 사이즈 원래대로 복귀\n scale.value = withSpring(1, {\n damping: 15,\n stiffness: 300\n });\n },\n })\n ).current;\n\n return (\n !localVisible ? null :\n <ModalBackground\n zIndex={Z_INDEX_VALUE.BOTTOM_SHEET1}\n key={localVisible ? 'visiblebs' : 'hiddenbs'}\n modalBgColor={palette.modalBgColor}\n onPress={() => {\n if (isBackgroundTouchClose) setBottomSheetVisible(false);\n }}\n >\n <Animated.View\n style={[\n styles.container,\n {\n width: windowWidth - marginHorizontal * 2,\n height: maxHeight,\n marginHorizontal,\n bottom: marginBottom + bottom,\n backgroundColor: palette.background.base,\n },\n animatedStyles,\n ]}\n >\n <View\n style={[\n styles.pressableView,\n { paddingHorizontal: padding, paddingBottom: padding },\n ]}\n >\n <View {...panResponder.panHandlers}>\n <View style={[styles.gestureBarContainer, { paddingBottom: padding }]}>\n <View style={[styles.gestureBar, { backgroundColor: palette.divider }]} />\n </View>\n {headerComponent}\n </View>\n\n {component}\n </View>\n </Animated.View>\n </ModalBackground>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n position: 'absolute',\n borderRadius: 26,\n overflow: 'hidden',\n zIndex: Z_INDEX_VALUE.BOTTOM_SHEET2,\n maxWidth: MAX_OVERLAY_WIDTH,\n },\n pressableView: {\n width: '100%',\n height: '100%',\n },\n gestureBarContainer: {\n width: '100%',\n paddingTop: 10,\n justifyContent: 'center',\n alignItems: 'center',\n },\n gestureBar: {\n width: 45,\n height: 3,\n borderRadius: 2,\n },\n});\n\nexport default BottomSheetOverlay;"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/overlay/BottomSheetOverlay/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';\nimport { StyleSheet, View, PanResponder, Keyboard, Platform } from 'react-native';\nimport { useBottomSheet } from '../../model/useOverlay';\nimport Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';\nimport ModalBackground from '../ui/ModalBackground';\nimport { useTheme } from '../../model';\nimport { useSafeAreaInsets, initialWindowMetrics } from 'react-native-safe-area-context';\nimport { ShowBottomSheetProps } from '../../model/types';\nimport { MAX_OVERLAY_WIDTH, Z_INDEX_VALUE } from '../../model/utils';\n\nconst IS_IOS = Platform.OS === 'ios';\n\nconst keyboardEvents = ({\n showEvent: IS_IOS ? 'keyboardWillShow' as const : 'keyboardDidShow' as const,\n hideEvent: IS_IOS ? 'keyboardWillHide' as const : 'keyboardDidHide' as const,\n});\n\nconst ANIMATION_CONFIG = {\n keyboard: {\n show: { duration: IS_IOS ? 250 : 300 },\n hide: { duration: IS_IOS ? 150 : 200 },\n },\n spring: {\n damping: 50,\n stiffness: 300,\n mass: 0.7,\n velocity: 100,\n restDisplacementThreshold: 0.2,\n },\n close: { duration: 150 },\n scale: { duration: 200 },\n scaleRestore: {\n damping: 15,\n stiffness: 300,\n },\n} as const;\n\nconst GESTURE_CONSTANTS = {\n scaleAmount: 0.985,\n horizontalDamping: 18,\n verticalUpDamping: 18,\n verticalDownDamping: 1.5,\n closeVelocityThreshold: 0.5,\n closeDistanceRatio: 1 / 3,\n hideDelay: 200,\n} as const;\n\nfunction BottomSheetOverlay({\n headerComponent,\n component,\n options = {},\n}: ShowBottomSheetProps) {\n const {\n isBackgroundTouchClose = true,\n marginHorizontal = 10,\n marginBottom = 10,\n padding = 14,\n } = options;\n const { palette, dimensions: { width: windowWidth, height: windowHeight } } = useTheme();\n const { bottomSheetVisible, setBottomSheetVisible, height } = useBottomSheet();\n const { bottom } = useSafeAreaInsets();\n \n // 화면의 크기보다 높이가 높으면 화면의 크기로 제한 \n const maxHeight = useMemo(() => \n Math.min(\n windowHeight - 30 - (initialWindowMetrics?.insets.bottom || 0) - (initialWindowMetrics?.insets.top || 0),\n height\n ),\n [windowHeight, height]\n );\n \n const translateY = useRef(useSharedValue(0)).current;\n const translateX = useRef(useSharedValue(0)).current;\n const scale = useRef(useSharedValue(1)).current;\n \n const startX = useRef(0);\n const startY = useRef(0);\n const [localVisible, setLocalVisible] = useState(false);\n\n const handleKeyboardShow = useCallback((event: any) => {\n const targetY = IS_IOS ? (-event.endCoordinates.height + bottom) : 0;\n translateY.value = withTiming(targetY, ANIMATION_CONFIG.keyboard.show);\n }, [translateY, bottom]);\n\n const handleKeyboardHide = useCallback(() => {\n translateY.value = withTiming(0, ANIMATION_CONFIG.keyboard.hide);\n }, [translateY]);\n\n // 소프트 키보드 핸들링\n useEffect(() => {\n const keyboardShowSubscription = Keyboard.addListener(keyboardEvents.showEvent, handleKeyboardShow);\n const keyboardHideSubscription = Keyboard.addListener(keyboardEvents.hideEvent, handleKeyboardHide);\n\n return () => {\n keyboardShowSubscription.remove();\n keyboardHideSubscription.remove();\n };\n }, [keyboardEvents.showEvent, keyboardEvents.hideEvent, handleKeyboardShow, handleKeyboardHide]);\n\n // BottomSheet 표시/숨김 애니메이션 처리\n useEffect(() => {\n if (bottomSheetVisible) {\n Keyboard.dismiss();\n setLocalVisible(true);\n translateY.value = withSpring(0, ANIMATION_CONFIG.spring);\n } else {\n translateY.value = withTiming(maxHeight + 100, ANIMATION_CONFIG.close);\n setTimeout(() => {\n setLocalVisible(false);\n }, GESTURE_CONSTANTS.hideDelay);\n }\n }, [bottomSheetVisible, translateY, maxHeight]);\n\n const animatedStyles = useAnimatedStyle(() => {\n return {\n transform: [\n { translateY: translateY.value },\n { translateX: translateX.value },\n { scale: scale.value }\n ],\n };\n }, []);\n\n const handlePanResponderGrant = useCallback(() => {\n Keyboard.dismiss();\n startX.current = translateX.value;\n startY.current = translateY.value;\n scale.value = withTiming(GESTURE_CONSTANTS.scaleAmount, ANIMATION_CONFIG.scale);\n }, [translateX, translateY, scale]);\n\n const handlePanResponderMove = useCallback((_, gestureState) => {\n const newTranslateX = (startX.current + gestureState.dx) / GESTURE_CONSTANTS.horizontalDamping;\n translateX.value = newTranslateX;\n\n const newTranslateY = startY.current + gestureState.dy;\n if (newTranslateY < 0) {\n translateY.value = newTranslateY / GESTURE_CONSTANTS.verticalUpDamping;\n } else {\n translateY.value = newTranslateY / GESTURE_CONSTANTS.verticalDownDamping;\n }\n }, [translateX, translateY]);\n\n const handlePanResponderRelease = useCallback((_, gestureState) => {\n translateX.value = withTiming(0, { duration: 100 });\n\n // 빠른 플리킹 제스처를 했을 때, 혹은 화면의 1/3 이상 내렸을 때, 닫기\n const shouldClose = gestureState.vy > GESTURE_CONSTANTS.closeVelocityThreshold || \n translateY.value > maxHeight * GESTURE_CONSTANTS.closeDistanceRatio;\n \n if (shouldClose) {\n translateY.value = withTiming(maxHeight + 100, ANIMATION_CONFIG.close);\n setBottomSheetVisible(false);\n } else {\n translateY.value = withTiming(0, ANIMATION_CONFIG.close);\n }\n\n // 사이즈 원래대로 복귀\n scale.value = withSpring(1, ANIMATION_CONFIG.scaleRestore);\n }, [translateX, translateY, scale, maxHeight, setBottomSheetVisible]);\n\n const panResponder = useMemo(\n () => PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n onPanResponderGrant: handlePanResponderGrant,\n onPanResponderMove: handlePanResponderMove,\n onPanResponderRelease: handlePanResponderRelease,\n }),\n [handlePanResponderGrant, handlePanResponderMove, handlePanResponderRelease]\n );\n\n // 배경 터치 핸들러\n const handleBackgroundPress = useCallback(() => {\n if (isBackgroundTouchClose) setBottomSheetVisible(false);\n }, [isBackgroundTouchClose, setBottomSheetVisible]);\n\n const containerStyle = useMemo(() => [\n styles.container,\n {\n width: windowWidth - marginHorizontal * 2,\n height: maxHeight,\n marginHorizontal,\n bottom: marginBottom + bottom,\n backgroundColor: palette.background.base,\n },\n animatedStyles,\n ], [windowWidth, marginHorizontal, maxHeight, marginBottom, bottom, palette.background.base, animatedStyles]);\n\n const pressableViewStyle = useMemo(() => [\n styles.pressableView,\n { paddingHorizontal: padding, paddingBottom: padding },\n ], [padding]);\n\n const gestureBarContainerStyle = useMemo(() => [\n styles.gestureBarContainer,\n { paddingBottom: padding }\n ], [padding]);\n\n const gestureBarStyle = useMemo(() => [\n styles.gestureBar,\n { backgroundColor: palette.divider }\n ], [palette.divider]);\n\n if (!localVisible) {\n return null;\n }\n\n return (\n <ModalBackground\n zIndex={Z_INDEX_VALUE.BOTTOM_SHEET1}\n key={localVisible ? 'visiblebs' : 'hiddenbs'}\n modalBgColor={palette.modalBgColor}\n onPress={handleBackgroundPress}\n >\n <Animated.View style={containerStyle}>\n <View style={pressableViewStyle}>\n <View {...panResponder.panHandlers}>\n <View style={gestureBarContainerStyle}>\n <View style={gestureBarStyle} />\n </View>\n {headerComponent}\n </View>\n\n {component}\n </View>\n </Animated.View>\n </ModalBackground>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n position: 'absolute',\n borderRadius: 26,\n overflow: 'hidden',\n zIndex: Z_INDEX_VALUE.BOTTOM_SHEET2,\n maxWidth: MAX_OVERLAY_WIDTH,\n },\n pressableView: {\n width: '100%',\n height: '100%',\n },\n gestureBarContainer: {\n width: '100%',\n paddingTop: 10,\n justifyContent: 'center',\n alignItems: 'center',\n },\n gestureBar: {\n width: 45,\n height: 3,\n borderRadius: 2,\n },\n});\n\nconst arePropsEqual = (\n prevProps: ShowBottomSheetProps, \n nextProps: ShowBottomSheetProps\n): boolean => {\n return (\n prevProps.headerComponent === nextProps.headerComponent &&\n prevProps.component === nextProps.component &&\n prevProps.options?.isBackgroundTouchClose === nextProps.options?.isBackgroundTouchClose &&\n prevProps.options?.marginHorizontal === nextProps.options?.marginHorizontal &&\n prevProps.options?.marginBottom === nextProps.options?.marginBottom &&\n prevProps.options?.padding === nextProps.options?.padding\n );\n};\n\nexport default React.memo(BottomSheetOverlay, arePropsEqual);"]}
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ render: () => React.ReactNode;
3
+ offset?: number;
4
+ }
5
+ declare function ZSAboveKeyboard({ render, offset, }: Props): import("react").JSX.Element;
6
+ export default ZSAboveKeyboard;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSAboveKeyboard/index.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,iBAAS,eAAe,CAAC,EACvB,MAAM,EACN,MAAU,GACX,EAAE,KAAK,+BA4CP;AAED,eAAe,eAAe,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Keyboard, Platform, StyleSheet, Dimensions } from 'react-native';
3
+ import Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated';
4
+ import { getActualTopInset } from '../../model/utils';
5
+ function ZSAboveKeyboard({ render, offset = 0, }) {
6
+ const [topValue, setTopValue] = useState(0);
7
+ const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
8
+ const [componentHeight, setComponentHeight] = useState(0);
9
+ const screenHeight = Dimensions.get('window').height;
10
+ const actualTop = getActualTopInset();
11
+ useEffect(() => {
12
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
13
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
14
+ const keyboardShowSubscription = Keyboard.addListener(showEvent, (event) => {
15
+ // 키보드 바로 위에 위치하도록 계산: 화면 높이 - 키보드 높이 - 컴포넌트 높이 - offset
16
+ const topValue = screenHeight - event.endCoordinates.height - componentHeight - offset + actualTop;
17
+ setTopValue(topValue);
18
+ setIsKeyboardVisible(true);
19
+ });
20
+ const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {
21
+ setTopValue(0);
22
+ setIsKeyboardVisible(false);
23
+ });
24
+ return () => {
25
+ keyboardShowSubscription.remove();
26
+ keyboardHideSubscription.remove();
27
+ };
28
+ }, [screenHeight, offset, componentHeight, actualTop]);
29
+ const handleLayout = (event) => {
30
+ const { height } = event.nativeEvent.layout;
31
+ setComponentHeight(height + (Platform.OS === 'ios' ? 100 : 80));
32
+ };
33
+ return (<Animated.View entering={FadeInDown} exiting={FadeOutDown} style={[styles.container, isKeyboardVisible ? { top: topValue } : { bottom: 0 }]} onLayout={handleLayout}>
34
+ {render()}
35
+ </Animated.View>);
36
+ }
37
+ export default ZSAboveKeyboard;
38
+ const styles = StyleSheet.create({
39
+ container: {
40
+ position: 'absolute',
41
+ justifyContent: 'center',
42
+ alignItems: 'center',
43
+ zIndex: 8400,
44
+ width: '100%',
45
+ },
46
+ });
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSAboveKeyboard/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from 'react';\nimport { Keyboard, Platform, StyleSheet, Dimensions } from 'react-native';\nimport Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated';\nimport { getActualTopInset } from '../../model/utils';\n\ninterface Props {\n render: () => React.ReactNode;\n offset?: number;\n}\n\nfunction ZSAboveKeyboard({\n render,\n offset = 0,\n}: Props) {\n const [topValue, setTopValue] = useState(0);\n const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);\n const [componentHeight, setComponentHeight] = useState(0);\n const screenHeight = Dimensions.get('window').height;\n const actualTop = getActualTopInset();\n\n useEffect(() => {\n const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';\n const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';\n\n const keyboardShowSubscription = Keyboard.addListener(showEvent, (event) => {\n // 키보드 바로 위에 위치하도록 계산: 화면 높이 - 키보드 높이 - 컴포넌트 높이 - offset\n const topValue = screenHeight - event.endCoordinates.height - componentHeight - offset + actualTop;\n setTopValue(topValue);\n setIsKeyboardVisible(true);\n });\n\n const keyboardHideSubscription = Keyboard.addListener(hideEvent, () => {\n setTopValue(0);\n setIsKeyboardVisible(false);\n });\n\n return () => {\n keyboardShowSubscription.remove();\n keyboardHideSubscription.remove();\n };\n }, [screenHeight, offset, componentHeight, actualTop]);\n\n const handleLayout = (event: any) => {\n const { height } = event.nativeEvent.layout;\n setComponentHeight(height + (Platform.OS === 'ios' ? 100 : 80));\n };\n\n return (\n <Animated.View\n entering={FadeInDown}\n exiting={FadeOutDown}\n style={[styles.container, isKeyboardVisible ? { top: topValue } : { bottom: 0 }]}\n onLayout={handleLayout}\n >\n {render()}\n </Animated.View>\n );\n}\n\nexport default ZSAboveKeyboard;\n\nconst styles = StyleSheet.create({\n container: {\n position: 'absolute',\n justifyContent: 'center',\n alignItems: 'center',\n zIndex: 8400,\n width: '100%',\n },\n});\n"]}
@@ -14,9 +14,21 @@ export type ZSContainerProps = ViewProps & {
14
14
  translucent?: boolean;
15
15
  onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
16
16
  scrollEventThrottle?: number;
17
+ scrollToFocusedInput?: boolean;
17
18
  };
18
19
  export type ZSContainerRef = ScrollView;
19
- declare const ZSContainer: React.ForwardRefExoticComponent<ViewProps & {
20
+ export declare const styles: {
21
+ flex1: {
22
+ flex: number;
23
+ width: "100%";
24
+ };
25
+ scrollContainerStyle: {
26
+ flexGrow: number;
27
+ alignItems: "center";
28
+ width: "100%";
29
+ };
30
+ };
31
+ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<ViewProps & {
20
32
  backgroundColor?: string;
21
33
  statusBarColor?: string;
22
34
  barStyle?: "light-content" | "dark-content";
@@ -30,17 +42,7 @@ declare const ZSContainer: React.ForwardRefExoticComponent<ViewProps & {
30
42
  translucent?: boolean;
31
43
  onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
32
44
  scrollEventThrottle?: number;
33
- } & React.RefAttributes<ScrollView>>;
34
- export declare const styles: {
35
- flex1: {
36
- flex: number;
37
- width: "100%";
38
- };
39
- scrollContainerStyle: {
40
- flexGrow: number;
41
- alignItems: "center";
42
- width: "100%";
43
- };
44
- };
45
- export default ZSContainer;
45
+ scrollToFocusedInput?: boolean;
46
+ } & React.RefAttributes<ScrollView>>>;
47
+ export default _default;
46
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAgE,MAAM,OAAO,CAAC;AACvG,OAAO,EAAE,SAAS,EAAyB,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAA4B,MAAM,cAAc,CAAC;AAI/I,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;IACnD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACpE,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAExC,QAAA,MAAM,WAAW;sBAjBG,MAAM;qBACP,MAAM;eACZ,eAAe,GAAG,cAAc;YACnC,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;yBAC7B,OAAO;mBACb,SAAS;sBACN,SAAS;qBACV,SAAS;mCACK,OAAO;gCACV,MAAM;kBACpB,OAAO;eACV,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI;0BAC7C,MAAM;oCAuH5B,CAAC;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;CAGjB,CAAC;AAEH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAsF,MAAM,OAAO,CAAC;AAC7H,OAAO,EAAE,SAAS,EAAyB,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAA4B,MAAM,cAAc,CAAC;AAW/I,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;IACnD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACpE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAiJxC,eAAO,MAAM,MAAM;;;;;;;;;;CAGjB,CAAC;;sBApKiB,MAAM;qBACP,MAAM;eACZ,eAAe,GAAG,cAAc;YACnC,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;yBAC7B,OAAO;mBACb,SAAS;sBACN,SAAS;qBACV,SAAS;mCACK,OAAO;gCACV,MAAM;kBACpB,OAAO;eACV,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI;0BAC7C,MAAM;2BACL,OAAO;;AAkLhC,wBAAsD"}
@@ -1,73 +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 ZSContainer = forwardRef(function ZSContainer({ backgroundColor, statusBarColor, barStyle, edges = ['top', 'bottom'], scrollViewDisabled = false, topComponent, bottomComponent, rightComponent, showsVerticalScrollIndicator = true, keyboardScrollExtraOffset = 30, translucent, scrollEventThrottle = 16, ...props }, forwardedRef) {
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 showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
14
- const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
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
- }, [keyboardScrollExtraOffset]);
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
- return (<SafeAreaView style={[{ backgroundColor: backgroundColor || palette.background.base }, styles.flex1]} edges={edges}>
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 && topComponent}
57
- <ScrollView ref={scrollViewRef} style={styles.flex1} contentContainerStyle={[styles.scrollContainerStyle, { paddingBottom: Platform.OS === 'ios' ? keyboardHeight || 0 : 0 }]} bounces={false} overScrollMode="never" showsVerticalScrollIndicator={showsVerticalScrollIndicator} keyboardShouldPersistTaps="handled" automaticallyAdjustKeyboardInsets={false} onScroll={handleScroll} onTouchStart={handleTouch} scrollEventThrottle={scrollEventThrottle}>
58
- <View style={[styles.flex1, props.style]}>
59
- {props.children}
60
- </View>
61
- </ScrollView>
62
- {bottomComponent && bottomComponent}
75
+ {topComponent}
76
+ {scrollViewDisabled ? (<View style={styles.flex1}>
77
+ {props.children}
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}>
80
+ {props.children}
81
+ </View>
82
+ </ScrollView>)}
83
+ {bottomComponent}
63
84
  </View>
64
85
 
65
- {(barStyle || statusBarColor || translucent) && (<StatusBar barStyle={barStyle} backgroundColor={statusBarColor || palette.background.base} translucent={translucent}/>)}
86
+ {shouldShowStatusBar && (<StatusBar barStyle={barStyle} backgroundColor={statusBarColor || palette.background.base} translucent={translucent}/>)}
66
87
  </SafeAreaView>);
67
88
  });
68
89
  export const styles = StyleSheet.create({
69
90
  flex1: { flex: 1, width: '100%' },
70
91
  scrollContainerStyle: { flexGrow: 1, alignItems: 'center', width: '100%' },
71
92
  });
72
- export default ZSContainer;
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);
73
113
  //# sourceMappingURL=index.js.map