22
33import { Suspense } from "@suspensive/react" ;
44import { useSuspenseQuery } from "@tanstack/react-query" ;
5- import Link from "next/link" ;
5+ import { slice } from "es-toolkit/compat" ;
6+ import { motion } from "motion/react" ;
7+ import Image from "next/image" ;
68import { useState } from "react" ;
79
810import { storeCheersQueryOptions } from "@/app/(store)/_api/shop" ;
@@ -11,10 +13,13 @@ import { Button } from "@/components/ui/Button";
1113import { Skeleton } from "@/components/ui/Skeleton" ;
1214import { Spacer } from "@/components/ui/Spacer" ;
1315import { HStack , VStack } from "@/components/ui/Stack" ;
16+ import { Tag } from "@/components/ui/Tag" ;
1417import { Text } from "@/components/ui/Text" ;
1518import { TextButton } from "@/components/ui/TextButton" ;
19+ import { ALL_TAGS } from "@/constants/tag.constants" ;
1620
1721import * as styles from "./StoreCheers.css" ;
22+ import { getContentBackgroundColor , getHeaderBackgroundColor } from "./utils" ;
1823
1924export const StoreCheers = ( { storeId } : { storeId : number } ) => {
2025 return (
@@ -32,17 +37,21 @@ export const StoreCheers = ({ storeId }: { storeId: number }) => {
3237
3338const CHEERS_SIZE = 50 ;
3439
40+ const ITEMS_PER_PAGE = 3 ;
41+
42+ const THEMES = [ "yellow" , "pink" , "blue" ] as const ;
43+ type Theme = ( typeof THEMES ) [ number ] ;
44+
3545const CheerContent = ( { storeId } : { storeId : number } ) => {
3646 const {
3747 data : { cheers } ,
3848 } = useSuspenseQuery ( storeCheersQueryOptions ( storeId , CHEERS_SIZE ) ) ;
3949
4050 const [ visibleCount , setVisibleCount ] = useState ( 3 ) ;
4151
42- const ITEMS_PER_PAGE = 3 ;
4352 const totalItems = cheers . length ;
4453 const isAllVisible = visibleCount >= totalItems ;
45- const shouldShowToggleButton = totalItems > ITEMS_PER_PAGE ;
54+ const showToggleButton = totalItems > ITEMS_PER_PAGE ;
4655
4756 const handleToggle = ( ) => {
4857 if ( isAllVisible ) {
@@ -59,16 +68,17 @@ const CheerContent = ({ storeId }: { storeId: number }) => {
5968 return (
6069 < VStack >
6170 < VStack gap = { 24 } >
62- { visibleCards . map ( card => (
71+ { visibleCards . map ( ( card , index ) => (
6372 < CheerCard
6473 key = { card . id }
6574 author = { card . memberNickname }
6675 content = { card . description }
67- memberId = { card . memberId }
76+ tags = { card . tags }
77+ theme = { THEMES [ index % THEMES . length ] as Theme }
6878 />
6979 ) ) }
7080 </ VStack >
71- { shouldShowToggleButton && (
81+ { showToggleButton && (
7282 < >
7383 < Spacer size = { 20 } />
7484 < Button
@@ -82,64 +92,114 @@ const CheerContent = ({ storeId }: { storeId: number }) => {
8292 </ >
8393 ) }
8494
85- < Spacer size = { shouldShowToggleButton ? 12 : 20 } />
86-
87- < Link href = { `/stores/register?storeId=${ storeId } ` } >
88- < Button variant = 'primary' size = 'large' fullWidth >
89- 가게 응원하기
90- </ Button >
91- </ Link >
95+ < Spacer size = { showToggleButton ? 12 : 20 } />
9296 </ VStack >
9397 ) ;
9498} ;
9599
96100const CheerCard = ( {
97101 author,
98102 content,
99- memberId,
103+ tags,
104+ theme,
100105} : {
101106 author : string ;
102107 content : string ;
103- memberId : number ;
108+ tags : string [ ] ;
109+ theme : Theme ;
104110} ) => {
105111 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
106-
107112 const isLongText = content . length > 50 ;
108113
114+ const selectedTags = ALL_TAGS . filter ( tag => tags . includes ( tag . name ) ) ;
115+ const visibleTags = isExpanded ? selectedTags : slice ( selectedTags , 0 , 2 ) ;
116+ const additionalTagsCount = Math . max (
117+ 0 ,
118+ selectedTags . length - visibleTags . length
119+ ) ;
120+ const showAdditionalTags = additionalTagsCount > 0 && ! isExpanded ;
121+
109122 return (
110- < VStack gap = { 12 } >
111- < HStack align = 'center' gap = { 8 } >
112- < Avatar memberId = { memberId } className = { styles . cheerCardProfileImage } />
123+ < motion . div
124+ className = { styles . cheerCard }
125+ onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
126+ role = 'button'
127+ tabIndex = { 0 }
128+ transition = { { duration : 0.3 } }
129+ whileTap = { { scale : 0.99 } }
130+ >
131+ < div
132+ className = { styles . cheerCardHeader }
133+ style = { {
134+ backgroundColor : getHeaderBackgroundColor ( theme ) ,
135+ } }
136+ >
137+ < Avatar
138+ // TODO: 추후 theme 지정 가능하게끔 수정
139+ memberId = { THEMES . findIndex ( t => t === theme ) }
140+ className = { styles . cheerCardAvatar }
141+ />
113142 < Text as = 'span' typo = 'body1Sb' color = 'text.normal' >
114143 { author }
115144 </ Text >
116- </ HStack >
117-
118- < HStack align = 'stretch' >
119- < hr className = { styles . cheerCardDivider } />
120- < VStack className = { styles . cheerCardContent } gap = { 4 } align = 'start' >
121- < Text
122- as = 'p'
123- typo = 'body2Rg'
124- color = 'text.normal'
125- className = { styles . cheerCardContentText }
126- data-expanded = { isExpanded }
127- data-long-text = { isLongText }
128- >
129- { content }
130- </ Text >
131- { isLongText && (
132- < TextButton
133- size = 'small'
134- variant = 'assistive'
135- onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
145+ </ div >
146+
147+ < div
148+ className = { styles . cheerCardContent }
149+ style = { {
150+ backgroundColor : getContentBackgroundColor ( theme ) ,
151+ } }
152+ >
153+ < VStack gap = { 8 } align = 'start' >
154+ < VStack gap = { 4 } align = 'start' >
155+ < Text
156+ as = 'p'
157+ typo = 'body2Rg'
158+ color = 'text.normal'
159+ className = { styles . cheerCardContentText }
160+ data-expanded = { isExpanded }
161+ data-long-text = { isLongText }
136162 >
137- { isExpanded ? "접기" : "더보기" }
138- </ TextButton >
139- ) }
163+ { content }
164+ </ Text >
165+ { isLongText && (
166+ < TextButton
167+ size = 'small'
168+ variant = 'assistive'
169+ onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
170+ >
171+ { isExpanded ? "접기" : "더보기" }
172+ </ TextButton >
173+ ) }
174+ </ VStack >
175+
176+ < HStack gap = { 8 } align = 'start' wrap = 'wrap' >
177+ { visibleTags . map ( tag => (
178+ < Tag key = { tag . name } >
179+ < Image
180+ src = { tag . iconUrl }
181+ alt = { tag . label }
182+ width = { 16 }
183+ height = { 16 }
184+ className = { styles . tagIcon }
185+ />
186+ < Text as = 'span' typo = 'caption1Sb' color = 'text.primary' >
187+ { tag . label }
188+ </ Text >
189+ </ Tag >
190+ ) ) }
191+
192+ { showAdditionalTags && (
193+ < Tag >
194+ < Text as = 'span' typo = 'caption1Sb' color = 'text.primary' >
195+ +{ additionalTagsCount }
196+ </ Text >
197+ </ Tag >
198+ ) }
199+ </ HStack >
140200 </ VStack >
141- </ HStack >
142- </ VStack >
201+ </ div >
202+ </ motion . div >
143203 ) ;
144204} ;
145205
0 commit comments