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

✨ #85 홈피드 구현 및 플레이리스트 타입 정리 #122

Merged
merged 6 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 0 additions & 69 deletions src/components/MusicItem.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Heart } from 'lucide-react'
import { useLikeButton } from '@/api/playlist/likePlaylist'
import styled from '@emotion/styled'
import { colors } from '@/constants/color'
import { useLikeButton } from '@/service/playlist/likePlaylist'

type StyledHeartProps = {
isLiked: boolean
Expand Down
82 changes: 82 additions & 0 deletions src/components/common/ImageGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react'
import styled from '@emotion/styled'
import defaultThumbnail from '@/assets/default-thumbnail.png'

type ImageGridSize = 'small' | 'large'

interface ImageGridProps {
thumbnails: string[]
size?: ImageGridSize
}

const ImageGrid: React.FC<ImageGridProps> = ({ thumbnails, size = 'small' }) => {
const displayThumbnails = [...thumbnails]
if (thumbnails.length === 3) {
displayThumbnails.push(defaultThumbnail)
Comment on lines +14 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 세 개의 썸네일을 처리하는 접근 방식을 재고하세요

정확히 세 개의 썸네일이 있을 때 기본 썸네일을 추가하는 것은 임의로 보입니다. 썸네일 수에 따라 그리드 레이아웃을 조정하거나 이 경우 사용자 정의 동작을 위한 prop을 허용하는 등 더 유연한 접근 방식을 고려하세요.

const gridTemplateAreas = {
  1: '"a"',
  2: '"a b"',
  3: '"a b" "c ."',
  4: '"a b" "c d"'
};

const gridStyle = {
  display: 'grid',
  gridTemplateAreas: gridTemplateAreas[thumbnails.length] || gridTemplateAreas[4]
};
Original comment in English

suggestion: Reconsider the approach for handling three thumbnails

Adding a default thumbnail when there are exactly three thumbnails seems arbitrary. Consider a more flexible approach, such as adjusting the grid layout based on the number of thumbnails, or allowing the component to accept a prop for custom behavior in this case.

const gridTemplateAreas = {
  1: '"a"',
  2: '"a b"',
  3: '"a b" "c ."',
  4: '"a b" "c d"'
};

const gridStyle = {
  display: 'grid',
  gridTemplateAreas: gridTemplateAreas[thumbnails.length] || gridTemplateAreas[4]
};

}

return (
<Container count={displayThumbnails.length} size={size}>
{displayThumbnails.map((thumbnail, index) => (
<div className="image-container" key={index}>
<img src={thumbnail} alt={`Thumbnail ${index + 1}`} />
</div>
))}
</Container>
)
}

export default ImageGrid

const sizeStyles = {
small: '70px',
large: '52px',
}

const Container = styled.div<{ count: number; size: ImageGridSize }>`
position: relative;
width: ${({ size }) => sizeStyles[size]};
height: ${({ size }) => sizeStyles[size]};
aspect-ratio: 1 / 1;
display: grid;
grid-template-columns: repeat(${(props) => Math.min(props.count, 2)}, 1fr);
border-radius: 10px;
overflow: hidden;

.play-button {
z-index: 2;
position: absolute;
bottom: 15px;
left: 10px;
width: 45px;
height: 45px;
background-color: rgba(128, 128, 128, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;

.triangle {
width: 0;
height: 0;
border-left: 10px solid white;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
}
}

.image-container {
position: relative;
width: 100%;
padding-top: 100%;

img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
`
1 change: 1 addition & 0 deletions src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const Navbar = () => {
export default Navbar

const StyledMenuContainer = styled.div`
z-index: 99;
position: fixed;
left: 0;
right: 0;
Expand Down
98 changes: 59 additions & 39 deletions src/components/playlist/MusicItem.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,66 @@
import styled from '@emotion/styled'
import { BasicVideoProps, showplaylistProps } from '@/types/playlistType'
import { BasicVideoProps, PlaylistBaseProps } from '@/types/playlistType'
import { Trash2 } from 'lucide-react'
import { colors } from '@/constants/color'
import { fontSize } from '@/constants/font'
import PlaylistThumbnails from '@/components/search/PlaylistThumbnails'
import { Link } from 'react-router-dom'
import ImageGrid from '@/components/common/ImageGrid'

interface MusicItemProps {
videoList: BasicVideoProps[] | showplaylistProps[]
videoList: BasicVideoProps[] | PlaylistBaseProps[]
onItemDelete?: (id: number) => void | ''
variant: 'profilePL' | 'createPL'
}

const isVideoListProps = (item: showplaylistProps | BasicVideoProps): item is BasicVideoProps => {
const isVideoListProps = (item: PlaylistBaseProps | BasicVideoProps): item is BasicVideoProps => {
return 'channelTitle' in item
}

const MusicItem = ({ videoList, onItemDelete, variant }: MusicItemProps) => {
return (
<Container variant={variant}>
{videoList.map((item, idx) => (
<div key={idx} className="item-video">
{variant === 'profilePL' && 'playlistId' in item ? (
<Link to={`/playlist/${item.playlistId}`} className="thumbnail-list">
<div className="thumbnail">
<PlaylistThumbnails playlistId={item.playlistId || ''} />
</div>
<div className="text-content">
<p>{item.title}</p>
{item.isPrivate && <div className="tag-private">비공개</div>}
</div>
</Link>
) : (
<>
<img src={item.thumbnail} alt={`${item.title}`} />
<div className="text-content">
<p>{item.title}</p>
{isVideoListProps(item) && <span>{item.channelTitle}</span>}
</div>
<button
className="btn-delete"
type="button"
onClick={() => {
if (onItemDelete) {
onItemDelete(idx)
}
}}
>
<Trash2 color={colors.lightGray} />
</button>
</>
)}
</div>
))}
{videoList.map((item, idx) => {
const thumbnails = Array.isArray(item.thumbnail) ? item.thumbnail : [item.thumbnail]

return (
<div key={idx} className="item-video">
{variant === 'profilePL' && 'playlistId' in item ? (
<Link to={`/playlist/${item.playlistId}`} className="thumbnail-list">
<div className="thumbnail">
<ImageGrid thumbnails={thumbnails} />
</div>
<div className="text-content">
<p>{item.title}</p>
{item.isPrivate && <div className="tag-private">비공개</div>}
</div>
</Link>
) : (
<>
<div className="content-container">
<div className="thumbnail-img">
<ImageGrid thumbnails={thumbnails} />
</div>
<div className="text-content">
<p>{item.title}</p>
{isVideoListProps(item) && <span>{item.channelTitle}</span>}
</div>
</div>
<button
className="btn-delete"
type="button"
onClick={() => {
if (onItemDelete) {
onItemDelete(idx)
}
}}
>
<Trash2 color={colors.lightGray} />
</button>
</>
)}
</div>
)
})}
</Container>
)
}
Expand All @@ -66,6 +74,7 @@ export const Container = styled.div<{ variant: 'profilePL' | 'createPL'; isPriva
padding: ${(props) => (props.variant === 'createPL' ? '10px' : '5px 0')};
border-radius: ${(props) => (props.variant === 'createPL' ? '5px' : '0')};
background-color: ${(props) => (props.variant === 'createPL' ? colors.white : 'transparent')};
justify-content: space-between;

a {
display: flex;
Expand All @@ -81,6 +90,10 @@ export const Container = styled.div<{ variant: 'profilePL' | 'createPL'; isPriva
}
}

.content-container {
display: flex;
}

.thumbnail-list {
padding: 5px 20px;
}
Expand All @@ -98,11 +111,18 @@ export const Container = styled.div<{ variant: 'profilePL' | 'createPL'; isPriva
width: ${(props) => (props.variant === 'createPL' ? '120px' : 'transparent')};
height: ${(props) => (props.variant === 'createPL' ? '90px' : 'transparent')};
object-fit: cover;
border-radius: 3px;
margin-right: ${(props) => (props.variant === 'createPL' ? '14px' : '10px')};
display: block;
}

.thumbnail-img {
margin-right: 16px;
}

.video-thumbnail {
width: 100px;
}

.text-content {
display: flex;
flex-direction: column;
Expand Down
76 changes: 76 additions & 0 deletions src/components/playlist/PlaylistThumbnailFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react'
import styled from '@emotion/styled'
import defaultThumbnail from '@/assets/default-thumbnail.png'

interface PlaylistThumbnailFeedProps {
thumbnails: string[]
}

const PlaylistThumbnailFeed: React.FC<PlaylistThumbnailFeedProps> = ({ thumbnails = [] }) => {
const displayThumbnails = [...thumbnails]
if (thumbnails.length === 3) {
displayThumbnails.push(defaultThumbnail)
}

return (
<Container count={displayThumbnails.length}>
<div className="play-button">
<div className="triangle"></div>
</div>
{displayThumbnails.map((thumbnail, index) => (
<div className="image-container" key={index}>
<img src={thumbnail} alt={`Thumbnail ${index + 1}`} />
</div>
))}
</Container>
)
}

export default PlaylistThumbnailFeed

const Container = styled.div<{ count: number }>`
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
display: grid;
grid-template-columns: repeat(${(props) => Math.min(props.count, 2)}, 1fr);
border-radius: 10px;
overflow: hidden;

.play-button {
z-index: 2;
position: absolute;
bottom: 15px;
left: 10px;
width: 45px;
height: 45px;
background-color: rgba(128, 128, 128, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;

.triangle {
width: 0;
height: 0;
border-left: 10px solid white;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
}
}

.image-container {
position: relative;
width: 100%;
padding-top: 100%;

img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
`
File renamed without changes.
Loading
Loading