-
Notifications
You must be signed in to change notification settings - Fork 51
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
[전수빈] week13 #467
The head ref may contain hidden characters: "part3-\uC804\uC218\uBE48-week13"
[전수빈] week13 #467
Changes from all commits
46129bc
2dbc7d9
e6125a9
59e29e6
8b92c95
7de08e4
133b0fc
8f8431a
697ff5f
650213e
b74bcc0
5f2b593
5648d22
73774b6
14527b4
92dfd93
a445ee6
2cd9907
de98cae
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,13 @@ | ||
{ | ||
"presets": ["next/babel"], | ||
"plugins": [ | ||
[ | ||
"babel-plugin-styled-components", | ||
{ | ||
"fileName": true, | ||
"displayName": true, | ||
"pure": true | ||
} | ||
] | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import styled from "styled-components"; | ||
import FolderAddWhIcon from "@/public/assets/button/img_add.png"; | ||
import { device } from "@/styles/globalStyle"; | ||
import Image from "next/image"; | ||
import { modalState } from "@/recoil/modal"; | ||
import { useRecoilState } from "recoil"; | ||
|
||
const AddFloatingBtn = () => { | ||
const [, setModalOpened] = useRecoilState(modalState); | ||
|
||
return ( | ||
<AddFloatingBtnContainer | ||
onClick={() => | ||
setModalOpened((prev: any) => ({ | ||
...prev, | ||
defaultModal: { | ||
display: true, | ||
content: { | ||
id: 0, | ||
title: "", | ||
}, | ||
state: "folderAdd", | ||
}, | ||
})) | ||
} | ||
> | ||
<div className="floatingBtnTitle">폴더 추가</div> | ||
<Image | ||
width="16" | ||
height="16" | ||
className="floatingBtnIcon" | ||
src={FolderAddWhIcon} | ||
alt="folderAddIcon" | ||
/> | ||
</AddFloatingBtnContainer> | ||
); | ||
}; | ||
|
||
export default AddFloatingBtn; | ||
|
||
const AddFloatingBtnContainer = styled.div` | ||
display: none; | ||
|
||
@media all and (${device.mobile}) { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
padding: 0.8rem 2.4rem; | ||
border-radius: 2rem; | ||
border: 1px solid #fff; | ||
background: var(--primary); | ||
width: fit-content; | ||
height: 3.7rem; | ||
box-sizing: border-box; | ||
gap: 0.4rem; | ||
position: fixed; | ||
z-index: 5; | ||
bottom: 10.1rem; | ||
margin: 0 auto; | ||
left: 50%; | ||
transform: translate(-50%, 0); | ||
|
||
.floatingBtnTitle { | ||
color: var(--gray10); | ||
text-align: center; | ||
font-size: 1.6rem; | ||
font-weight: 500; | ||
letter-spacing: -0.3px; | ||
} | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import styled from "styled-components"; | ||
|
||
interface IProps { | ||
children: 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. button 안에 string만 받기보다는 |
||
onClick: (e: React.MouseEvent) => void; | ||
type: "red" | "default"; | ||
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.
|
||
} | ||
|
||
const DefaultBtn = ({ children, onClick, type }: IProps) => { | ||
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. 컴포넌트명은 축약어 (Btn)를 이용하지 마시고 명확하게 full name을 사용해주세요 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. 또한 DefaultButton이라는 이름 자체가 어색합니다. Button의 기본은 Button입니다. |
||
return ( | ||
<DefaultBtnContainer onClick={onClick} type={type}> | ||
{children} | ||
</DefaultBtnContainer> | ||
); | ||
}; | ||
|
||
export default DefaultBtn; | ||
|
||
export const DefaultBtnContainer = styled.button<{ type: string }>` | ||
border-radius: 0.8rem; | ||
background: ${(props) => | ||
props.type === "red" | ||
? "var(--red)" | ||
: "linear-gradient(91deg, var(--primary) 0.12%, #6ae3fe 101.84%)"}; | ||
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 Button = styled.button<{type: 'primary' | 'secondary'}>`
color: ${(props) => `var(--${props.type})`}
`;
export const GradientButton = styled(Button)`
color: linear-gradient(91deg, ${(props) => `var(--${props.type})`}, 0.12%, #6ae3fe 101.84%)
` 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. @jayjnu 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. 오늘 말씀드린것처럼 확장에는 열려있되 수정에는 닫혀있게 하기 위함입니다. |
||
color: #f5f5f5; | ||
padding: 1.6rem 2rem; | ||
font-size: 1.8rem; | ||
font-weight: 600; | ||
line-height: 2.2rem; | ||
box-sizing: border-box; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
cursor: pointer; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import moment from "moment"; | ||
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. moment는 browser js에서 사용하기에 너무 무거워서 사용을 지양하셔야 합니다. |
||
import LogoImg from "@/public/assets/common/img_logo.png"; | ||
import { | ||
CardContainer, | ||
CardImgContainer, | ||
CardWrapper, | ||
ContentContainer, | ||
OptionMenuContainer, | ||
} from "./cardStyled"; | ||
import OptionIcon from "@/public/assets/card/img_option.png"; | ||
import StarIcon from "@/public/assets/card/img_star.png"; | ||
import { useState } from "react"; | ||
import Image from "next/image"; | ||
|
||
const calculateTimeAgo = (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. 별도의 utility 함수로 분리하는 것이 좋습니다. |
||
const createdDate = moment(createdAt, "YYYY-MM-DDTHH:mm:ss[Z]"); | ||
const currentDate = moment(); | ||
const diff = currentDate.diff(createdDate, "seconds"); | ||
|
||
if (diff < 120) { | ||
return "1 minute ago"; | ||
} else if (diff <= 3540) { | ||
return `${Math.floor(diff / 60)} minutes ago`; | ||
} else if (diff < 3600) { | ||
return "1 hour ago"; | ||
} else if (diff <= 82800) { | ||
return `${Math.floor(diff / 3600)} hours ago`; | ||
} else if (diff < 86400) { | ||
return "1 day ago"; | ||
} else if (diff <= 2592000) { | ||
return `${Math.floor(diff / 86400)} days ago`; | ||
} else if (diff <= 28512000) { | ||
return `${Math.floor(diff / 2592000)} months ago`; | ||
} else if (diff <= 31536000) { | ||
return "1 year ago"; | ||
} else { | ||
return `${Math.floor(diff / 31536000)} years ago`; | ||
} | ||
}; | ||
|
||
interface ICardProp { | ||
cardData: any; | ||
onClickDelete?: any; | ||
onClickAdd?: any; | ||
isFolder: boolean; | ||
} | ||
|
||
const Card = ({ cardData, onClickDelete, onClickAdd, isFolder }: ICardProp) => { | ||
const ago = calculateTimeAgo(cardData.created_at || cardData.createdAt); | ||
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. 일정시간이 지났음을 표현할 때 좋은 단어로는 elapse라는 동사가 있습니다. |
||
const createdAtFormat = moment( | ||
cardData.created_at || cardData.createdAt | ||
).format("YYYY.MM.DD"); | ||
const [isOpenOption, setIsOpenOption] = useState(false); | ||
|
||
const openUrl = () => { | ||
window.open(cardData.url, "__blank"); | ||
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. 정말 피치못할 사정이 있는것이아니면 영역을 클릭했을 때 url 이동이 필요하거나 새 페이지를 열거나 하는 기능이 필요한 경우, 해당 기능은 a 태그를 이용해서 구현하시는 것이 좋습니다. |
||
}; | ||
|
||
return ( | ||
<CardWrapper> | ||
<CardContainer onClick={openUrl}> | ||
{cardData.image_source || cardData.imageSource ? ( | ||
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. 이미지 컴포넌트 렌더링영역에만 분기를 치면 어떨까 싶어요 |
||
<CardImgContainer> | ||
<Image | ||
priority | ||
className="cardImage" | ||
src={cardData.image_source || cardData.imageSource} | ||
alt="cardImg" | ||
fill | ||
/> | ||
{isFolder && ( | ||
<Image | ||
src={StarIcon} | ||
className="starIcon" | ||
alt="starIcon" | ||
width="34" | ||
height="34" | ||
/> | ||
)} | ||
</CardImgContainer> | ||
) : ( | ||
<CardImgContainer> | ||
<Image | ||
priority | ||
src={LogoImg} | ||
alt="logoImg" | ||
className="noImgLogo" | ||
width="133" | ||
height="24" | ||
/> | ||
{isFolder && ( | ||
<Image | ||
src={StarIcon} | ||
className="starIcon" | ||
alt="starIcon" | ||
width="34" | ||
height="34" | ||
/> | ||
)} | ||
</CardImgContainer> | ||
)} | ||
|
||
<ContentContainer> | ||
<div className="contentOptionContainer"> | ||
<div className="contentAgo">{ago}</div> | ||
{isFolder && ( | ||
<Image | ||
className="optionBtn" | ||
src={OptionIcon} | ||
alt="optionIcon" | ||
width="21" | ||
height="17" | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
setIsOpenOption(!isOpenOption); | ||
}} | ||
/> | ||
)} | ||
</div> | ||
<div className="content">{cardData.description}</div> | ||
<div className="contentAt">{createdAtFormat}</div> | ||
</ContentContainer> | ||
</CardContainer> | ||
|
||
{isOpenOption && ( | ||
<OptionMenu | ||
onClickDelete={onClickDelete} | ||
onClickAdd={onClickAdd} | ||
content={{ id: cardData.id, title: cardData.url }} | ||
/> | ||
)} | ||
</CardWrapper> | ||
); | ||
}; | ||
|
||
export default Card; | ||
|
||
interface IOptionMenuProp { | ||
onClickDelete: any; | ||
onClickAdd: any; | ||
content: { | ||
id: number; | ||
title: string; | ||
}; | ||
} | ||
|
||
const OptionMenu = ({ | ||
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. 하나의 컴포넌트 파일에는 하나의 컴포넌트만 정의해주시면 좋을 것 같네요. |
||
onClickDelete, | ||
onClickAdd, | ||
content, | ||
}: IOptionMenuProp) => { | ||
return ( | ||
<OptionMenuContainer> | ||
<div | ||
className="optionMenuItem" | ||
onClick={() => onClickDelete("linkDelete", content)} | ||
> | ||
삭제하기 | ||
</div> | ||
<div className="optionMenuItem" onClick={() => onClickAdd()}> | ||
폴더에 추가 | ||
</div> | ||
</OptionMenuContainer> | ||
); | ||
}; |
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.
interface 선언시
I-
prefix를 붙이는것은 잘 받아들여지는 컨벤션은 아닙니다.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.
Prop type을 선언하실때는
${ComponentName}Props
컨벤션을 사용하시는 것이 좋습니다.