-
Notifications
You must be signed in to change notification settings - Fork 0
AlertModal 및 ProfileImage 컴포넌트 생성 #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { View } from "react-native"; | ||
| import { globalGray100 } from "../../ui/index"; | ||
| import AlertModal from "../../ui/organisms/AlertModal"; | ||
|
|
||
| const meta: Meta<typeof AlertModal> = { | ||
| title: "organisms/AlertModal", | ||
| component: AlertModal, | ||
| decorators: [ | ||
| (Story) => ( | ||
| <View | ||
| style={{ | ||
| backgroundColor: globalGray100, | ||
| padding: 20, | ||
| }} | ||
| > | ||
| <Story /> | ||
| </View> | ||
| ), | ||
| ], | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof AlertModal>; | ||
|
|
||
| // 1. 버튼 하나 | ||
| export const SingleButton: Story = { | ||
| args: { | ||
| title: "알림", | ||
| content: "작업이 완료되었습니다.", | ||
| confirmLabel: "확인", | ||
| onConfirm: () => { | ||
| console.log("확인 클릭"); | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| // 2. 버튼 두 개 | ||
| export const TwoButtons: Story = { | ||
| args: { | ||
| title: "알림", | ||
| content: "이 작업을 진행하시겠습니까?", | ||
| cancelLabel: "취소", | ||
| confirmLabel: "확인", | ||
| onCancel: () => { | ||
| console.log("취소 클릭"); | ||
| }, | ||
| onConfirm: () => { | ||
| console.log("확인 클릭"); | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| // 3. 이미지 있음 | ||
| export const WithDefaultProfileImage: Story = { | ||
| args: { | ||
| title: "모달 타이틀", | ||
| content: "모달 본문내용 모달 본문내용 모달 본문내용 모달 본문내용", | ||
| confirmLabel: "맞아요", | ||
| onConfirm: () => { | ||
| console.log("확인 클릭"); | ||
| }, | ||
| }, | ||
| render: (args) => ( | ||
| <AlertModal {...args}> | ||
| <AlertModal.ProfileImage size={56} /> | ||
| </AlertModal> | ||
| ), | ||
| }; | ||
|
|
||
| // 4. 긴 텍스트 | ||
| export const LongContent: Story = { | ||
| args: { | ||
| title: "긴 제목이 들어가는 경우 테스트", | ||
| content: | ||
| "이것은 매우 긴 콘텐츠 텍스트입니다. 모달의 width가 화면 크기에 따라 조절되고, 콘텐츠는 자동으로 wrap되어야 합니다. 여러 줄에 걸쳐 표시되는 긴 텍스트를 테스트하기 위한 예시입니다.", | ||
| cancelLabel: "취소", | ||
| confirmLabel: "확인", | ||
| onCancel: () => { | ||
| console.log("취소 클릭"); | ||
| }, | ||
| onConfirm: () => { | ||
| console.log("확인 클릭"); | ||
| }, | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { Image } from "react-native"; | ||
|
|
||
| const defaultUserImage = require("../../../assets/icons/user.svg"); | ||
|
|
||
| interface ProfileImageProps { | ||
| uri?: string; | ||
| size?: number; | ||
| } | ||
|
|
||
| const ProfileImage = ({ uri, size }: ProfileImageProps) => { | ||
| const imageSource = uri ? { uri } : defaultUserImage; | ||
|
|
||
| return <Image source={imageSource} style={{ width: size, height: size }} />; | ||
| }; | ||
|
|
||
| export default ProfileImage; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import { StyleSheet, View } from "react-native"; | ||
| import NemoText from "../atoms/NemoText"; | ||
| import ProfileImage from "../atoms/ProfileImage"; | ||
| import { | ||
| globalGray0, | ||
| globalGray700, | ||
| globalSpacingLg, | ||
| globalSpacingSm, | ||
| globalSpacingXs, | ||
| } from "../index"; | ||
| import ModalButton from "../molecules/ModalButton"; | ||
|
|
||
| interface AlertModalProps { | ||
| children?: React.ReactNode; | ||
| title: string; | ||
| content?: string; | ||
| cancelLabel?: string; | ||
| confirmLabel: string; | ||
| onCancel?: () => void; | ||
| onConfirm: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * AlertModal 컴포넌트 | ||
| * | ||
| * @example | ||
| * // 버튼 하나만 | ||
| * <AlertModal | ||
| * title="알림" | ||
| * content="작업이 완료되었습니다." | ||
| * confirmLabel="확인" | ||
| * onConfirm={() => {}} | ||
| * /> | ||
| * | ||
| * @example | ||
| * // 버튼 두 개 | ||
| * <AlertModal | ||
| * title="알림" | ||
| * content="이 작업을 진행하시겠습니까?" | ||
| * cancelLabel="취소" | ||
| * confirmLabel="확인" | ||
| * onCancel={() => {}} | ||
| * onConfirm={() => {}} | ||
| * /> | ||
| * | ||
| * @example | ||
| * // 프로필 이미지 포함 (기본 이미지) | ||
| * <AlertModal | ||
| * title="모달 타이틀" | ||
| * content="모달 본문내용" | ||
| * confirmLabel="맞아요" | ||
| * onConfirm={() => {}} | ||
| * > | ||
| * <AlertModal.ProfileImage /> | ||
| * </AlertModal> | ||
| * | ||
| * @example | ||
| * // 프로필 이미지 포함 (커스텀 이미지) | ||
| * <AlertModal | ||
| * title="모달 타이틀" | ||
| * content="모달 본문내용" | ||
| * cancelLabel="취소하기" | ||
| * confirmLabel="맞아요" | ||
| * onCancel={() => {}} | ||
| * onConfirm={() => {}} | ||
| * > | ||
| * <AlertModal.ProfileImage uri={userData?.profileImage} size={56} /> | ||
| * </AlertModal> | ||
| */ | ||
|
|
||
| const AlertModal = ({ | ||
| title, | ||
| content, | ||
| cancelLabel, | ||
| confirmLabel, | ||
| onCancel, | ||
| onConfirm, | ||
| children, | ||
| }: AlertModalProps) => { | ||
| return ( | ||
| <View style={styles.container}> | ||
| {children && <View style={styles.image}>{children}</View>} | ||
|
|
||
| <View style={styles.title}> | ||
| <NemoText level="h3">{title}</NemoText> | ||
| </View> | ||
|
|
||
| {content && ( | ||
| <View style={styles.content}> | ||
| <NemoText level="body2" style={{ color: globalGray700 }}> | ||
| {content} | ||
| </NemoText> | ||
| </View> | ||
| )} | ||
|
|
||
| <View style={styles.footer}> | ||
| {cancelLabel && onCancel && ( | ||
| <View style={styles.buttonWrapper}> | ||
| <ModalButton | ||
| label={cancelLabel} | ||
| variant="secondary" | ||
| onPress={onCancel} | ||
| /> | ||
| </View> | ||
| )} | ||
| <View style={styles.buttonWrapper}> | ||
| <ModalButton | ||
| label={confirmLabel} | ||
| variant="primary" | ||
| onPress={onConfirm} | ||
| /> | ||
| </View> | ||
| </View> | ||
| </View> | ||
| ); | ||
| }; | ||
|
|
||
| // Composition을 위해 ProfileImage를 붙임 | ||
| AlertModal.ProfileImage = ProfileImage; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| width: "100%", | ||
| marginHorizontal: 53, | ||
| backgroundColor: globalGray0, | ||
| borderRadius: 14, | ||
| gap: globalSpacingSm, | ||
| padding: globalSpacingLg, | ||
| alignSelf: "center", | ||
| }, | ||
| image: { | ||
| alignItems: "center", | ||
| }, | ||
| title: { | ||
| alignItems: "center", | ||
| }, | ||
| content: { | ||
| alignItems: "center", | ||
| }, | ||
| footer: { | ||
| flexDirection: "row", | ||
| gap: globalSpacingXs, | ||
| }, | ||
| buttonWrapper: { | ||
| flex: 1, | ||
| }, | ||
| }); | ||
|
|
||
| export default AlertModal; | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
프랍으로 받는 것과 칠드런으로 받는 것의 차이가 뭔가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
children으로 받는 것은 AlertModal에 프로필 이미지가 들어가냐의 여부입니다. 그 외에 것들은 props로 넘겨줍니다.
ProfileImage는 기본 이미지 처리, uri 분기, 사이즈 관리 등의 조건 로직을 가지므로
AlertModal이 이를 props로 직접 제어하는 것은 책임 과도하다고 판단했습니다.
따라서 AlertModal은 레이아웃과 인터랙션만 담당하고,
프로필 이미지는 children으로 주입받는 구조로 설계했습니다.
또한, ProfileImage는 AlertModal 외의 영역에서도 사용될 수 있다고 판단했습니다.
모달 내부 구현에 종속시키지 않고 children으로 전달하도록 설계했습니다.
예시
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 지금보니 Input 들어가는 상황을 처리 안해줬네요. 다시 추가해서 커밋 올리도록 하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AlertModal 하나에 프랍과 이벤트가 많이 들어가고 옵셔널로 들어갈 수 있는 게 많다보니 나중에 사용할 때 헷갈릴 수도 있다고 생각합니다.
특히, 취소 버튼이 있을 때 취소 이벤트가 필수적으로 들어가야하는 부분에서 취소 이벤트만 들어가는 경우가 생길 수도 있다고 생각합니다.
해당 컴포넌트를 구성하는 컴포넌트들이 아톰들로 이루어져 있어서 단계로 보면 molecules로 들어가야 하나, 책임에 대해 생각하면 지금처럼 organisms에 있는게 맞다고 생각이 드네요..흠.. 내부 컴포넌트들을 한 번 moledules에 정의해서 책임 분리를 한 후에 진행하면 어떨까합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 의견 감사합니다 😊 저도 같은 문제로 고민 중이었습니다.
기존 단일 컴포넌트에 모든 props를 받던 구조이다보니 사용하실 때, 어려움이 발생한다는걸 인지하였습니다.
이를 보다 용이하게 molecule 단위로 책임 분리 (Title/Text/Input/Actions) 나눠서 AlertModal 컴포넌트 안에 가져다 쓰는 형태로 개선하였습니다.
자세한 사용 방법이나 코드는 #56 여기서 확인해주시면 감사하겠습니다.