File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-14
08-18
08-18
File name
Commit message
Commit date
"use client";
import { useState, type FormEvent } from "react";
import { cx } from "../../utils";
import { FoxSelectText } from "../fox-select-text";
import type { FoxSelectItem } from "../fox-select";
export interface FoxListSearchProps {
/** 앞쪽 셀렉트에 넣을 검색 대상. 비우면 셀렉트 자체가 나오지 않는다. */
fields?: FoxSelectItem[];
/** 선택된 검색 대상(제어). */
field?: string;
onFieldChange?: (field: string) => void;
/** 입력값(제어). 주지 않으면 컴포넌트가 스스로 들고 있다. */
keyword?: string;
onKeywordChange?: (keyword: string) => void;
/** 돋보기를 누르거나 Enter를 쳤을 때. 현재 검색어와 대상을 함께 넘긴다. */
onSearch?: (keyword: string, field?: string) => void;
placeholder?: string;
/** 글자가 없으므로 이 값이 검색 버튼의 접근성 이름이 된다. */
label?: string;
disabled?: boolean;
hidden?: boolean;
className?: string;
}
/**
* @fox 목록 검색 상자 — 시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) list-toolbar의 search
*
* 마크업 계약 (React 밖 소비자용):
* <form class="fox-list-search">
* <div class="fox-list-search__box">
* <span class="fox-list-search__field">…select-txt…</span> <!-- 선택 -->
* <input class="fox-list-search__input">
* <button class="fox-list-search__submit">…svg…</button>
* </div>
* </form>
*
* `FoxInput`을 쓰지 않는다 — 시안은 입력칸 **앞쪽**에 검색 대상 셀렉트가 들어가는데 FoxInput에
* 그 슬롯이 없다. 크기·radius·테두리는 시안이 지정한 값이 FoxInput md와 같지만, 좌우 여백만
* `form/padding/sm`으로 더 좁다(안에 셀렉트와 버튼이 들어가기 때문이다).
*
* `<form>`이라 Enter가 그대로 제출이 된다 — 키 이벤트를 따로 듣지 않는다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxListSearch({
fields,
field,
onFieldChange,
keyword,
onKeywordChange,
onSearch,
placeholder = "검색어를 입력하세요.",
label = "검색",
disabled = false,
hidden = false,
className,
}: FoxListSearchProps) {
const [innerKeyword, setInnerKeyword] = useState(keyword ?? "");
const controlled = keyword !== undefined;
const value = controlled ? keyword : innerKeyword;
if (hidden) {
return null;
}
function handleChange(next: string) {
if (!controlled) {
setInnerKeyword(next);
}
onKeywordChange?.(next);
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
onSearch?.(value, field);
}
return (
<form
className={cx("fox-list-search", className)}
role="search"
onSubmit={handleSubmit}
>
<div className="fox-list-search__box">
{fields && fields.length > 0 && (
<span className="fox-list-search__field">
<FoxSelectText
size="sm"
options={fields}
value={field}
onValueChange={onFieldChange}
ariaLabel="검색 대상"
disabled={disabled}
/>
</span>
)}
<input
className="fox-list-search__input"
type="search"
value={value}
placeholder={placeholder}
disabled={disabled}
aria-label={placeholder}
onChange={(event) => handleChange(event.target.value)}
/>
{/* 시안의 이 버튼은 btn-ico 인스턴스가 아니라 로컬 프레임(32×32)이라 FoxIconButton의
크기 체계(24·28·40·56)와 맞지 않는다. 그래서 직접 그린다. */}
<button
className="fox-list-search__submit"
type="submit"
aria-label={label}
disabled={disabled}
>
<i className="fox-ico fox-ico-MagnifyingGlass" aria-hidden="true" />
</button>
</div>
</form>
);
}