@0610studio/zs-ui 0.0.13 → 0.0.15
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 +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -1
- package/build/index.js.map +1 -1
- package/build/model/index.d.ts +4 -0
- package/build/model/index.d.ts.map +1 -0
- package/build/model/index.js +4 -0
- package/build/model/index.js.map +1 -0
- package/build/types/index.d.ts +9 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/package.json +6 -1
- package/.eslintrc.js +0 -5
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +0 -43
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/java/kr/co/studio0610/zsui/ZsUiModule.kt +0 -47
- package/android/src/main/java/kr/co/studio0610/zsui/ZsUiView.kt +0 -7
- package/expo-module.config.json +0 -9
- package/ios/ZsUi.podspec +0 -27
- package/ios/ZsUiModule.swift +0 -44
- package/ios/ZsUiView.swift +0 -7
- package/src/ZsUi.types.ts +0 -7
- package/src/ZsUiModule.ts +0 -5
- package/src/ZsUiModule.web.ts +0 -13
- package/src/ZsUiView.tsx +0 -11
- package/src/ZsUiView.web.tsx +0 -11
- package/src/assets/SvgCheck.tsx +0 -16
- package/src/assets/SvgX.tsx +0 -22
- package/src/index.ts +0 -52
- package/src/model/types.ts +0 -104
- package/src/model/useNotify.ts +0 -14
- package/src/model/useNotifyProvider.tsx +0 -259
- package/src/model/useThemeProvider.tsx +0 -99
- package/src/model/utils.ts +0 -31
- package/src/notify/AlertNotify/index.tsx +0 -172
- package/src/notify/BottomSheetNotify/index.tsx +0 -177
- package/src/notify/BottomSheetNotify/model/useBottomSheetNotify.tsx +0 -270
- package/src/notify/BottomSheetNotify/types/index.ts +0 -3
- package/src/notify/BottomSheetNotify/ui/BSTextInput/index.tsx +0 -28
- package/src/notify/BottomSheetNotify/ui/ContentsComponent/index.tsx +0 -86
- package/src/notify/LoadingNotify/index.tsx +0 -46
- package/src/notify/PopOver/PopOverButton.tsx +0 -56
- package/src/notify/PopOver/PopOverMenu.tsx +0 -95
- package/src/notify/SnackbarNotify/index.tsx +0 -43
- package/src/notify/SnackbarNotify/ui/SnackbarItem.tsx +0 -103
- package/src/notify/index.ts +0 -21
- package/src/notify/ui/ModalBackground.tsx +0 -46
- package/src/theme/index.ts +0 -3
- package/src/theme/palette.ts +0 -384
- package/src/theme/types.ts +0 -170
- package/src/theme/typography.ts +0 -199
- package/src/ui/ThrottleButton/index.tsx +0 -119
- package/src/ui/ZSBottomButton/index.tsx +0 -151
- package/src/ui/ZSContainer/index.tsx +0 -92
- package/src/ui/ZSPressable/index.tsx +0 -82
- package/src/ui/ZSRadioGroup/index.tsx +0 -141
- package/src/ui/ZSText/index.tsx +0 -22
- package/src/ui/ZSTextField/index.tsx +0 -200
- package/src/ui/ZSTextField/ui/ButtonClose.tsx +0 -20
- package/src/ui/ZSTextField/ui/ErrorComponent.tsx +0 -25
- package/src/ui/ZSView/index.tsx +0 -30
- package/src/ui/atoms/AnimatedWrapper.tsx +0 -90
- package/src/ui/atoms/ScrollViewAtom.tsx +0 -17
- package/src/ui/atoms/TextAtom.tsx +0 -15
- package/src/ui/atoms/ViewAtom.tsx +0 -15
- package/src/ui/index.ts +0 -27
- package/src/ui/types.ts +0 -12
- package/tsconfig.json +0 -9
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import React, { forwardRef, useImperativeHandle, useMemo } from 'react';
|
|
2
|
-
import { StyleSheet, Dimensions, ViewProps, Keyboard } from 'react-native';
|
|
3
|
-
import { GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
|
-
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
|
5
|
-
import useBottomSheetNotify from './model/useBottomSheetNotify';
|
|
6
|
-
import { BottomSheetNotifyRef } from './types';
|
|
7
|
-
import ContentsComponent from './ui/ContentsComponent';
|
|
8
|
-
import { useTheme } from '../../model/useThemeProvider';
|
|
9
|
-
import { ThemeBackground } from '../../theme';
|
|
10
|
-
import ViewAtom from '../../ui/atoms/ViewAtom';
|
|
11
|
-
import { ZSView } from '../../ui';
|
|
12
|
-
|
|
13
|
-
const DEFAULT_BORDER_RADIUS = 24;
|
|
14
|
-
const BS_MAX_HEIGHT = Dimensions.get('window').height - 120;
|
|
15
|
-
|
|
16
|
-
interface Props extends ViewProps {
|
|
17
|
-
marginBottomBS?: number;
|
|
18
|
-
bottomSheetBackgroundColor?: string;
|
|
19
|
-
bottomSheetPadding?: number;
|
|
20
|
-
closeOffset?: number;
|
|
21
|
-
contentsGestureEnable?: boolean;
|
|
22
|
-
isHandleVisible?: boolean;
|
|
23
|
-
bottomSheetMarginX?: number;
|
|
24
|
-
isBottomRadius?: boolean;
|
|
25
|
-
maxHeight?: number;
|
|
26
|
-
isScrollView?: boolean;
|
|
27
|
-
bottomSheetComponent: React.ReactNode;
|
|
28
|
-
showsVerticalScrollIndicator: boolean;
|
|
29
|
-
headerComponent?: React.ReactNode;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function BottomSheetNotify(props: Props, ref: React.Ref<BottomSheetNotifyRef>) {
|
|
33
|
-
const {
|
|
34
|
-
marginBottomBS = 15,
|
|
35
|
-
bottomSheetPadding = 20,
|
|
36
|
-
bottomSheetBackgroundColor,
|
|
37
|
-
closeOffset = Dimensions.get('window').height,
|
|
38
|
-
contentsGestureEnable = true,
|
|
39
|
-
isHandleVisible = true,
|
|
40
|
-
bottomSheetMarginX = 10,
|
|
41
|
-
isBottomRadius = true,
|
|
42
|
-
isScrollView = true,
|
|
43
|
-
maxHeight = BS_MAX_HEIGHT,
|
|
44
|
-
bottomSheetComponent,
|
|
45
|
-
showsVerticalScrollIndicator,
|
|
46
|
-
headerComponent
|
|
47
|
-
} = props;
|
|
48
|
-
|
|
49
|
-
const {
|
|
50
|
-
HANDLE_HEIGHT,
|
|
51
|
-
bottomSheetVisible,
|
|
52
|
-
bsAnimatedStyle,
|
|
53
|
-
onGestureEvent,
|
|
54
|
-
handleVisible,
|
|
55
|
-
onTapEvent,
|
|
56
|
-
openPosition,
|
|
57
|
-
screenWidth,
|
|
58
|
-
screenHeight,
|
|
59
|
-
panGestureRef,
|
|
60
|
-
listScrollPosition,
|
|
61
|
-
bsModalBgStyle,
|
|
62
|
-
backgroundPressHandler
|
|
63
|
-
} = useBottomSheetNotify({
|
|
64
|
-
bottomSheetPadding,
|
|
65
|
-
closeOffset,
|
|
66
|
-
contentsGestureEnable,
|
|
67
|
-
bottomSheetMarginX,
|
|
68
|
-
isHandleVisible,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const { palette: { background } } = useTheme();
|
|
72
|
-
|
|
73
|
-
const styles = useMemo(
|
|
74
|
-
() => createStyles({ background }),
|
|
75
|
-
[background]
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
useImperativeHandle(ref, () => ({
|
|
79
|
-
handleVisible,
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
return bottomSheetVisible && bottomSheetComponent ? (
|
|
83
|
-
<Animated.View
|
|
84
|
-
style={[styles.modalBg, bsModalBgStyle]}
|
|
85
|
-
entering={FadeIn.duration(50)}
|
|
86
|
-
exiting={FadeOut.duration(50)}
|
|
87
|
-
onTouchEnd={backgroundPressHandler} // 외부 터치 시 시트 닫기
|
|
88
|
-
>
|
|
89
|
-
<GestureHandlerRootView style={styles.rootViewWrapper}>
|
|
90
|
-
<GestureDetector gesture={onGestureEvent}>
|
|
91
|
-
<Animated.View
|
|
92
|
-
onTouchEnd={(e) => {
|
|
93
|
-
e.stopPropagation();
|
|
94
|
-
Keyboard.dismiss(); // 키보드 숨김
|
|
95
|
-
}}
|
|
96
|
-
style={[
|
|
97
|
-
styles.sheet,
|
|
98
|
-
{
|
|
99
|
-
width: screenWidth,
|
|
100
|
-
height: screenHeight,
|
|
101
|
-
paddingHorizontal: bottomSheetPadding,
|
|
102
|
-
left: bottomSheetMarginX,
|
|
103
|
-
right: bottomSheetMarginX,
|
|
104
|
-
borderTopLeftRadius: DEFAULT_BORDER_RADIUS,
|
|
105
|
-
borderTopRightRadius: DEFAULT_BORDER_RADIUS,
|
|
106
|
-
borderBottomLeftRadius: isBottomRadius ? DEFAULT_BORDER_RADIUS : 0,
|
|
107
|
-
borderBottomRightRadius: isBottomRadius ? DEFAULT_BORDER_RADIUS : 0,
|
|
108
|
-
backgroundColor: bottomSheetBackgroundColor || background.base,
|
|
109
|
-
},
|
|
110
|
-
bsAnimatedStyle, // 애니메이션 스타일 적용
|
|
111
|
-
]}
|
|
112
|
-
>
|
|
113
|
-
{isHandleVisible && (
|
|
114
|
-
<ZSView style={[styles.handleContainer, { height: HANDLE_HEIGHT }]}>
|
|
115
|
-
<ViewAtom style={styles.handle} />
|
|
116
|
-
</ZSView>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
|
-
<GestureDetector gesture={onTapEvent}>
|
|
120
|
-
<ContentsComponent
|
|
121
|
-
HANDLE_HEIGHT={HANDLE_HEIGHT}
|
|
122
|
-
panGestureRef={panGestureRef}
|
|
123
|
-
listScrollPosition={listScrollPosition}
|
|
124
|
-
openPosition={openPosition}
|
|
125
|
-
marginBottomBS={marginBottomBS}
|
|
126
|
-
screenHeight={screenHeight}
|
|
127
|
-
bottomSheetComponent={bottomSheetComponent}
|
|
128
|
-
bottomSheetPadding={bottomSheetPadding}
|
|
129
|
-
maxHeight={maxHeight}
|
|
130
|
-
isScrollView={isScrollView}
|
|
131
|
-
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
132
|
-
headerComponent={headerComponent}
|
|
133
|
-
/>
|
|
134
|
-
</GestureDetector>
|
|
135
|
-
</Animated.View>
|
|
136
|
-
</GestureDetector>
|
|
137
|
-
</GestureHandlerRootView>
|
|
138
|
-
</Animated.View>
|
|
139
|
-
) : null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const createStyles = ({
|
|
144
|
-
background,
|
|
145
|
-
}: {
|
|
146
|
-
background: ThemeBackground;
|
|
147
|
-
}) =>
|
|
148
|
-
StyleSheet.create({
|
|
149
|
-
modalBg: {
|
|
150
|
-
position: 'absolute',
|
|
151
|
-
width: Dimensions.get('window').width,
|
|
152
|
-
height: Dimensions.get('window').height,
|
|
153
|
-
bottom: 0,
|
|
154
|
-
},
|
|
155
|
-
sheet: {
|
|
156
|
-
position: 'absolute',
|
|
157
|
-
zIndex: 9000,
|
|
158
|
-
overflow: 'hidden',
|
|
159
|
-
},
|
|
160
|
-
handleContainer: {
|
|
161
|
-
width: '100%',
|
|
162
|
-
alignItems: 'center',
|
|
163
|
-
paddingTop: 13,
|
|
164
|
-
},
|
|
165
|
-
handle: {
|
|
166
|
-
backgroundColor: background.layer2,
|
|
167
|
-
width: 50,
|
|
168
|
-
height: 4,
|
|
169
|
-
borderRadius: 2,
|
|
170
|
-
},
|
|
171
|
-
rootViewWrapper: {
|
|
172
|
-
width: '100%',
|
|
173
|
-
height: '100%',
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
export default forwardRef(BottomSheetNotify);
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { BackHandler, Dimensions, Keyboard, Platform, useWindowDimensions } from 'react-native';
|
|
3
|
-
import { Gesture, GestureType } from 'react-native-gesture-handler';
|
|
4
|
-
import { useSharedValue, useAnimatedStyle, withTiming, Easing, runOnJS, useDerivedValue } from 'react-native-reanimated';
|
|
5
|
-
|
|
6
|
-
const DIMENSIONS_HEIGHT = Dimensions.get('window').height;
|
|
7
|
-
const INPUT_HEIGHT_CORRECTION = 40; // 인풋 높이 보정
|
|
8
|
-
const NATURAL_GESTURE_TOP = -17; // 자연스러운 제스쳐를 위해 상단 여유 공간 추가
|
|
9
|
-
const NATURAL_GESTURE_X = 8;
|
|
10
|
-
const DEFAULT_BG_OPACITY = 40;
|
|
11
|
-
const HANDLE_HEIGHT = 35;
|
|
12
|
-
|
|
13
|
-
const timingConfig100 = {
|
|
14
|
-
duration: 100,
|
|
15
|
-
easing: Easing.inOut(Easing.quad),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const timingConfig200 = {
|
|
19
|
-
duration: 200,
|
|
20
|
-
easing: Easing.inOut(Easing.quad),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
interface Props {
|
|
24
|
-
bottomSheetPadding: number;
|
|
25
|
-
closeOffset: number;
|
|
26
|
-
contentsGestureEnable: boolean;
|
|
27
|
-
isHandleVisible: boolean;
|
|
28
|
-
bottomSheetMarginX: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function useBottomSheetNotify({
|
|
32
|
-
bottomSheetPadding,
|
|
33
|
-
closeOffset,
|
|
34
|
-
contentsGestureEnable,
|
|
35
|
-
bottomSheetMarginX,
|
|
36
|
-
isHandleVisible,
|
|
37
|
-
}: Props) {
|
|
38
|
-
const handleHeight = isHandleVisible ? HANDLE_HEIGHT : 0;
|
|
39
|
-
const { width: windowWidth } = useWindowDimensions();
|
|
40
|
-
const panGestureRef = useRef<GestureType>(Gesture.Pan());
|
|
41
|
-
const listScrollPosition = useSharedValue(0);
|
|
42
|
-
const gestureComponent = useSharedValue('');
|
|
43
|
-
const tabAbsoluteY = useSharedValue(0);
|
|
44
|
-
const screenWidth = useSharedValue(Dimensions.get('window').width);
|
|
45
|
-
const screenHeight = useSharedValue(1);
|
|
46
|
-
const openPosition = useSharedValue(0);
|
|
47
|
-
const bsScale = useSharedValue(1);
|
|
48
|
-
const bgOpacity = useSharedValue(DEFAULT_BG_OPACITY);
|
|
49
|
-
const translateX = useSharedValue(0);
|
|
50
|
-
const translateY = useSharedValue(closeOffset);
|
|
51
|
-
const fullScreen = useSharedValue(false);
|
|
52
|
-
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
|
|
53
|
-
const [bottomSheetVisible, setBottomSheetVisible] = useState(false);
|
|
54
|
-
|
|
55
|
-
// ** 바텀시트 백그라운드 애니메이션 스타일 정의
|
|
56
|
-
const bsModalBgStyle = useAnimatedStyle(() => ({
|
|
57
|
-
backgroundColor: `#1E1E1E${bgOpacity.value}`,
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
// ** 바텀시트 애니메이션 스타일 정의
|
|
61
|
-
const bsAnimatedStyle = useAnimatedStyle(() => ({
|
|
62
|
-
transform: [
|
|
63
|
-
{ translateX: translateX.value },
|
|
64
|
-
{ translateY: translateY.value },
|
|
65
|
-
{ scale: bsScale.value },
|
|
66
|
-
],
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
// ** 화면 너비 설정
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
screenWidth.value = windowWidth - (bottomSheetMarginX ? bottomSheetMarginX * 2 : 0);
|
|
72
|
-
}, [windowWidth, bottomSheetMarginX]);
|
|
73
|
-
|
|
74
|
-
// ** 바텀시트 초기화
|
|
75
|
-
const initBottomSheet = useCallback(() => {
|
|
76
|
-
screenHeight.value = 1;
|
|
77
|
-
openPosition.value = 0;
|
|
78
|
-
}, [screenHeight, openPosition]);
|
|
79
|
-
|
|
80
|
-
// ** 백버튼 핸들러 정의 (안드로이드 전용)
|
|
81
|
-
const backPressHandler = useCallback(() => {
|
|
82
|
-
if (bottomSheetVisible) {
|
|
83
|
-
setBottomSheetVisible(false);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}, [bottomSheetVisible]);
|
|
88
|
-
|
|
89
|
-
// ** 백버튼 이벤트 리스너 설정
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
const backHandler = BackHandler.addEventListener('hardwareBackPress', backPressHandler);
|
|
92
|
-
|
|
93
|
-
return () => backHandler.remove();
|
|
94
|
-
}, [backPressHandler]);
|
|
95
|
-
|
|
96
|
-
// ** 바텀시트 보이기 상태 감지 후 초기화
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (!bottomSheetVisible) initBottomSheet();
|
|
99
|
-
}, [bottomSheetVisible, initBottomSheet]);
|
|
100
|
-
|
|
101
|
-
// ** 백그라운드 클릭시 키보드나 바텀시트 닫기
|
|
102
|
-
const backgroundPressHandler = useCallback(() => {
|
|
103
|
-
if (isKeyboardVisible) {
|
|
104
|
-
Keyboard.dismiss();
|
|
105
|
-
} else {
|
|
106
|
-
handleVisible(false);
|
|
107
|
-
}
|
|
108
|
-
}, [isKeyboardVisible]);
|
|
109
|
-
|
|
110
|
-
// ** 키보드 닫기
|
|
111
|
-
const dismissKeyboard = useCallback(() => {
|
|
112
|
-
Keyboard.dismiss();
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
// ** 애니메이션을 사용한 바텀시트 초기 크기 설정
|
|
116
|
-
const initSize = useCallback(() => {
|
|
117
|
-
bsScale.value = withTiming(1, timingConfig100);
|
|
118
|
-
translateX.value = withTiming(0, timingConfig100);
|
|
119
|
-
bgOpacity.value = DEFAULT_BG_OPACITY;
|
|
120
|
-
}, [bsScale, translateX, bgOpacity]);
|
|
121
|
-
|
|
122
|
-
// ** 소프트 키보드 핸들링 (보이기, 숨기기)
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
const keyboardDidShowListener = Keyboard.addListener(
|
|
125
|
-
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
|
|
126
|
-
(event) => {
|
|
127
|
-
setIsKeyboardVisible(true);
|
|
128
|
-
if (!fullScreen.value) return;
|
|
129
|
-
const tabAbsoluteYValue = tabAbsoluteY.value;
|
|
130
|
-
const screenTopToTarget = tabAbsoluteYValue - openPosition.value; // 모달 상단에서 인풋까지 거리
|
|
131
|
-
const keyboardHeight = event.endCoordinates.height; // 키보드 높이
|
|
132
|
-
const keyboardLine = DIMENSIONS_HEIGHT - keyboardHeight;
|
|
133
|
-
|
|
134
|
-
translateY.value = withTiming(keyboardLine - screenTopToTarget - INPUT_HEIGHT_CORRECTION, timingConfig200);
|
|
135
|
-
}
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const keyboardDidHideListener = Keyboard.addListener(
|
|
139
|
-
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
|
|
140
|
-
() => {
|
|
141
|
-
setIsKeyboardVisible(false);
|
|
142
|
-
|
|
143
|
-
// 키보드가 사라질 때 화면의 높이를 원래대로 돌립니다.
|
|
144
|
-
if (!fullScreen.value) return;
|
|
145
|
-
handleVisible(true);
|
|
146
|
-
}
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return () => {
|
|
150
|
-
keyboardDidShowListener.remove();
|
|
151
|
-
keyboardDidHideListener.remove();
|
|
152
|
-
};
|
|
153
|
-
}, [fullScreen, openPosition, tabAbsoluteY, translateY]);
|
|
154
|
-
|
|
155
|
-
// ** 바텀시트 열기 함수
|
|
156
|
-
const openBottomSheet = useCallback(() => {
|
|
157
|
-
setTimeout(() => {
|
|
158
|
-
if (screenHeight.value === 1) return openBottomSheet();
|
|
159
|
-
translateY.value = withTiming(openPosition.value, timingConfig200);
|
|
160
|
-
fullScreen.value = true;
|
|
161
|
-
}, 200);
|
|
162
|
-
}, [screenHeight, openPosition, translateY]);
|
|
163
|
-
|
|
164
|
-
// ** 바텀시트 위치 변경
|
|
165
|
-
const onOpenPositionChange = useCallback(
|
|
166
|
-
(value: number) => {
|
|
167
|
-
if (fullScreen.value && screenHeight.value !== 1) {
|
|
168
|
-
translateY.value = withTiming(value, timingConfig200);
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
[fullScreen, screenHeight, translateY]
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
// ** 애니메이션 값이 변경될 때 처리
|
|
175
|
-
useDerivedValue(() => {
|
|
176
|
-
runOnJS(onOpenPositionChange)(openPosition.value);
|
|
177
|
-
return openPosition.value;
|
|
178
|
-
}, [openPosition]);
|
|
179
|
-
|
|
180
|
-
// ** 바텀시트 상태 관리 (열기/닫기)
|
|
181
|
-
const handleVisible = useCallback(
|
|
182
|
-
(isOpen: boolean) => {
|
|
183
|
-
if (isOpen) {
|
|
184
|
-
setBottomSheetVisible(true);
|
|
185
|
-
openBottomSheet();
|
|
186
|
-
} else {
|
|
187
|
-
translateY.value = withTiming(closeOffset, timingConfig200);
|
|
188
|
-
fullScreen.value = false;
|
|
189
|
-
setTimeout(() => {
|
|
190
|
-
setBottomSheetVisible(false);
|
|
191
|
-
}, 200);
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
[closeOffset, fullScreen, openBottomSheet, translateY]
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
// ** 탭 제스처 설정
|
|
198
|
-
const onTapEvent = Gesture.Tap().onStart((event) => {
|
|
199
|
-
tabAbsoluteY.value = event.absoluteY;
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// ** 팬 제스처 설정
|
|
203
|
-
const onGestureEvent = Gesture.Pan()
|
|
204
|
-
.onStart((event) => {
|
|
205
|
-
'worklet';
|
|
206
|
-
runOnJS(dismissKeyboard)();
|
|
207
|
-
|
|
208
|
-
// 제스쳐 영역 판단
|
|
209
|
-
if (openPosition.value + handleHeight > event.absoluteY) {
|
|
210
|
-
gestureComponent.value = 'Handler';
|
|
211
|
-
bsScale.value = withTiming(0.98, timingConfig100);
|
|
212
|
-
} else {
|
|
213
|
-
gestureComponent.value = 'Contents';
|
|
214
|
-
if (contentsGestureEnable) bsScale.value = withTiming(0.98, timingConfig100);
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
.onUpdate((event) => {
|
|
218
|
-
'worklet';
|
|
219
|
-
// 제스처 제어 로직
|
|
220
|
-
if (!contentsGestureEnable && gestureComponent.value === 'Contents') return;
|
|
221
|
-
|
|
222
|
-
const translateXValue = event.translationX;
|
|
223
|
-
const translateYValue = event.translationY;
|
|
224
|
-
const calcBg = Math.round(DEFAULT_BG_OPACITY + translateYValue * -1);
|
|
225
|
-
|
|
226
|
-
// 백그라운드 컬러 업데이트
|
|
227
|
-
if (calcBg < 70 && calcBg > DEFAULT_BG_OPACITY) {
|
|
228
|
-
bgOpacity.value = calcBg;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 자연스러운 X축 움직임
|
|
232
|
-
if (NATURAL_GESTURE_X > translateXValue && translateXValue > -NATURAL_GESTURE_X && bsScale.value !== 1) {
|
|
233
|
-
translateX.value = translateXValue;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 상단 제스처 제한
|
|
237
|
-
if (fullScreen.value && translateYValue < NATURAL_GESTURE_TOP) return;
|
|
238
|
-
if (!fullScreen.value && translateYValue > 0 && translateY.value === closeOffset) return;
|
|
239
|
-
|
|
240
|
-
const result = translateYValue + (fullScreen.value ? openPosition.value : 30);
|
|
241
|
-
translateY.value = result;
|
|
242
|
-
})
|
|
243
|
-
.onEnd(() => {
|
|
244
|
-
'worklet';
|
|
245
|
-
runOnJS(initSize)();
|
|
246
|
-
const shouldOpen = translateY.value < openPosition.value + screenHeight.value / 2;
|
|
247
|
-
runOnJS(handleVisible)(shouldOpen);
|
|
248
|
-
})
|
|
249
|
-
.withRef(panGestureRef);
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
HANDLE_HEIGHT,
|
|
253
|
-
bottomSheetVisible,
|
|
254
|
-
bsAnimatedStyle,
|
|
255
|
-
onGestureEvent,
|
|
256
|
-
handleVisible,
|
|
257
|
-
screenWidth,
|
|
258
|
-
screenHeight,
|
|
259
|
-
handleHeight,
|
|
260
|
-
openPosition,
|
|
261
|
-
bottomSheetPadding,
|
|
262
|
-
onTapEvent,
|
|
263
|
-
panGestureRef,
|
|
264
|
-
listScrollPosition,
|
|
265
|
-
bsModalBgStyle,
|
|
266
|
-
backgroundPressHandler,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export default useBottomSheetNotify;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useRef } from "react";
|
|
2
|
-
import { Platform, Pressable } from "react-native";
|
|
3
|
-
import { TextInput } from "react-native-gesture-handler";
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const BSTextInput = ({ ...props }: Props) => {
|
|
9
|
-
const textInputRef = useRef<TextInput>(null);
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
Platform.OS === 'ios' ?
|
|
13
|
-
<Pressable onPress={() => { textInputRef?.current?.focus(); }}>
|
|
14
|
-
<TextInput
|
|
15
|
-
ref={textInputRef}
|
|
16
|
-
pointerEvents='none'
|
|
17
|
-
{...props}
|
|
18
|
-
/>
|
|
19
|
-
</Pressable>
|
|
20
|
-
:
|
|
21
|
-
<TextInput
|
|
22
|
-
ref={textInputRef}
|
|
23
|
-
{...props}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default BSTextInput;
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
2
|
-
import { Dimensions, LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Text, View } from 'react-native';
|
|
3
|
-
import { GestureType, ScrollView } from 'react-native-gesture-handler';
|
|
4
|
-
import { SharedValue } from 'react-native-reanimated';
|
|
5
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
|
-
import { ZSView } from '../../../../ui';
|
|
7
|
-
|
|
8
|
-
// const ANDROID_STATUS_BAR_HEIGHT = Platform.OS === 'android' ? 25 : 0;
|
|
9
|
-
const ANDROID_STATUS_BAR_HEIGHT = 0;
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
HANDLE_HEIGHT: number;
|
|
13
|
-
panGestureRef: React.MutableRefObject<GestureType>;
|
|
14
|
-
listScrollPosition: SharedValue<number>;
|
|
15
|
-
openPosition: SharedValue<number>;
|
|
16
|
-
marginBottomBS: number;
|
|
17
|
-
screenHeight: SharedValue<number>;
|
|
18
|
-
bottomSheetComponent: React.ReactNode;
|
|
19
|
-
bottomSheetPadding: number;
|
|
20
|
-
maxHeight: number;
|
|
21
|
-
isScrollView: boolean;
|
|
22
|
-
showsVerticalScrollIndicator: boolean;
|
|
23
|
-
headerComponent?: React.ReactNode;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 화살표 함수 대신 일반 함수 사용
|
|
27
|
-
function ContentsComponent({
|
|
28
|
-
HANDLE_HEIGHT,
|
|
29
|
-
panGestureRef,
|
|
30
|
-
listScrollPosition,
|
|
31
|
-
openPosition,
|
|
32
|
-
marginBottomBS,
|
|
33
|
-
screenHeight,
|
|
34
|
-
bottomSheetComponent,
|
|
35
|
-
bottomSheetPadding,
|
|
36
|
-
maxHeight,
|
|
37
|
-
isScrollView,
|
|
38
|
-
showsVerticalScrollIndicator,
|
|
39
|
-
headerComponent
|
|
40
|
-
}: Props) {
|
|
41
|
-
const { bottom } = useSafeAreaInsets();
|
|
42
|
-
|
|
43
|
-
// onLayout 함수를 useCallback으로 최적화
|
|
44
|
-
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
45
|
-
const { height } = event.nativeEvent.layout;
|
|
46
|
-
const contentMaxHeight = maxHeight + HANDLE_HEIGHT;
|
|
47
|
-
const resultHeight = Math.min(height, contentMaxHeight); // 더 간결하게 변경
|
|
48
|
-
// 성능 문제 방지를 위해 runOnUI 사용
|
|
49
|
-
screenHeight.value = resultHeight + HANDLE_HEIGHT;
|
|
50
|
-
openPosition.value = Dimensions.get('window').height - resultHeight - marginBottomBS - bottom - ANDROID_STATUS_BAR_HEIGHT - HANDLE_HEIGHT;
|
|
51
|
-
}, [maxHeight, HANDLE_HEIGHT, screenHeight, openPosition, marginBottomBS, bottom]);
|
|
52
|
-
|
|
53
|
-
// 현재 스크롤 위치를 관리하는 함수
|
|
54
|
-
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
55
|
-
listScrollPosition.value = event.nativeEvent.contentOffset.y;
|
|
56
|
-
}, [listScrollPosition]);
|
|
57
|
-
|
|
58
|
-
return isScrollView ? (
|
|
59
|
-
<ScrollView
|
|
60
|
-
simultaneousHandlers={[panGestureRef]}
|
|
61
|
-
onScroll={handleScroll}
|
|
62
|
-
style={{ maxHeight }}
|
|
63
|
-
keyboardShouldPersistTaps="handled"
|
|
64
|
-
bounces={false}
|
|
65
|
-
bouncesZoom={false}
|
|
66
|
-
overScrollMode="never"
|
|
67
|
-
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
68
|
-
scrollEventThrottle={16}
|
|
69
|
-
stickyHeaderIndices={headerComponent ? [0] : undefined}
|
|
70
|
-
>
|
|
71
|
-
{headerComponent && headerComponent}
|
|
72
|
-
|
|
73
|
-
<ZSView style={{ width: '100%', minHeight: 1, paddingBottom: bottomSheetPadding }} onLayout={onLayout}>
|
|
74
|
-
{bottomSheetComponent}
|
|
75
|
-
</ZSView>
|
|
76
|
-
</ScrollView>
|
|
77
|
-
) : (
|
|
78
|
-
<ZSView style={{ width: '100%', minHeight: 1, paddingBottom: bottomSheetPadding, maxHeight }} onLayout={onLayout}>
|
|
79
|
-
{headerComponent && headerComponent}
|
|
80
|
-
|
|
81
|
-
{bottomSheetComponent}
|
|
82
|
-
</ZSView>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default React.memo(ContentsComponent); // React.memo로 성능 최적화
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { ActivityIndicator, BackHandler } from "react-native";
|
|
2
|
-
import React, { ReactNode, useEffect, useCallback } from "react";
|
|
3
|
-
import { useNotify } from "../../model/useNotify";
|
|
4
|
-
import ModalBackground from "../ui/ModalBackground";
|
|
5
|
-
|
|
6
|
-
// 함수 선언식으로 변경
|
|
7
|
-
function LoadingNotify({
|
|
8
|
-
loaderComponent,
|
|
9
|
-
}: {
|
|
10
|
-
loaderComponent?: () => ReactNode;
|
|
11
|
-
}) {
|
|
12
|
-
const { loaderVisible } = useNotify();
|
|
13
|
-
|
|
14
|
-
// BackHandler 이벤트 처리 최적화
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
const handleBackPressed = () => {
|
|
17
|
-
if (loaderVisible) return true; // 로더가 보이는 경우 뒤로가기 방지
|
|
18
|
-
return false;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const handler = BackHandler.addEventListener(
|
|
22
|
-
"hardwareBackPress",
|
|
23
|
-
handleBackPressed
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
// cleanup 함수 추가
|
|
27
|
-
return () => handler.remove();
|
|
28
|
-
}, [loaderVisible]);
|
|
29
|
-
|
|
30
|
-
// loaderComponent를 메모이제이션
|
|
31
|
-
const renderLoader = useCallback(() => {
|
|
32
|
-
return loaderComponent ? (
|
|
33
|
-
loaderComponent()
|
|
34
|
-
) : (
|
|
35
|
-
<ActivityIndicator size="large" color="#fff" />
|
|
36
|
-
);
|
|
37
|
-
}, [loaderComponent]);
|
|
38
|
-
|
|
39
|
-
return loaderVisible ? (
|
|
40
|
-
<ModalBackground>
|
|
41
|
-
{renderLoader()}
|
|
42
|
-
</ModalBackground>
|
|
43
|
-
) : null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default LoadingNotify;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useCallback } from 'react';
|
|
2
|
-
import { View, Pressable, ViewProps, StyleSheet } from 'react-native';
|
|
3
|
-
import { useNotify } from '../../model/useNotify';
|
|
4
|
-
|
|
5
|
-
interface PopOverButtonProps extends ViewProps {
|
|
6
|
-
width: number;
|
|
7
|
-
height: number;
|
|
8
|
-
backgroundColor?: string;
|
|
9
|
-
popOverMenuComponent: React.ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const PopOverButton: React.FC<PopOverButtonProps> = ({
|
|
13
|
-
width,
|
|
14
|
-
height,
|
|
15
|
-
backgroundColor = 'transparent',
|
|
16
|
-
popOverMenuComponent,
|
|
17
|
-
children,
|
|
18
|
-
...props
|
|
19
|
-
}) => {
|
|
20
|
-
const buttonRef = useRef<View>(null);
|
|
21
|
-
const { showPopOverMenu } = useNotify();
|
|
22
|
-
|
|
23
|
-
const handlePress = useCallback(() => {
|
|
24
|
-
buttonRef.current?.measure((fx, fy, measuredWidth, measuredHeight, pageX, pageY) => {
|
|
25
|
-
if (pageX !== undefined && pageY !== undefined) {
|
|
26
|
-
const rbX = pageX + measuredWidth;
|
|
27
|
-
const rbY = pageY + measuredHeight;
|
|
28
|
-
|
|
29
|
-
showPopOverMenu({ px: rbX, py: rbY, component: popOverMenuComponent });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}, [showPopOverMenu, popOverMenuComponent]);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Pressable onPress={handlePress} style={styles.pressable}>
|
|
36
|
-
<View
|
|
37
|
-
ref={buttonRef}
|
|
38
|
-
style={[styles.button, { width, height, backgroundColor }]}
|
|
39
|
-
{...props}
|
|
40
|
-
>
|
|
41
|
-
{children}
|
|
42
|
-
</View>
|
|
43
|
-
</Pressable>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const styles = StyleSheet.create({
|
|
48
|
-
pressable: {
|
|
49
|
-
alignItems: 'flex-start',
|
|
50
|
-
},
|
|
51
|
-
button: {
|
|
52
|
-
justifyContent: 'center',
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export default PopOverButton;
|