Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: document search #306

Merged
merged 12 commits into from
Dec 11, 2024
Merged
12 changes: 12 additions & 0 deletions src/features/document/components/document-type-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ const DocumentTypeIcon = ({ type, containerClassName, iconClassName }: Props) =>
</div>
)
}

// default
return (
<div
className={cn(
'flex-center size-[36px] shrink-0 rounded-full bg-fill-secondary-orange text-text-primary-inverse',
containerClassName
)}
>
<Icon name="document" className={cn('size-[16px]', iconClassName)} />
</div>
)
}

export default DocumentTypeIcon
56 changes: 35 additions & 21 deletions src/features/search/components/header-in-document.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
'use client'

import Icon from '@/shared/components/custom/icon'
import { Input } from '@/shared/components/ui/input'
import { useRouter } from 'next/navigation'
import { RefObject, useState } from 'react'
import { ChangeEventHandler, RefObject } from 'react'

interface Props {
searchHeaderRef: RefObject<HTMLDivElement>
inputValue: string
onChangeInputValue: ChangeEventHandler<HTMLInputElement>
searchInputRef: RefObject<HTMLInputElement>
isSearchFocused: boolean
setIsSearchFocused: (value: boolean) => void
onDeleteKeyword: () => void
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
}

const HeaderInDocument = ({ searchHeaderRef, isSearchFocused, setIsSearchFocused }: Props) => {
const HeaderInDocument = ({
inputValue,
onChangeInputValue,
searchInputRef,
isSearchFocused,
setIsSearchFocused,
onDeleteKeyword,
onSubmit,
}: Props) => {
const router = useRouter()
const [keyword, setKeyword] = useState('')

const handleCancel = () => {
if (isSearchFocused) {
setIsSearchFocused(false)
return
} else {
router.push('/document')
Copy link
Member

Choose a reason for hiding this comment

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

router.back이 아닌 document로 보내는 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

router.back이 아닌 document로 보내는 이유가 있을까요?

앗 안그래도 저도 통합검색 구현하면서 잘못된 부분들이 좀 있길래 수정했는데ㅎㅎ 이 부분도 정우님 말씀처럼 router.back으로 통합검색 쪽에서 수정했습니다!

}
}

return (
<header
ref={searchHeaderRef}
className="flex-center relative right-1/2 z-20 h-[56px] w-full max-w-mobile grow translate-x-1/2 bg-background-base-01 px-[16px] text-subtitle2-medium"
>
<div tabIndex={-1} className="relative grow">
<header className="flex-center relative right-1/2 z-20 h-[56px] w-full max-w-mobile grow translate-x-1/2 bg-background-base-01 px-[16px] text-subtitle2-medium">
<form onSubmit={onSubmit} tabIndex={-1} className="relative grow">
<Input
Copy link
Member

Choose a reason for hiding this comment

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

autoFocus 속성이 들어가도 좋을 것 같아용!

value={keyword}
onChange={(e) => setKeyword(e.target.value)}
ref={searchInputRef}
onFocus={() => setIsSearchFocused(true)}
value={inputValue}
onChange={onChangeInputValue}
placeholder="노트명, 노트, 퀴즈 검색"
className="h-[40px] placeholder:text-text-placeholder-01"
variant={'round'}
left={<Icon name="search-bar" className="size-[20px] text-icon-secondary" />}
right={
<button>
<button type="button" onClick={onDeleteKeyword}>
<Icon
name="cancel-circle"
className="size-[24px]"
Expand All @@ -38,15 +58,9 @@ const HeaderInDocument = ({ searchHeaderRef, isSearchFocused, setIsSearchFocused
</button>
}
/>
</div>
<button
onClick={() => {
if (isSearchFocused) {
setIsSearchFocused(false)
} else router.back()
}}
className="ml-[17px] w-fit text-text-secondary"
>
</form>

<button type="button" onClick={handleCancel} className="ml-[17px] w-fit text-text-secondary">
취소
</button>
</header>
Expand Down
63 changes: 54 additions & 9 deletions src/features/search/components/recent-searches.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,66 @@
'use client'

import Icon from '@/shared/components/custom/icon'
import Text from '@/shared/components/ui/text'
import { getLocalStorage, removeLocalStorage, setLocalStorage } from '@/shared/utils/storage'
import { RefObject, useEffect, useState } from 'react'
import { RECENT_SEARCHES } from '../config'

interface Props {
containerRef: RefObject<HTMLDivElement>
onUpdateKeyword: (keyword: string) => void
}

const RecentSearches = ({ containerRef, onUpdateKeyword }: Props) => {
const [recentSearches, setRecentSearches] = useState<string[]>([])

useEffect(() => {
const storageSearches = getLocalStorage<string[]>(RECENT_SEARCHES) ?? []
setRecentSearches(storageSearches)
}, [])

/** 로컬스토리지에서 특정 검색어 삭제 */
const deleteRecentSearch = (keyword: string) => {
const newRecentSearches = recentSearches.filter((search) => search !== keyword)
setLocalStorage(RECENT_SEARCHES, newRecentSearches)
setRecentSearches(newRecentSearches)
}

/** 전체 검색어 삭제 */
const deleteAllRecentSearches = () => {
removeLocalStorage(RECENT_SEARCHES)
setRecentSearches([])
}

const RecentSearches = () => {
return (
<div className="flex flex-col border-t border-border-divider px-[16px] py-[20px]">
<div className="mb-[24px] flex items-center justify-between text-text1-medium">
<div
ref={containerRef}
className="flex flex-col border-t border-border-divider px-[16px] py-[20px]"
>
<div className="mb-[14px] flex items-center justify-between text-text1-medium">
<Text typography="text1-bold" className="text-text-secondary">
최근 검색어
</Text>
<button className="text-text-caption">전체삭제</button>
<button className="text-text-caption" onClick={deleteAllRecentSearches}>
전체삭제
</button>
</div>

<div className="flex flex-col gap-[20px]">
{Array.from({ length: 5 }).map((_, idx) => (
<div key={idx} className="flex items-center justify-between">
<Text typography="text1-medium">최근 검색어 {idx}</Text>
<button className="text-icon-tertiary">
<div className="flex flex-col">
{recentSearches.map((keyword) => (
<div
key={keyword}
onClick={() => onUpdateKeyword(keyword)}
className="flex cursor-pointer items-center justify-between py-[10px]"
>
<Text typography="text1-medium">{keyword}</Text>
<button
onClick={(e) => {
e.stopPropagation()
deleteRecentSearch(keyword)
}}
className="text-icon-tertiary"
>
<Icon name="cancel" className="size-[16px]" />
</button>
</div>
Expand Down
13 changes: 8 additions & 5 deletions src/features/search/components/search-item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import Tag from '@/shared/components/ui/tag'
import Text from '@/shared/components/ui/text'
import { cn } from '@/shared/lib/utils'
import DocumentTypeIcon from '@/features/document/components/document-type-icon'
import Link from 'next/link'

interface Props {
documentId: number | undefined // api 수정되면 undefined 제거
createType: Document.ItemInList['documentType']
documentTitle: string
matchingSentence: string
documentTitle: React.ReactNode
matchingSentence: React.ReactNode
resultType: 'document' | 'quiz'
relativeDirectory: string
lastItem?: boolean
}

const SearchItem = ({
documentId,
createType,
documentTitle,
matchingSentence,
Expand All @@ -22,7 +25,8 @@ const SearchItem = ({
lastItem,
}: Props) => {
return (
<div
<Link
href={documentId ? '/document/' + documentId : '#'}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
href={documentId ? '/document/' + documentId : '#'}
href={documentId ? `/document/${documentId}` : '#'}

className={cn(
'border-b border-border-divider py-[24px] flex flex-col',
lastItem && 'border-none'
Expand All @@ -37,7 +41,6 @@ const SearchItem = ({
<Text typography="subtitle2-bold">{documentTitle}</Text>
</div>

{/* todo: 키워드와 일치하는 부분 색상 accent표시 하는 로직 필요 */}
<Text>{matchingSentence}</Text>

<div className="mt-[8px] flex items-center">
Expand All @@ -50,7 +53,7 @@ const SearchItem = ({
<Text>{relativeDirectory}</Text>
</div>
</div>
</div>
</Link>
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/search/components/search-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PropsWithChildren } from 'react'

const SearchList = ({ length, children }: PropsWithChildren & { length: number }) => {
return (
<div className="h-[calc(100dvh-88px-56px)] overflow-y-auto p-[16px] text-text1-medium">
<div className="h-[calc(100dvh-56px)] overflow-y-auto p-[16px] text-text1-medium">
<Text>
퀴즈 노트 <span className="text-text-accent">{length}</span>
</Text>
Expand Down
1 change: 1 addition & 0 deletions src/features/search/config/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const RECENT_SEARCHES = 'recentSearches'
Loading
Loading