diff --git a/src/components/BottomSheet/ActionListBottomSheet/ActionButton.tsx b/src/components/BottomSheet/ActionListBottomSheet/ActionButton.tsx new file mode 100644 index 00000000..1cd12224 --- /dev/null +++ b/src/components/BottomSheet/ActionListBottomSheet/ActionButton.tsx @@ -0,0 +1,20 @@ +import Text from '@/components/Text'; +import TouchableRipple from '@/components/TouchableRipple'; + +import { styles } from './index.style'; + +export type ActionButton = { + label: string; + onPress: () => void; +}; + +export const ActionButton = ({ + label, + onPress, +}: ActionButton) => ( + + + {label} + + +); \ No newline at end of file diff --git a/src/components/BottomSheet/ActionListBottomSheet/index.style.ts b/src/components/BottomSheet/ActionListBottomSheet/index.style.ts new file mode 100644 index 00000000..c708babc --- /dev/null +++ b/src/components/BottomSheet/ActionListBottomSheet/index.style.ts @@ -0,0 +1,9 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + actionButtonContainer: { + justifyContent: 'center', + alignItems: 'center', + paddingVertical: 12, + }, +}); \ No newline at end of file diff --git a/src/components/BottomSheet/ActionListBottomSheet/index.tsx b/src/components/BottomSheet/ActionListBottomSheet/index.tsx new file mode 100644 index 00000000..91f3422d --- /dev/null +++ b/src/components/BottomSheet/ActionListBottomSheet/index.tsx @@ -0,0 +1,29 @@ +import { Shapes } from '@components'; +import { View } from 'react-native'; + +import type { BottomSheetProps } from '..'; +import { BottomSheet } from '../index'; +import { ActionButton } from './ActionButton'; + +interface ActionListBottomSheetProps extends BottomSheetProps { + actionItems: ActionButton[]; +} + +const ActionListBottomSheet = ({ + actionItems, + ...props +}: ActionListBottomSheetProps) => ( + + {actionItems.map((item, idx) => ( + + {idx > 0 && } + + + ))} + +); + +export default ActionListBottomSheet; diff --git a/src/components/BottomSheet/BottomSheetCommon.tsx b/src/components/BottomSheet/BottomSheetCommon.tsx index ea27640b..560c046b 100644 --- a/src/components/BottomSheet/BottomSheetCommon.tsx +++ b/src/components/BottomSheet/BottomSheetCommon.tsx @@ -2,7 +2,7 @@ import { Backdrop } from '@components'; import type { PropsWithChildren } from 'react'; import { useEffect, useRef, useState } from 'react'; import type { LayoutChangeEvent } from 'react-native'; -import { Animated, Dimensions, View } from 'react-native'; +import { Animated, Dimensions, Keyboard } from 'react-native'; import { useHardwareBack } from '@/hooks/useHardwareBack'; @@ -13,6 +13,7 @@ export interface BottomSheetCommonProps extends PropsWithChildren { onClose?: () => void; /** iOS에서는 false로 넘겨도 되며, 기본값 true */ closeOnBackdropPress?: boolean; + closeOnHardwareBack?: boolean; /** SafeAreaInsets.bottom 혹은 0 */ bottomPadding: number; /** useNativeDriver 플래그 */ @@ -23,6 +24,7 @@ const BottomSheetCommon = ({ enabled, onClose, closeOnBackdropPress = true, + closeOnHardwareBack = true, bottomPadding, useNativeDriver, children, @@ -53,7 +55,7 @@ const BottomSheetCommon = ({ }; useHardwareBack(() => { - if (enabled && onClose) { + if (closeOnHardwareBack && enabled && onClose) { onClose(); return true; } @@ -63,6 +65,7 @@ const BottomSheetCommon = ({ useEffect(() => { if (enabled) { setHidden(false); + Keyboard.dismiss(); } const anim = Animated.timing(animation, { toValue: enabled ? 1 : 0, diff --git a/src/components/BottomSheet/index.android.tsx b/src/components/BottomSheet/index.android.tsx index a1691bc5..e414d2e3 100644 --- a/src/components/BottomSheet/index.android.tsx +++ b/src/components/BottomSheet/index.android.tsx @@ -11,7 +11,7 @@ type BottomSheetProps = Omit< 'bottomPadding' | 'useNativeDriver' >; -const BottomSheet = (props: BottomSheetProps) => { +export const BottomSheet = (props: BottomSheetProps) => { const insets = useSafeAreaInsets(); return ( @@ -23,4 +23,4 @@ const BottomSheet = (props: BottomSheetProps) => { ); }; -export default BottomSheet; +export { default as ActionListBottomSheet } from './ActionListBottomSheet'; diff --git a/src/components/BottomSheet/index.ios.tsx b/src/components/BottomSheet/index.ios.tsx index be4e2749..1dbd686a 100644 --- a/src/components/BottomSheet/index.ios.tsx +++ b/src/components/BottomSheet/index.ios.tsx @@ -13,7 +13,7 @@ type BottomSheetProps = Omit< 'bottomPadding' | 'useNativeDriver' >; -const BottomSheet = (props: BottomSheetProps) => { +export const BottomSheet = (props: BottomSheetProps) => { const insets = useSafeAreaInsets(); return ( @@ -32,4 +32,4 @@ const BottomSheet = (props: BottomSheetProps) => { ); }; -export default BottomSheet; +export { default as ActionListBottomSheet } from './ActionListBottomSheet'; \ No newline at end of file diff --git a/src/components/BottomSheet/index.tsx b/src/components/BottomSheet/index.tsx index 2573424b..f558455f 100644 --- a/src/components/BottomSheet/index.tsx +++ b/src/components/BottomSheet/index.tsx @@ -6,13 +6,13 @@ import type { import BottomSheetCommon from './BottomSheetCommon'; /** 공통Props에서 bottomPadding/useNativeDriver만 뺀 타입 */ -type BottomSheetProps = Omit< +export type BottomSheetProps = Omit< BottomSheetCommonProps, 'bottomPadding' | 'useNativeDriver' >; // TODO: BottomSheet disable 시에, 가상 DOM으로부터 언마운트하도록 구현 변경 (Dialog의 구현처럼) -const BottomSheet = (props: BottomSheetProps) => ( +export const BottomSheet = (props: BottomSheetProps) => ( ( /> ); -export default BottomSheet; +export { default as ActionListBottomSheet } from './ActionListBottomSheet'; diff --git a/src/components/Dialog/Alert/index.style.ts b/src/components/Dialog/Alert/index.style.ts index 74005162..f9df1489 100644 --- a/src/components/Dialog/Alert/index.style.ts +++ b/src/components/Dialog/Alert/index.style.ts @@ -1,6 +1,5 @@ import { StyleSheet } from 'react-native'; -import type { TextProps } from '@/components/Text'; import { theme } from '@/theme'; export const styles = StyleSheet.create({ @@ -19,11 +18,3 @@ export const styles = StyleSheet.create({ alignSelf: 'stretch', }, }); - -export const TITLE_TEXT_STYLE: TextProps = { - font: 't2', -}; - -export const MESSAGE_TEXT_STYLE: TextProps = { - font: 'b3', -}; \ No newline at end of file diff --git a/src/components/Dialog/Alert/index.tsx b/src/components/Dialog/Alert/index.tsx index e4960825..edc0ac55 100644 --- a/src/components/Dialog/Alert/index.tsx +++ b/src/components/Dialog/Alert/index.tsx @@ -4,7 +4,8 @@ import { View } from 'react-native'; import { useDialog } from '@/hooks/useDialog'; import { useTranslatedText } from '@/hooks/useTranslatedText'; -import { MESSAGE_TEXT_STYLE, styles, TITLE_TEXT_STYLE } from './index.style'; +import { MESSAGE_TEXT_STYLE, TITLE_TEXT_STYLE } from '../index.style'; +import { styles } from './index.style'; interface AlertDialogProps { title: string; diff --git a/src/components/Dialog/Any/index.style.ts b/src/components/Dialog/Any/index.style.ts index 75c55aaf..84ec8709 100644 --- a/src/components/Dialog/Any/index.style.ts +++ b/src/components/Dialog/Any/index.style.ts @@ -1,8 +1,6 @@ import { StyleSheet } from 'react-native'; export const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', + container: { }, }); diff --git a/src/components/Dialog/Any/index.tsx b/src/components/Dialog/Any/index.tsx index a223b3a6..0a87de53 100644 --- a/src/components/Dialog/Any/index.tsx +++ b/src/components/Dialog/Any/index.tsx @@ -1,8 +1,10 @@ import type { ReactNode } from 'react'; import { View } from 'react-native'; +import Text from '@/components/Text'; import { useDialog } from '@/hooks/useDialog'; +import { TITLE_TEXT_STYLE } from '../index.style'; import { styles } from './index.style'; interface AnyDialogProps { diff --git a/src/components/Dialog/Confirm/index.style.ts b/src/components/Dialog/Confirm/index.style.ts index 9c1b35a9..b0f68192 100644 --- a/src/components/Dialog/Confirm/index.style.ts +++ b/src/components/Dialog/Confirm/index.style.ts @@ -23,21 +23,3 @@ export const styles = StyleSheet.create({ alignSelf: 'stretch', }, }); - -export const TITLE_TEXT_STYLE: TextProps = { - font: 't2', -}; - -export const MESSAGE_TEXT_STYLE: TextProps = { - font: 'b3', -}; - -export const CLOSE_BUTTON_STYLE: ButtonProps = { - style: { - alignSelf: 'stretch', - backgroundColor: 'transparent', - borderWidth: 1, - borderColor: theme.color.neutral[500], - }, - textColor: theme.color.neutral[500], -}; \ No newline at end of file diff --git a/src/components/Dialog/Confirm/index.tsx b/src/components/Dialog/Confirm/index.tsx index 49568373..5d585412 100644 --- a/src/components/Dialog/Confirm/index.tsx +++ b/src/components/Dialog/Confirm/index.tsx @@ -4,7 +4,8 @@ import { View } from 'react-native'; import { useDialog } from '@/hooks/useDialog'; import { useTranslatedText } from '@/hooks/useTranslatedText'; -import { CLOSE_BUTTON_STYLE, MESSAGE_TEXT_STYLE, styles, TITLE_TEXT_STYLE } from './index.style'; +import { CLOSE_BUTTON_STYLE, MESSAGE_TEXT_STYLE, TITLE_TEXT_STYLE } from '../index.style'; +import { styles } from './index.style'; interface AlertDialogProps { title: string; diff --git a/src/components/Dialog/index.style.ts b/src/components/Dialog/index.style.ts index db5b138b..637d8b47 100644 --- a/src/components/Dialog/index.style.ts +++ b/src/components/Dialog/index.style.ts @@ -2,6 +2,9 @@ import { StyleSheet } from 'react-native'; import { theme } from '@/theme'; +import type { ButtonProps } from '../Button/Basic'; +import type { TextProps } from '../Text'; + export const styles = StyleSheet.create({ container: { position: 'absolute', @@ -19,3 +22,21 @@ export const styles = StyleSheet.create({ padding: theme.spacing[400], }, }); + +export const TITLE_TEXT_STYLE: TextProps = { + font: 't2', +}; + +export const MESSAGE_TEXT_STYLE: TextProps = { + font: 'b3', +}; + +export const CLOSE_BUTTON_STYLE: ButtonProps = { + style: { + alignSelf: 'stretch', + backgroundColor: 'transparent', + borderWidth: 1, + borderColor: theme.color.neutral[500], + }, + textColor: theme.color.neutral[500], +}; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 8467105d..e0ec2239 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,7 +2,7 @@ export { default as Accordion } from './Accordion'; export { default as Avatar } from './Avatar'; export { default as AvatarGroup } from './AvatarGroup'; export { default as Backdrop } from './Backdrop'; -export { default as BottomSheet } from './BottomSheet'; +export { ActionListBottomSheet, BottomSheet } from './BottomSheet'; export { default as Button } from './Button'; export { default as Checkbox } from './Checkbox'; export { default as DateTimePicker } from './DateTimePicker'; diff --git a/src/locales/ko/groupChat.json b/src/locales/ko/groupChat.json index 760e014a..6793f189 100644 --- a/src/locales/ko/groupChat.json +++ b/src/locales/ko/groupChat.json @@ -1,3 +1,4 @@ { - "write-message": "메시지 작성" + "write-message": "메시지 작성", + "group-member-list": "그룹 멤버 목록" } \ No newline at end of file diff --git a/src/screens/group/GroupChatScreen/components/ChatMoreActionsBottomSheet/index.tsx b/src/screens/group/GroupChatScreen/components/ChatMoreActionsBottomSheet/index.tsx new file mode 100644 index 00000000..fe5af0de --- /dev/null +++ b/src/screens/group/GroupChatScreen/components/ChatMoreActionsBottomSheet/index.tsx @@ -0,0 +1,42 @@ +import { ActionListBottomSheet } from '@/components'; +import type { GetChatRoomInfoResponse } from '@/features/chat/model/getChatRoomInfo'; +import { useTranslatedText } from '@/hooks'; +import { dialogService } from '@/utils/dialog'; + +import UserInfosDialog from '../UserInfosDialog'; + +interface ChatMoreActionsBottomSheetProps { + userInfos: GetChatRoomInfoResponse['data']['userInfos']; + bottomSheetEnabled: boolean; + setBottomSheetEnabled: (enabled: boolean) => void; +} + +const ChatMoreActionsBottomSheet = ({ + userInfos, + bottomSheetEnabled, + setBottomSheetEnabled, +}: ChatMoreActionsBottomSheetProps) => { + const tViewGroupMembers = useTranslatedText({ tKey: 'groupChat.group-member-list' }); + + return ( + { + dialogService.add({ + content: ( + + ), + }); + }, + }, + ]} + enabled={bottomSheetEnabled} + onClose={() => setBottomSheetEnabled(false)} + /> + ); + +}; + +export default ChatMoreActionsBottomSheet; diff --git a/src/screens/group/GroupChatScreen/components/ScreenHeader/index.tsx b/src/screens/group/GroupChatScreen/components/ScreenHeader/index.tsx index ff9c0fa8..fed9d945 100644 --- a/src/screens/group/GroupChatScreen/components/ScreenHeader/index.tsx +++ b/src/screens/group/GroupChatScreen/components/ScreenHeader/index.tsx @@ -11,12 +11,14 @@ interface ScreenHeaderProps { groupName: string; isHost: boolean; color: Color; + onMoreButtonPress: () => void; } const ScreenHeader = ({ groupName, isHost, color, + onMoreButtonPress, }: ScreenHeaderProps) => { const navigation = useStackNavigation(); const [isNotiEnabled, setIsNotiEnabled] = useState(true); @@ -46,6 +48,7 @@ const ScreenHeader = ({ diff --git a/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.style.ts b/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.style.ts new file mode 100644 index 00000000..439ce7d7 --- /dev/null +++ b/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.style.ts @@ -0,0 +1,39 @@ +import { StyleSheet } from 'react-native'; + +import type { TextProps } from '@/components/Text'; +import { theme } from '@/theme'; + +export const styles = StyleSheet.create({ + userInfoContainer: { + flexDirection: 'row', + gap: 12, + paddingHorizontal: 16, + alignItems: 'center', + }, + scrollView: { + flexGrow: 0, + maxHeight: 500, + borderWidth: 1, + borderColor: theme.color.neutral[300], + borderRadius: theme.radius[200], + paddingVertical: 8, + }, + scrollViewContentContainer: { + paddingVertical: 8, + gap: 12, + }, +}); + +export const TITLE_TEXT_STYLE: TextProps = { + font: 't1', + textStyle: { + alignSelf: 'center', + }, + containerStyle: { + paddingBottom: 12, + }, +}; + +export const USER_INFO_NICKNAME_TEXT_STYLE: TextProps = { + font: 'b3', +}; \ No newline at end of file diff --git a/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.tsx b/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.tsx new file mode 100644 index 00000000..f4d965bf --- /dev/null +++ b/src/screens/group/GroupChatScreen/components/UserInfosDialog/index.tsx @@ -0,0 +1,37 @@ +import { ScrollView, View } from 'react-native'; + +import { Avatar, Dialog, Text, TText } from '@/components'; +import type { GetChatRoomInfoResponse } from '@/features/chat/model/getChatRoomInfo'; + +import { styles, TITLE_TEXT_STYLE } from './index.style'; + +interface UserInfosDialogProps { + userInfos: GetChatRoomInfoResponse['data']['userInfos']; +} + +const UserInfosDialog = ({ userInfos }: UserInfosDialogProps) => ( + + + + + { + userInfos.map((userInfo) => ( + + + {userInfo.nickname} + + )) + } + + + +); + +export default UserInfosDialog; \ No newline at end of file diff --git a/src/screens/group/GroupChatScreen/index.tsx b/src/screens/group/GroupChatScreen/index.tsx index 1428d006..74ec8788 100644 --- a/src/screens/group/GroupChatScreen/index.tsx +++ b/src/screens/group/GroupChatScreen/index.tsx @@ -10,6 +10,7 @@ import { StackableScreenLayout } from '@/layout'; import type { Chat } from './components/ChatItem/types'; import ChatList from './components/ChatList'; +import ChatMoreActionsBottomSheet from './components/ChatMoreActionsBottomSheet'; import MessageInput from './components/MessageInput'; import ScreenHeader from './components/ScreenHeader'; import { useGroupChatBootstrap } from './hooks/useGroupChatBootstrap'; @@ -74,6 +75,8 @@ const GroupChatScreen = () => { sendChatMessage, }); + const [isMoreActionsBottomSheetEnabled, setIsMoreActionsBottomSheetEnabled] = useState(false); + if (!chatRoomInfoData || isChatRoomInfoPending || !profileData || isProfilePending) { return null; } @@ -86,6 +89,7 @@ const GroupChatScreen = () => { color={GROUP_COLORS[groupColorKey].strong} groupName={groupName} isHost={false} + onMoreButtonPress={() => setIsMoreActionsBottomSheetEnabled(true)} /> } keyboardControllerType='keyboardAvoidingView' @@ -103,6 +107,11 @@ const GroupChatScreen = () => { groupColorKey={groupColorKey} handleSend={handleSend} /> + ); }; diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx index 61f138c8..4efe66fb 100644 --- a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx @@ -17,7 +17,7 @@ const ColorPickItem = ({ }: ColorPickItemProps) => { const styles = createStyles(isSelected, colorKey); - return ( + return ( onSelect(colorKey)} style={styles.container}