From dfaf8e1b89789006f2d1914e68e8f2ad6b9c41be Mon Sep 17 00:00:00 2001 From: halionaz Date: Mon, 11 Aug 2025 12:57:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=ED=95=84=ED=84=B0=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EB=B2=84=ED=8A=BC!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/components/Filter/index.tsx | 18 +++++++++- .../home/components/Filter/style.css.ts | 33 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/features/home/components/Filter/index.tsx b/src/features/home/components/Filter/index.tsx index 9de9fe73..0333419d 100644 --- a/src/features/home/components/Filter/index.tsx +++ b/src/features/home/components/Filter/index.tsx @@ -1,11 +1,14 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Swiper, SwiperSlide, type SwiperRef } from 'swiper/react'; +import { cx } from '@styled-system/css'; +import { useSearchParams } from 'react-router'; import * as s from './style.css'; import FilterNavigator from '@/features/home/components/Filter/FilterNavigator'; import FilterContents from '@/features/home/components/Filter/FilterContents'; import { FilterTypeArray, FilterTypeToIndexMap, type FilterType } from '@/features/home/types'; +import { useGetItemCount } from '@/features/home/apis/useGetItemCount'; interface Props { state: FilterType; @@ -14,8 +17,15 @@ interface Props { close: () => void; } const Filter = ({ state, setState, itemCounts, close }: Props) => { + const [searchParams, setSearchParams] = useSearchParams(); const swiperRef = useRef(null); + const selected = useMemo(() => FilterTypeArray.some(type => searchParams.getAll(type).length > 0), [searchParams]); + + const { data: totalCount } = useGetItemCount({}); + + const resetFilter = () => setSearchParams({}); + useEffect(() => { // 스와이퍼와 state 동기화 swiperRef.current?.swiper.slideTo(FilterTypeToIndexMap[state]); @@ -39,6 +49,12 @@ const Filter = ({ state, setState, itemCounts, close }: Props) => { ))} + diff --git a/src/features/home/components/Filter/style.css.ts b/src/features/home/components/Filter/style.css.ts index f29ccc01..d2bec73c 100644 --- a/src/features/home/components/Filter/style.css.ts +++ b/src/features/home/components/Filter/style.css.ts @@ -6,7 +6,6 @@ export const Container = css({ export const Wrapper = css({ width: 'full', - height: '25rem', display: 'flex', flexDir: 'column', justifyContent: 'space-between', @@ -21,6 +20,38 @@ export const SwiperWrapper = css({ }, }); +export const ResetButton = cva({ + base: { + display: 'flex', + height: '3.125rem', + p: '0.625rem 0.5rem', + justifyContent: 'center', + alignItems: 'center', + gap: '0.625rem', + width: 'full', + flexShrink: 0, + color: '80', + fontSize: '1rem', + fontWeight: 400, + lineHeight: 1.2, + letterSpacing: '-0.04rem', + '& strong': { + color: '100', + fontWeight: 700, + }, + }, + variants: { + visible: { + true: { + visibility: 'visible', + }, + false: { + visibility: 'hidden', + }, + }, + }, +}); + export const ViewButton = css({ height: '3.125rem', display: 'flex', From 724453a15b7fed321e88334cf6d9afc9329f8351 Mon Sep 17 00:00:00 2001 From: halionaz Date: Mon, 11 Aug 2025 18:32:57 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EC=84=A0=ED=83=9D=EB=90=9C=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=91=9C=EC=8B=9C=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/SelectButton/index.tsx | 19 +++++++--- .../components/SelectButton/style.css.ts | 35 ++++++++++--------- src/common/components/TagOptionBtn/index.tsx | 13 ++++--- .../components/TagOptionBtn/style.css.ts | 8 +++-- .../home/components/SearchControls/index.tsx | 16 ++++++--- .../post/components/TagIcon/index.tsx | 3 +- src/libs/assets/HockeyIcon.tsx | 2 +- 7 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/common/components/SelectButton/index.tsx b/src/common/components/SelectButton/index.tsx index 57fe051f..492bc4d8 100644 --- a/src/common/components/SelectButton/index.tsx +++ b/src/common/components/SelectButton/index.tsx @@ -1,18 +1,29 @@ import type { PropsWithChildren } from 'react'; import * as s from './style.css'; +import type { TagType } from '@/libs/types/item'; +import { Symbol } from '@/common/components/TagOptionBtn'; interface Props extends PropsWithChildren { - active: boolean; onClick?: () => void; + selected?: TagType[]; } -const SelectButton = ({ children, active, onClick }: Props) => { +const SelectButton = ({ children, onClick, selected }: Props) => { + const isSelected = selected && selected.length > 0; // TODO: 자연스럽게 바뀌게 애니메이션 추가 return ( - ); }; diff --git a/src/common/components/SelectButton/style.css.ts b/src/common/components/SelectButton/style.css.ts index fad64dd4..12c8dda5 100644 --- a/src/common/components/SelectButton/style.css.ts +++ b/src/common/components/SelectButton/style.css.ts @@ -1,4 +1,4 @@ -import { cva } from '@styled-system/css'; +import { css, cva } from '@styled-system/css'; export const Container = cva({ base: { @@ -17,7 +17,7 @@ export const Container = cva({ flexShrink: 0, }, variants: { - active: { + isSelected: { true: { borderColor: 'main-54', bgColor: 'main-26', @@ -30,19 +30,20 @@ export const Container = cva({ }, }); -export const Icon = cva({ - base: { - color: '54', - fontSize: '0.875rem', - }, - variants: { - direction: { - up: { - transform: 'rotate(180deg)', - }, - down: { - transform: 'rotate(0deg)', - }, - }, - }, +export const Icon = css({ + color: '54', + fontSize: '0.875rem', +}); + +export const SelectedIconContainer = css({ + display: 'flex', + alignItems: 'center', + gap: '0.125rem', +}); + +export const SelectedIcon = css({ + fontSize: '0.75rem', + w: '0.75rem', + h: '0.75rem', + color: 'main', }); diff --git a/src/common/components/TagOptionBtn/index.tsx b/src/common/components/TagOptionBtn/index.tsx index 69301e15..bea5c604 100644 --- a/src/common/components/TagOptionBtn/index.tsx +++ b/src/common/components/TagOptionBtn/index.tsx @@ -3,10 +3,15 @@ import * as s from './style.css'; import { COLOR_MAP } from '@/libs/constants/colorMap'; import { type TagType, TAG_TYPES_MAP, isIconType, isColorType } from '@/libs/types/item'; import { TagIcon } from '@/features/post/components/TagIcon'; +import { cx } from '@styled-system/css'; -const Symbol = ({ type }: { type: TagType }) => { +interface SymbolProps { + type: TagType; + className?: string; +} +export const Symbol = ({ type, className }: SymbolProps) => { // 아이콘 - if (isIconType(type)) return ; + if (isIconType(type)) return ; // 컬러 if (isColorType(type)) { @@ -15,7 +20,7 @@ const Symbol = ({ type }: { type: TagType }) => { type === 'COLOR_OTHER' ? { backgroundImage: 'linear-gradient(180deg, #FF6164 0%, #FF9462 28.85%, #FFBE62 67.31%, #8BFF61 100%)' } : { backgroundColor: colorValue }; - return ; + return ; } // 아무 타입에도 속하지 않는 경우 : 아무 심볼 없음! @@ -36,7 +41,7 @@ const TagOptionBtn = ({ isSelected = false, onClick, type }: Props) => { + ); +}; + +const PriceFilter = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const startPrice = Number(searchParams.get('start-price') || 0); + const endPrice = Number(searchParams.get('end-price') || MAX_PRICE); + + const resetPrice = () => { + setSearchParams(prev => { + prev.delete('start-price'); + prev.delete('end-price'); + return prev; + }); + }; + + const handlePriceChange = (start: number, end: number) => { + setSearchParams(prev => { + prev.set('start-price', start.toString()); + prev.set('end-price', end.toString()); + return prev; + }); + }; + + const isCustomPrice = useMemo(() => { + return ( + searchParams.get('start-price') !== null && + searchParams.get('end-price') !== null && + !PriceOptions.some(option => option.start === startPrice && option.end === endPrice) + ); + }, [searchParams, startPrice, endPrice]); + + return ( +
+
+ {PriceOptions.map(option => { + const isSelected = option.start === startPrice && option.end === endPrice; + return ( + { + if (isSelected) { + resetPrice(); + return; + } + handlePriceChange(option.start, option.end); + }} + label={option.label} + /> + ); + })} + { + if (isCustomPrice) { + resetPrice(); + return; + } + handlePriceChange(0, MAX_PRICE); + }} + label="직접 입력" + /> +
+
+ ); +}; +export default PriceFilter; diff --git a/src/features/home/components/Filter/FilterContents/index.tsx b/src/features/home/components/Filter/FilterContents/index.tsx index 32bf6410..a708f49c 100644 --- a/src/features/home/components/Filter/FilterContents/index.tsx +++ b/src/features/home/components/Filter/FilterContents/index.tsx @@ -5,6 +5,7 @@ import * as s from './style.css'; import { FilterTypeToList, type FilterType } from '@/features/home/types'; import { type TagType } from '@/libs/types/item'; import TagOptionBtn from '@/common/components/TagOptionBtn'; +import PriceFilter from '@/features/home/components/Filter/FilterContents/PriceFilter'; interface Props { type: FilterType; @@ -25,7 +26,7 @@ const FilterContents = ({ type }: Props) => { return (
{type === 'price' ? ( -
아 만들기 개귀찮네
+ ) : (
{FilterTypeToList[type].map(t => { diff --git a/src/features/home/components/Filter/FilterContents/style.css.ts b/src/features/home/components/Filter/FilterContents/style.css.ts index c3eb4a23..072357f6 100644 --- a/src/features/home/components/Filter/FilterContents/style.css.ts +++ b/src/features/home/components/Filter/FilterContents/style.css.ts @@ -1,4 +1,4 @@ -import { css } from '@styled-system/css'; +import { css, cva } from '@styled-system/css'; export const Container = css({ width: 'full', @@ -11,3 +11,79 @@ export const TagOptionButtonWrapper = css({ gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.625rem', }); + +export const PriceTagButton = cva({ + base: { + width: 'full', + height: '3.12063rem', + p: '0.8125rem 0.6875rem', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + rounded: '0.5rem', + border: '1px solid', + transition: 'all 0.2s ease-in-out', + fontSize: '1rem', + letterSpacing: '-0.04rem', + }, + variants: { + isSelected: { + true: { + bgColor: 'main-26', + borderColor: 'main-54', + color: '100', + fontWeight: 600, + }, + false: { + bgColor: 'systemGray5', + borderColor: 'systemGray5', + color: '80', + fontWeight: 400, + }, + }, + }, +}); + +export const RightIcon = cva({ + base: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + w: '1.125rem', + h: '1.125rem', + flexShrink: 0, + rounded: 'full', + border: '1.5px solid', + transition: 'all 0.2s ease-in-out', + '& div': { + w: '0.625rem', + h: '0.625rem', + rounded: 'full', + transition: 'all 0.2s ease-in-out', + }, + }, + variants: { + isSelected: { + true: { + bgColor: 'main-54', + borderColor: 'main', + '& div': { + bgColor: 'main', + }, + }, + false: { + bgColor: '20', + borderColor: '54', + '& div': { + bgColor: 'transparent', + }, + }, + }, + }, +}); + +export const PriceWrapper = css({ + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gap: '0.625rem', +}); diff --git a/src/features/home/components/Filter/index.tsx b/src/features/home/components/Filter/index.tsx index 4adecc9f..91577eae 100644 --- a/src/features/home/components/Filter/index.tsx +++ b/src/features/home/components/Filter/index.tsx @@ -18,7 +18,13 @@ interface Props { const Filter = ({ state, setState, itemCounts, close }: Props) => { const [searchParams, setSearchParams] = useSearchParams(); - const selected = useMemo(() => FilterTypeArray.some(type => searchParams.getAll(type).length > 0), [searchParams]); + const selected = useMemo(() => { + return ( + FilterTypeArray.some(type => searchParams.getAll(type).length > 0) || + searchParams.get('start-price') !== null || + searchParams.get('end-price') !== null + ); + }, [searchParams]); const { data: totalCount } = useGetItemCount({}); diff --git a/src/features/home/types.ts b/src/features/home/types.ts index 569e647b..95e932f9 100644 --- a/src/features/home/types.ts +++ b/src/features/home/types.ts @@ -32,21 +32,14 @@ export interface ItemInterface { tradeMethods: TradeMethods[]; } -export const ItemOrderArray = [ - 'RECENT', - 'LIKE', - // 'RENTAL_FEE', - // 'SALE_PRICE' -] as const; +export const ItemOrderArray = ['RECENT', 'LIKE'] as const; export type ItemOrderType = (typeof ItemOrderArray)[number]; export const ItemOrderMap: Record = { RECENT: '최신순', LIKE: '좋아요순', - // RENTAL_FEE: '가격순', // TODO: 흠... - // SALE_PRICE: '가격순', }; -// TODO: quality, price는 백엔드 api가 없음 +// TODO: price는 백엔드 api가 없음 export const FilterTypeArray = [ 'transaction-type', 'product-type', diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx index 0eacab7c..dab7a60e 100644 --- a/src/pages/SearchPage/index.tsx +++ b/src/pages/SearchPage/index.tsx @@ -19,6 +19,8 @@ const SearchPage = () => { transactionTypes: searchParams.getAll('transaction-type') as TransactionType[], tradeMethods: searchParams.getAll('trade-method') as TradeMethods[], qualities: searchParams.getAll('quality') as Quality[], + startPrice: searchParams.get('start-price') ? Number(searchParams.get('start-price')) : undefined, + endPrice: searchParams.get('end-price') ? Number(searchParams.get('end-price')) : undefined, // date: searchParams.get('date') || undefined, }; const { data: totalCount } = useGetItemCount(searchFilters); From 154dfc3fd943aa5ab807ab9b322dc9e86de9516e Mon Sep 17 00:00:00 2001 From: halionaz Date: Tue, 12 Aug 2025 13:37:12 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20price?= =?UTF-8?q?=20=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Filter/FilterContents/index.tsx | 2 +- .../Filter/FilterContents/style.css.ts | 78 +---------- .../Filter/PriceFilter/CustomPriceInput.tsx | 85 ++++++++++++ .../Filter/PriceFilter/PriceButton.tsx | 19 +++ .../PriceFilter.tsx => PriceFilter/index.tsx} | 29 ++-- .../Filter/PriceFilter/style.css.ts | 130 ++++++++++++++++++ .../home/components/SearchControls/index.tsx | 11 +- 7 files changed, 254 insertions(+), 100 deletions(-) create mode 100644 src/features/home/components/Filter/PriceFilter/CustomPriceInput.tsx create mode 100644 src/features/home/components/Filter/PriceFilter/PriceButton.tsx rename src/features/home/components/Filter/{FilterContents/PriceFilter.tsx => PriceFilter/index.tsx} (82%) create mode 100644 src/features/home/components/Filter/PriceFilter/style.css.ts diff --git a/src/features/home/components/Filter/FilterContents/index.tsx b/src/features/home/components/Filter/FilterContents/index.tsx index a708f49c..3146df79 100644 --- a/src/features/home/components/Filter/FilterContents/index.tsx +++ b/src/features/home/components/Filter/FilterContents/index.tsx @@ -5,7 +5,7 @@ import * as s from './style.css'; import { FilterTypeToList, type FilterType } from '@/features/home/types'; import { type TagType } from '@/libs/types/item'; import TagOptionBtn from '@/common/components/TagOptionBtn'; -import PriceFilter from '@/features/home/components/Filter/FilterContents/PriceFilter'; +import PriceFilter from '@/features/home/components/Filter/PriceFilter'; interface Props { type: FilterType; diff --git a/src/features/home/components/Filter/FilterContents/style.css.ts b/src/features/home/components/Filter/FilterContents/style.css.ts index 072357f6..c3eb4a23 100644 --- a/src/features/home/components/Filter/FilterContents/style.css.ts +++ b/src/features/home/components/Filter/FilterContents/style.css.ts @@ -1,4 +1,4 @@ -import { css, cva } from '@styled-system/css'; +import { css } from '@styled-system/css'; export const Container = css({ width: 'full', @@ -11,79 +11,3 @@ export const TagOptionButtonWrapper = css({ gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.625rem', }); - -export const PriceTagButton = cva({ - base: { - width: 'full', - height: '3.12063rem', - p: '0.8125rem 0.6875rem', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - rounded: '0.5rem', - border: '1px solid', - transition: 'all 0.2s ease-in-out', - fontSize: '1rem', - letterSpacing: '-0.04rem', - }, - variants: { - isSelected: { - true: { - bgColor: 'main-26', - borderColor: 'main-54', - color: '100', - fontWeight: 600, - }, - false: { - bgColor: 'systemGray5', - borderColor: 'systemGray5', - color: '80', - fontWeight: 400, - }, - }, - }, -}); - -export const RightIcon = cva({ - base: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - w: '1.125rem', - h: '1.125rem', - flexShrink: 0, - rounded: 'full', - border: '1.5px solid', - transition: 'all 0.2s ease-in-out', - '& div': { - w: '0.625rem', - h: '0.625rem', - rounded: 'full', - transition: 'all 0.2s ease-in-out', - }, - }, - variants: { - isSelected: { - true: { - bgColor: 'main-54', - borderColor: 'main', - '& div': { - bgColor: 'main', - }, - }, - false: { - bgColor: '20', - borderColor: '54', - '& div': { - bgColor: 'transparent', - }, - }, - }, - }, -}); - -export const PriceWrapper = css({ - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - gap: '0.625rem', -}); diff --git a/src/features/home/components/Filter/PriceFilter/CustomPriceInput.tsx b/src/features/home/components/Filter/PriceFilter/CustomPriceInput.tsx new file mode 100644 index 00000000..05b95b35 --- /dev/null +++ b/src/features/home/components/Filter/PriceFilter/CustomPriceInput.tsx @@ -0,0 +1,85 @@ +import { useEffect, useState } from 'react'; +import * as s from './style.css'; +import { MAX_PRICE } from '@/libs/constants'; +import { useSearchParams } from 'react-router'; + +interface Props { + active: boolean; +} +const CustomPriceInput = ({ active }: Props) => { + const [searchParams, setSearchParams] = useSearchParams(); + + const defaultStartPrice = searchParams.get('start-price') ? Number(searchParams.get('start-price')) : NaN; + const defaultEndPrice = searchParams.get('end-price') ? Number(searchParams.get('end-price')) : NaN; + + const [startPrice, setStartPrice] = useState(defaultStartPrice); + const [endPrice, setEndPrice] = useState(defaultEndPrice); + + useEffect(() => { + if (!active) { + setStartPrice(NaN); + setEndPrice(NaN); + } + }, [active]); + + const handlePriceChange = (e: React.ChangeEvent, type: 'start' | 'end') => { + const handlePrice = type === 'start' ? setStartPrice : setEndPrice; + const value = e.target.value; + // 빈 문자열이면 NaN으로 설정 + if (value === '') { + handlePrice(NaN); + return; + } + // 숫자가 아니거나 0으로 시작하는 경우 (단, '0' 자체는 허용) 무시 + if (!/^\d+$/.test(value) || (value.length > 1 && value.startsWith('0'))) { + return; + } + const numValue = Number(value); + if (numValue > MAX_PRICE) return; + handlePrice(numValue); + }; + + const submitPrice = () => { + if (isNaN(startPrice) || isNaN(endPrice)) return; + if (startPrice > endPrice) { + alert('시작 가격이 끝 가격보다 클 수 없습니다.'); + return; + } + setSearchParams(prev => { + prev.set('start-price', startPrice.toString()); + prev.set('end-price', endPrice.toString()); + return prev; + }); + }; + + return ( +
+
+ handlePriceChange(e, 'start')} + disabled={!active} + onBlur={submitPrice} + /> + +
+ - +
+ handlePriceChange(e, 'end')} + disabled={!active} + onBlur={submitPrice} + /> + +
+
+ ); +}; +export default CustomPriceInput; diff --git a/src/features/home/components/Filter/PriceFilter/PriceButton.tsx b/src/features/home/components/Filter/PriceFilter/PriceButton.tsx new file mode 100644 index 00000000..7be35d98 --- /dev/null +++ b/src/features/home/components/Filter/PriceFilter/PriceButton.tsx @@ -0,0 +1,19 @@ +import * as s from './style.css'; + +interface Props { + isSelected: boolean; + onClick: () => void; + label: string; +} +const PriceButton = ({ isSelected, onClick, label }: Props) => { + return ( + + ); +}; + +export default PriceButton; diff --git a/src/features/home/components/Filter/FilterContents/PriceFilter.tsx b/src/features/home/components/Filter/PriceFilter/index.tsx similarity index 82% rename from src/features/home/components/Filter/FilterContents/PriceFilter.tsx rename to src/features/home/components/Filter/PriceFilter/index.tsx index d67d4026..294ce250 100644 --- a/src/features/home/components/Filter/FilterContents/PriceFilter.tsx +++ b/src/features/home/components/Filter/PriceFilter/index.tsx @@ -3,6 +3,8 @@ import { useSearchParams } from 'react-router'; import * as s from './style.css'; import { useMemo } from 'react'; +import PriceButton from '@/features/home/components/Filter/PriceFilter/PriceButton'; +import CustomPriceInput from '@/features/home/components/Filter/PriceFilter/CustomPriceInput'; const PriceOptions = [ { @@ -11,44 +13,28 @@ const PriceOptions = [ end: 9999, }, { - label: '1만원-2만원', + label: '1만원 - 2만원', start: 10000, end: 19999, }, { - label: '2만원-3만원', + label: '2만원 - 3만원', start: 20000, end: 29999, }, { - label: '3만원-4만원', + label: '3만원 - 4만원', start: 30000, end: 39999, }, { - label: '4만원-5만원', + label: '4만원 - 5만원', start: 40000, end: 49999, }, { label: '5만원 이상', start: 50000, end: MAX_PRICE }, ]; -interface PriceButtonProps { - isSelected: boolean; - onClick: () => void; - label: string; -} -const PriceButton = ({ isSelected, onClick, label }: PriceButtonProps) => { - return ( - - ); -}; - const PriceFilter = () => { const [searchParams, setSearchParams] = useSearchParams(); const startPrice = Number(searchParams.get('start-price') || 0); @@ -79,7 +65,7 @@ const PriceFilter = () => { }, [searchParams, startPrice, endPrice]); return ( -
+
{PriceOptions.map(option => { const isSelected = option.start === startPrice && option.end === endPrice; @@ -110,6 +96,7 @@ const PriceFilter = () => { label="직접 입력" />
+
); }; diff --git a/src/features/home/components/Filter/PriceFilter/style.css.ts b/src/features/home/components/Filter/PriceFilter/style.css.ts new file mode 100644 index 00000000..653f10ab --- /dev/null +++ b/src/features/home/components/Filter/PriceFilter/style.css.ts @@ -0,0 +1,130 @@ +import { css, cva } from '@styled-system/css'; + +export const Container = css({ + display: 'flex', + flexDir: 'column', + gap: '0.625rem', +}); + +export const PriceTagButton = cva({ + base: { + width: 'full', + height: '3.12063rem', + p: '0.8125rem 0.6875rem', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + rounded: '0.5rem', + border: '1px solid', + transition: 'all 0.2s ease-in-out', + fontSize: '1rem', + letterSpacing: '-0.04rem', + }, + variants: { + isSelected: { + true: { + bgColor: 'main-26', + borderColor: 'main-54', + color: '100', + fontWeight: 600, + }, + false: { + bgColor: 'systemGray5', + borderColor: 'systemGray5', + color: '80', + fontWeight: 400, + }, + }, + }, +}); + +export const RightIcon = cva({ + base: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + w: '1.125rem', + h: '1.125rem', + flexShrink: 0, + rounded: 'full', + border: '1.5px solid', + transition: 'all 0.2s ease-in-out', + '& div': { + w: '0.625rem', + h: '0.625rem', + rounded: 'full', + transition: 'all 0.2s ease-in-out', + }, + }, + variants: { + isSelected: { + true: { + bgColor: 'main-54', + borderColor: 'main', + '& div': { + bgColor: 'main', + }, + }, + false: { + bgColor: '20', + borderColor: '54', + '& div': { + bgColor: 'transparent', + }, + }, + }, + }, +}); + +export const PriceWrapper = css({ + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gap: '0.625rem', +}); + +export const CustomPriceInputContainer = cva({ + base: { + display: 'flex', + alignItems: 'center', + gap: '0.5rem', + transition: 'all 0.2s ease-in-out', + }, + variants: { + active: { + true: { opacity: 1 }, + false: { opacity: 0.3 }, + }, + }, +}); + +export const CustomPriceInput = css({ + width: '8.75rem', + height: '2.25rem', + pr: '0.75rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + color: '100', + fontSize: '1rem', + letterSpacing: '-0.04rem', + gap: '0.25rem', + bgColor: 'systemGray5', + rounded: '0.375rem', + '& input': { + flexShrink: 0, + width: 'full', + height: 'full', + flexGrow: 1, + flexBasis: 0, + fontWeight: 600, + textAlign: 'right', + pl: '0.75rem', + '&:focus': { + outline: 'none', + }, + }, + '& span': { + flexShrink: 0, + fontWeight: 400, + }, +}); diff --git a/src/features/home/components/SearchControls/index.tsx b/src/features/home/components/SearchControls/index.tsx index e07b94db..be82bc82 100644 --- a/src/features/home/components/SearchControls/index.tsx +++ b/src/features/home/components/SearchControls/index.tsx @@ -44,7 +44,16 @@ const SearchControls = ({ itemCounts }: Props) => {
{FilterTypeArray.map(filter => { - const selected = searchParams.getAll(filter) as TagType[]; + const selected = (() => { + if (filter === 'price') { + if (searchParams.get('start-price') !== null && searchParams.get('end-price') !== null) { + return [searchParams.get('start-price') as TagType, searchParams.get('end-price') as TagType]; + } + return []; + } + return searchParams.getAll(filter) as TagType[]; + })(); + return ( handleFilterClick(filter)} selected={selected}> {FilterTypeMap[filter]}