diff --git a/assets/icons/user.svg b/assets/icons/user.svg new file mode 100644 index 0000000..06bf78d --- /dev/null +++ b/assets/icons/user.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/shared/stories/organisms/AlertModal.stories.tsx b/shared/stories/organisms/AlertModal.stories.tsx new file mode 100644 index 0000000..9a44d81 --- /dev/null +++ b/shared/stories/organisms/AlertModal.stories.tsx @@ -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 = { + title: "organisms/AlertModal", + component: AlertModal, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; + +type Story = StoryObj; + +// 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) => ( + + + + ), +}; + +// 4. 긴 텍스트 +export const LongContent: Story = { + args: { + title: "긴 제목이 들어가는 경우 테스트", + content: + "이것은 매우 긴 콘텐츠 텍스트입니다. 모달의 width가 화면 크기에 따라 조절되고, 콘텐츠는 자동으로 wrap되어야 합니다. 여러 줄에 걸쳐 표시되는 긴 텍스트를 테스트하기 위한 예시입니다.", + cancelLabel: "취소", + confirmLabel: "확인", + onCancel: () => { + console.log("취소 클릭"); + }, + onConfirm: () => { + console.log("확인 클릭"); + }, + }, +}; diff --git a/shared/ui/atoms/ProfileImage.tsx b/shared/ui/atoms/ProfileImage.tsx new file mode 100644 index 0000000..be52805 --- /dev/null +++ b/shared/ui/atoms/ProfileImage.tsx @@ -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 ; +}; + +export default ProfileImage; diff --git a/shared/ui/organisms/AlertModal.tsx b/shared/ui/organisms/AlertModal.tsx new file mode 100644 index 0000000..4dc4a44 --- /dev/null +++ b/shared/ui/organisms/AlertModal.tsx @@ -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 + * // 버튼 하나만 + * {}} + * /> + * + * @example + * // 버튼 두 개 + * {}} + * onConfirm={() => {}} + * /> + * + * @example + * // 프로필 이미지 포함 (기본 이미지) + * {}} + * > + * + * + * + * @example + * // 프로필 이미지 포함 (커스텀 이미지) + * {}} + * onConfirm={() => {}} + * > + * + * + */ + +const AlertModal = ({ + title, + content, + cancelLabel, + confirmLabel, + onCancel, + onConfirm, + children, +}: AlertModalProps) => { + return ( + + {children && {children}} + + + {title} + + + {content && ( + + + {content} + + + )} + + + {cancelLabel && onCancel && ( + + + + )} + + + + + + ); +}; + +// 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;