-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from Atractorrr/refactor/inbox-ui-#ATR-571
보관함 필터 UI 개편
- Loading branch information
Showing
18 changed files
with
563 additions
and
290 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { default as DEFAULT_LIST_SIZE } from './defaultListSize' | ||
export * from './sortButton' |
11 changes: 11 additions & 0 deletions
11
apps/service/src/entities/user-article/constant/sortButton.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { SortType } from '../model' | ||
|
||
export const SORT_LABEL: Record<SortType, string> = { | ||
'receivedAt,desc': '최신 순', | ||
'receivedAt,asc': '오래된 순', | ||
} | ||
|
||
export const SORT_MENU: Array<[SortType, string]> = [ | ||
['receivedAt,desc', SORT_LABEL['receivedAt,desc']], | ||
['receivedAt,asc', SORT_LABEL['receivedAt,asc']], | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 12 additions & 2 deletions
14
apps/service/src/entities/user-article/model/useUserCategoriesQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,25 @@ | ||
'use client' | ||
|
||
import { useQuery } from '@tanstack/react-query' | ||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query' | ||
import { useAuth } from '@/entities/auth' | ||
import { getUserCategories } from '../api' | ||
import userArticleQueryKeys from './userArticleQueryKeys' | ||
|
||
export default function useUserCategoriesQuery() { | ||
export function useUserCategoriesQuery({ enabled }: { enabled?: boolean }) { | ||
const { userEmail } = useAuth() | ||
|
||
return useQuery({ | ||
queryKey: userArticleQueryKeys.userCategories({ userEmail }), | ||
queryFn: () => getUserCategories({ userEmail }), | ||
enabled, | ||
}) | ||
} | ||
|
||
export function useUserCategoriesSuspenseQuery() { | ||
const { userEmail } = useAuth() | ||
|
||
return useSuspenseQuery({ | ||
queryKey: userArticleQueryKeys.userCategories({ userEmail }), | ||
queryFn: () => getUserCategories({ userEmail }), | ||
}) | ||
} |
80 changes: 80 additions & 0 deletions
80
apps/service/src/features/filter-article/ui/CategoryButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
'use client' | ||
|
||
import { PropsWithChildren, useState } from 'react' | ||
import { ChevronDownOutline, TagOutline } from '@attraction/icons' | ||
import { | ||
Drawer, | ||
DrawerClose, | ||
DrawerContent, | ||
DrawerFooter, | ||
DrawerHeader, | ||
DrawerTitle, | ||
DrawerTrigger, | ||
} from '@attraction/design-system' | ||
import { useCheckDevice } from '@/shared/lib' | ||
import type { NewsletterCategory } from '@/shared/type' | ||
import { NEWSLETTER_CATEGORY } from '@/shared/constant' | ||
import FilterButton from './FilterButton' | ||
import CategoryDropdown from './CategoryDropdown' | ||
import CategorySelectForDrawer from './CategorySelectForDrawer' | ||
|
||
export interface CategorySelectProps { | ||
selectedCategory?: NewsletterCategory | ||
setCategory: (category?: NewsletterCategory) => void | ||
} | ||
|
||
function DropdownToDrawer({ | ||
children, | ||
...props | ||
}: PropsWithChildren<CategorySelectProps>) { | ||
const { isMobileView } = useCheckDevice() | ||
const [isOpen, setOpen] = useState(false) | ||
const close = () => setOpen(false) | ||
|
||
if (isMobileView) { | ||
return ( | ||
<Drawer open={isOpen} onOpenChange={setOpen}> | ||
<DrawerTrigger asChild>{children}</DrawerTrigger> | ||
<DrawerContent> | ||
<DrawerHeader> | ||
<DrawerTitle>카테고리 선택</DrawerTitle> | ||
</DrawerHeader> | ||
<CategorySelectForDrawer {...props} close={close} /> | ||
<DrawerFooter> | ||
<DrawerClose asChild> | ||
<button | ||
type="button" | ||
title="닫기" | ||
className="w-full whitespace-nowrap rounded-lg bg-gray-50 px-6 py-3 transition-colors active:bg-gray-100 xs:text-lg md:px-10 dark:bg-gray-700 dark:active:bg-gray-600" | ||
onClick={close}> | ||
닫기 | ||
</button> | ||
</DrawerClose> | ||
</DrawerFooter> | ||
</DrawerContent> | ||
</Drawer> | ||
) | ||
} | ||
|
||
return ( | ||
<CategoryDropdown {...props} open={isOpen} onOpenChange={setOpen}> | ||
{children} | ||
</CategoryDropdown> | ||
) | ||
} | ||
|
||
export default function CategoryButton({ ...props }: CategorySelectProps) { | ||
return ( | ||
<DropdownToDrawer {...props}> | ||
<FilterButton active={!!props.selectedCategory} title="카테고리 변경"> | ||
<TagOutline className="text-lg xs:text-xl" /> | ||
<span className="whitespace-nowrap text-sm xs:text-base"> | ||
{props.selectedCategory | ||
? NEWSLETTER_CATEGORY[props.selectedCategory] | ||
: '카테고리'} | ||
</span> | ||
<ChevronDownOutline className="text-base" /> | ||
</FilterButton> | ||
</DropdownToDrawer> | ||
) | ||
} |
72 changes: 41 additions & 31 deletions
72
apps/service/src/features/filter-article/ui/CategoryDropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,67 @@ | ||
'use client' | ||
|
||
import type { PropsWithChildren } from 'react' | ||
import { | ||
DropdownMenuRadioItem, | ||
DropdownMenuRadioGroup, | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuTrigger, | ||
} from '@attraction/design-system/dist' | ||
import { useUserCategoriesQuery } from '@/entities/user-article' | ||
import { NewsletterCategory } from '@/shared/type' | ||
import { NEWSLETTER_CATEGORY } from '@/shared/constant' | ||
import { LoadingSpinner, WarnTxt } from '@/shared/ui' | ||
import type { CategorySelectProps } from './CategoryButton' | ||
|
||
export interface CategoryDropdownProps { | ||
selectedCategory?: NewsletterCategory | ||
setCategory: (category?: NewsletterCategory) => void | ||
interface CategoryDropdownProps extends PropsWithChildren<CategorySelectProps> { | ||
open?: boolean | ||
onOpenChange?: (status: boolean) => void | ||
} | ||
|
||
export default function CategoryDropdown({ | ||
selectedCategory, | ||
setCategory, | ||
open, | ||
onOpenChange, | ||
children, | ||
}: CategoryDropdownProps) { | ||
const { data, isLoading, isError } = useUserCategoriesQuery() | ||
const { data, isLoading, isError } = useUserCategoriesQuery({ enabled: open }) | ||
|
||
return ( | ||
<> | ||
{isLoading && <LoadingSpinner />} | ||
{isError && ( | ||
<div className="p-3"> | ||
<WarnTxt content="카테고리를 불러오지 못했어요" color="red" /> | ||
</div> | ||
)} | ||
{data && ( | ||
<> | ||
{data.length === 0 && <WarnTxt content="구독한 뉴스레터가 없어요" />} | ||
<DropdownMenuRadioGroup value={selectedCategory ?? 'all'}> | ||
{data.length > 0 && ( | ||
<DropdownMenu open={open} onOpenChange={onOpenChange}> | ||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-52"> | ||
{isLoading && <LoadingSpinner />} | ||
{isError && ( | ||
<div className="p-3"> | ||
<WarnTxt content="카테고리를 불러오지 못했어요" color="red" /> | ||
</div> | ||
)} | ||
{data && ( | ||
<> | ||
{data.length === 0 && ( | ||
<WarnTxt content="구독한 뉴스레터가 없어요" type="info" /> | ||
)} | ||
<DropdownMenuRadioGroup value={selectedCategory ?? 'all'}> | ||
<DropdownMenuRadioItem | ||
value="all" | ||
title="카테고리 선택: 전체" | ||
onClick={() => setCategory(undefined)}> | ||
전체 | ||
</DropdownMenuRadioItem> | ||
)} | ||
{data.map((category) => ( | ||
<DropdownMenuRadioItem | ||
key={category} | ||
title={`카테고리 선택: ${NEWSLETTER_CATEGORY[category]}`} | ||
value={category} | ||
onClick={() => setCategory(category)}> | ||
{NEWSLETTER_CATEGORY[category]} | ||
</DropdownMenuRadioItem> | ||
))} | ||
</DropdownMenuRadioGroup> | ||
</> | ||
)} | ||
</> | ||
{data.map((category) => ( | ||
<DropdownMenuRadioItem | ||
key={category} | ||
title={`카테고리 선택: ${NEWSLETTER_CATEGORY[category]}`} | ||
value={category} | ||
onClick={() => setCategory(category)}> | ||
{NEWSLETTER_CATEGORY[category]} | ||
</DropdownMenuRadioItem> | ||
))} | ||
</DropdownMenuRadioGroup> | ||
</> | ||
)} | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
) | ||
} |
72 changes: 72 additions & 0 deletions
72
apps/service/src/features/filter-article/ui/CategorySelectForDrawer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
'use client' | ||
|
||
import { Suspense } from 'react' | ||
import { ErrorBoundary } from 'react-error-boundary' | ||
import { Checkbox } from '@attraction/design-system' | ||
import { useUserCategoriesSuspenseQuery } from '@/entities/user-article' | ||
import { NEWSLETTER_CATEGORY } from '@/shared/constant' | ||
import { GuideTxt, LoadingSpinner } from '@/shared/ui' | ||
import type { CategorySelectProps } from './CategoryButton' | ||
|
||
interface CategorySelectForDrawerProps extends CategorySelectProps { | ||
close: () => void | ||
} | ||
|
||
function UserCategorySelect({ | ||
selectedCategory, | ||
setCategory, | ||
close, | ||
}: CategorySelectForDrawerProps) { | ||
const { data } = useUserCategoriesSuspenseQuery() | ||
|
||
return ( | ||
<> | ||
<li className="flex px-3"> | ||
<Checkbox | ||
className="block w-full rounded-lg p-2 transition-colors active:bg-gray-50 dark:active:bg-gray-700" | ||
checked={!selectedCategory} | ||
onChange={() => { | ||
setCategory(undefined) | ||
close() | ||
}} | ||
label="전체" | ||
/> | ||
</li> | ||
{data.map((category) => ( | ||
<li key={category} className="flex px-3"> | ||
<Checkbox | ||
className="block w-full rounded-lg p-2 transition-colors active:bg-gray-50 dark:active:bg-gray-700" | ||
checked={category === selectedCategory} | ||
onChange={() => { | ||
setCategory(category) | ||
close() | ||
}} | ||
label={NEWSLETTER_CATEGORY[category]} | ||
/> | ||
</li> | ||
))} | ||
</> | ||
) | ||
} | ||
|
||
export default function CategorySelectForDrawer( | ||
props: CategorySelectForDrawerProps, | ||
) { | ||
return ( | ||
<ErrorBoundary | ||
fallback={ | ||
<div className="scrollbar-hidden h-[48vh] overflow-y-auto px-5 py-16"> | ||
<GuideTxt | ||
title="카테고리를 불러오지 못했어요" | ||
sub="동일한 현상이 지속된다면 관리자에게 문의해주세요" | ||
/> | ||
</div> | ||
}> | ||
<ul className="scrollbar-hidden h-[48vh] overflow-y-auto"> | ||
<Suspense fallback={<LoadingSpinner />}> | ||
<UserCategorySelect {...props} /> | ||
</Suspense> | ||
</ul> | ||
</ErrorBoundary> | ||
) | ||
} |
34 changes: 34 additions & 0 deletions
34
apps/service/src/features/filter-article/ui/FilterButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'use client' | ||
|
||
import React, { | ||
DetailedHTMLProps, | ||
InputHTMLAttributes, | ||
LegacyRef, | ||
forwardRef, | ||
} from 'react' | ||
|
||
const FilterButton = forwardRef< | ||
HTMLInputElement, | ||
DetailedHTMLProps< | ||
InputHTMLAttributes<HTMLButtonElement>, | ||
HTMLButtonElement | ||
> & { active?: boolean } | ||
>(({ type, className, active: isActive = false, onClick, ...props }, ref) => ( | ||
<button | ||
ref={ref as LegacyRef<HTMLButtonElement> | undefined} | ||
type="button" | ||
className={`flex h-10 items-center justify-center gap-2 rounded-lg px-3 py-2 transition-colors ${ | ||
isActive | ||
? 'bg-blue-50 text-blue-400 dark:bg-gray-50 dark:text-gray-700' | ||
: 'bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600' | ||
}`} | ||
onClick={(e) => { | ||
onClick?.(e) | ||
e.currentTarget?.scrollIntoView({ inline: 'center', behavior: 'smooth' }) | ||
}} | ||
{...props} | ||
/> | ||
)) | ||
FilterButton.displayName = 'DefaultFilterButton' | ||
|
||
export default FilterButton |
Oops, something went wrong.