diff --git a/src/app/layout/MainLayout.tsx b/src/app/layout/MainLayout.tsx index 508ba677..a9e814ee 100644 --- a/src/app/layout/MainLayout.tsx +++ b/src/app/layout/MainLayout.tsx @@ -5,7 +5,9 @@ const MainLayout = () => { return (
- +
+ +
); }; diff --git a/src/app/styles/index.css b/src/app/styles/index.css index 1fbead8c..f50729aa 100644 --- a/src/app/styles/index.css +++ b/src/app/styles/index.css @@ -94,7 +94,9 @@ --radius-s: 8px; /* Layout */ + --header-height: 92px; --spacing-header-content: 54px; + --spacing-header-total: calc(var(--header-height) + var(--spacing-header-content)); } @layer base { diff --git a/src/pages/buy-detail/ui/BuyDetailPage.tsx b/src/pages/buy-detail/ui/BuyDetailPage.tsx index 21fd10b7..6aa8c88f 100644 --- a/src/pages/buy-detail/ui/BuyDetailPage.tsx +++ b/src/pages/buy-detail/ui/BuyDetailPage.tsx @@ -40,7 +40,7 @@ const BuyDetailPage = () => { } return ( -
+
diff --git a/src/pages/buy/ui/BuyFilter.tsx b/src/pages/buy/ui/BuyFilter.tsx new file mode 100644 index 00000000..ff03a431 --- /dev/null +++ b/src/pages/buy/ui/BuyFilter.tsx @@ -0,0 +1,136 @@ +import { Checkbox } from '@shared/ui/Checkbox'; +import { cn } from '@shared/utils/cn'; +import { useState, type ReactNode } from 'react'; + +const FilterSection = ({ title, children }: { title: string; children: ReactNode }) => { + return ( +
+

{title}

+
{children}
+
+
+ ); +}; + +type FilterItem = { + id: string; + label: string; +}; + +export type BuyFilterProps = { + manufacturers: FilterItem[]; + models: FilterItem[]; + priceRanges: FilterItem[]; + selectedManufacturers: string[]; + selectedModels: string[]; + selectedPrices: string[]; + availableOnly: boolean; + showAllModels: boolean; + onToggleManufacturer: (id: string) => void; + onToggleModel: (id: string) => void; + onTogglePrice: (id: string) => void; + onSetAvailableOnly: (value: boolean) => void; + onSetShowAllModels: (value: boolean) => void; + onReset: () => void; + defaultModelCount?: number; +}; + +export const BuyFilter = ({ + manufacturers, + models, + priceRanges, + selectedManufacturers, + selectedModels, + selectedPrices, + availableOnly, + showAllModels, + onToggleManufacturer, + onToggleModel, + onTogglePrice, + onSetAvailableOnly, + onSetShowAllModels, + onReset, + defaultModelCount = 8, +}: BuyFilterProps) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + ); +}; diff --git a/src/pages/buy/ui/BuyPage.tsx b/src/pages/buy/ui/BuyPage.tsx index fd37c0f2..38e72a71 100644 --- a/src/pages/buy/ui/BuyPage.tsx +++ b/src/pages/buy/ui/BuyPage.tsx @@ -1,23 +1,12 @@ import { useBuyFilter } from '@pages/buy/model/useBuyFilter'; +import { BuyFilter } from '@pages/buy/ui/BuyFilter'; import { CloseIcon } from '@shared/assets/icons'; import { ROUTES } from '@shared/constants'; import { MANUFACTURERS, MODELS, PRICE_RANGES } from '@shared/mocks/data/buy'; import { Card } from '@shared/ui/Card'; -import { Checkbox } from '@shared/ui/Checkbox'; import { SearchBar } from '@shared/ui/SearchBar'; import { cn } from '@shared/utils/cn'; import { Link } from 'react-router'; -import type { ReactNode } from 'react'; - -const FilterSection = ({ title, children }: { title: string; children: ReactNode }) => { - return ( -
-

{title}

-
{children}
-
-
- ); -}; const FilterChip = ({ label, onRemove }: { label: string; onRemove: () => void }) => { return ( @@ -32,8 +21,6 @@ const FilterChip = ({ label, onRemove }: { label: string; onRemove: () => void } ); }; -const DEFAULT_MODEL_COUNT = 8; - const BuyPage = () => { const { query, @@ -57,7 +44,7 @@ const BuyPage = () => { const showEmpty = filteredItems.length === 0; return ( -
+
setQuery(value)} @@ -66,76 +53,36 @@ const BuyPage = () => { />
- + -
-
+
+
{activeChips.map((chip) => ( removeChip(chip)} /> ))}
{showEmpty ? ( -
+
관련된 상품이 없어요.
) : ( -
+
{filteredItems.map((item) => ( { return (
{message.status === 'loading' ? ( -
+
diff --git a/src/pages/chatbot/ui/ChatbotPage.tsx b/src/pages/chatbot/ui/ChatbotPage.tsx index 88e48cab..180d37a4 100644 --- a/src/pages/chatbot/ui/ChatbotPage.tsx +++ b/src/pages/chatbot/ui/ChatbotPage.tsx @@ -13,7 +13,7 @@ const INITIAL_BOT_MESSAGE = `루핏이 예상 수리비를 빠르게 계산해 보험/케어 가입 여부: 예) 애플케어 O/X, 통신사 보험 O/X -가능하면\u00A0원하는\u00A0방향도\u00A0한\u00A0줄로\u00A0적어주세요:\u00A0“정품\u00A0우선”\u00A0/\u00A0“최대한\u00A0저렴하\u2060게”\u00A0/\u00A0“빨리” +가능하면 원하는 방향도 한 줄로 적어주세요: "정품 우선" / "최대한 저렴하게" / "빨리" 예시) “아이폰 15, 액정 깨짐, 애플케어 X, 최대한 저렴하게” @@ -80,18 +80,18 @@ const ChatbotPage = () => { return (
-
+
-
-
-
-
+
, + content: ( + + ), }, { id: 3, @@ -19,13 +26,19 @@ export const CAROUSEL_SLIDES = [ imageSrc={Carousel3Image} title="나의 폰 판매" subtitle="나는 쓰지 않는 폰, 다른 사람은 쓰기 좋은 폰" + href="/sell" /> ), }, { id: 4, content: ( - + ), }, { diff --git a/src/pages/main/ui/MainPage.tsx b/src/pages/main/ui/MainPage.tsx index b86095e3..260eb5db 100644 --- a/src/pages/main/ui/MainPage.tsx +++ b/src/pages/main/ui/MainPage.tsx @@ -11,7 +11,7 @@ const MainPage = () => { const navigate = useNavigate(); return ( -
+
diff --git a/src/pages/mypage/ui/CommonTabs.tsx b/src/pages/mypage/ui/CommonTabs.tsx index 841bcf98..958f244d 100644 --- a/src/pages/mypage/ui/CommonTabs.tsx +++ b/src/pages/mypage/ui/CommonTabs.tsx @@ -35,14 +35,14 @@ export const CommonTabs = ({

{title}

-
+
{tabs.map((tab) => (
-
+
총 {shops.length}개
-
{renderShopList()}
+
{renderShopList()}
); diff --git a/src/pages/repair/ui/RepairShopCard.tsx b/src/pages/repair/ui/RepairShopCard.tsx index 722fce22..9163dbda 100644 --- a/src/pages/repair/ui/RepairShopCard.tsx +++ b/src/pages/repair/ui/RepairShopCard.tsx @@ -10,7 +10,6 @@ export type RepairShopCardProps = { lat?: number; lng?: number; placeUrl?: string; - onContact?: () => void; onFindRoute?: () => void; onSelect?: () => void; }; @@ -27,19 +26,9 @@ export const RepairShopCard = ({ lat, lng, placeUrl, - onContact, onFindRoute, onSelect, }: RepairShopCardProps) => { - const handleContact = () => { - if (onContact) { - return onContact(); - } - if (phone) { - window.location.href = `tel:${phone}`; - } - }; - const handleFindRoute = () => { if (onFindRoute) { return onFindRoute(); @@ -66,8 +55,15 @@ export const RepairShopCard = ({ className="flex w-full flex-col items-start justify-between gap-6 rounded-(--radius-l) bg-gray-900 px-6 py-8 text-left md:flex-row md:items-center md:gap-[31px] md:px-[42px] md:py-[44px]" >
- {name} - {address} + {name} + {address} + {phone ? ( + e.stopPropagation()} className="typo-body-2 text-brand-primary"> + {phone} + + ) : ( +   + )}
@@ -76,16 +72,6 @@ export const RepairShopCard = ({ - diff --git a/src/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.variants.ts b/src/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.variants.ts index 0a276a1b..93b6e76a 100644 --- a/src/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.variants.ts +++ b/src/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.variants.ts @@ -3,12 +3,12 @@ import { tv } from 'tailwind-variants'; export const chatbotFloatingButtonVariants = tv({ slots: { root: [ - 'group flex items-center', + 'group flex flex-col justify-center items-center', 'w-[80px] h-[80px]', 'rounded-full', 'bg-gray-50', 'shadow-[0_4px_4px_0_var(--color-gray-100)]', - 'p-0', + 'py-[9px] px-[8px]', 'overflow-hidden', 'transition-[width,background-color,box-shadow] duration-200 ease-out', 'hover:py-[9px] hover:pl-[8px] hover:pr-[19px]', @@ -19,12 +19,11 @@ export const chatbotFloatingButtonVariants = tv({ content: [ 'flex items-center justify-center w-full h-full', 'gap-0 transition-[gap] duration-200 ease-out', - 'group-hover:justify-start group-focus-visible:justify-start', - 'group-hover:gap-0 group-focus-visible:gap-0', - 'group-hover:w-[144px] group-hover:h-[67px] group-focus-visible:w-[144px] group-focus-visible:h-[67px]', - 'group-hover:mx-auto group-focus-visible:mx-auto', + 'group-hover:justify-center group-focus-visible:justify-center', + 'group-hover:gap-[8px] group-focus-visible:gap-[8px]', + 'group-hover:w-[144px] group-focus-visible:w-[144px]', ], - icon: ['w-[74px] h-[67px] shrink-0'], + icon: ['w-[47px] h-[38px] shrink-0'], label: [ 'typo-title-3 text-black whitespace-nowrap', 'opacity-0 max-w-0 translate-x-2', diff --git a/src/shared/ui/FavoriteButton/FavoriteButton.variants.ts b/src/shared/ui/FavoriteButton/FavoriteButton.variants.ts index c7217e79..1e1f3b9b 100644 --- a/src/shared/ui/FavoriteButton/FavoriteButton.variants.ts +++ b/src/shared/ui/FavoriteButton/FavoriteButton.variants.ts @@ -2,7 +2,7 @@ import { tv } from 'tailwind-variants'; export const favoriteButtonVariants = tv({ slots: { - root: ['group inline-flex items-center justify-center', 'w-[44px] h-[44px]', 'transition-colors'], + root: ['group inline-flex items-center justify-center', 'w-[44px] h-[44px]', 'cursor-pointer', 'transition-colors'], icon: ['w-[33px] h-[29.26px]', 'transition-colors', '[--icon-fill-active:var(--color-brand-primary)]'], }, diff --git a/src/shared/ui/Header/Header.variants.ts b/src/shared/ui/Header/Header.variants.ts index 4c140b0a..83749727 100644 --- a/src/shared/ui/Header/Header.variants.ts +++ b/src/shared/ui/Header/Header.variants.ts @@ -6,12 +6,16 @@ export const headerVariants = tv({ slots: { // Layout root: [ + 'fixed', + 'top-0', + 'left-0', + 'right-0', + 'z-50', 'flex', 'w-full', 'max-w-[1440px]', 'mx-auto', - 'h-[92px]', - 'mb-(--spacing-header-content)', + 'h-(--header-height)', 'justify-between', 'items-center', 'bg-white', diff --git a/src/shared/ui/Modal/Modal.tsx b/src/shared/ui/Modal/Modal.tsx index 0782d1f0..02269f16 100644 --- a/src/shared/ui/Modal/Modal.tsx +++ b/src/shared/ui/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import { useFocusTrap } from '@shared/hooks'; +import { useBodyScrollLock, useFocusTrap } from '@shared/hooks'; import { Button } from '@shared/ui/Button/Button'; import { useCallback, useEffect, useRef, useState } from 'react'; import { modalStyles } from './Modal.styles'; @@ -24,6 +24,7 @@ export const Modal = ({ const modalRef = useRef(null); useFocusTrap(modalRef, !closing); + useBodyScrollLock(true); const handleClose = useCallback( (callback: () => void) => { diff --git a/src/shared/ui/TradeItem/TradeItem.tsx b/src/shared/ui/TradeItem/TradeItem.tsx index 6adcae7d..5caa7a1b 100644 --- a/src/shared/ui/TradeItem/TradeItem.tsx +++ b/src/shared/ui/TradeItem/TradeItem.tsx @@ -43,20 +43,24 @@ export const TradeItem = ({ const resolvedStatusLabel = statusLabel ?? statusConfig?.text; return ( -
+
-
+
{imageUrl ? ( - {modelName} + {modelName} ) : (
)} -
+
{modelName} {price} {date} @@ -66,7 +70,7 @@ export const TradeItem = ({ {status === 'favorite' ? ( ) : ( - {resolvedStatusLabel} + {resolvedStatusLabel} )}
diff --git a/tests/unit/shared/ui/Card/Card.test.tsx b/tests/unit/shared/ui/Card/Card.test.tsx index ee418aaf..3e23a911 100644 --- a/tests/unit/shared/ui/Card/Card.test.tsx +++ b/tests/unit/shared/ui/Card/Card.test.tsx @@ -48,7 +48,7 @@ describe('Card', () => { const { container } = render(); const card = container.firstChild; - expect(card).toHaveClass('w-[180px]', 'h-[299px]'); + expect(card).toHaveClass('w-full', 'h-auto', 'lg:w-[180px]', 'lg:h-[299px]'); }); it('seller variant 적용', () => { diff --git a/tests/unit/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.test.tsx b/tests/unit/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.test.tsx index 050c4193..c1689db0 100644 --- a/tests/unit/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.test.tsx +++ b/tests/unit/shared/ui/ChatbotFloatingButton/ChatbotFloatingButton.test.tsx @@ -19,10 +19,10 @@ describe('ChatbotFloatingButton', () => { expect(screen.getByRole('button')).toBeInTheDocument(); }); - it('아이콘 이미지 렌더링', () => { + it('아이콘 렌더링', () => { const { container } = render(); - const img = container.querySelector('img'); - expect(img).toBeInTheDocument(); + const icon = container.querySelector('svg'); + expect(icon).toBeInTheDocument(); }); }); @@ -46,8 +46,8 @@ describe('ChatbotFloatingButton', () => { it('아이콘에 aria-hidden 적용', () => { const { container } = render(); - const img = container.querySelector('img'); - expect(img).toHaveAttribute('aria-hidden', 'true'); + const icon = container.querySelector('svg'); + expect(icon).toHaveAttribute('aria-hidden', 'true'); }); it('키보드로 클릭 가능', async () => {