Skip to content

Commit

Permalink
Merge pull request #419 from sj0724/part3-박상준-week13
Browse files Browse the repository at this point in the history
[박상준] Week13
  • Loading branch information
o-seung-yeon authored May 15, 2024
2 parents 0c228dc + e52e0c0 commit edd3579
Show file tree
Hide file tree
Showing 98 changed files with 4,322 additions and 494 deletions.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
24 changes: 24 additions & 0 deletions Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ReactElement, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';

const ModalPortal = ({ children }: { children: ReactElement }) => {
const [mounted, setMounted] = useState<boolean>(false);
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);

useEffect(() => {
setPortalElement(document.getElementById('modal'));
}, []);

useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);

return (
<>
{mounted && portalElement ? createPortal(children, portalElement) : null}
</>
);
};

export default ModalPortal;
36 changes: 36 additions & 0 deletions components/Button/Button.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import styled from 'styled-components';
import { ButtonProps } from './Button';

const buttonSize = {
xs: '4.8',
sm: '10',
md: '28',
lg: '40',
};

export const Cta = styled.span<ButtonProps>`
cursor: pointer;
text-decoration: none;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.8rem;
background: var(--Gradient-purpleblue-to-skyblue);
color: var(--Gray-cta);
padding: 1.6rem 2rem;
font-family: Pretendard;
font-size: 1.6rem;
font-style: normal;
font-weight: 700;
line-height: normal;
width: ${({ size }) => buttonSize[size]}rem;
position: relative;
&:hover {
opacity: 0.8;
}
@media (max-width: 768px) {
font-size: 1.4rem;
}
`;
14 changes: 14 additions & 0 deletions components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ButtonHTMLAttributes } from 'react';
import * as S from './Button.styled';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
size: 'xs' | 'sm' | 'md' | 'lg';
}

export function Button({ children, size }: ButtonProps) {
return (
<>
<S.Cta size={size}>{children}</S.Cta>
</>
);
}
100 changes: 100 additions & 0 deletions components/Card/Card.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import styled from 'styled-components';

export const EmptyImg = styled.div`
height: 100%;
background-color: var(--EmptyArea);
border-radius: 1.5rem 1.5rem 0 0;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
img {
opacity: 0.2;
width: 13.3rem;
height: 2.4rem;
}
`;

export const ItemImg = styled.div<{ image: string }>`
height: 100%;
background-image: url(${(props) => props.image});
border-radius: 1.5rem 1.5rem 0 0;
background-size: cover;
background-position: center;
&:hover {
background-size: 130%;
}
`;

export const ItemCard = styled.div`
width: 34rem;
height: 33.4rem;
display: flex;
flex-direction: column;
box-shadow: 0px 5px 25px 0px rgba(0, 0, 0, 0.08);
border-radius: 1.5rem;
text-decoration: none;
color: #000;
position: relative;
font-size: 1.6rem;
&:hover {
background-color: var(--Background);
}
@media (max-width: 768px) {
font-size: 1.4rem;
}
`;

export const StarIcon = styled.img`
width: 3.4rem;
height: 3rem;
flex-shrink: 0;
position: absolute;
top: 1.5rem;
right: 1.5rem;
z-index: 10;
`;

export const ItemInfo = styled.div`
display: flex;
flex-direction: column;
padding: 1.5rem 2rem;
width: 100%;
height: 13.5rem;
gap: 1rem;
position: relative;
`;

export const KebabIcon = styled.img`
cursor: pointer;
width: 2.1rem;
height: 1.7rem;
flex-shrink: 0;
position: absolute;
right: 2rem;
top: 1.5rem;
`;

export const ItemDate = styled.p`
color: var(--Description);
font-size: 1.3rem;
`;

export const ItemDescription = styled.p`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
a {
text-decoration: none;
color: #000;
}
`;

export const ItemFullDate = styled.p`
font-size: 1.4rem;
`;
80 changes: 80 additions & 0 deletions components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useEffect, useRef, useState } from 'react';
import { changeDate, calculateDate } from '../../util/util';
import * as S from './Card.styled';
import KebabMenu from '../KebabMenu/KebabMenu';
import { Link } from '../../hooks/useGetFolder';
import Image from 'next/image';

function Card({ item }: { item: Link }) {
const [createdAt, setCreatedAt] = useState({ time: 0, result: '' });
const [fullDate, setFullDate] = useState('');
const { image_source } = item;
const [kebabView, setKebaView] = useState(false);
const [like, setLike] = useState(false);
const kebabRef = useRef<HTMLObjectElement>(null);

const { url, description } = item;

const createdText = `${createdAt.time} ${createdAt.result} ago`;

useEffect(() => {
const nowDate = new Date();
let createdate = new Date(item.created_at);
const date = (Number(nowDate) - Number(createdate)) / 1000;
setCreatedAt(calculateDate(date));
setFullDate(changeDate(createdate));
}, [item]);

useEffect(() => {
function handleClickOutside(e: any) {
if (
kebabView &&
kebabRef.current &&
!kebabRef.current.contains(e.target)
) {
setKebaView(false);
}
}

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [kebabView]);

return (
<S.ItemCard>
<S.StarIcon
src={like ? '/full_star.svg' : '/star.svg'}
alt="별 이미지"
onClick={() => {
setLike(!like);
}}
/>
{image_source ? (
<S.ItemImg image={image_source} />
) : (
<S.EmptyImg>
<Image src="/logo.svg" alt="빈 이미지" width={133} height={24} />
</S.EmptyImg>
)}
<S.ItemInfo>
<S.KebabIcon
src="/kebab.svg"
alt="kebabIcon"
onClick={() => setKebaView(!kebabView)}
/>
<S.ItemDate>{createdText}</S.ItemDate>
<S.ItemDescription>
<a href={url} target="_blank" rel="noreferrer">
{description ? description : url}
</a>
</S.ItemDescription>
<S.ItemFullDate>{fullDate}</S.ItemFullDate>
</S.ItemInfo>
{kebabView && <KebabMenu menuRef={kebabRef} />}
</S.ItemCard>
);
}

export default Card;
31 changes: 31 additions & 0 deletions components/ContentsContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ReactNode } from 'react';
import styled from 'styled-components';

const Container = styled.div<{ $empty: number }>`
gap: 2rem;
display: grid;
grid-template-columns: ${(props) =>
props.$empty > 0 ? 'repeat(3, 1fr)' : 'none'};
margin: 0 auto;
position: relative;
@media (max-width: 1199px) {
grid-template-columns: ${(props) => (props.$empty > 0 ? '1fr 1fr' : '1fr')};
}
@media (max-width: 767px) {
grid-template-columns: 1fr;
}
`;

function ContentsContainer({
children,
content,
}: {
children: ReactNode;
content: number;
}) {
return <Container $empty={content}>{children}</Container>;
}

export default ContentsContainer;
21 changes: 21 additions & 0 deletions components/FolderButton/FolderButton.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styled from 'styled-components';

export const FolderName = styled.span<{ $select: string | boolean }>`
display: flex;
justify-content: center;
align-items: center;
padding: 0.8rem 1.2rem;
border-radius: 0.5rem;
border: 1px solid var(--Primary);
background-color: ${(props) =>
props.$select === 'select' ? 'var(--Primary)' : '#fff'};
cursor: pointer;
height: 3.5rem;
font-size: 1.6rem;
white-space: nowrap;
color: ${(props) => (props.$select === 'select' ? '#fff' : '#000')};
@media (max-width: 768px) {
font-size: 1.2rem;
}
`;
33 changes: 33 additions & 0 deletions components/FolderButton/FolderButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import * as S from './FolderButton.styled';
import { Folder } from '@/hooks/useGetFolderList';

function FolderButton({
item,
setFolderId,
setFolderName,
isSelected,
handleMenuClick,
index,
}: {
item: Folder;
setFolderId: React.Dispatch<React.SetStateAction<number>>;
setFolderName: React.Dispatch<React.SetStateAction<string>>;
isSelected: string;
handleMenuClick: (index: number) => void;
index: number;
}) {
const changeFolder = () => {
setFolderId(item.id);
setFolderName(item.name);
handleMenuClick(index);
};

return (
<S.FolderName onClick={changeFolder} $select={isSelected}>
{item.name}
</S.FolderName>
);
}

export default FolderButton;
Loading

0 comments on commit edd3579

Please sign in to comment.