Skip to content

Commit

Permalink
[Feat] 채팅 모달 및 채팅 버튼 ui 구현 (#71)
Browse files Browse the repository at this point in the history
* style: 그라데이션 글로벌 변수 추가

* style: 반응형 브레이크포인트 추가

* feat: 채팅 버튼 구현

* feat: 채팅 모달 레이아웃 구현(widget) -> features로 내부 컴포넌트 분리 예정

* feat: 모달 등록, index 정리
  • Loading branch information
joarthvr authored Dec 1, 2024
1 parent 30a3fec commit 2f0e3c8
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/app/ModalProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import type { PropsWithChildren } from 'react';
import { useShallow } from 'zustand/shallow';

import { useModalStore } from '@/shared/model/modalStore';
import { LoginModal } from '@/widgets/LoginModal/ui/LoginModal';
import { LoginModal, ChattingModal } from '@/widgets';

const MODAL_COMPONENTS = {
login: LoginModal,
chatting: ChattingModal,
};

const ModalProvider = ({ children }: PropsWithChildren) => {
Expand Down
9 changes: 9 additions & 0 deletions src/app/styles/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ $header-horizontal-padding: 4rem;
/* form **/
$form-gray-color: #d7d7d7;
$form-section-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 25%);

// 그라데이션
$palette-gradient: linear-gradient(151deg, $red 0%, $yellow 32%, $green 71%, $blue 100%);

// 반응형 브레이크포인트
$mobile: 480px;
$tablet: 768px;
$laptop: 1024px;
$desktop: 1200px;
Empty file added src/features/chatting/index.ts
Empty file.
2 changes: 1 addition & 1 deletion src/shared/model/modalStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { create } from 'zustand';

type ModalType = 'login' | null;
type ModalType = 'login' | 'chatting' | null;

interface ModalState {
isOpen: boolean;
Expand Down
54 changes: 54 additions & 0 deletions src/widgets/ChattingModal/ChattingModal.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.modalDialogLayout {
width: 100%;
max-width: 500px;
height: 100vh;
margin: 0 auto;
background-color: #2d2d2d;
}

.container {
display: flex;
flex-direction: column;
height: 100%;
}

.header {
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 10%);

.title {
font-size: 1.2rem;
font-weight: 500;
color: white;
}
}

.chatContent {
flex: 1;
padding: 1rem;
overflow-y: auto;
}

.bottomNav {
padding: 0.5rem 0;
border-top: 1px solid rgba(255, 255, 255, 10%);

.navList {
display: flex;
justify-content: space-around;
padding: 0;
margin: 0;
list-style: none;
}

.navItem {
padding: 0.5rem 1rem;
color: white;
cursor: pointer;

&:hover {
background-color: rgba(255, 255, 255, 10%);
border-radius: 4px;
}
}
}
33 changes: 33 additions & 0 deletions src/widgets/ChattingModal/ChattingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styles from './ChattingModal.module.scss';

import { Modal } from '@/shared/ui';

interface ChattingModalProps {
isOpen: boolean;
onClose: () => void;
}

export const ChattingModal = ({ isOpen, onClose }: ChattingModalProps) => {
return (
<Modal classNames={styles.modalDialogLayout} isOpen={isOpen} onClose={onClose}>
<div className={styles.container}>
{/* Header */}
<header className={styles.header}>
<h2 className={styles.title}>장금숙</h2>
</header>

{/* Chat Content Area */}
<article className={styles.chatContent}>{/* 채팅 메시지들이 들어갈 영역 */}</article>

{/* Bottom Navigation */}
<nav className={styles.bottomNav}>
<ul className={styles.navList}>
<li className={styles.navItem}></li>
<li className={styles.navItem}>알림</li>
<li className={styles.navItem}>설정</li>
</ul>
</nav>
</div>
</Modal>
);
};
2 changes: 2 additions & 0 deletions src/widgets/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NO_PAD_ROUTES } from './constants';
//style
import styles from './Layout.module.scss';
//component
import { ChattingBtn } from './ui/ChattingBtn/ChattingBtn';
import { Footer } from './ui/Footer/Footer';
import { Header } from './ui/Header/Header';

Expand All @@ -29,6 +30,7 @@ export const Layout = () => {
<Header />
<main className={cn({ [styles.noPadding]: isNoPadHeader })}>
<Outlet />
<ChattingBtn />
</main>
<Footer />
</>
Expand Down
92 changes: 92 additions & 0 deletions src/widgets/Layout/ui/ChattingBtn/ChattingBtn.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
@mixin mobile {
@media (max-width: #{$tablet - 1px}) {
@content;
}
}

@mixin tablet {
@media (min-width: #{$tablet}) and (max-width: #{$laptop - 1px}) {
@content;
}
}

.wrapper {
position: fixed;
right: 2rem;
bottom: 2rem;
z-index: 100;

@include mobile {
right: 1rem;
bottom: 1rem;
}
}

.container {
position: relative;
width: 5rem;
height: 5rem;
cursor: pointer;
background-color: $primary-color;
border-radius: 50%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 10%);
transition: transform 0.3s ease-in-out;

@include mobile {
width: 3.5rem;
height: 3.5rem;
}

@include tablet {
width: 4rem;
height: 4rem;
}

&:hover {
background: $palette-gradient;
transform: scale(1.05);
}

svg {
position: absolute;
top: 50%;
left: 50%;
width: 3rem;
height: 3rem;
fill: $secondary-color;
transform: translate(-50%, -50%);

@include mobile {
width: 2rem;
height: 2rem;
}

@include tablet {
width: 2.5rem;
height: 2.5rem;
}
}
}

.notification {
position: absolute;
top: 0.1rem;
right: 0.002px;
width: 0.65rem;
height: 0.65rem;
background-color: $red;
border-radius: 50%;

@include mobile {
top: 0.05rem;
right: 0.001px;
width: 0.5rem;
height: 0.5rem;
}

@include tablet {
top: 0.075rem;
width: 0.55rem;
height: 0.55rem;
}
}
24 changes: 24 additions & 0 deletions src/widgets/Layout/ui/ChattingBtn/ChattingBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styles from './ChattingBtn.module.scss';

import Logo from '@/shared/assets/paletteLogo.svg?react';
import { useModalStore } from '@/shared/model/modalStore';

interface ChattingBtnProps {
hasNotification?: boolean;
}

export const ChattingBtn = ({ hasNotification = true }: ChattingBtnProps) => {
const open = useModalStore(state => state.actions.open);
const onOpenModal = () => {
console.log('openChattingModal');
open('chatting');
};
return (
<div className={styles.wrapper}>
<button aria-label='채팅 모달 열기' className={styles.container} onClick={onOpenModal}>
<Logo />
</button>
{hasNotification && <span className={styles.notification} />}
</div>
);
};
4 changes: 4 additions & 0 deletions src/widgets/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
export * from './ArchiveGrid';
export * from './ChattingModal/ChattingModal';
export * from './DetailArchive';
export * from './GatheringGrid';
export * from './GatheringSelectCon/index';
export * from './Layout';
export * from './LoginModal/ui/LoginModal';
export * from './PortfolioGrid/PortFolioGrid';
export * from './SearchAll/SearchAll';
export * from './SearchTap/SearchTap';
export * from './UserContents/UserContents';
export * from './WriteArchive';
export * from './WriteGathering/WriteGatheringDetail';
export * from './WriteGathering/WriteGatheringOpts';
export * from './UserContents/UserContents';
export * from './GatheringDetail/index';

1 comment on commit 2f0e3c8

@github-actions
Copy link

Choose a reason for hiding this comment

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

⚡ Lighthouse report for http://localhost:3000/

Category Score
🔴 Performance 25
🟢 Accessibility 97
🟢 Best Practices 92
🟠 SEO 83

Detailed Metrics

Metric Value
🔴 First Contentful Paint 41.9 s
🔴 Largest Contentful Paint 71.0 s
🔴 Total Blocking Time 860 ms
🟢 Cumulative Layout Shift 0
🔴 Speed Index 55.4 s

Please sign in to comment.