최근에 Expo를 통해 웹과 모바일 앱을 동시 지원하는 프로젝트를 개발하고 있습니다
React-Native Mui를 사용하고 있는데 style을 수정한 방법을 공유하고자 글을 작성합니다
Modal을 통해 사용자에게 정보를 보여주고 확인을 받고자 했습니다
React-Native Mui - Dialog를 사용하게 되었는데 해당 컴포넌트는 모바일에서는 가운데에 위치해있지만, 웹에서는 뷰포트가 아니라 전체 화면을 기준으로 가운데에 위치해서 스크롤을 내려야 Modal이 보이는 문제가 있었습니다
position을 absolute가 아니라 fixed로 바꾸면 해결이 되지 않을까 싶었는데 ide에도 style을 파라미터로 넘길 수 없고, 넘겨도 적용되지 않았습니다
node_modules/@react-native-material/core/lib/module/Dialog.js의 코드를 보겠습니다
import React, { useEffect, useMemo, useState } from 'react';
import { Animated, Easing, Platform, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import { Portal } from './base/PortalContext';
import Surface from './Surface';
const Dialog = _ref => {
let {
visible = false,
onDismiss,
children
} = _ref;
const [portalVisible, setPortalVisible] = useState(visible);
const animatedValue = useMemo(() => new Animated.Value(visible ? 1 : 0), []);
useEffect(() => {
if (visible) setPortalVisible(true);
Animated.timing(animatedValue, {
toValue: visible ? 1 : 0,
duration: 225,
easing: Easing.out(Easing.cubic),
useNativeDriver: Platform.OS !== 'android'
}).start(() => {
if (!visible) setPortalVisible(false);
});
}, [visible]);
if (!portalVisible) return null;
return /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(TouchableWithoutFeedback, {
onPress: onDismiss
}, /*#__PURE__*/React.createElement(Animated.View, {
style: [StyleSheet.absoluteFill, styles.backdrop, {
opacity: animatedValue
}]
})), /*#__PURE__*/React.createElement(Animated.View, {
style: [StyleSheet.absoluteFill, styles.container, {
opacity: animatedValue
}],
pointerEvents: "box-none",
needsOffscreenAlphaCompositing: Platform.OS === 'android'
}, /*#__PURE__*/React.createElement(Surface, {
category: "medium",
elevation: 24,
style: [styles.surface]
}, children)));
};
const styles = StyleSheet.create({
backdrop: {
backgroundColor: 'rgba(0,0,0,.5)'
},
container: {
justifyContent: 'center',
alignItems: 'center'
},
surface: {
width: 280,
marginHorizontal: 40
}
});
export default Dialog;
//# sourceMappingURL=Dialog.js.map
파라미터로 style을 아예 받고 있지 않아서 넣을 수 없습니다
Dialog에 style을 추가할 수 있도록 CustomDialog를 만들면 됩니다
Dialog를 복사 붙여넣기합니다
props에 dialog에 대한 style을 뜻하는 dialogStyle을 추가하고
dialog 부분의 style에 반영합니다
import { Surface, Portal } from '@react-native-material/core';
import React, { useEffect, useMemo, useState } from 'react';
import { Animated, Easing, Platform, StyleSheet, TouchableWithoutFeedback } from 'react-native';
const CustomDialog = ({ visible = false, onDismiss, dialogStyle, children }) => {
const [portalVisible, setPortalVisible] = useState(visible);
const animatedValue = useMemo(() => new Animated.Value(visible ? 1 : 0), []);
useEffect(() => {
if (visible) setPortalVisible(true);
Animated.timing(animatedValue, {
toValue: visible ? 1 : 0,
duration: 225,
easing: Easing.out(Easing.cubic),
useNativeDriver: Platform.OS !== 'android',
}).start(() => {
if (!visible) setPortalVisible(false);
});
}, [visible]);
if (!portalVisible) return null;
return /*#__PURE__*/ React.createElement(
Portal,
null,
/*#__PURE__*/ React.createElement(
TouchableWithoutFeedback,
{
onPress: onDismiss,
},
/*#__PURE__*/ React.createElement(Animated.View, {
style: [
StyleSheet.absoluteFill,
styles.backdrop,
{
opacity: animatedValue,
},
],
})
),
/*#__PURE__*/ React.createElement(
Animated.View,
{
style: [
StyleSheet.absoluteFill,
styles.container,
{
opacity: animatedValue,
},
dialogStyle,
],
pointerEvents: 'box-none',
needsOffscreenAlphaCompositing: Platform.OS === 'android',
},
/*#__PURE__*/ React.createElement(
Surface,
{
category: 'medium',
elevation: 24,
style: [styles.surface],
},
children
)
)
);
};
const styles = StyleSheet.create({
backdrop: {
backgroundColor: 'rgba(0,0,0,.5)',
},
container: {
justifyContent: 'center',
alignItems: 'center',
},
surface: {
width: 280,
marginHorizontal: 40,
},
});
export default CustomDialog;
//# sourceMappingURL=Dialog.js.map
import {
Button,
DialogHeader,
DialogContent,
DialogActions,
Text as MuiText,
} from '@react-native-material/core';
import { useMemo } from 'react';
import CustomDialog from './CustomDialog';
const Modal = ({ visible, onClose, isWeb }) => {
const dialogStyle = useMemo(() => (isWeb ? { position: 'fixed' } : {}), [isWeb]);
return (
<CustomDialog visible={visible} onDismiss={onClose} dialogStyle={dialogStyle}>
<DialogHeader title="Dialog Header" />
<DialogContent>
<MuiText>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum eligendi inventore,
laboriosam laudantium minima minus nesciunt pariatur sequi.
</MuiText>
</DialogContent>
<DialogActions>
<Button title="Cancel" compact variant="text" onPress={onClose} />
<Button title="Ok" compact variant="text" onPress={onClose} />
</DialogActions>
</CustomDialog>
);
};
export default Modal;
위처럼 CustomDialog의 dialogStyle에 모바일에서는 빈 객체를 넣어주고, 웹에서는 position: 'fixed'를 넣어주어 해결할 수 있습니다
정리
이 문제로 꽤 고생했습니다
라이브러리를 그대로 사용하지 말고 내부적으로 코드를 보면서 추가 커스텀이 가능한지를 알아보는게 좋은 경험이 되었습니다
참고