diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg
new file mode 100644
index 00000000..88b48132
--- /dev/null
+++ b/src/assets/icons/add.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/logout.svg b/src/assets/icons/logout.svg
new file mode 100644
index 00000000..cb87385a
--- /dev/null
+++ b/src/assets/icons/logout.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/profileDialog.svg b/src/assets/icons/profileDialog.svg
new file mode 100644
index 00000000..57914426
--- /dev/null
+++ b/src/assets/icons/profileDialog.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/setting.svg b/src/assets/icons/setting.svg
new file mode 100644
index 00000000..257a4a0e
--- /dev/null
+++ b/src/assets/icons/setting.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx
new file mode 100644
index 00000000..6980a584
--- /dev/null
+++ b/src/components/Dialog/Dialog.stories.tsx
@@ -0,0 +1,76 @@
+import Icon from '@components/Icon';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import Dialog from '.';
+import * as styles from './style.css';
+
+const meta: Meta = {
+ title: 'Components/Dialog',
+ component: Dialog,
+ parameters: {
+ componentSubtitle: '다양한 액션을 제공하는 컴포넌트',
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Small: Story = {
+ render: () => (
+ <>
+
+ >
+ ),
+};
+
+export const MediumFolder: Story = {
+ render: () => (
+ <>
+
+ >
+ ),
+};
+
+export const MediumProfile: Story = {
+ render: () => (
+ <>
+
+ >
+ ),
+};
diff --git a/src/components/Dialog/components/DialogButton.tsx b/src/components/Dialog/components/DialogButton.tsx
new file mode 100644
index 00000000..c57bc153
--- /dev/null
+++ b/src/components/Dialog/components/DialogButton.tsx
@@ -0,0 +1,24 @@
+import { type PropsWithChildren } from 'react';
+
+import Button from '@components/Button';
+import * as styles from '@components/Dialog/style.css';
+import { useDialogContext } from '@hooks/useDialogContext';
+
+interface DialogButtonProps {
+ onClick: () => void;
+}
+
+const DialogButton = ({
+ children,
+ onClick,
+}: PropsWithChildren) => {
+ const { type } = useDialogContext();
+
+ return (
+
+ );
+};
+
+export default DialogButton;
diff --git a/src/components/Dialog/components/DialogTitle.tsx b/src/components/Dialog/components/DialogTitle.tsx
new file mode 100644
index 00000000..e4f05213
--- /dev/null
+++ b/src/components/Dialog/components/DialogTitle.tsx
@@ -0,0 +1,14 @@
+import { type PropsWithChildren } from 'react';
+
+import * as styles from '@components/Dialog/style.css';
+
+const DialogTitle = ({ children }: PropsWithChildren) => {
+ return (
+ <>
+ {children}
+
+ >
+ );
+};
+
+export default DialogTitle;
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
new file mode 100644
index 00000000..b57ae9d9
--- /dev/null
+++ b/src/components/Dialog/index.tsx
@@ -0,0 +1,32 @@
+import { createContext, type PropsWithChildren } from 'react';
+
+import DialogButton from '@components/Dialog/components/DialogButton';
+import DialogTitle from '@components/Dialog/components/DialogTitle';
+import * as styles from '@components/Dialog/style.css';
+
+interface DialogContextProps {
+ type: 'small' | 'medium';
+}
+
+type DialogRootProps = DialogContextProps & PropsWithChildren;
+
+export const DialogContext = createContext(null);
+
+const DialogRoot = ({ children, type }: DialogRootProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const Dialog = Object.assign(DialogRoot, {
+ Title: DialogTitle,
+ Button: DialogButton,
+});
+
+export default Dialog;
diff --git a/src/components/Dialog/style.css.ts b/src/components/Dialog/style.css.ts
new file mode 100644
index 00000000..e5a1591d
--- /dev/null
+++ b/src/components/Dialog/style.css.ts
@@ -0,0 +1,145 @@
+import { style } from '@vanilla-extract/css';
+import { recipe } from '@vanilla-extract/recipes';
+
+import { sprinkles } from '@styles/sprinkles.css';
+import { COLORS } from '@styles/tokens';
+
+export const dialogRoot = recipe({
+ base: {
+ borderRadius: '12px',
+ boxShadow: '0px 8px 15px 0px rgba(28, 28, 28, 0.08);',
+ },
+ variants: {
+ type: {
+ small: {
+ width: '100px',
+ padding: '8px',
+ },
+ medium: {
+ width: '228px',
+ padding: '12px',
+ },
+ },
+ },
+});
+
+export const dialogTitle = style({
+ position: 'relative',
+ padding: '6px 12px 10px',
+ lineHeight: '40px',
+});
+
+export const line = style({
+ height: '1px',
+ width: '196px',
+ backgroundColor: COLORS['Grey/150'],
+ margin: '4px 0',
+});
+
+export const dialogButton = recipe({
+ base: [
+ sprinkles({
+ typography: '15/Body/Regular',
+ }),
+ {
+ color: COLORS['Grey/900'],
+ borderRadius: '4px',
+ display: 'block',
+ ':hover': {
+ backgroundColor: COLORS['Grey/100'],
+ },
+ },
+ ],
+ variants: {
+ type: {
+ small: {
+ padding: '8px',
+ width: '84px',
+ selectors: {
+ '& + &': {
+ marginTop: '8px',
+ },
+ },
+ },
+ medium: {
+ padding: '8px 12px',
+ width: '204px',
+ textAlign: 'left',
+ selectors: {
+ '& + &': {
+ marginTop: '8px',
+ },
+ },
+ },
+ },
+ },
+});
+
+export const badge = style([
+ sprinkles({
+ typography: '11/Caption/Medium',
+ }),
+ {
+ marginLeft: '4px',
+ backgroundColor: COLORS['Blue/Light'],
+ color: COLORS['Blue/Default'],
+ width: '32px',
+ height: '20px',
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ marginTop: '-2px',
+ padding: '3px 6px',
+ borderRadius: '100px',
+ },
+]);
+
+export const iconTitleText = style([
+ sprinkles({
+ typography: '16/Title/Medium',
+ }),
+ {
+ color: COLORS['Grey/900'],
+ marginLeft: '48px',
+ },
+]);
+
+export const iconMediumText = style([
+ sprinkles({
+ typography: '15/Body/Medium',
+ }),
+ {
+ color: COLORS['Grey/400'],
+ marginLeft: '28px',
+ },
+]);
+
+export const iconRegularText = style([
+ sprinkles({
+ typography: '15/Body/Regular',
+ }),
+ {
+ color: COLORS['Grey/800'],
+ marginLeft: '28px',
+ },
+]);
+
+export const icon = style({
+ position: 'absolute',
+ marginTop: '2px',
+});
+
+export const circle = style({
+ width: '40px',
+ height: '40px',
+ backgroundColor: COLORS['Grey/100'],
+ position: 'absolute',
+ top: '6px',
+ borderRadius: '50%',
+ zIndex: -1,
+});
+
+export const profileIcon = style({
+ position: 'absolute',
+ top: '13px',
+ left: '20px',
+});
diff --git a/src/constants/icon.ts b/src/constants/icon.ts
index 501e2eeb..9d171579 100644
--- a/src/constants/icon.ts
+++ b/src/constants/icon.ts
@@ -1,16 +1,24 @@
+import Add from '@assets/icons/add.svg';
import Archive from '@assets/icons/archive.svg';
import Close from '@assets/icons/close.svg';
import Copy from '@assets/icons/copy.svg';
+import Logout from '@assets/icons/logout.svg';
import Menu from '@assets/icons/menu.svg';
import Profle from '@assets/icons/profile.svg';
+import ProfileDialog from '@assets/icons/profileDialog.svg';
+import Setting from '@assets/icons/setting.svg';
import Submit from '@assets/icons/submit.svg';
export const iconFactory = {
+ add: Add,
archive: Archive,
close: Close,
copy: Copy,
+ logout: Logout,
menu: Menu,
profile: Profle,
+ profileDialog: ProfileDialog,
+ setting: Setting,
submit: Submit,
};
diff --git a/src/hooks/useDialogContext.ts b/src/hooks/useDialogContext.ts
new file mode 100644
index 00000000..23636c72
--- /dev/null
+++ b/src/hooks/useDialogContext.ts
@@ -0,0 +1,15 @@
+import { useContext } from 'react';
+
+import { DialogContext } from '../components/Dialog';
+
+export const useDialogContext = () => {
+ const ctx = useContext(DialogContext);
+
+ if (!ctx) {
+ throw new Error(
+ 'useDialogContext hook must be used within a Dialog component',
+ );
+ }
+
+ return ctx;
+};