-
Notifications
You must be signed in to change notification settings - Fork 4
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
공지사항 목록 컴포넌트 / 공지사항 상세 컴포넌트 UI 구현 / 공지사항 ROUTER 설정 #749
Changes from 10 commits
7a9a4a4
7f841f3
efc71a3
b69cf4b
de57115
962d1ae
5b1f7f9
6118863
94de948
3c60fa1
4795a40
98375d0
4000618
bb9881b
0f730f9
9ce18cd
41aec0a
284b2e1
60464df
fd69f50
4d83d1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import NoticeDetail from '.'; | ||
|
||
const meta: Meta<typeof NoticeDetail> = { | ||
component: NoticeDetail, | ||
}; | ||
|
||
const MOCK_NOTICE_DETAIL = { | ||
title: '🎉보투게더 출시 기념 이벤트 종료', | ||
content: `안녕하세요, 보투게더(VoTogether)는 우아한테크코스에서 진행한 프로젝트로, 투표 중심의 커뮤니티 플랫폼입니다! | ||
|
||
🤷♂️: 돈이 부족한 취준생인데 알바를 병행하는게 좋을까요, 최대한 절약하는게 좋을까요? | ||
🤷♀️: 요즘 체력이 떨어지는데 어떤 운동을 시작하면 좋을까요? | ||
🤷♂️: 오늘의 점심 메뉴는 뭐가 좋을까? | ||
다 함께, 즐겁게, 심플하게! 보투게더를 이용해 보세요! | ||
|
||
✅ 보투게더(VoTogether)는 투표를 통해 의견을 공유하고, 일상의 재미를 발견하는 커뮤니티 서비스예요. | ||
|
||
🖋 고민이 있으신가요? 글을 써보세요! | ||
😆 심심하신가요? 투표를 해보세요! | ||
❔ 궁금하신가요? 관심사를 탐색해 보세요! | ||
|
||
보투게더는 사람들의 다양한 주제로 질문하고 답변하면서, 사람들의 반응을 확인할 수 있다는 점에서 특별해요. | ||
자, 이제 보투게더를 이용할 준비되셨나요? 😆😃 | ||
나의 이야기가 우리의 이야기가 되는 공간, 보투게더에서 우리 함께해요! 👍 | ||
|
||
🚀보투게더 출시 이벤트 종료 및 상품 수령 대상자 공지 | ||
보투게더 서비스의 런칭 기념 이벤트에 참여해주신 많은 분들 감사드립니다!! | ||
랭킹 순위에서 보투게더 팀원(관리자)은 제외하고 집계되었으며, 상품 수령 대상 닉네임은 아래와 같습니다. | ||
|
||
⭐ 게시글을 가장 많이 작성🖋️해주신 이용자! - Swimminggoggles | ||
⭐ 작성한 게시글에 가장 많은 투표✅를 해주신 이용자! - 익명의스내기 | ||
⭐ 최고 인기글🔥을 작성하신 이용자! - 익명의익명 | ||
|
||
위 닉네임들에 해당하시는 분들은 상품 수령을 위하여,보투게더 사이트에 로그인된 화면 인증샷을 채널톡으로 보내주시기 바랍니다! | ||
앞으로도 보투게더 서비스에 많은 관심과 사랑 부탁드립니다. 감사합니다😊😄`, | ||
createdAt: '2023-09-23 18:23', | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof NoticeDetail>; | ||
|
||
export const Default: Story = { | ||
render: () => <NoticeDetail {...MOCK_NOTICE_DETAIL} />, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import SquareButton from '@components/common/SquareButton'; | ||
|
||
import * as S from './style'; | ||
|
||
interface NoticeDetailProps { | ||
title: string; | ||
content: string; | ||
createdAt: string; | ||
} | ||
|
||
export default function NoticeDetail({ title, content, createdAt }: NoticeDetailProps) { | ||
const navigate = useNavigate(); | ||
const createdDate = createdAt.slice(0, 10); | ||
|
||
return ( | ||
<S.Container> | ||
<S.Category tabIndex={0}>VoTogether 공지사항</S.Category> | ||
<S.Title tabIndex={0}>{title}</S.Title> | ||
<S.CreatedAt tabIndex={0}>작성일 : {createdDate}</S.CreatedAt> | ||
<S.Content tabIndex={0}>{content}</S.Content> | ||
<S.ButtonContainer> | ||
<S.ButtonWrapper> | ||
<SquareButton | ||
theme="fill" | ||
onClick={() => { | ||
navigate('/'); | ||
}} | ||
> | ||
홈으로 가기 | ||
</SquareButton> | ||
</S.ButtonWrapper> | ||
<S.ButtonWrapper> | ||
<SquareButton | ||
theme="blank" | ||
onClick={() => { | ||
navigate('/notices'); | ||
}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PATH.NOTICE 어떠신가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 제안 감사합니다 |
||
> | ||
{`공지사항\n목록으로 가기`} | ||
</SquareButton> | ||
</S.ButtonWrapper> | ||
</S.ButtonContainer> | ||
</S.Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { styled } from 'styled-components'; | ||
|
||
import { theme } from '@styles/theme'; | ||
|
||
export const Container = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
|
||
width: 100%; | ||
max-width: 600px; | ||
padding-top: 48px; | ||
|
||
@media (min-width: ${theme.breakpoint.sm}) { | ||
padding-top: 30px; | ||
} | ||
`; | ||
|
||
export const Category = styled.span` | ||
font: var(--text-body); | ||
`; | ||
|
||
export const Title = styled.h1` | ||
margin-top: 20px; | ||
|
||
font: var(--text-title); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 조금 작은거 같은데 제가 알림 PR에다가 이거보다 큰 --text-page-title인가,, 만들었는데 나중에 그걸로 바꿔도 좋을 것 같아요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋아요! |
||
`; | ||
|
||
export const CreatedAt = styled.span` | ||
margin: 20px 0; | ||
|
||
font: var(--text-body); | ||
font-size: 1.4rem; | ||
text-align: right; | ||
|
||
color: var(--text-dark-gray); | ||
`; | ||
|
||
export const Content = styled.p` | ||
font: var(--text-body); | ||
|
||
white-space: pre-wrap; | ||
`; | ||
|
||
export const ButtonContainer = styled.div` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
align-items: center; | ||
gap: 20px; | ||
|
||
margin-top: 50px; | ||
|
||
@media (min-width: ${theme.breakpoint.sm}) { | ||
flex-direction: row; | ||
gap: 80px; | ||
|
||
padding: 0 100px; | ||
} | ||
`; | ||
|
||
export const ButtonWrapper = styled.div` | ||
display: flex; | ||
width: 100%; | ||
height: 40px; | ||
|
||
@media (min-width: ${theme.breakpoint.sm}) { | ||
width: 140px; | ||
height: 60px; | ||
|
||
white-space: pre-wrap; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import NoticeItem from '.'; | ||
|
||
const meta: Meta<typeof NoticeItem> = { | ||
component: NoticeItem, | ||
decorators: [storyFn => <div style={{ width: '576px' }}>{storyFn()}</div>], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof NoticeItem>; | ||
|
||
const MOCK_NOTICE_ITEM = { | ||
id: 1, | ||
title: '방방뛰는 코끼리', | ||
createdAt: '2022-01-11 12:23', | ||
}; | ||
|
||
export const Default: Story = { | ||
render: () => <NoticeItem {...MOCK_NOTICE_ITEM} />, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { PATH } from '@constants/path'; | ||
|
||
import * as S from './style'; | ||
|
||
interface NoticeItemProps { | ||
id: number; | ||
title: string; | ||
/** | ||
* yyyy-mm-dd 형식 | ||
*/ | ||
createdAt: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DateString처럼 이것도 타입 강화하는건 어떻게 생각하시나요? |
||
} | ||
export default function NoticeItem({ id, title, createdAt }: NoticeItemProps) { | ||
const createdDate = createdAt.slice(0, 10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. 시간은 안보여주는 디자인으로 구현해서 잘라서 보여줬어요 yyyy-mm-dd 형식으로욤 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헷갈리는 주석을 때문에 혼동이 와서 주석을 삭제했어요 🔥 |
||
|
||
return ( | ||
<S.Container> | ||
<S.DetailLink to={`${PATH.NOTICES}/${id}`}> | ||
<S.Title>{title}</S.Title> | ||
<S.CreatedAt>{createdDate}</S.CreatedAt> | ||
</S.DetailLink> | ||
</S.Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Link } from 'react-router-dom'; | ||
|
||
import { styled } from 'styled-components'; | ||
|
||
import { theme } from '@styles/theme'; | ||
|
||
export const Container = styled.li``; | ||
|
||
export const DetailLink = styled(Link)` | ||
display: flex; | ||
flex-direction: column; | ||
width: 100%; | ||
|
||
padding: 10px 15px; | ||
|
||
@media (min-width: ${theme.breakpoint.sm}) { | ||
flex-direction: row; | ||
justify-content: space-between; | ||
|
||
padding: 16px 20px; | ||
} | ||
`; | ||
|
||
export const Title = styled.span` | ||
width: 100%; | ||
display: -webkit-box; | ||
|
||
text-overflow: ellipsis; | ||
word-break: break-word; | ||
|
||
overflow: hidden; | ||
|
||
-webkit-line-clamp: 1; | ||
-webkit-box-orient: vertical; | ||
|
||
font: var(--text-body); | ||
|
||
transition: color 0.2s ease-in-out; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 옹 hover를 위한 애니메이션인가요???
여기 안에 넣지 않으신 이유가 있나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hover안에 transition을 두면 마우스를 치웠을 때는 transition 속성이 작동하지 않아서 hover 밖에 위치시켜주었어요 |
||
|
||
&:hover { | ||
color: rgba(51, 122, 183, 1); | ||
} | ||
`; | ||
|
||
export const CreatedAt = styled.span` | ||
font: var(--text-body); | ||
font-size: 1.4rem; | ||
|
||
text-align: right; | ||
|
||
color: var(--text-dark-gray); | ||
|
||
@media (min-width: ${theme.breakpoint.sm}) { | ||
width: 90px; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import NoticeList from '.'; | ||
|
||
const meta: Meta<typeof NoticeList> = { | ||
component: NoticeList, | ||
decorators: [storyFn => <div style={{ width: '576px' }}>{storyFn()}</div>], | ||
}; | ||
|
||
const MOCK_NOTICE_LIST = [ | ||
{ | ||
id: 1, | ||
title: '방방뛰는 코끼리 엄청나게 긴 게시글 공지사항입니다.', | ||
createdAt: '2022-01-11 12:23', | ||
}, | ||
{ | ||
id: 2, | ||
title: '방방뛰는 코끼리', | ||
createdAt: '2022-01-11 12:23', | ||
}, | ||
{ | ||
id: 3, | ||
title: '방방뛰는 코끼리', | ||
createdAt: '2022-01-11 12:23', | ||
}, | ||
{ | ||
id: 4, | ||
title: '방방뛰는 코끼리', | ||
createdAt: '2022-01-11 12:23', | ||
}, | ||
{ | ||
id: 5, | ||
title: '방방뛰는 코끼리', | ||
createdAt: '2022-01-11 12:23', | ||
}, | ||
]; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof NoticeList>; | ||
|
||
export const Default: Story = { | ||
render: () => ( | ||
<div style={{ width: '100vw', padding: '15px' }}> | ||
<NoticeList noticeList={MOCK_NOTICE_LIST} /> | ||
</div> | ||
), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import NoticeItem from './NoticeItem'; | ||
import * as S from './style'; | ||
|
||
interface NoticeListProps { | ||
noticeList: { | ||
id: number; | ||
title: string; | ||
/** | ||
* yyyy-mm-dd 형식 | ||
*/ | ||
createdAt: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. StringDate 타입 재사용 어떠신가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. 제안해주신대로 api에서 데이터를 변환시켜 주었습니다 👍👍👍 구현하는 과정에서 StringDateOnly 타입이 추가되었어요 /**
* yyyy-mm-dd HH-MM
*/
export type StringDate = `${number}-${number}-${number} ${number}:${number}`;
/**
* yyyy-mm-dd
*/
export type StringDateOnly = `${number}-${number}-${number}`; |
||
}[]; | ||
} | ||
|
||
export default function NoticeList({ noticeList }: NoticeListProps) { | ||
return ( | ||
<S.Container> | ||
{noticeList.map(notice => ( | ||
<NoticeItem key={notice.id} {...notice} /> | ||
))} | ||
</S.Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { styled } from 'styled-components'; | ||
|
||
export const Container = styled.ul` | ||
width: 100%; | ||
|
||
border-radius: 5px; | ||
border: 1px solid rgba(0, 0, 0, 0.2); | ||
|
||
> li { | ||
border-bottom: 1px solid rgba(0, 0, 0, 0.2); | ||
|
||
&:last-child { | ||
border-bottom: none; | ||
} | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import NoticeDetailPage from '.'; | ||
|
||
const meta: Meta<typeof NoticeDetailPage> = { | ||
component: NoticeDetailPage, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof NoticeDetailPage>; | ||
|
||
export const Default: Story = { | ||
render: () => <NoticeDetailPage />, | ||
}; |
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.
혹시 그 StringDate로 하지 않으신 이유가 있으신가요??
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.
Notice 타입을 다른 PR에서 해서 타입 적용하는데 어려움이 있었어요 ㅠㅠ
제안해주신대로 StringDate로 변경했습니다