Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/components/BottomSheet/ActionListBottomSheet/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<TouchableRipple containerStyle={styles.actionButtonContainer} onPress={onPress}>
<Text font='b2'>
{label}
</Text>
</TouchableRipple>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StyleSheet } from 'react-native';

export const styles = StyleSheet.create({
actionButtonContainer: {
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 12,
},
});
29 changes: 29 additions & 0 deletions src/components/BottomSheet/ActionListBottomSheet/index.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<BottomSheet {...props}>
{actionItems.map((item, idx) => (
<View key={`actionButton-${item.label}`}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

리스트를 렌더링할 때 key는 각 항목을 고유하게 식별해야 합니다. 현재 item.label을 사용하고 있는데, 만약 actionItems 배열에 동일한 label을 가진 항목이 여러 개 존재할 경우 React가 항목을 제대로 구분하지 못해 예기치 않은 동작을 유발할 수 있습니다. key의 고유성을 보장하기 위해 idx를 함께 사용하는 것을 권장합니다.

Suggested change
<View key={`actionButton-${item.label}`}>
<View key={`actionButton-${item.label}-${idx}`}>

Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using item.label as part of the React key can cause issues if labels are not unique or if they change (e.g., when language changes in i18n). Consider using the array index as the key instead, or if actionItems have unique IDs, use those. Example: key={idx} or key={item.id || idx}.

Suggested change
<View key={`actionButton-${item.label}`}>
<View key={`actionButton-${idx}`}>

Copilot uses AI. Check for mistakes.
{idx > 0 && <Shapes.Line />}
<ActionButton
label={item.label}
onPress={item.onPress}
/>
</View>
))}
</BottomSheet>
);

export default ActionListBottomSheet;
7 changes: 5 additions & 2 deletions src/components/BottomSheet/BottomSheetCommon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -13,6 +13,7 @@ export interface BottomSheetCommonProps extends PropsWithChildren {
onClose?: () => void;
/** iOS에서는 false로 넘겨도 되며, 기본값 true */
closeOnBackdropPress?: boolean;
closeOnHardwareBack?: boolean;
/** SafeAreaInsets.bottom 혹은 0 */
bottomPadding: number;
/** useNativeDriver 플래그 */
Expand All @@ -23,6 +24,7 @@ const BottomSheetCommon = ({
enabled,
onClose,
closeOnBackdropPress = true,
closeOnHardwareBack = true,
bottomPadding,
useNativeDriver,
children,
Expand Down Expand Up @@ -53,7 +55,7 @@ const BottomSheetCommon = ({
};

useHardwareBack(() => {
if (enabled && onClose) {
if (closeOnHardwareBack && enabled && onClose) {
onClose();
return true;
}
Expand All @@ -63,6 +65,7 @@ const BottomSheetCommon = ({
useEffect(() => {
if (enabled) {
setHidden(false);
Keyboard.dismiss();
}
const anim = Animated.timing(animation, {
toValue: enabled ? 1 : 0,
Expand Down
4 changes: 2 additions & 2 deletions src/components/BottomSheet/index.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type BottomSheetProps = Omit<
'bottomPadding' | 'useNativeDriver'
>;

const BottomSheet = (props: BottomSheetProps) => {
export const BottomSheet = (props: BottomSheetProps) => {
const insets = useSafeAreaInsets();

return (
Expand All @@ -23,4 +23,4 @@ const BottomSheet = (props: BottomSheetProps) => {
);
};

export default BottomSheet;
export { default as ActionListBottomSheet } from './ActionListBottomSheet';
4 changes: 2 additions & 2 deletions src/components/BottomSheet/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type BottomSheetProps = Omit<
'bottomPadding' | 'useNativeDriver'
>;

const BottomSheet = (props: BottomSheetProps) => {
export const BottomSheet = (props: BottomSheetProps) => {
const insets = useSafeAreaInsets();

return (
Expand All @@ -32,4 +32,4 @@ const BottomSheet = (props: BottomSheetProps) => {
);
};

export default BottomSheet;
export { default as ActionListBottomSheet } from './ActionListBottomSheet';
6 changes: 3 additions & 3 deletions src/components/BottomSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ 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) => (
<BottomSheetCommon
{...props}
bottomPadding={0}
useNativeDriver={!filterPlatform(['windows'])}
/>
);

export default BottomSheet;
export { default as ActionListBottomSheet } from './ActionListBottomSheet';
9 changes: 0 additions & 9 deletions src/components/Dialog/Alert/index.style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { StyleSheet } from 'react-native';

import type { TextProps } from '@/components/Text';
import { theme } from '@/theme';

export const styles = StyleSheet.create({
Expand All @@ -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',
};
3 changes: 2 additions & 1 deletion src/components/Dialog/Alert/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions src/components/Dialog/Any/index.style.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { StyleSheet } from 'react-native';

export const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
container: {
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of flex: 1 and alignItems: 'center' from the Dialog.Any container style is a breaking change that could affect existing usages of Dialog.Any. While the existing usages in DateTimePickerWindows and GroupColorPickerDialog appear to handle their own layout, ensure that all usages of Dialog.Any throughout the codebase are tested to verify they still display correctly with this change.

Suggested change
container: {
container: {
flex: 1,
alignItems: 'center',

Copilot uses AI. Check for mistakes.
},
});
2 changes: 2 additions & 0 deletions src/components/Dialog/Any/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Comment on lines +4 to +7

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

사용하지 않는 Text 컴포넌트와 TITLE_TEXT_STYLE 상수가 import되었습니다. 코드의 가독성과 유지보수성을 위해 불필요한 import는 제거하는 것이 좋습니다.

Suggested change
import Text from '@/components/Text';
import { useDialog } from '@/hooks/useDialog';
import { TITLE_TEXT_STYLE } from '../index.style';
import { useDialog } from '@/hooks/useDialog';

Comment on lines +4 to +7
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports Text and TITLE_TEXT_STYLE are not used in this component. These were likely added in preparation for a feature but are not actually utilized. Remove these unused imports to keep the code clean.

Suggested change
import Text from '@/components/Text';
import { useDialog } from '@/hooks/useDialog';
import { TITLE_TEXT_STYLE } from '../index.style';
import { useDialog } from '@/hooks/useDialog';

Copilot uses AI. Check for mistakes.
import { styles } from './index.style';

interface AnyDialogProps {
Expand Down
18 changes: 0 additions & 18 deletions src/components/Dialog/Confirm/index.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
};
3 changes: 2 additions & 1 deletion src/components/Dialog/Confirm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions src/components/Dialog/index.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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],
};
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion src/locales/ko/groupChat.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"write-message": "메시지 작성"
"write-message": "메시지 작성",
"group-member-list": "그룹 멤버 목록"
}
Original file line number Diff line number Diff line change
@@ -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 (
<ActionListBottomSheet
actionItems={[
{
label: tViewGroupMembers,
onPress: () => {
dialogService.add({
content: (
<UserInfosDialog userInfos={userInfos} />
),
});
},
Comment on lines +26 to +32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

멤버 목록 보기 버튼을 누르면 다이얼로그가 표시되는데, 이 때 뒤에 있는 BottomSheet가 닫히지 않아 사용자 경험을 해칠 수 있습니다. 다이얼로그를 표시하기 전에 setBottomSheetEnabled(false)를 호출하여 BottomSheet를 먼저 닫아주는 것이 좋겠습니다.

          onPress: () => {
            setBottomSheetEnabled(false);
            dialogService.add({
              content: (
                <UserInfosDialog userInfos={userInfos} />
              ),
            });
          },

Comment on lines +26 to +32
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bottom sheet should be closed before opening the dialog. Currently, when the user presses the "View Group Members" action, the dialog opens while the bottom sheet remains visible in the background. This creates a poor user experience with overlapping UI elements. Add a call to setBottomSheetEnabled(false) before dialogService.add() to close the bottom sheet first.

Copilot uses AI. Check for mistakes.
},
]}
enabled={bottomSheetEnabled}
onClose={() => setBottomSheetEnabled(false)}
/>
);

Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the blank line between the closing JSX and the closing brace. This is inconsistent with the formatting in other components in the codebase.

Suggested change

Copilot uses AI. Check for mistakes.
};

export default ChatMoreActionsBottomSheet;
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -46,6 +48,7 @@ const ScreenHeader = ({
<Button.Icon
Icon={MoreIcon}
{...ICON_STYLE(color)}
onPress={onMoreButtonPress}
/>
</View>
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Comment on lines +35 to +38
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The USER_INFO_NICKNAME_TEXT_STYLE constant is exported but never imported or used anywhere in the codebase. Since the nickname text styling is applied inline with the font prop in UserInfosDialog/index.tsx line 28, this exported constant is redundant. Remove it to avoid confusion.

Suggested change
};
export const USER_INFO_NICKNAME_TEXT_STYLE: TextProps = {
font: 'b3',

Copilot uses AI. Check for mistakes.
};
Loading