From 664243a6e33f061b3d226469321326dc779712ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=EC=9D=80?= Date: Sun, 3 Mar 2024 13:11:04 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=EB=A6=AC=EC=95=A1=EC=85=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=8D=BC?= =?UTF-8?q?=EB=B8=94=EB=A6=AC=EC=8B=B1=20(#191)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: CountEmoji 컴포넌트 추가 * chore: 필요한 asset 추가 * chore: Emojis 컴포넌트 추가 (+버튼 클릭시 뜨게 되는 컴포넌트) * chore: ReactionButton 컴포넌트 추가 (+버튼) * chore: ReactUserCount 컴포넌트 추가 * chore: ReactedEmojis 컴포넌트 추가 * chore: Reaction 컴포넌트 추가 및 적용 * chore: 필요한 util 함수 및 theme 추가 * chore: 바텀시트를 구성하는 컴포넌트들 추가 * chore: ReactUserBottomSheet 컴포넌트 추가 * chore: Emojis 합성 컴포넌트 추가 및 적용하여 리팩터링 * chore: 지난 번에 수정된 Emoji 컴포넌트 반영하여 스토리 수정 * chore: 누락된 파일 추가 * chore: 네이밍 관련 피드백 반영 * chore: 인라인 → tailwind로 스타일 적용 * chore: Tabs에서 Trigger 컴포넌트만 불러와서 사용 * chore: CountEmoji 스토리에 size 지정 * chore: feature 폴더에 존재하는 MapCard 관련 스토리 제거 * chore: StartMapCard 컴포넌트 - 디자인 변경사항 반영 * chore: 공통으로 쓰이는 emoji 관련 컴포넌트를 features/emoji 폴더로 이동 --- src/app/globals.css | 5 ++ src/assets/icons/react-emoji-plus-icon.svg | 10 +++ src/assets/images/bandiboodi-start.png | Bin 0 -> 30770 bytes src/assets/images/reaction-members.png | Bin 0 -> 13549 bytes src/components/atoms/emoji/Emoji.stories.tsx | 20 ++++++ src/components/atoms/emoji/Emoji.tsx | 11 +++ src/components/atoms/emoji/index.ts | 2 + src/components/atoms/index.ts | 1 + .../countEmoji/CountEmoji.stories.tsx | 35 +++++++++ .../molecules/countEmoji/CountEmoji.tsx | 26 +++++++ src/components/molecules/countEmoji/index.ts | 1 + .../molecules/emojis/Emojis.stories.tsx | 58 +++++++++++++++ src/components/molecules/emojis/Emojis.tsx | 35 +++++++++ src/components/molecules/emojis/index.ts | 1 + src/components/molecules/index.ts | 2 + src/features/emoji/BottomSheet/Content.tsx | 33 +++++++++ src/features/emoji/BottomSheet/Header.tsx | 36 ++++++++++ src/features/emoji/BottomSheet/ReactUser.tsx | 19 +++++ .../BottomSheet/ReactUserBottomSheet.tsx | 41 +++++++++++ src/features/emoji/BottomSheet/Tab.tsx | 14 ++++ src/features/emoji/BottomSheet/index.ts | 1 + src/features/emoji/ReactionAddButton.tsx | 20 ++++++ src/features/emoji/index.ts | 2 + .../components/detail/GoalDetailContent.tsx | 2 + .../goal/components/detail/emoji/Emojis.tsx | 37 ++++++++++ .../components/detail/emoji/ReactedEmojis.tsx | 30 ++++++++ .../goal/components/detail/emoji/Reaction.tsx | 47 ++++++++++++ .../detail/emoji/ReactionUserTotalCount.tsx | 50 +++++++++++++ .../goal/components/detail/emoji/index.ts | 1 + .../{EmptyMapCard => }/EmptyMapCard.tsx | 5 +- .../EmptyMapCard/EmptyMapCard.stories.tsx | 25 ------- .../{MapCard => }/MapCard.tsx | 5 +- .../MapCard/MapCard.stories.tsx | 36 ---------- .../MapCardCollections/StartMapCard.tsx | 18 +++++ .../StartMapCard/StartMapCard.stories.tsx | 20 ------ .../StartMapCard/StartMapCard.tsx | 20 ------ .../components/MapCardCollections/index.ts | 8 +-- .../LoginBottomSheet.stories.tsx | 21 ------ .../MapCardPositioner.stories.tsx | 68 ------------------ src/utils/suffix.ts | 9 +++ styles/theme/boxShadow.ts | 1 + 41 files changed, 578 insertions(+), 198 deletions(-) create mode 100644 src/assets/icons/react-emoji-plus-icon.svg create mode 100644 src/assets/images/bandiboodi-start.png create mode 100644 src/assets/images/reaction-members.png create mode 100644 src/components/atoms/emoji/Emoji.stories.tsx create mode 100644 src/components/atoms/emoji/Emoji.tsx create mode 100644 src/components/atoms/emoji/index.ts create mode 100644 src/components/molecules/countEmoji/CountEmoji.stories.tsx create mode 100644 src/components/molecules/countEmoji/CountEmoji.tsx create mode 100644 src/components/molecules/countEmoji/index.ts create mode 100644 src/components/molecules/emojis/Emojis.stories.tsx create mode 100644 src/components/molecules/emojis/Emojis.tsx create mode 100644 src/components/molecules/emojis/index.ts create mode 100644 src/features/emoji/BottomSheet/Content.tsx create mode 100644 src/features/emoji/BottomSheet/Header.tsx create mode 100644 src/features/emoji/BottomSheet/ReactUser.tsx create mode 100644 src/features/emoji/BottomSheet/ReactUserBottomSheet.tsx create mode 100644 src/features/emoji/BottomSheet/Tab.tsx create mode 100644 src/features/emoji/BottomSheet/index.ts create mode 100644 src/features/emoji/ReactionAddButton.tsx create mode 100644 src/features/emoji/index.ts create mode 100644 src/features/goal/components/detail/emoji/Emojis.tsx create mode 100644 src/features/goal/components/detail/emoji/ReactedEmojis.tsx create mode 100644 src/features/goal/components/detail/emoji/Reaction.tsx create mode 100644 src/features/goal/components/detail/emoji/ReactionUserTotalCount.tsx create mode 100644 src/features/goal/components/detail/emoji/index.ts rename src/features/home/components/MapCardCollections/{EmptyMapCard => }/EmptyMapCard.tsx (92%) delete mode 100644 src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.stories.tsx rename src/features/home/components/MapCardCollections/{MapCard => }/MapCard.tsx (92%) delete mode 100644 src/features/home/components/MapCardCollections/MapCard/MapCard.stories.tsx create mode 100644 src/features/home/components/MapCardCollections/StartMapCard.tsx delete mode 100644 src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.stories.tsx delete mode 100644 src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.tsx delete mode 100644 src/features/home/components/loginBottomSheet/LoginBottomSheet.stories.tsx delete mode 100644 src/features/home/components/mapCardPositioner/MapCardPositioner.stories.tsx create mode 100644 src/utils/suffix.ts diff --git a/src/app/globals.css b/src/app/globals.css index e128fc50..923917bd 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -12,3 +12,8 @@ @apply max-w-[var(--layout-max-w)] min-h-[var(--layout-min-h)] w-full mx-auto; } } + +::-webkit-scrollbar { + /* 가로 스크롤바 보이지 않도록 */ + display: none; +} diff --git a/src/assets/icons/react-emoji-plus-icon.svg b/src/assets/icons/react-emoji-plus-icon.svg new file mode 100644 index 00000000..efa9045a --- /dev/null +++ b/src/assets/icons/react-emoji-plus-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/images/bandiboodi-start.png b/src/assets/images/bandiboodi-start.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d33405670f8e64b058306c41e77df7a5c5ddbd GIT binary patch literal 30770 zcmWh!XH*kB7w)1hr7dL&r9g{pME3L*TK1GOMFa!{L5A!h3bvFz%TTtKDN|5}fT(2( z2nfg&l#PhWa3Mqa{C?!*Bsn=J_vYs2=DAOj$QKMz2rL2s0HBPG^sE2?VEq3J9Q?1x z$a#9^Uj+=dGSmS)8pVD3*XSXe+31U3Jz0Bq@^SIx+xE%A>aU5Vy^)2Th1K70-u-HS z{d;TmWP9^uaq^^p`peAf_ug0A^Y4Gp%>C{k`tf4y)7;3(`p2Wm=^rC+e(e7^dC~Rt zQR}DoD_<%bKP@jGu5E5_ef>7Gu+=uS@p0#9a(1KT+1LK=rGb&v(TUZz=j-)dAI2xX zOuv0UJoGNNe51T|J~4m&-JAHCi8o~_tGUr@_4%t$JEvY$ey(rmY0COC-kY>MbMNEQ zqu`|Zc(>2np9UEBADLNL?I4CY{)vMKvASRlJ2^S&J~>JBQjvdQbqD~2!;JMvHWBZJ z@4dfKE{}|p0J4hbr&Qb2n8@%-?VQkklwh|$EAt&b6gu|`SkwL+XC`;61Fie=Ov6iE z!F6Y*@829nx$MMk|3&tmpWg_msQoykU^2h}tu$e{NKqA_`|EsD+M2$y`Rem0p`vs5 zjdGy65rumXjqNaOh3r9*C-*nbWmsjcsjMfR$!skM;CMVz9{u0jSVTMH>=0rswCt48 zWRG-}0cF5ODJ8tLlY~opB58Ii%=M%Dljea4*NYJO*pHDFT2jHWz7ycHICrJHQTmX)Zl6x7 zjB2wEr6g8^Z|_Q`)uEE(ZKqRjY13v>0=sd3^E`7NPmmHzIi{nXKjdF480sMSuV1+N zPTqq5w$j5XQ5T&U-y4}SM^?fa+8ox75_*Ychvc#?y+onHgB*4~ZwI8$3?`=%YN z1wZat^6&oB6B!K=W8)dMu;0I&r;xYcAN;2w8Tv3^R`5xEd1fyvvbIX%1xm1uBsA-) zb@SXM_fyBZ=`6$euZXlf!jO=;vS|E>;dI9@ zT`8o*8t0IaZdVbY0|S=1`zYRoRtKa#K!EJ(hYdPE$d8Wign6PsSBq~%J|DSr(Y*kD zv}nn<4Vrj<29M%<#jF520YF!Q?ii8R)td$awQ@eBE+!yd5|P*iku&3RKq@nrDh-SH z)c=FIlBawxNnd#>)stwksR%FE^sTL^=9ZcQ_S}^}!|66r%1uEL$&ah_T)J87qxm>+ zrLV7Ob)JnEeNDgpB&x!I*|M|-7^u)Q>PlJx7`=<9g_*nRxC|M2DU@{oz(6o7Rc*?D zoDs%kAnx14H&!U@D6IXlR7vredkCMYP$(#x1Kv|2!*#)h9FP}!Ihu|1=WnO+qc;g3 z-x+tnN zjC*_!>TC)B&?;Sn0@J;CKxt59PREXm&CRjO=dV`#uifDl9(@oJ5y)MHx{yRy4vkYW zVy3hvdUf2y=-i?L!WG0H=Zb(l>x!i7B_My9u94%$>`x_lw{O{S>m{7wIA_NqLId!I zGz+p1!EjscdhvS$AfvaoAU07D{x2Jbu8zjvpX}eY%c>iv&Kbp?F7@-wF%pB2W2c?n zbR}!{I#b^Y@c|+4%!42%MGSrHCeeZ0tF$wEJ+XG6d6#@X>@=9EF&r0s5Q(b5aIf{c3#wY<1;V++`U3jg9y$3~n zyy&gQDYnEMVAT(mn|}6R0_#)}l2TsKVUbS(m53nfn{f-rP*9eCkk#k;I`rZcSw{c~$cFkW0x3LG1oc5KTv#~c zd?KUnkwTIhkWLD~GfB?kV4{4N+fR(Z1q+ocPO&1~!FgnTQT6^_CS1U7APM~>3Dwjv z<+Se+K&jLWzRq-rK%;Q&GxN``OH-z2s1T%O~ zSI~^3?r;Q?)r`HnzEuf|{OHe{H9^d7`C``WGH#+jk{=MG2M3wqA>ooU7!c0QC99fx z{rP`%DHINFa@9L-Z1Rsz-G{p2D6RZoCc>=Q>t^R(F~?n0WGf9HDL!Tbp7FBAOkBLv zhtcaEz<9i)aaYT{_JQ;3IUHiRK`#km(Z~)4-pAVBdk;6pW-kOZPJY z6v3$2+r0)XDu1<{reTgVcsLow0pt!7`GFBg2eXJeTzM$~Z*Bt0+c~aCTFK&zFd*t+ ze^t?}?_t@Nqm2@XTeZbOlK$~=8LIi$H7$y9y-;@&rUt_k<08tA3=ko7Q^{I9TNyPH zVen=1m$QH4F{HZ7k~Mn!hfDNc88F?=wqI{WxM-Rof=Z&-LQz z17pWJiGK~sby>=^F{OzC@WU^6^{Sr$ZQ=G`K4`=KR4hYOl>?a29+64Re0Z8H>H6%3 z0snU&!>JGR19LLu-N`R4t27_*;MNnP&gYA1wNjyEc#QQcsGpgSlGDzKMygTnkfU@* zcDsX(QsFSGD&J`b{Ed8+hJ4iR#LpkO(Z?%$6pvsV|^ujFIe+FyXmE07%Gru4f85R<`mifjW?g}Nz~}AOXffnk@}uWG1>irk#m@v ziWMGG7Mj0am+icvZ=v3MN@Q}Ov-XZzeYH4uHZdnG>Z&gu-hZ7HW5*wFHDuTN@z?De z>xBzyHyw@TSw1JbR*^``wH{3mTOa0H6$nWr0$HMaK31@CVDZOg8k~U^v2}s_QzjsP zNFtU2Yb2XbcDy6w?C!Jd0V>ruPhqtG%YU2FnWp6{)8ZNsHq%Xbf#G-si!AcsG-tk- zRdIHkn%#|y>)mu`@S*Tcw+6*1Ph#E8}f0n~r}kLv*P$q3E*$ z`emW^gSqkUpj1i1F%XdcEYuR5JZwL!$XMwOkz4&#v2nYwFBW)fFAg4ltn!>h8VExp z%Q+BdCUy0F>6`M{@tz5JUsdFW4cHF|8dk@7hV=qyW&qj~FP^eq=P!FpjT>u!yI_O+ z`lm#e_Oe*Zb{68{zk-594OK8;5`nZ1;D3+Aj9XlQheZ}oY=WDj(Y<*~eUx?6qa>^uKFP4wh6_Fo+iRK2^!RuI z>odLd(|FymsUDbGI{s*4JHE&QrNUg;@+jz+uaIl!LR#cDAd;?nO!k%58li{ zU#R@P#Qj+E+y#>*`Z?%Y%aC2;H76TPl|IK16+V`Xj|q8j#;IXrAxAgC4IeMV>ZB$c zHi7?mRz1u8pGSE;U2SK=D0LX48lzm%|9uEegj;FHQpLJyDt*VkRV8Q?| z`nmfReLg-T7kcIogD})}oo*#*Ly1aD07cbmV; zbz0o`{U9&2&-(J+u_N`(g$T#z5kbVZfU|l;NLO6q<`hc#cW5X?(ILNmzJ+yZHH@$b|yl{#=9jz+BhxgTy#8~WCHnnzUF4uJpZR@Ufqeb=l zyT0h3e=~uK>vNAO?UI-M9?U~0X96%^OYX@J9gOW0wox2B3AH_sJ~W-lfBJVm*H-GW zO*_TgK7fv)og5G3I5_M_G(Yh5SMkjdS z(#>SV+>N|L?&nijqq1B%w3eRtEcLh|LY5OAdlE$0fNls|R@*sekXY{@PLcmK@y{^? z_v86GMo3-Dz^SV=YOkS-jW}jsDB%PB%&y#(Rk6j2gzG(anVGKs^$&KNqH`8E>xI<9 zYcsJ?Tb^>^Vo(Pa4IM2yN{N?Pf)lfB+pp2`7>yb`gf14nFbKW@X;Xts{|o0+3W_pO zzt$jdx4j?cQ&;;nlWxnuIskN+7$lAcvFpdeS6)Qr3DV7Yv}~rrGtD=ZvkR^pYN!9* zf50F9T@#~4kaVYwA~a4>3T4H@Dt0*dxTo@n4OQUkWAY%p>`Rq0hrZS6!PxxX_|Cq7 z?4#IW5B-u~AsAA?xv6*8zLkMDDVm>Bd@*q7Spba{_WDmqThUfM{+5eg@}hVmx#ABN zpEwR6^(9Uv1A9ec7rn9odr#S5j;af%6(pt3Uz3cIO1H{%`pu`kc~YySLt(GWue=F= zj#=!C%PBKGOK7%8o5&9TR%w<{$BVp$RG{pDPYrhzzg{2nu)JSkQ^^c{vi_vhV6Dc>|{VgMOvENlh5-*l(2Po z*%MLH1n&eY9`8AI6^L=*r4ujRZ7!a*fh(u^6L-aI1MV^xu00V>+F6*~ULcg)W00O- zu}=uaP0QzdBX}QF_+3A^pUytVnCpn~dYnbF&Eb+y8{Zq#-<}CN%g@j>{c|%MUjMhx zEI0rlDB?oqCX0~-pkbSWP7a(5hF4>n@v4?MugKvE7)eyt%BzLT8pX2P^|V;Lx9#%8 z)5pqJU1B%Ghkbx7PfOn3yilC~lr2VELQ-@Zcb`1L=M*rT1Xh%=nvVqtbc$J1C_G|c z_=@eNSP@0@h)5l2fo8Xx#k;S`&+`MzSOfq-Tw-iHT1qwV6akQs_%?y@3p(d^4J;$J z9-{}X01|eOg)MB^8ZVSmsEw}11JdflZ%P8;%dv@1@2Q2#_0D`^bd2~CE0p_KSdohn zofsNmq=mb)O?>IJyIPg8xC^4Hi-@CN>M^^20%XRCSx@^EZ&^v0`%<`!pngWst=k`7 z+H<4a#vu=l=>1kh>YLe4D12w%NMcu9VGWh`mNvU$7hrT6xudW*JJ;Adp2f2ZTfb+WxS59A+nnFEL&lXB9O)Gc(Pvryg)wiASr|)Y{H+;~e-?3H!63^x$k%<(fl0b}* zawy~P4G_8LFtJ#*_8|VhKs6Jo^H{xP*rk%#UeR-}yrTJ)=*RaLz+8#lMBv`@q4*F^=TsAX>tPHcvq~T1eO*h#%2Dgawn#bK z0g?8?Ymob#L3wBj3rlyS1kQV^@mo=$fW9XmSvBydL=d-48y73ffcUe>nC{)4s{r&N zgj+4ARMPR$5heZXggd>JTr?38fdG+B@*()tok=eKEPTk05~%Rpvs*QfMPavG3C1VAxG zLqC5Q-ZPg6z(fAfInH->zwiTzG-eOl6q+Q1SzWJml1>YsJ(ItZp@_OD@Jg0PtFD^4 zd#-|)pFvkEOPHpTFGOexYjFM!ANb_PLS58VZpz~N!yaceiSQYCM%6*wK78ut8d?K` z(EqGvNy(^2;W2Y`nF_e7=zR~O8*dCb68Z^Pji!x-hN!;@+K_B1b)hyayt|_0@Y_<0H*BTJn_?O1#w8da0c( zqWz&t$_S5#Kk|i_PbSuSngL0N-<~~=ILaWz~$6^{{Qnm-XUY+@A=lAc} z&c9c81Eac|y<34XIkFOpLd9ADqkP=EGM2m7UWVU@3VwTiEKnF4RUI<=Rlqgoj$16m zR(RAxjGIOJrImD3O{uwtjR~nnG4HQ@1Bov9-1#rRRWY-jznYp*(?up@FAr}HsK@A2 zGRzkje*8&L->x#q7{|j|rM@3$coJQ-N)F@TlU$AL>b>FO0Cs%^EEqOIe*4s{-3tv& zdDJUl-%5zRgom9DfFZ~_@w8gawEp%VyetElhtz~o3@ZCdKgJZ;F9oy zqP|lXrE0@d1rQUngjJA)Nl|o)Stpq08BY(~7oHy$N84Uo58J?`?=V=J2TOOPZD^$3 zWK;Ks_ie}br0h5f?#`BYmP31h&qzyJ*c!@G3d+DGavz%!7$H^g_gYRwqtF1}OjDmN zJP#a4!sYRx^+41e3cpv>e^0Ojdx}}=ZX%ngfG|XtiixYJFsyK{w9OJu6-FuJ+r5`B*!vJG-!4~Ds6s_EiqeV+6E$61B6oGLBd8Ag=lWk? zJOX`~WFF2(7jy(g#1v``y4!6x*o98Nw4Kr+~z2p-?$y$KQZ@`VdXVy z(pE$|3B!?l%@H8ca`QW6!JCge!+BEdIugO04e3|<1Zvt7 ze}<|sc^3Snp)+M=Z5P5{C`Xhdo6WXXu`vUit_~a*#NWgQb@|tkv~)RnD_MWj&xnS1PkuGZmcBd+&r(oK|gR> zz?F%O7`@xzdRqj)ee$<=owWvl;L2T8WC@1Z>l&a?cGlHFb;$S)U9Hj^`j2s?{&}x7 zIO)#0n}o2${_y`C^m+uQ?pnXe|u*zxJmgAU|MCk_23hRkhaJ}6n5d9egv$`65L#%KHI z+M%k2La>%>u@4sSe5*`Kk}uk5zw~ozQr5Z-tB2+Cx%XWnlW|@>fCJqCliXYiwL!a~PV-@} zz+}k_P`<-dO!N1cr~jpk>qwP6&T3pv6)G6KiIXIv=Z*?lIxs%FNQ?y-hJL)&i7$OS zIqBlLPIMTkA>7mH;k%)WnBh0f`*L`l&uc8bv`s0Jes!H9z8XOX12##iAaoZW5eURc zn47-!W(=V72KZwkt^;pQNhfwWEjoWE6?zk(l#K`RCL(Gxc6Ss(EoHT&O4QYbu`O&kmRz z3cse&{E1(KAM^ZC!cY~#6T@u*rP;TLim6^Vt8PLoM7_@8G-l;9k%`-lX{>Z`y0TdB zcvb30=kpDy(>MG5_q|P1LW;cOAE#A{7z1%{#nNmz$A{EZ>RX^^MParyo?iW@X88)R zD&WQ3*r0&4@41rqWDddK!%yyfeERt^La;`}X)Si_&%w8E-wYR7);oJ>jklvp>^=2d zCGj_`ZW&pS=hgCQI8GXa5mzd+6Sg8)W&nJCF%!_g2o~PlT z;dyn`qR?Nc@?9frwRLzkw&sV!)f-=J9HcrW0I7^`cRr>_4yc}%Uuen5;FFze*ZmW{ z0_ITdKXu?9$j(=e%rdt)IoD0z+R?6ne1Ib5bb!TLxU&1k$9rmS?>(Ml{d!AG4C>=S zXGzE(+if{6#DmRbO{Zj^yqHM?;xJtQrx7m3xTsEU?;yeY_SKkAO2HSsxD$n?IA9U9 ze>q7T5j3jVrQC(!^ zdgiS4@MA{Rvj6Z0o{aloTSN>%1;f(&Pa$8Tj7Bsx+{L;m`zT49Ph#`6Q^g)GJ)#2U zCZBJ}g8{6yvEpLUPplZ;Jpr2rhNcW6$H^cTvPN{GzPULdtKS&?5czJfzK?> zXS-ysp>?QJMBvXzw5x$mj{V4MbtOd(_1|s#;OWItzWXhUU`zbA@G*tQqBR?ZI`?q< zNOOY5UTh>2tNy)X>S*rvSZRR z!6rbwNe}4>$1{MX%O`){!qyMI4#`Th-mNzwuwPK`;|cG0;HgDGcW)x=2YlQVWhQy% z6^?>u2KeM!*}`j}kKQ%0KNGYB#NXa*5@wqD;nr1dQE)mc>iw8I&fMnmakT?kKO6Yv zsUFYlZ@^(qRhPXNaMSqhVVscgCH8f1QL$z6YXL10QC%^+8ZKnN07!w^8f3LOS(L^R z?z8poR!L(t8}w#}mc|-0%~gQ_o7p6@B1HM=IOJ=FufSd z&O###$8XA`x)uNKu624Ma2)m*M;Z&4QT2Gd0fK*Kg#*Uph9loIH9umGet>`v?m9~0 zZP*WQS;b+zt$;_}@uEb?*1dVRCNF+E57;&0zpsB=%J7`*M}M$2V}rP%c?ikUjajq3 zUZr`?&Mz?EUDJuok!LA+IHd-SjlTY4u$YBf(AGRu^3r&XA#sNYa9tpsA>}mMpFK5+ zfNMEN|LycjIfNihTyIADvI40XP6$YnxsvtLWTPfS*7dboeO^N%)#0Ja)z~kr_yBUSJbv`o(ni})Woxe_Ah`N+yUa~ei1$V9Xx$5l zzTNBlffAw^B7xPU^{L!5=eyBWHae&t+&eKTR*k{oIZfHBMDExrmUIyz6Fd;eN!CMx zE)9x3;1-NGY52R$t{TVk45&bK7NOk`&=kETxdojDC?u+!u{e3>#}eBQ&Tc!!ed@Lj z+22+@pvFvPyje{;5x4Q zz{+Q-sQEO=Fspal$j}pELt43SX~qh|$!COBM1t)UQ?c(l#%p6Z!Xmg~9k$ZwF~-xq zwAG563TYHS81&7T#N;aA~9 z%EN>>Wi5IclD~7$y1OQole$je0yMXXEb2s78cB~Bqi%o~(&OSlm{qitdy`Y_RNACR zjMa2VQ*-Yr?P{RoI@O~LyhAHg-^I{a zRp8P-ilYkY-jr5DdfzKg7Y~JbzxDZzOQ`C)1eZ>TOSPIW@b21hd`qtOl2X-Q-QvQ% zTN4#M{ppSdAPp8-him&1ek-Xa=dM5ZXOtbPar0eONyf}~&7YpgCokU|Jt zHpfQSha)Q_o8b`UM@DZs4NS^{C_>(?A1ea9)bvW|QXw*0kLSv>)uijIs5$t;AyRrG zWSr5Vjew&WlB{uQJhjWxyU=NnwY-!uG(cCx4h|)Hw{6H~yg~p3-eD$Cg0&o4wTDkn^?0_Z^rXstiqWJd9H%_$c_p^^Y}IMWl(vT|HsE%-7?g>I zH`n=#DW$dMOab?*938&+Iz??pbZ$pU_ei{_e@ZJV_hM%DEVlIw#N1Gt_0tLLT{&*w z1_CN!pw2k*i&yY?R*VTSpfGq%Lg=C=pS>Y~R`Oi(OvME@HS3_NKj$?>big%6sd~!tGIK8=!bOGrCH+-Byqh*Fg|Gz= zFXxhx%lyJ##|DD$veZN1+3!v`02rgFbK)H5Lc^n<>(@cg**ppIig0S7AgVkpxdlpQ zwH$mR9JDy0ics+-AcKfXXt;CUT0Zg59#!YRTer$}@a4Rh<=Dc-aPn3lD2FbK0z6>_ zS-R;4;v2sOeT5GCeRwJ^0E2O#5=kg_7lR1$AZf%#EIs>+kyHq@IhhK6PNx(5&T{SB zR2_b69c%CZ{HrlHci!OoVBL0TOm|+-HBwyZjq9}vNlOPkiZ)>WpJlyP&{|j?_%4w{ zj|9Ty#oiY5HgemUB3rReElG+X>L<@S8S3B;!FaQfmtoI+)!{?-(N>+)cmN6P?e+5y z%|mQ>aT-8h*z0YNqbJjfUGO@{60$=xm5^=UX$J9W*uPCfjnGzEweXc9@ObR<0jOSBf;yQYT zm&RR)@V4EX@gA0HeWi^}P{d=X^qZ|w?q*L!?9!_YVHyq&@yp5cs!}eE=TeioU@9i} zB;H$Iqq-udoZEt5x$5#4?ylsei|o`($3s^b@h^Jk3-)aefQ_?_!M=sr%0->dp`Vxn z&|K$#2w1TZ8#IfGyySELe4|>kfDa01`Z#{Isfw!WBFXZm>os-dccX;9uRc0?rPsDq zBgNHLM9O$G|0x-jT#}Mnq8-428Nl%ZxR#*j<)UN(P}1f;r59{s%;={n(cG?Z*Fe|F zmO!w%**N3^w2m*s=&p{&Ss_UvEphqp0oA@}WZ7&PM>E~Igi4V06sON#4reCby)q@| zjO+>E3G(9dC;O--^M!*$WO%j%>N)Wu0(p&E_>v+E8E~*k$dty82VFNWN3>%>?_J9o zm?I(nKtRBBDt*12rTF^nHu_D1pjD<0<8Vj#3RGhu1&daUTP+QOiz=K}(c_i%jH;X* z6B9+-blkG1fruM>9vRzzdDP=!M=Ak~E~V)s1#Wr|>E~Ek6&66S>^U?mymB{MP0pJy8GL`7AKeE2f88&}wctD10hyM^b$RmXwoboEDi zC!xcO@g5Aqp5c z%D!hyu$sD#H?EFRk%T#k$xIhySt%@huiOY2{cWXkVAkiH&3y)RucQm3b3U)BRInB( zif|WO7f_-sL771#R!Az}hSL2PR9cm* zpfhHPP^DJ)Z1R-6P7GgF1qjohEr+aPt&gFf^eU;RW^N*qd2bq@FDRS5-fW{^q6E7} ze1!Mmk2CvofwB9biLcG~=BGz@{sJI;WhOn`?gH{eROh6$8zuwp7N)h%@CLBh($G1{ zQKyPMh!#sD#-io15cI-3^-Z9EHZXCQ6}}uD3pA^h_xAE;FOiPxae(XX2iE;Heb3*j zazoUqFxKaoz?IQ>e)cr@k_=wJy~iM76S}5{po^08&^(#Kl}L0+eFkw z8b!Ew&DT{Mq~c93mgQg5IvBfsA66H=?)hiQA+Mg!&if!O&>Iy zfB9`qG1_pwi`mPBkpgC=p&@U^u!~Y^0V*_J3H0q95?sqoKddShXbqNfSLysOrhBI6 zrLWBz9{|J&_XfcJQFxyzJBuY=Wd80_06=)}QV5PeGXJp`gbC$hP@|%4R${t-;rZ5y zux;nZUp@SJgk5E}NTLe_Xyd;O4i21PqM$acs?8>*g%5%k4F%s66#@uba!dnk*IBH+84_*pbpC zPgxh(c|A@D<3{ez+j~`s_T^nH4l^-dSF_ZHpf|fzWdh5~86jMM=R<|0sbGWpO6a+! z=@?G8ETzV;Qc3^Z&YdV0*e9Ss8s5ch3dU}NE_*7RL@>wY2!y;A`l$VB-KI*Fpg@G# z1-}pnxSHjhb9~Wc4)flrNd0zQGWHC@%u13qkmy~t;W(P;EsroY5P^!jG7I0xGi-b= zLZBw03JKeNy9b}gTXx;}Uch@2g^XgdcH=6n*zfa+COlKNdgsC@??a}k*5Rf5zL;^| zDz~5~>XzGfwf1~C+{r(sA(R;of$Qc1ys5tKOGyqpx{y_=-}htf`a z`7PA%-Xj%B$?;TBOxEX;k9hdQf=!aB2Cf5_6+IO8wY|$I4_6f*RPJ6;DI{%Bp(qXn z1<~+c-b`+$1AdqVGVf?GE1N32wXFT;NwN@)UeY0@uAP11rk+OvQ&hrgsSDaDS96sooIS` zI#}JS=*RcnvS;CWFG(mf+oBi7sKjRGn4caKamES{(l7H@OG?l)g*vtky{@m|Gf@E; z4WX78Ja(ScwAN+WAol2y5=EZ8gX|^65bE;Wv6^*HiWm!@krC=@W$2FXC`ypC=SYZV zNi@=Ih&NfSkW4^LF&4;HWZwfearBG9Ulitn@`B%6jX31L^SeJSr$Jo}XeoNBaOON- z<*3=!&-S?^KcrVQsVio}G+&@DHHFk)fwDj$j471lTKl+W$*;En|$>4se*A#17Q=>(|$GhbR~Db|cV(OB)2gB4;wWv{}6 zclla5UlzGqV7x2fp6mo^kCoqq7SrfU3VDd>++3mcHqO~u{i7h(Bui0dvZGLVf1Cv(79(`SU$r2^VIK3Dn*{syPJt!3ZqUD1V~-Tv!NKTo1yco0T;9F9 z6|K0`+eOH88n(JI)=Q|SHpoIMJGLTH1WnxJJu;FgJQaZa3c%GduBB5Lh=EB`@zcma zU_jn~d7(yjSP_13TYPP;N3QsI6Y3#){C8 zO2DBX?-?iFll)xlmiqYAGGwo7v_dZ_COmlx>y|_l%9{QM82=@C=%FWK&I#45x;Do+c~BF;ZggU zx(^qOT^@f=({xEY@9>0^U&WFlpDTnpX$`Hn&oQwBC)5gZDeu@I+JPhMOMgFgyr{qU z^i4P8R=eLhRYUALUqW}r6FMD8>g<|`xiEzyF@APDGcYl zXV;xk5Ohc30e-=LqxUAjVXR$l&rH`ULCcuRq*hBX2OREUOm#1Rb3`)|f{V#7^XF%beE|s15YUwn7+F2!|o31yr z!P!43MQ}80?3>a(wVZCx4`(C=E1+h$4@*R3EdFz}t}v;xKPYa#!k#9e0GX6Ubv?+U&676137>}jH_1wt$_<3foV zg^pFtoAR%M4UyE+0;}|7jGJxQI4e|~hws-hGyX*Tt+z!-Iv%CBoNxQ}>b}(!uJF~v zjq!!#OOaX*#dy7JjJFFFr|v9x=31j$GBfiA8;JGF)hGrQNtN$ZX6)*rJAQmOyndyx zOQAOpqHD6-o#(!dk8a!JPUB;RXMq(%cy%joKb97=^!K^Q5*pY)eKI{oR%sNazN?w8 z{{D)WEpmAL$A59ES2p6d)3m;w){x|ufGe??_@rmMoUHOR@6MMc?a!K^46X19RWO&O zJ|UUYgH&C+_4rFecJkt|jZZkbC~N*i^A3llkR0Rybe$w8Ihw?Pb&!}I& zH=U!(O%2P_WuKH+LI=Ao-5FtE)8Qr?HW0}~y-9GXRiaU6Su`uZmw53cb79FF4;jVb zPg?4f4*HzDJ-H6c2#YJNYkk=+g_;1AP zQ|uQrv5h>}B)(skJ2~Mxy3S9V-#O`-ba9&%Vt*x~jRPl6;$hZ3_ub&X0?klrd1`H* z8olR|IdVF)aCW*X4C+0N2pg4mM#3&vQRl9sAiuHZu>}RY!!kCqi+?w46Ll>yDxNo- zO_4l(fb+kJN&Am=I2VR4gCeR&S2?$5%J`12d;#`+wPsU5ZUlgU7NWy1_~K7IIi##2 zJc955xpigi>59Km;~pAb`FHR45ur|UurJMqZ)Bx^U9GhAmzl2{EgGx)Bl_#N)no*s zQ7l%I1}Ctp2WMR~0E4SY;a`7-mBowoIwQ&CLy4k>>eUjK)un6vSP0rsX4d%Mry#*$ zAR-G%WqvQl>eo69YYYVWuCK+F65bWw##8Mj{ z_3|fIBLkx9F;OEvHf2-oy2=Inz{9xg-kr$9$6Q9gAH5^{>)9X>=Y5f+%bQlo&cQM~ zdQ+OeiJ!HRjLOpwmIdohOTxsuq=Pm)ecM-FWucWzf0{()OHohe2Iy7fZFMVUlU*c9GU6@_3c@I{R{^$&Cgt1C1R+QjqxX+ z_uHG9tXjaOWm`~*?Ezo8M}YVO1YCkJCV!50`!*o}SwSgZ=zfO5b? zY%o2kZ`F>^i&b^$WCb$4?&99f%$(D~C+&w97MBr|{_B^_NvW%Ft`=^tqyAvzg#TD0+^DxUQ^!%n&M zmW*1Wj>iI%`F#6sGI=m_Vp58X`ddT(Pj_J$4;F_y{k$okzC`AS=!vO`L(AihKu_9U zd~Rh)p1FEti@ER>z|F)=?XH?D_&3&ne-c!i^q2fBpFSH3#Nw%DHOLtpq%0 z1HsTx#jE^B3QNT$hm9<3hyr)T{~9{)Kq%ZejNft&hqKQpsjR2bGy6ciiULi zhdVDomza!`Zhxj+`)I+z4(I_8KthO^N}w1wZuAe(?(fAjZyHN4q!gJXRVt+tLk~1U zk5{ia46lHipfO_7yWs?zY#GC88sLxl=2setx>-=;dbfe>D`#p~!dwXR^DKdK6HZQs z$}0OrNPlsx=8|A*OLVITaoBksb}dPSx!@Fn{AH1_&r^z_fvV-I4NwlOTh(7~-! zga7hhw;Rj#p#NT3f%|KZHB*_61+1$Ba$kQM_A^UI2At(V9twooHP; zZ{1x#3)51bI0f2{H6TnuDJ+)QX7`tYri_zLgLO~@RWhT@bEhW&)adQ=&d_+Ybj;K5 zgb8oxfPk=_a!do{CbNfK4;|O3O-uE%YSfw56xZZ{Ehplisq37B#bVR8A_VEgv-z?OH)&g7xSyB>bo<#Zu4K-I5{he{F< z?-IwE_%z(M_iFz!vz#b=pd^l`zLm#{eCllAe;Hi0*TIN%8vT{%h9&aUHSr)Mlx1!i zhAWluVC+D#^ClZ~e3;^}^6s8{nh}ngwG2KX3+0`GAhD04pS-zzoKK!ChE0^kT6M+W zmzH|^?1{2CJ*k+-o0+aR@*E}y0HZNVk);uqA*E#2m>Y9X|0Z-WvPz?=iYe@AyiN@W z2z>V$$Me#kjYr&l_hleoIooa3g)Jn1xQVH)BMk*qb=Q+8tA~RyZvFF~n^8VFifG;C zB^^eaT!lZ+16i%eUs$S?`-(^SXbODM^DXVY#6Ex~HC3*PvaE=yD7nT|AZLf$Glsnv zI9%e3uuks|6DdrQXGsF+>fW9k>Bw{Q2FeAP(Op*_=leyRElaMK*mA%_$LLAeX*P-iu^V-4k?7DT}ihS4cC02RUfi z`e7UAV~A6DwbyBVmAU|gk;~TEU&@>L_JyC@k#G1BkZY%M{4e&Slpe*AGB!aV=}S-V z+1&q!r7x&cL2>Q%Fwe)HuTMNqtRzK4Z{xLfJz~3LfwByj{t#kCjP11GXOb^vT3zV zAe$atiEDIyklKX(ah>W%VDraNHv_pAb=S2>U#je5d41OUc z4c>U#rsa1L?m0a3C*?dN+r^7HDMXj=1~miJ+@_6=X660qt4iMVPb91piPPN=dYDo{ zw>jS2FTh8`#zM|zlq{GFjkO0Ejwj(W^~OwcSSE*w0QM-uFR|Y{N;U4msl|CWqU;lc zet9%0w}lcZc6DAjh5L3}T>)Y~is1?dcsnyQCUSW2<9_T7w3R_)Q7!d{wWOENoYD5x zDq0OwI~u_ZEv^hhibt}(<{MgDE36?&0K*DQb*p>RIGt8HC5_pW0Z_R>hB7&7lLmVTxixbPWsG(8g|9B(WH5CJ$KJ>B1Op z9?3xr;n$ZMUi8Dklx=Dy4X>#L$4%Q?*xj(>l&V`on*zcpr? zPI(%h@9+#~(;NRO3wXn5~Edw!c# zlaPa_*1M=A#(AfYtaWXN=f|K|zcSNJ; z5^xv(xZxJki2FC#>qe1`AYYHYQnR+4=A$53DN>BZWyrV81MlM3YGpW8R#L68;+kmF zhU>LaFr+X!6?D(QfeJT(=GPsk3WEKA?4R zxeIk|bQsJPiwWl0`V$sb1tnvZavZ;(mcym!11_w@LyFzwCK_RtYgAijYuPUMu_go! z62h9r6+ORqf@i+D6@DO=n6HKe?d7KEnIW9R0}%_f$X@8f?tJrFIx+udZ)?XRLUudUs)c&5%(DO#~q)MPt#xkVgNF$P7%c(1abOAxjR6 z6L8Mqqvjh(s{XoCpv%al<3Rm;Z1U1Xt|q%^Q~(t}QHH%hZdLN3;?p_^YdJhmdm~Qi z&xVAkpVIkf$!2M%rfZ1UOVM!rU#Qoe&*QY(L!UkZlu zNZI4oZ$0IuzqwU#OFd3?dhaQY4iAXCZ$$55(cCkdTl?Cd&msvGdrMRK&%xKlV_B~A z9~MdCkg_*;ysUHr&|OT*g8t}Q-kgwMlk$I_(6nWmb2X1Ajd!nIOd6wvQtd5=KD_ais}VUa zk07xUU>y%L(;mj5sEXp=J`8R-6F{Iut?vF4dDkFnKTU;@S$b>G&tc!I@PB;INv4vVu*Vr*Ifbm9w&a>he9he(+rd3GR<=y~$ULOu! zOj}c-m;wH9ME`o&JKd(bhWDNU5z!ww#gB2SWdy4l!kK3_={q0EU-7Wwm^~9FH!(c0 z?!8vXn=KKvMt9WL;>?gmwrx}T4-x;Nx!A6(@WJPgY2L!%bRU zdSeg@eE>{EKKsIvkR$W3XZcT3udETHmle6~#+8E)z7e_Y1$CDaajIs?;*bgS+!yyb z_G#F$qNZZGK-HgnarIfmtzy@ZNAGHkjvNes2C~+4nek~k%AJj&Tak6fVlY~#vF@+F z$@pSEnJXDpk2^Z%fh3MvbnL})=b*HU0m43t2_>t+H_SvrOiw20b`Ay7RTAITXc65> zlZFUiCIdK6ZK!HDJZ9$V3WfE&N6@j%7brV2IrcocmT3o&$B~;$X&EaG{5tb`jOs>C zQ45n(TaPm>8MZ1Aq~7^v981-shP=SIdtW&hZe}?2m2jnApOvS_FrtILdu14}x0PY!NtTuGG>B z!po_oNL0URdmvvs)Kg`ypdwDYjVoz-a7FnJC-b=p0rH^3vVt|1hL3|cy5zGZ)tf0$ z<2Qk0iITot99QKY$ApJ{wfVH+ef-I?zI>tBSe#W3ogUt?-5sHp>HR`0bL}YeE~@K9 zc}+@X-5rM@WP*JPet6$(ZP@kI#fb@~+5e$YG-Aw?chP}gH>ZXj(sBOG;mKXOtbEe} zmEfJEEsKLudPc71t9gJSV|J5Oz=agQ3zUu6;d6N|-6o#X3p#4flvEg<|z4y7AUH)h51% zMq0T2W!ep1&d(oXsO$krykIUzQMPLh|HOC&x>CCN*b!M*7NID$Cz4ThIa$u$i)f>- z#^j5ra=;aXJO6Y%VFqgK5TiRjxL*6ogM%eS*+z4=#8ecD-dc~GBdYsWQ&d9nZ)Ghu zOjr7Qna}2)V%&bfbOrPqRMNq66Cm-NZuvVdDnsetJbRp*BP!28$j zN#(bWTMJt+`~#|hK^s>f8R8YmftltI~xwRdkZLTJjrIwn34Z_vr4yl zua(oNp$uCp8vgVG4Rp1aNEa*xJj4g+^Qf)Gn{}7S-meZf&CQKq>B-&&p!f4(Uz~vYPEV_)*Hv%P;2ojMdM4@$ZQt%#N@+;7QykT z9;$Pn>T7&^D%fc$8IN@APu?<}(($!(xq$ds&(!}0RGVD?! zKh}(X8=S9f!DJ7Y;uEVyL5Z*~M8^53xyo~YJdv~Irx7qsPF<^qhGC%+B_&92YjTjHU;U^so4}&mdgWHEOSF{lS!zsIJ80aC~JN$sNxt@mJ13Eu&#W>#zb9=CfHP%iz zv6UgizK^~4uD>WE2?Vyys2A49E*UX8zM#s{(GjsG#D>A+1& zrf5{U;#4u1Z`V%kSYk0gjO-+pKGM35GpyGpqrTMDiY?AaLctJjqM~X6zZ+WPlK--v zmh}|~t|XlIP;_r{xFUgJf_{~1&O}IqG+5c;k0gR!kd7C{Po@t2L!toFNe$k9>?8O` zQ~GNFOKoF#Uwy^J$rv(D>agohkq|?n_{|bD#}!aNHUS07$ACCt&~h#9OROg{$#tKB zF>U8RLT9X*zBD-9{D%8w6#@AQ}4hexW+>Jvf@i&?f`nP-0}%w-6w!Z^2Xt z4R9%~1Cj#i#uaxs3{jWAU9@{1egPzuSzPWX9HQrb)4c>f%F6cLNpL9#sE7^<~324?>S5wgP4)>DFYOY=9>L}2z#yFx6TF#AoF$#z~w61ma5O(w=Xn}@O>XCN6F_;<) z-y78{_$MxS{;^I0MS863;xGrqLK;dr$4@=7j3sCTxi{8jC9bjsRIF&}Vo+yhuMObfN@wV#D3FgZN%2R4PUT9AXtlsSKke8`un@dZixQkwW|R6dXc z#~CbnZ3=wKt!@ZV-|jSI=mNSv$j~}$ow0|*4H;6>x&c9RY>>{^c!v_fS5M=GF~2Rw zjhP`9m!oI|U*&Yjsv!^L8%dUSJh1Eie+&8kh>>(&#veVIs69yJBVF;iNlFuU?)j1Z zTm8QnMVBFI^e3ZMsGk7MsVLjUUXn+_aX9)}2&I}*RB-TU+A(YY! zfa2%Se>zUaF5l%MbEFNSYMLLnV>RSuI7;Xi!<}9>l7I;$tlZfhu8Q2-DE=(O>4~6r z$GUkFpJ$sFLnm3$7!BMaaJnjHKbHBw>nbE}uwa&w0NrDbmQez{w5ykP1A=twFKQIfHdKHw zVa1hGXDNv={YE|f3623e-9w3P)0|SE{gf9&aKON#lX`;xu=wpe#S~{FA!-y)uFS;m zLMaN_YrMKHOq|64raf{SF`(K9oGg7h_>GG3xHF`~&tP9n0+Z&uJ-zXE9<7ElVq-#=e>RgMmg*}eRR8`?k+as zfvam!h+dDti?hUsiTca4_Zu$#m`NKt5oIb+UE*dX?b;W|Uj7z)Z>?&vsZ+QA=e=~} zVv<)(x7i^>FZJ!mU=zgB&cos+$kuRs+l&qqC<+^P;ey+8hIM+W^XyYs^d6$AOe1fkp=!)*Y<^8DUDFtq%y%ZqqTmw)ZS%Yhxue_cdi(U zTn<#14JgGJOp4QG4^DkLS*Z46<4ARoq|O4|=&zC{e>eO*oQB;_ zQVAe4B3&>&%DcRNfZUirlljy^EiGw0-LQ?wrxxJil)>X8%QMPs#2Vj6Oe*H@JgX70 z&l2x*NmHEsyC*8D0W6{Kq42GEzkPR6E><5f@aXv0w25Npcl$&WjjSdR2#ZW~-Tw?? zB2vwJtV&xJEap7g^__Yu9wWT-1VAUF9|0?(Omxt`?$k&RRdIAVlNICD+E+t0vFzhm zm2L^h-FPe%LYek#(RaZ5S5$-9tXB+gO{91tN36e_2$K&YZOeYa;-tPopWen#P&UIG z&>wmq>=G#eLzeiDNj6@3l=IuS4!c7&Vn0;SNwGDL72%Vm6)?-Z#6MmzRA^mHuzifI zM6TMc0y{fHdj0F`;G4iHvMI^s;A`vST0+m8Rl;~6tJhA69IPS{L`mcy z@6wV7x%zugsUl3MEe{~Mj-P=&CC7p~{Aqf_*j|-cl8I2xslg@d%E@47--0CGYLvRB zXIXY$fNR1>G29+a28C|HZ?fXcEzPhG3`tGG?r990q+^{L3Dr$g}TK}~e zLi0rphax7jTb`l3n?6q}i7*C}M11h`cu7VTmn&O39{V-I8&XOWkcuAjPZN9IS{RT8 zFp(i(Na)Svs#pA20z{eBC9e#{yL&ED27xf*x-sGyP#XjcF$5w8e1m?9YBaL+qtg=B z*xOPq4Lq~jS(MQ7`@Z)?*e#jaV>40ojaYAz%>1+6TF&^!UvV!33{*ivMNAyDUg_A; z8#&kNmi`*4pLERzY8<9a@DK+K>=kYpzy(Kk=Y-^kuUbsj!gn_G>cK3cjF5Bk2$1)* zkO@%b{fo{YtJ2E;<|0fl9(~;YWHacoVayp2x`IfEYL@-a{kkXwv?vR6F6sT`GB*Cg zfqFSMQ89Wxfi;w{cgqvo&mLG(XCS8kkc8D2h?lJxdnT+0;h1)f>lHyZ9FD<&#UO|g z4lW$~?<&mIEu2LUftxe&*nPJ9DNANj|7Z6~nsKv8vVX;BNbw+9A6oNvgX?=g45|+M z^S0k>VL?UvE%vQ@vN&r*u1E1W09*lTHx!X4(&>a*#ND2c2plv=c=Pf4v=2v%i`Cz; z3)9)zcNS0xdDZ1Z+7W)>>g`fVhtjdaMh``R=4n9qKKGEw40e7*4S?1?W9R)8Y)73A zcefHoQb)jiES_$yn!e{BXd}X&?~v^fF&LP_Nv{+UlWyi#oVU}-H?H0gsoE%HaX$B6 z5?9Px^Rq|r?w*-KJo)pzcQ7_t!Mf;>_HzLHCL)Aq{(g+oi9)%ZcDKUPU;6fs_dZw- z6L7b$mQgAptTi!0&~4FRo`{Kj^xUzS9IDAR$PH3hn%q>z)TWFw!T{KokI2!YJ1f>tQwf=+|Cj z?@)snjw%jlEWKL-I1c-0rZ;z3J_G>>K98u_+BKwnJ)!;iX}Ja(H`(4c+w5+c!z!xr zLVNL2#Fga-Mo29LAyORf4@SgQRSEk2exx{SP>)KmRImSHZVf}B(&$KRDs_hwwOjN^ zarAO*uXUH^c z`G@%RUep<;oDV>_K;``hFfZ}L6t#=(@`=&)`OV&H3O*w~I{ODb(QV*}b5l685hEK8 z=`q$bitDe+Z7AX2^G8?YAiIU;?*?8=h<+Y;fB7Y0 zn;owr`E}Iiw&%UK?aFyR2WDEYi#+ATHK)(-#I(P){%_FnvN1H5f3-6A(lf zXq(_MsjyGmZqA zE?&Nc`}xNV^4L90h0!>&-An0D|7Snqis(sMabNVQ{7bD38MfvD{>Mv)+t&V+=huz< zuKm}`cE|5;<-)$NuM$LN%R@Fac zYCC4-a?4q#5@DnE14hLrpF})W zdlgS-sW0J49(evSzbv_LU)s*DZwBc6_XASQ_bNN6brUzLbzu(GOi!Bo_M$F)qHS~# z2L53~YQN0sR&dd^D`C4u@80LXt^FaaTB%#Oj{+8db+6C4k{o%?6N}6z-&cry!W_gB z-i;b468GTk7K*!&p+MW8UvJG`1tP~|MP0C$Qh%zzZ|jH&Qi@q5|8dV4|ZNgrbg zf=KD}{}KR*`oE02rV3bBfA?qh=)3Suuez8tFLHdTS$hDQun%==zln}_qjYs^B+G(eQ5->Zp4H_|};!_!5t>eB*|7c@$5N*>VuT!!?9{Ch)r zae8HJ63zsxz?k(?k28Ep8%w@340@>Kb#n=om3n=pra4Pa{SnF}m>7f{B=J@nIRu@j-G}|;W$64;FwQ0v*YDdwXaEg7a?D0~`y_Zm>a!x<)z}h0rkyJ? zaMoJF;6n%n5^pa=Elces45ZbD5 zO`r{^Wh!C}WBzcT5&Xo**>^Qi+)-;_cGh(z?pY{*g6q?;LTj5OA=H_1=X8n7oJ#p5 zzUHR!gd=y9Y+iwJ=rP6K|I=!KIrqkYIi%&_Hw=8N?Atp{+@Z*p^?AmLyo_rxFNVvq zS=E_7(Fc>2X_$_$12qSNsFc^#CTPcHdplhTPj8n!KM4ZWg>RaXSI?PVm;9=UjMEK3 z5dv;0rY`Mk+=k)dV?H-E$X}uq?WkuiZy1T}g+^E2wt4|^uOP%1UqQX^w^8~~7{ju5 zc!PkowyreAf;4nTvG>*D?k(nAkB@d)=r5iH^SFXk^ku#+8gOsz@H~-(er2~4W23zA zinNuEkJP*4WdYYI=?)m^E zplm9K0(rwsZtm8%m#=OQ+O zv-!iA6IM|hl!6F_5_M$fYj2(#w*2&H{t0->k3<#lz)*(3mj39M-HAHi7_LXBzVM0L zI@{X16R}smN1P4*aK)0X`n;C2G!0fV5;P~Ke>>I78yhQ{8=gEfu{fzj9D}Fb*eg#S zueM>`**k#a9*C>catIh1!JBot#ob)yqV2`h3(VViPWw>8Gz(Vum*4()u)WNJOaEG5 z4>e;_roQ4$)HVb_tI%#~`hKS=$Q({ssUV+@!wutwIaH3oL#GW=r_*%Gj?)SJw&(Ic7sm^{KmVF!F8S`(8FOc5 zC5Fk=&}&PtFQCLIPAl%On;(iLf6kk!C0S>I@Kx%bbz6z!8g!z^q2+2Zt89XwZ{8xJ z_b`zr*OjLCnt5B_?^$IeYy8A;!XSLnBzs;W=5GXVMkE@w`aS@qDG8f&sTwe4h6}AT zc=?sTqZjkrj=#Da9IOFAs;TIEA`v$IX{W1CV`hvOLH)^5Q}fU=Kq8mkt}*VU>ZDmtr2n(V(1Tfhr_O&6& zZS?QmKIdipsfIZo+R(ew)t-M0j@5B~=j3%**WOK|_S}|rE$^3<-|RO{xw)jc<8+|g z0u8;~2J~SZ(J!FTYpCb^)q=}4ii14n6&0&c0qH@f=KOPfx-UVbYcfj0yA4-QC`Dl_ z*Wn6in9VXnT+_u>p(l)pJ?Sy#?W-idA2QG55Q_+_klr-OIC1@kntCtkav7rC8k8$2 z;>nkS&eUV8Z_u-D6PUQhma#;!4l6)D^^!f$&id0qys2CU%?mo%Y&KG06*cwRa9-Y4ectoN3>p;Vy%7Gv=9~69OFgk{`d63+!7WDkM`jw9 ztM18K#)DE!%$-<}{&WWUau^nE@~~p-gEVR-t>+MOmSx4Q-4l3zSZ<@AlWhvfh#Ky-eZfUkn)3GKc8aWm*Nn zTn|?Tz4D9F!-T-(v=OJj4XuwA|H)CAV^KVpk`?=0lDpiFJmW&tCx6OJL{-=+|D}Xp z-+Wy(e?rb7JsIj@l2&cb5gz)B=(t*?TOdjdYHb&yebeU(GygdlYL)O-7vAOCYsB>9 z=Ci3@+(P+s8!^u&I!L!yP*RjiZ4T^qf#BiK$(M+_hHplSGXYSDMG|}(ZsfV)>;7u= z)0=^PATcIUg~@;LLptJ@T=gF;Yac_=w4A3X3_iK7?1x`-g@H<$x$7` z68{a3idbuuy12~iDR-1J^I}N2op{_J+aV#V++#w~T;96Mux2DQ)k=IV-*|{?a+*nu zBx!Kvplq}s@#RZu;Lfw~gO@KfF8iJgK$9pX2hRT0DIvUiRKkr{3Lm#bUCPs#4qjak47ulH z8l)WIthxJN(^ek3o3`ms_YKsg&#HqB;kmp^7QI2)UF#_Q+M0&&FJuzs<<^!eEK4kr z87B(Ke+ZAxetY_BpV{UT)8#Xrg=?_f+q)fuMZGmaRh-(F7FE?oc@ua;7}=XG8qs(| zQ2dNyw3s?S*>J4K}9b3;m6ME2;kMTVEh5=@iHoMp!Jx zhud8$9oFIT5jS@Rn^X@SA zy);ZQyjQu`C00C1D^H+dvgn(~LOXMqZD;7@ zFF$rLL_6#KGq8Uj4}Zm$UiW#h{mN31mPcja^VuWGM!|cX9*F2vG9xQ$l)V)O&e{7_ z-I{zm#(>u$edaC=5AoiSW$Rv5@Pl*5w09Y~YeeZ4eh0nZCx={T(FYZEB;grtsdHL$ zPWyXGA|*F|g}mi`__%{1=5&!XmG;Tvx~o~-6%HP>)8j}yJAn9aP0H7#T_t$;8+}y4 zg5K&2P(n$)1Dx)djQeU(6(* zjS4x^U*fLVrlm#C<;l#LXksYKBjvv(W;zQuIOgv{zwq}Uq8Rm3-S5tsX~v;}d$sCc1H^{hiVT{jxf_N5N>0X6qrgAj+G3#y{G#_paK099cZ zK{60jR5ZwP44k#s3$n_1a}*AtQWnzPXMb0TaMpim9G zUEUJX{qD*f=+i2LVLnmAGNl9rka|xGG6Tlknd8y7hoxzFGeS(0u0RTTibQ|BS)@y> zY4vupaS!NA{C%^*|mWBEN_bTYL~=aEYG| zWx=LVFKf(`cc1|H!h#Crjw8Rcs0Grx=(VABeqFb`$B`so%?mVBA&oT1Fi8lqlj;Ta zSyZS(TAf|nrb(S=t&R2$g-=X--4Bz7bRa~2Oq;)>jDXq_?IJNV1V4afoO-HA(=rlW-xEf zb`gxByeBuwO`{oS=)<)hDv(sZQrpyAIOwOq$#{~z;Pz`@wXgB<@z@+KQq-5n2?&6| zab>`ha~|S$H#9|;;*Q{eFdYLTz#P2+>YEvrw?2~sb1~Q>;gcixP;9 zHP54r{V=0qon2qn|0)e$7txyggprMrgWIVwsr4+kzniish^uQUo=%32|JTXio^5Y@ zB+-?#ZDgcfkq5dmH>C)RAxck8Mj&en+YUd;hxgB278AbR^1iv$lWd8d6pV@gj!SA~Wg-_kd0q#BeA^uW|AIpj1K!F(|Qv4g}(bP(1^)f<+RHoM`$*j4zW%`$qOxJ;;UI}5q zVd+u?gC6?9RecAwz7XZoZmd@IY7czp6VrhP9qI}-D-o6?jDSj_h5BZVlmxm10OPlR#xbv|1OpNw~D?XK?O_coj}zq+n#BDh;$PBDYTu8w87 zdBud-?%Ua~%bw(7xc_hi?3*bT8RvqIn4_ppIBrZE8T5hR6bgI)@4 z|F4UTE;6zQ83~nsrg5#=@2&)pxX^vUdlGC1V==3xSiem(V$}CZT$h(IZIhCV1;2X) kO}0bZPN;vp|GR^K1{w8aA5ewr#YedSBYktdIvp3%|F1Kv^#A|> literal 0 HcmV?d00001 diff --git a/src/assets/images/reaction-members.png b/src/assets/images/reaction-members.png new file mode 100644 index 0000000000000000000000000000000000000000..3fad1943a1d36a1298b11e782ebe1bde82bc54d0 GIT binary patch literal 13549 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 literal 0 HcmV?d00001 diff --git a/src/components/atoms/emoji/Emoji.stories.tsx b/src/components/atoms/emoji/Emoji.stories.tsx new file mode 100644 index 00000000..8aa08888 --- /dev/null +++ b/src/components/atoms/emoji/Emoji.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Emoji } from './Emoji'; + +const meta: Meta = { + title: 'components/atoms/emoji', + component: Emoji, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/5c103242-786d-4700-bf32-b87e4c4a8970', + name: 'like', + size: 56, + }, +}; diff --git a/src/components/atoms/emoji/Emoji.tsx b/src/components/atoms/emoji/Emoji.tsx new file mode 100644 index 00000000..dcda6913 --- /dev/null +++ b/src/components/atoms/emoji/Emoji.tsx @@ -0,0 +1,11 @@ +import Image from 'next/image'; + +export interface EmojiProps { + size: number; + url: string; + name: string; +} + +export const Emoji = ({ size, url, name }: EmojiProps) => { + return {`emoji_${name}`}; +}; diff --git a/src/components/atoms/emoji/index.ts b/src/components/atoms/emoji/index.ts new file mode 100644 index 00000000..b6ddf21a --- /dev/null +++ b/src/components/atoms/emoji/index.ts @@ -0,0 +1,2 @@ +export type { EmojiProps } from './Emoji'; +export { Emoji } from './Emoji'; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index e58442e4..1a8ec7d9 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -1,6 +1,7 @@ export { Avatar } from './avatar'; export { BottomSheet } from './bottomSheet'; export { Button } from './button'; +export { Emoji } from './emoji'; export { Input } from './input'; export { Span } from './span'; export { Star } from './star'; diff --git a/src/components/molecules/countEmoji/CountEmoji.stories.tsx b/src/components/molecules/countEmoji/CountEmoji.stories.tsx new file mode 100644 index 00000000..9add104c --- /dev/null +++ b/src/components/molecules/countEmoji/CountEmoji.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { CountEmoji } from './CountEmoji'; + +const meta: Meta = { + title: 'components/molecules/countEmoji', + component: CountEmoji, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/5c103242-786d-4700-bf32-b87e4c4a8970', + name: 'like', + count: 10, + size: 56, + }, +}; + +export const Selected: Story = { + args: { + ...Basic.args, + reacted: true, + }, +}; + +export const OnlyView: Story = { + args: { + ...Basic.args, + onlyView: true, + }, +}; diff --git a/src/components/molecules/countEmoji/CountEmoji.tsx b/src/components/molecules/countEmoji/CountEmoji.tsx new file mode 100644 index 00000000..0af790c8 --- /dev/null +++ b/src/components/molecules/countEmoji/CountEmoji.tsx @@ -0,0 +1,26 @@ +import { Emoji } from '@/components'; +import type { EmojiProps } from '@/components/atoms/emoji'; + +interface CountEmojiProps extends EmojiProps { + count: number; + reacted?: boolean; + onlyView?: boolean; + onClick?: VoidFunction; +} + +export const CountEmoji = ({ count, reacted = false, onlyView = false, onClick, ...props }: CountEmojiProps) => { + return ( + + ); +}; diff --git a/src/components/molecules/countEmoji/index.ts b/src/components/molecules/countEmoji/index.ts new file mode 100644 index 00000000..d4c4b6f8 --- /dev/null +++ b/src/components/molecules/countEmoji/index.ts @@ -0,0 +1 @@ +export { CountEmoji } from './CountEmoji'; diff --git a/src/components/molecules/emojis/Emojis.stories.tsx b/src/components/molecules/emojis/Emojis.stories.tsx new file mode 100644 index 00000000..cf6f8077 --- /dev/null +++ b/src/components/molecules/emojis/Emojis.stories.tsx @@ -0,0 +1,58 @@ +import type { StoryObj } from '@storybook/react'; + +import { EmojisContainer, EmojisItem } from '../emojis'; + +const meta = { + title: 'components/molecules/emojis', +}; + +export default meta; + +type Story = StoryObj; + +const emojis = [ + { + id: 1, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/3727715a-a778-4630-82b7-663662516ecc', + name: '좋아요', + }, + { + id: 2, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/0515378c-b6cc-47cd-850d-837c8ed91080', + name: '응원해요', + }, + { + id: 3, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/f7b0d37e-c514-4c02-8b49-8b65e3e28ec9', + name: '놀라워요', + }, + { + id: 4, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/75a0d5d4-6988-4952-b105-24ecb77af270', + name: '열받아요', + }, + { + id: 5, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/c0d0e892-34e7-4a6d-ae06-0a8faf142ae6', + name: '슬퍼요', + }, + { + id: 6, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/9e10e382-3ace-4923-b6a3-f96b7830164d', + name: '대단해요', + }, +]; + +const ExampleEmojis = () => { + return ( + + {emojis.map((emoji) => ( + alert('예를 들어 mutate?')} /> + ))} + + ); +}; + +export const Example: Story = { + render: () => , +}; diff --git a/src/components/molecules/emojis/Emojis.tsx b/src/components/molecules/emojis/Emojis.tsx new file mode 100644 index 00000000..b3277b2a --- /dev/null +++ b/src/components/molecules/emojis/Emojis.tsx @@ -0,0 +1,35 @@ +import type { PropsWithChildren } from 'react'; +import type { AnimationProps } from 'framer-motion'; +import { m } from 'framer-motion'; + +import { Emoji, Typography } from '@/components'; +import type { EmojiProps } from '@/components/atoms/emoji'; + +interface EmojisContainerProps { + animate?: AnimationProps; + className?: string; +} + +interface EmojisItemProps extends EmojiProps { + onClick?: VoidFunction; +} + +export const EmojisContainer = ({ className, animate, children }: PropsWithChildren) => { + return ( + +
{children}
+
+ ); +}; + +export const EmojisItem = ({ onClick, ...emoji }: EmojisItemProps) => { + return ( + + ); +}; diff --git a/src/components/molecules/emojis/index.ts b/src/components/molecules/emojis/index.ts new file mode 100644 index 00000000..e1a26824 --- /dev/null +++ b/src/components/molecules/emojis/index.ts @@ -0,0 +1 @@ +export { EmojisContainer, EmojisItem } from './Emojis'; diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts index 28caa5e6..bb38b1bb 100644 --- a/src/components/molecules/index.ts +++ b/src/components/molecules/index.ts @@ -1,4 +1,6 @@ export { ContentWrapper } from './contentWrapper'; +export { CountEmoji } from './countEmoji'; +export { EmojisContainer, EmojisItem } from './emojis'; export { InfiniteScroller } from './infiniteScroll'; export { LimitedLengthInput } from './limitedLengthInput'; export { LoginIconSet } from './loginIconSet'; diff --git a/src/features/emoji/BottomSheet/Content.tsx b/src/features/emoji/BottomSheet/Content.tsx new file mode 100644 index 00000000..90a1cd3d --- /dev/null +++ b/src/features/emoji/BottomSheet/Content.tsx @@ -0,0 +1,33 @@ +import * as Tabs from '@radix-ui/react-tabs'; + +import { ReactUser } from './ReactUser'; + +interface ContentProps { + reactedEmojis: { + id: number; + url: string; + name: string; + reactCount: number; + reactUsers: ReactedUserProps[]; + }[]; +} + +export interface ReactedUserProps { + username: string; + image: string; + nickname: string; +} + +export const Content = ({ reactedEmojis }: ContentProps) => { + return ( + <> + {reactedEmojis.map((emoji) => ( + + {emoji.reactUsers.map((user) => ( + + ))} + + ))} + + ); +}; diff --git a/src/features/emoji/BottomSheet/Header.tsx b/src/features/emoji/BottomSheet/Header.tsx new file mode 100644 index 00000000..1af0dfe8 --- /dev/null +++ b/src/features/emoji/BottomSheet/Header.tsx @@ -0,0 +1,36 @@ +import * as Tabs from '@radix-ui/react-tabs'; + +import { Emoji, Typography } from '@/components'; +import { addSuffixIfExceedsLimit } from '@/utils/suffix'; + +import { Tab } from './Tab'; + +interface HeaderProps { + totalReactedEmojisCount: number; + reactedEmojis: { + id: number; + url: string; + name: string; + reactCount: number; + }[]; +} + +export const Header = ({ totalReactedEmojisCount, reactedEmojis }: HeaderProps) => { + return ( +
+ + 총 {addSuffixIfExceedsLimit(totalReactedEmojisCount, 999)}개 + + + {reactedEmojis.map((emoji) => ( + +
+ + {addSuffixIfExceedsLimit(emoji.reactCount, 999)} +
+
+ ))} +
+
+ ); +}; diff --git a/src/features/emoji/BottomSheet/ReactUser.tsx b/src/features/emoji/BottomSheet/ReactUser.tsx new file mode 100644 index 00000000..090b026f --- /dev/null +++ b/src/features/emoji/BottomSheet/ReactUser.tsx @@ -0,0 +1,19 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +import { Typography } from '@/components'; + +import type { ReactedUserProps } from './Content'; + +export const ReactUser = ({ username, image, nickname }: ReactedUserProps) => { + return ( + + + + ); +}; diff --git a/src/features/emoji/BottomSheet/ReactUserBottomSheet.tsx b/src/features/emoji/BottomSheet/ReactUserBottomSheet.tsx new file mode 100644 index 00000000..990522b7 --- /dev/null +++ b/src/features/emoji/BottomSheet/ReactUserBottomSheet.tsx @@ -0,0 +1,41 @@ +import * as Tabs from '@radix-ui/react-tabs'; + +import { BottomSheet } from '@/components'; + +import { Content } from './Content'; +import { Header } from './Header'; + +interface ReactUserBottomSheetProps { + open: boolean; + onClose: VoidFunction; +} + +export const ReactUserBottomSheet = ({ open, onClose }: ReactUserBottomSheetProps) => { + const data = { + totalReactedEmojisCount: 1000, + reactedEmojis: [ + { + id: 1, + name: '좋아요', + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/8f87efc3-ec65-4650-b763-74501bc2e1c0', + reactCount: 1000, + reactUsers: [ + { + username: 'BANDIBOODI-10', + image: 'https://github.com/depromeet/amazing3-fe/assets/112946860/0ab4b6f0-ebaf-4212-a79a-edf0f50a5d62', + nickname: '누민경', + }, + ], + }, + ], + }; + + return ( + + +
+ + + + ); +}; diff --git a/src/features/emoji/BottomSheet/Tab.tsx b/src/features/emoji/BottomSheet/Tab.tsx new file mode 100644 index 00000000..c4e43831 --- /dev/null +++ b/src/features/emoji/BottomSheet/Tab.tsx @@ -0,0 +1,14 @@ +import type { PropsWithChildren } from 'react'; +import type { TabsTriggerProps } from '@radix-ui/react-tabs'; +import { Trigger } from '@radix-ui/react-tabs'; + +export const Tab = ({ children, ...props }: PropsWithChildren) => { + return ( + + {children} + + ); +}; diff --git a/src/features/emoji/BottomSheet/index.ts b/src/features/emoji/BottomSheet/index.ts new file mode 100644 index 00000000..d76a788b --- /dev/null +++ b/src/features/emoji/BottomSheet/index.ts @@ -0,0 +1 @@ +export { ReactUserBottomSheet } from './ReactUserBottomSheet'; diff --git a/src/features/emoji/ReactionAddButton.tsx b/src/features/emoji/ReactionAddButton.tsx new file mode 100644 index 00000000..f67ae6ec --- /dev/null +++ b/src/features/emoji/ReactionAddButton.tsx @@ -0,0 +1,20 @@ +import type { ButtonHTMLAttributes } from 'react'; + +import PlusIcon from '@/assets/icons/react-emoji-plus-icon.svg'; + +interface ReactionAddButtonProps extends ButtonHTMLAttributes { + isOpen: boolean; +} + +export const ReactionAddButton = ({ isOpen, ...props }: ReactionAddButtonProps) => { + return ( + + ); +}; diff --git a/src/features/emoji/index.ts b/src/features/emoji/index.ts new file mode 100644 index 00000000..feb7f548 --- /dev/null +++ b/src/features/emoji/index.ts @@ -0,0 +1,2 @@ +export { ReactUserBottomSheet } from './BottomSheet'; +export { ReactionAddButton } from './ReactionAddButton'; diff --git a/src/features/goal/components/detail/GoalDetailContent.tsx b/src/features/goal/components/detail/GoalDetailContent.tsx index 6f05fa33..ac255323 100644 --- a/src/features/goal/components/detail/GoalDetailContent.tsx +++ b/src/features/goal/components/detail/GoalDetailContent.tsx @@ -9,6 +9,7 @@ import { isMyGoalAtom } from '../../atom'; import { AddTaskInput } from './AddTaskInput'; import DetailLayout from './DetailLayout'; +import { Reaction } from './emoji'; import { Tasks } from './Tasks'; import { AddSubGoalPrompt, ContentBody, DetailFooterButton, DetailHeader, Sticker } from '.'; @@ -37,6 +38,7 @@ export const GoalDetailContent = ({ id }: { id: number }) => { tag={goal.tagInfo.tagContent} more={goal.description} /> + {goal.tasks.length ? ( ) : ( diff --git a/src/features/goal/components/detail/emoji/Emojis.tsx b/src/features/goal/components/detail/emoji/Emojis.tsx new file mode 100644 index 00000000..278aa0da --- /dev/null +++ b/src/features/goal/components/detail/emoji/Emojis.tsx @@ -0,0 +1,37 @@ +import { EmojisContainer, EmojisItem } from '@/components'; + +const emojis = [ + { + id: 1, + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/3727715a-a778-4630-82b7-663662516ecc', + name: '좋아요', + }, +]; + +interface EmojisProps { + onToggle: VoidFunction; +} + +export const Emojis = ({ onToggle }: EmojisProps) => { + const handleReactEmoji = () => { + // TODO: 리액션 추가하는 api + onToggle(); + }; + + return ( + + {emojis.map((emoji) => ( + + ))} + + ); +}; + +const animate = { + initial: { opacity: 0, scale: 0.3 }, + animate: { opacity: 1, scale: 1 }, + transition: { duration: 0.2 }, +}; diff --git a/src/features/goal/components/detail/emoji/ReactedEmojis.tsx b/src/features/goal/components/detail/emoji/ReactedEmojis.tsx new file mode 100644 index 00000000..7016b54d --- /dev/null +++ b/src/features/goal/components/detail/emoji/ReactedEmojis.tsx @@ -0,0 +1,30 @@ +import { m } from 'framer-motion'; + +import { CountEmoji } from '@/components'; + +interface ReactedEmojis { + onCloseEmojis: VoidFunction; + reactedEmojis: { + url: string; + name: string; + count: number; + reacted: boolean; + }[]; +} + +export const ReactedEmojis = ({ onCloseEmojis, reactedEmojis }: ReactedEmojis) => { + const handleClick = () => { + // TODO: 리액션 추가 api 작업 + onCloseEmojis(); + }; + + return ( +
+ {reactedEmojis.map((emoji) => ( + + + + ))} +
+ ); +}; diff --git a/src/features/goal/components/detail/emoji/Reaction.tsx b/src/features/goal/components/detail/emoji/Reaction.tsx new file mode 100644 index 00000000..ae1c804b --- /dev/null +++ b/src/features/goal/components/detail/emoji/Reaction.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; + +import { ToolTip } from '@/components'; +import { ReactionAddButton } from '@/features/emoji/ReactionAddButton'; +import { ReactedEmojis } from '@/features/goal/components/detail/emoji/ReactedEmojis'; +import { ReactionUserTotalCount } from '@/features/goal/components/detail/emoji/ReactionUserTotalCount'; + +import { Emojis } from './Emojis'; + +// TODO: api 연결 후 제거 +const data = { + totalReactedEmojisCount: 2, + reactedEmojis: [ + { + url: 'https://github.com/depromeet/amazing3-fe/assets/112946860/8f87efc3-ec65-4650-b763-74501bc2e1c0', + name: '좋아요', + count: 10, + reacted: true, + }, + ], +}; + +export const Reaction = () => { + const [isOpenEmojis, setOpenEmojis] = useState(false); + + const handleToggleEmojis = () => setOpenEmojis((prev) => !prev); + const hasNoReactions = data.totalReactedEmojisCount === 0; + + return ( +
+ {!hasNoReactions && } +
+ setOpenEmojis(false)} /> + {isOpenEmojis && } + +
+ {hasNoReactions && ( +
+ +
+ )} + +
+
+
+ ); +}; diff --git a/src/features/goal/components/detail/emoji/ReactionUserTotalCount.tsx b/src/features/goal/components/detail/emoji/ReactionUserTotalCount.tsx new file mode 100644 index 00000000..34806166 --- /dev/null +++ b/src/features/goal/components/detail/emoji/ReactionUserTotalCount.tsx @@ -0,0 +1,50 @@ +import Image from 'next/image'; +import { useOverlay } from '@toss/use-overlay'; + +import ReactionMembersImage from '@/assets/images/reaction-members.png'; +import { Typography } from '@/components'; +import { ReactUserBottomSheet } from '@/features/emoji/BottomSheet'; +import { addSuffixIfExceedsLength, addSuffixIfExceedsLimit } from '@/utils/suffix'; + +interface ReactionUserTotalCountProps { + username: string; + count: number; +} + +export const ReactionUserTotalCount = ({ username, count }: ReactionUserTotalCountProps) => { + const { open } = useOverlay(); + const countExceptOne = count - 1; + + const visibleValue = { + username: addSuffixIfExceedsLength(username, 3, '..'), + count: addSuffixIfExceedsLimit(countExceptOne, 999), + }; + + const showText = () => { + if (countExceptOne === 0) { + return `${visibleValue.username}님이 목표에 반응했어요.`; + } + return ( + <> + {visibleValue.username}님 외  + {visibleValue.count}명이 목표에 반응했어요. + + ); + }; + + const handleOpenMembersBottomSheet = () => { + open(({ isOpen, close }) => ); + }; + + return ( + + ); +}; diff --git a/src/features/goal/components/detail/emoji/index.ts b/src/features/goal/components/detail/emoji/index.ts new file mode 100644 index 00000000..3d72dda0 --- /dev/null +++ b/src/features/goal/components/detail/emoji/index.ts @@ -0,0 +1 @@ +export { Reaction } from './Reaction'; diff --git a/src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.tsx b/src/features/home/components/MapCardCollections/EmptyMapCard.tsx similarity index 92% rename from src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.tsx rename to src/features/home/components/MapCardCollections/EmptyMapCard.tsx index d7480446..6aee100b 100644 --- a/src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.tsx +++ b/src/features/home/components/MapCardCollections/EmptyMapCard.tsx @@ -6,8 +6,9 @@ import bandiboodiGray from '@/assets/images/bandi-boodi-gray.png'; import { Typography } from '@/components'; import { useAuth, useIsMyMap } from '@/hooks'; -import { LoginBottomSheet } from '../../loginBottomSheet'; -import { MapCardLayout, type MapCardLayoutProps } from '../MapCardLayout'; +import { LoginBottomSheet } from '../loginBottomSheet'; + +import { MapCardLayout, type MapCardLayoutProps } from './MapCardLayout'; interface EmptyMapCardProps extends MapCardLayoutProps { alternativeTextIndex: 0 | 1 | 2 | 3; diff --git a/src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.stories.tsx b/src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.stories.tsx deleted file mode 100644 index 5d17b941..00000000 --- a/src/features/home/components/MapCardCollections/EmptyMapCard/EmptyMapCard.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { EmptyMapCard } from './EmptyMapCard'; - -const meta: Meta = { - title: 'features/home/mapCard/emptyMapCard', - component: EmptyMapCard, - argTypes: { - alternativeTextIndex: { - control: 'inline-radio', - options: [0, 1, 2, 3], - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Basic: Story = { - args: { - alternativeTextIndex: 1, - position: { x: 'top-[20px]', y: 'left-[20px]' }, - }, -}; diff --git a/src/features/home/components/MapCardCollections/MapCard/MapCard.tsx b/src/features/home/components/MapCardCollections/MapCard.tsx similarity index 92% rename from src/features/home/components/MapCardCollections/MapCard/MapCard.tsx rename to src/features/home/components/MapCardCollections/MapCard.tsx index 9490a3a2..f91e2003 100644 --- a/src/features/home/components/MapCardCollections/MapCard/MapCard.tsx +++ b/src/features/home/components/MapCardCollections/MapCard.tsx @@ -8,8 +8,9 @@ import { blueDataURL } from '@/constants'; import { useAuth } from '@/hooks'; import type { GoalProps } from '@/hooks/reactQuery/goal/useGetGoals'; -import { LoginBottomSheet } from '../../loginBottomSheet'; -import { MapCardLayout, type MapCardLayoutProps } from '../MapCardLayout'; +import { LoginBottomSheet } from '../loginBottomSheet'; + +import { MapCardLayout, type MapCardLayoutProps } from './MapCardLayout'; export interface MapCardProps extends MapCardLayoutProps { goal: GoalProps; diff --git a/src/features/home/components/MapCardCollections/MapCard/MapCard.stories.tsx b/src/features/home/components/MapCardCollections/MapCard/MapCard.stories.tsx deleted file mode 100644 index a4c8332d..00000000 --- a/src/features/home/components/MapCardCollections/MapCard/MapCard.stories.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { MapCard } from './MapCard'; - -const meta: Meta = { - title: 'features/home/mapCard/mapCard', - component: MapCard, -}; - -export default meta; - -type Story = StoryObj; - -export const Basic: Story = { - args: { - goal: { - id: 1, - stickerUrl: 'https://github.com/depromeet/amazing3-fe/assets/112946860/b266a620-a349-4f70-8236-be1612028a97', - deadline: '2024.01', - tagContent: '학업', - }, - position: { x: 'top-[20px]', y: 'left-[20px]' }, - }, -}; - -export const Ellipsis: Story = { - args: { - ...Basic.args, - goal: { - id: 1, - stickerUrl: 'https://github.com/depromeet/amazing3-fe/assets/112946860/b266a620-a349-4f70-8236-be1612028a97', - deadline: '2024.01', - tagContent: '가나다라마바사', - }, - }, -}; diff --git a/src/features/home/components/MapCardCollections/StartMapCard.tsx b/src/features/home/components/MapCardCollections/StartMapCard.tsx new file mode 100644 index 00000000..b80bf4b1 --- /dev/null +++ b/src/features/home/components/MapCardCollections/StartMapCard.tsx @@ -0,0 +1,18 @@ +import Image from 'next/image'; + +import BandiBoodiStartImage from '@/assets/images/bandiboodi-start.png'; + +import type { MapCardLayoutProps as StartMapCardProps } from './MapCardLayout'; + +export const StartMapCard = ({ position }: StartMapCardProps) => { + return ( + start_mapCard_image + ); +}; diff --git a/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.stories.tsx b/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.stories.tsx deleted file mode 100644 index d9ba5023..00000000 --- a/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import type { EmptyMapCard } from '../EmptyMapCard/EmptyMapCard'; - -import { StartMapCard } from './StartMapCard'; - -const meta: Meta = { - title: 'features/home/mapCard/startMapCard', - component: StartMapCard, -}; - -export default meta; - -type Story = StoryObj; - -export const Basic: Story = { - args: { - position: { x: 'top-[20px]', y: 'left-[20px]' }, - }, -}; diff --git a/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.tsx b/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.tsx deleted file mode 100644 index 8eeba651..00000000 --- a/src/features/home/components/MapCardCollections/StartMapCard/StartMapCard.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import Image from 'next/image'; - -import bandiboodiImage from '@/assets/images/bandi-boodi.png'; -import { Typography } from '@/components'; - -import type { MapCardLayoutProps as StartMapCardProps } from '../MapCardLayout'; -import { MapCardLayout } from '../MapCardLayout'; - -export const StartMapCard = ({ position }: StartMapCardProps) => { - return ( - - - START ! - -
- start_bandiboodi -
-
- ); -}; diff --git a/src/features/home/components/MapCardCollections/index.ts b/src/features/home/components/MapCardCollections/index.ts index 24cd62ea..70d083e2 100644 --- a/src/features/home/components/MapCardCollections/index.ts +++ b/src/features/home/components/MapCardCollections/index.ts @@ -1,4 +1,4 @@ -export { EmptyMapCard } from './EmptyMapCard/EmptyMapCard'; -export type { MapCardProps } from './MapCard/MapCard'; -export { MapCard } from './MapCard/MapCard'; -export { StartMapCard } from './StartMapCard/StartMapCard'; +export { EmptyMapCard } from './EmptyMapCard'; +export type { MapCardProps } from './MapCard'; +export { MapCard } from './MapCard'; +export { StartMapCard } from './StartMapCard'; diff --git a/src/features/home/components/loginBottomSheet/LoginBottomSheet.stories.tsx b/src/features/home/components/loginBottomSheet/LoginBottomSheet.stories.tsx deleted file mode 100644 index 22fc2e15..00000000 --- a/src/features/home/components/loginBottomSheet/LoginBottomSheet.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { LoginBottomSheet } from './LoginBottomSheet'; - -import './LoginBottomSheet.styles.css'; - -const meta: Meta = { - title: 'features/home/loginBottomSheet', - component: LoginBottomSheet, -}; - -export default meta; - -type Story = StoryObj; - -export const Basic: Story = { - args: { - open: true, - onClose: () => {}, - }, -}; diff --git a/src/features/home/components/mapCardPositioner/MapCardPositioner.stories.tsx b/src/features/home/components/mapCardPositioner/MapCardPositioner.stories.tsx deleted file mode 100644 index e24b33b7..00000000 --- a/src/features/home/components/mapCardPositioner/MapCardPositioner.stories.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import type { GoalProps } from '@/hooks/reactQuery/goal/useGetGoals'; - -import { MapCardPositioner } from './MapCardPositioner'; - -const meta: Meta = { - title: 'features/home/mapCardPositioner', - component: MapCardPositioner, - argTypes: { - type: { - control: { - type: 'radio', - options: ['A', 'B'], - }, - }, - isLast: { - control: 'boolean', - }, - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export default meta; - -type Story = StoryObj; - -const mockGoals: GoalProps[] = Array(5).fill({ - id: 1, - stickerUrl: 'https://github.com/depromeet/amazing3-fe/assets/112946860/b266a620-a349-4f70-8236-be1612028a97', - deadline: '2024.01', - tagContent: '학업', -}); - -export const TypeA: Story = { - args: { - goals: mockGoals, - type: 'A', - isLast: true, - }, -}; - -export const TypeB: Story = { - args: { - ...TypeA.args, - type: 'B', - }, -}; - -export const TypeAExistNext: Story = { - args: { - goals: mockGoals, - type: 'A', - }, -}; - -export const TypeBExistNext: Story = { - args: { - ...TypeAExistNext.args, - type: 'B', - }, -}; diff --git a/src/utils/suffix.ts b/src/utils/suffix.ts new file mode 100644 index 00000000..f79b3006 --- /dev/null +++ b/src/utils/suffix.ts @@ -0,0 +1,9 @@ +export const addSuffixIfExceedsLimit = (number: number, limit: number, suffix = '+') => { + if (number > limit) return limit + suffix; + return number; +}; + +export const addSuffixIfExceedsLength = (text: string, length: number, suffix = '...') => { + if (text.length > length) return text.slice(0, length) + suffix; + return text; +}; diff --git a/styles/theme/boxShadow.ts b/styles/theme/boxShadow.ts index f23262b5..612ec8c8 100644 --- a/styles/theme/boxShadow.ts +++ b/styles/theme/boxShadow.ts @@ -1,3 +1,4 @@ export const boxShadow = { thumb: '0px 0px 7.9px 0px rgba(0, 88, 255, 0.10)', + thumbStrong: '0px 0px 20px 0px rgba(0, 88, 255, 0.20)', };