Skip to content
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

✨ feat: 편지쓰기 페이지 UI 구현 #60

Merged
merged 11 commits into from
Feb 2, 2024
Merged
123 changes: 117 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@hookform/resolvers": "^3.3.4",
"@mui/material": "^5.15.5",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.17.12",
"axios": "^1.6.5",
"clsx": "^2.1.0",
Expand Down
17 changes: 2 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import { Link, Outlet } from 'react-router-dom';
import { Outlet } from 'react-router-dom';
import Background from '@/components/Background';
import BackgroundImg from '@/assets/ocean.jpeg';
import EmotionTestButton from '@/components/EmotionTestButton';
import { ROUTER_PATHS } from '@/router';
import BackgroundImg from '@/assets/background.png';

const App = () => {
return (
<Background imageUrl={BackgroundImg}>
<h1>
Hello World! Hello World! Hello World! Hello World! Hello World! Hello
World! Hello World! Hello World! Hello World! Hello World! Hello World!
Hello World! Hello World! Hello World! Hello World!
</h1>
<p>Vercel 배포!</p>
<EmotionTestButton size="small">이모션 테스트</EmotionTestButton>
<Link to={ROUTER_PATHS.TEST_CONSTANT}>Test Constant Page</Link>
<Link to={ROUTER_PATHS.TEST_VARIABLE('5')}>Test 5 Page</Link>
<Link to={ROUTER_PATHS.MODAL_TEST}>Modal Test Page</Link>
<Outlet />
<button css={{ width: '100%' }}>하단버튼</button>
</Background>
);
};
Expand Down
Binary file added src/assets/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/ocean.jpeg
Binary file not shown.
24 changes: 12 additions & 12 deletions src/components/IconButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { forwardRef } from 'react';
import style, { ButtonVariant } from './styles';

interface IconButtonProps extends React.ComponentProps<'button'> {
Expand All @@ -8,16 +8,16 @@ interface IconButtonProps extends React.ComponentProps<'button'> {
children: React.ReactNode;
}

const IconButton = ({
variant = 'header',
children,
...props
}: IconButtonProps) => {
return (
<button {...props} css={style.button(variant)}>
{children}
</button>
);
};
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
({ variant = 'header', children, ...props }, ref) => {
return (
<button {...props} ref={ref} css={style.button(variant)}>
{children}
</button>
);
},
);

IconButton.displayName = 'IconButton';

export default IconButton;
46 changes: 46 additions & 0 deletions src/components/Tooltip/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Meta, StoryObj } from '@storybook/react';
import { css } from '@emotion/react';
import { SoundOff } from '@/assets/icons';
import IconButton from '../IconButton';
import Tooltip from './';

const meta = {
title: 'Components/Tooltip',
component: Tooltip,
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof Tooltip>;

export default meta;

type Story = StoryObj<typeof meta>;

const styles = {
background: css`
display: flex;
justify-content: flex-end;
padding: 1rem 1rem 3rem;
background-color: #9eddfc;
`,
};

export const Primary: Story = {
args: {
side: 'bottom',
align: 'end',
delay: 1000,
triggerContent: (
<IconButton>
<SoundOff color="white" />
</IconButton>
),
children: '소리를 켜 바다를 느껴보세요',
},
decorators: (Story) => {
return (
<div css={styles.background}>
<Story />
</div>
);
},
};
65 changes: 65 additions & 0 deletions src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import * as T from '@radix-ui/react-tooltip';
import useTimeout from '@/hooks/useTimeout';
import textStyles from '@/styles/textStyles';
import styles from './styles';

interface TooltipProps {
/** 툴팁의 위치를 지정합니다. */
side?: 'left' | 'top' | 'right' | 'bottom';
/** 화살표 모양의 위치를 지정합니다. */
align?: 'start' | 'center' | 'end';
/** 마운트시 최초로 툴팁을 보여줄 시간을 지정합니다. */
delay?: number;
/** 툴팁을 보여줄 트리거 컨텐츠를 지정합니다. */
triggerContent: React.ReactNode;
/** 툴팁 내용을 지정합니다. */
children?: React.ReactNode;
}

/** 마우스를 hover할 때 툴팁 컨텐츠를 보여주는 컴포넌트입니다. */
const Tooltip = ({
side = 'bottom',
align = 'center',
delay = 0,
triggerContent,
children,
}: TooltipProps) => {
const [isOpen, setIsOpen] = useState(delay > 0);

useTimeout(() => setIsOpen(false), delay);

return (
<T.Provider>
<T.Root open={isOpen} onOpenChange={setIsOpen}>
<T.Trigger asChild>{triggerContent}</T.Trigger>
<AnimatePresence>
{isOpen && (
<T.Portal forceMount>
<T.Content
css={[styles.content, textStyles.c1r]}
asChild
sideOffset={5}
align={align}
side={side}
>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.1 }}
>
{children}
<T.Arrow css={styles.arrow} />
</motion.div>
</T.Content>
</T.Portal>
)}
</AnimatePresence>
</T.Root>
</T.Provider>
);
};

export default Tooltip;
20 changes: 20 additions & 0 deletions src/components/Tooltip/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { css } from '@emotion/react';

const styles = {
content: css`
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
background-color: white;
box-shadow:
0 0 4px 0 rgb(0 0 0 / 0.12),
2px 6px 12px 0 rgb(0 0 0 / 0.12);
text-align: center;
`,
arrow: css`
width: 0.75rem;
height: 0.5rem;
fill: white;
`,
};

export default styles;
Loading
Loading