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

[FIX] 검색 결과 포커스 해제 #739

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function PickDraggableRecord({
currentUpdateTitlePickId,
currentUpdateTagPickId,
} = useUpdatePickStore();
const searchElementId = useSearchElementId();
const { searchElementId, isOccurClickEvent } = useSearchElementId();
const { id: pickId, parentFolderId } = pickInfo;
const isSelected = selectedPickIdList.includes(pickId);
const {
Expand All @@ -53,6 +53,8 @@ export function PickDraggableRecord({
currentUpdateTagPickId === pickInfo.id,
});
const pickElementId = `pickId-${pickId}`;
const isSearchedPickHighlight =
searchElementId === pickElementId && !isOccurClickEvent;

const style: CSSProperties = {
transform: CSS.Transform.toString(transform),
Expand Down Expand Up @@ -100,7 +102,7 @@ export function PickDraggableRecord({

return (
<div
className={`${searchElementId === pickElementId ? searchedItemStyle : ''}`}
className={`${isSearchedPickHighlight ? searchedItemStyle : ''}`}
ref={setNodeRef}
{...attributes}
{...listeners}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { useEffect, useState } from 'react';
import Select from 'react-select';
import customSelectStyles from './customSelectStyles';
import * as styles from './filterOptions.css';
import { SearchSelectOption } from '@/types';

type MultiValue<Option> = readonly Option[];

export default function FilterOptions({
title,
icon,
options,
updateSearchState,
}: TagFilterOptionsProps) {
Expand All @@ -21,7 +23,8 @@ export default function FilterOptions({
}

return (
<div>
<div className={styles.filterOptionContainer}>
<div className={styles.icon}> {icon} </div>
<Select
placeholder={title}
isMulti
Expand All @@ -36,6 +39,7 @@ export default function FilterOptions({

interface TagFilterOptionsProps {
title: string;
icon: React.ReactNode;
options: SearchSelectOption[];
updateSearchState: (queryString: number[]) => void;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { FolderIcon, Tags } from 'lucide-react';
import { useTreeStore, useTagStore } from '@/stores';
import { useSearchPickStore } from '@/stores/searchPickStore';
import { createSearchSelectOptions } from '@/utils';
import FilterOptions from './FliterOptions';
import FilterOptions from './FilterOptions';
import * as styles from './searchDialog.css';

export default function FilterToggleContainer({
Expand Down Expand Up @@ -34,13 +35,15 @@ export default function FilterToggleContainer({
>
<FilterOptions
title="폴더"
icon={<FolderIcon size={18} />}
options={folderOptions}
updateSearchState={(queryString: number[]) =>
updateSearchState(queryString, setSearchFolder)
}
/>
<FilterOptions
title="태그"
icon={<Tags size={20} />}
options={tagOptions}
updateSearchState={(queryString: number[]) =>
updateSearchState(queryString, setSearchTag)
Expand Down
8 changes: 3 additions & 5 deletions frontend/techpick/src/components/Search2/HoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import React from 'react';
import Image from 'next/image';
import { useSearchPickStore } from '@/stores/searchPickStore';
import { formatDateString } from '@/utils';
import { checkImageUrlByUrl } from '@/utils/checkImageUrlByUrl';
import * as styles from './hoverCard.css';

function getImageUrl(url: string | undefined): string {
return url?.trim() || '/image/default_image.svg';
}

export default function HoverCard() {
const { searchResultList, hoverPickIndex } = useSearchPickStore();

Expand All @@ -23,12 +20,13 @@ export default function HoverCard() {
target="_blank"
>
<Image
src={getImageUrl(
src={checkImageUrlByUrl(
searchResultList[hoverPickIndex]?.linkInfo.imageUrl
)}
alt="link-image"
height={200}
width={200}
className={styles.hoverCardImage}
/>
</a>
</div>
Expand Down
9 changes: 5 additions & 4 deletions frontend/techpick/src/components/Search2/SearchDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { SearchIcon, FilterIcon, Loader } from 'lucide-react';
import { FilterIcon } from 'lucide-react';
import { useSearchPickStore } from '@/stores/searchPickStore';
import FilterToggleContainer from './FilterToggleContainer';
import HoverCard from './HoverCard';
Expand All @@ -13,7 +13,6 @@ export default function SearchDialog({
isOpen,
onOpenChange,
}: SearchDialogProps) {
const { isLoading } = useSearchPickStore();
const [filterVisible, setFilterVisible] = useState(false);
const { preFetchSearchPicks, reset } = useSearchPickStore();
const toggleFilter = () => {
Expand All @@ -35,9 +34,11 @@ export default function SearchDialog({
<VisuallyHidden>Pick Search</VisuallyHidden>
</DialogPrimitive.Title>
<div className={styles.searchBar}>
{isLoading ? <Loader size={20} /> : <SearchIcon size={20} />}
<SearchInput />
<button className={styles.filterButton} onClick={toggleFilter}>
<button
className={styles.iconButtonContainer}
onClick={toggleFilter}
>
<FilterIcon size={20} />
</button>
</div>
Expand Down
41 changes: 32 additions & 9 deletions frontend/techpick/src/components/Search2/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useCallback, useEffect, useState } from 'react';
import { debounce } from 'es-toolkit';
import { Loader, SearchIcon } from 'lucide-react';
import { useSearchPickStore } from '@/stores/searchPickStore';
import * as styles from './searchDialog.css';

export default function SearchInput() {
const { setSearchQuery } = useSearchPickStore();
const { isLoading, setSearchQuery } = useSearchPickStore();
const [searchQueryInput, setSearchQueryInput] = useState<string>('');

const setDebounceSearchQuery = useCallback(
Expand All @@ -15,24 +16,46 @@ export default function SearchInput() {
[]
);

const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
/**
* @description 검색어 입력 시 검색어를 업데이트하고, 검색어를 디바운싱하여 검색 쿼리를 업데이트합니다.
* 인풋이 변경될 때 자동으로 호출됩니다.
*/
const handleSearchDebounce = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQueryInput(e.target.value);
setDebounceSearchQuery(e.target.value);
};

/**
* @description 검색어를 즉시 검색 쿼리로 업데이트합니다.
* 키보드이벤트, 마우스이벤트로 호출됩니다.
*/
const handleSearch = () => {
setSearchQuery(searchQueryInput);
};

useEffect(() => {
return () => {
setDebounceSearchQuery.cancel();
};
}, [setDebounceSearchQuery]);

return (
<input
type="text"
value={searchQueryInput}
onChange={handleSearch}
placeholder="검색어를 입력하세요"
className={styles.searchInput}
/>
<>
<div className={styles.iconButtonContainer}>
{isLoading ? (
<Loader size={20} />
) : (
<SearchIcon size={20} onClick={handleSearch} />
)}
</div>
<input
type="text"
value={searchQueryInput}
onChange={handleSearchDebounce}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
placeholder="검색어를 입력하세요"
className={styles.searchInput}
/>
</>
);
}
14 changes: 14 additions & 0 deletions frontend/techpick/src/components/Search2/filterOptions.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { style } from '@vanilla-extract/css';
import { colorVars } from 'techpick-shared';

export const filterOptionContainer = style({
display: 'flex',
flexDirection: 'row',
gap: '4px',
alignItems: 'center',
});

export const icon = style({
display: 'flex',
color: colorVars.gray8,
});
6 changes: 6 additions & 0 deletions frontend/techpick/src/components/Search2/hoverCard.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ export const hoverCardDate = style({
color: colorVars.gray10,
fontSize: typography.fontSize.sm,
});

export const hoverCardImage = style({
objectFit: 'contain',
borderRadius: '8px',
backgroundColor: colorVars.gold2,
});
27 changes: 16 additions & 11 deletions frontend/techpick/src/components/Search2/searchDialog.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,24 @@ export const searchListContainer = style({
export const searchBar = style({
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '16px',
paddingBottom: '4px',
marginBottom: '8px',
borderBottom: `1px solid ${colorVars.gray4}`,
});

export const searchIcon = style({
color: '#aaa',
export const iconButtonContainer = style({
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '8px',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: colorVars.gray8,
':hover': {
backgroundColor: colorVars.gray2,
},
});

export const searchInput = style({
Expand All @@ -47,15 +58,9 @@ export const searchInput = style({
fontSize: '14px',
});

export const filterButton = style({
background: 'none',
border: 'none',
cursor: 'pointer',
});

export const filterContainer = style({
gap: '16px',
marginBottom: '16px',
marginBottom: '8px',
});

export const showFilterContainer = style({
Expand Down
21 changes: 19 additions & 2 deletions frontend/techpick/src/hooks/useSearchElementId.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';

export default function useSearchElementId() {
const searchElementId = useSearchParams().get('searchId');
const [isOccurClickEvent, setIsOccerClickEvent] = useState<boolean>(false);

useEffect(() => {
const handleSearchElementIdChange = () => {
Expand All @@ -17,5 +18,21 @@ export default function useSearchElementId() {
handleSearchElementIdChange();
}, [searchElementId]);

return searchElementId;
useEffect(() => {
const handleClick = () => {
setIsOccerClickEvent(true);
};

document.addEventListener('click', handleClick);

return () => {
document.removeEventListener('click', handleClick);
setIsOccerClickEvent(false);
};
}, [searchElementId]);

return {
searchElementId,
isOccurClickEvent,
};
}
6 changes: 6 additions & 0 deletions frontend/techpick/src/utils/checkImageUrlByUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function checkImageUrlByUrl(url: string | undefined): string {
if (!url || !url.trim().startsWith('http')) {
return '/image/default_image.svg';
}
return url;
}
Loading