feat : remove notice search text [no ci]
This commit is contained in:
@@ -14,11 +14,11 @@ Use this project’s shared search implementation for finding entities by name o
|
|||||||
- Restrict fuzzy candidates to names, aliases, and individual whitespace-separated words. Keep overview prose and internal keys available for existing direct searches, but exclude them from fuzzy candidates.
|
- Restrict fuzzy candidates to names, aliases, and individual whitespace-separated words. Keep overview prose and internal keys available for existing direct searches, but exclude them from fuzzy candidates.
|
||||||
- Normalize with NFC, trim, lowercase, and compare Unicode code points. Keep queries shorter than four code points strict.
|
- Normalize with NFC, trim, lowercase, and compare Unicode code points. Keep queries shorter than four code points strict.
|
||||||
- Use bounded optimal-string-alignment Damerau–Levenshtein distance, including adjacent transpositions. Allow one edit for 4–7 code points and two for longer queries, capped at 25% of query length. Rank by edit distance with stable source-order ties.
|
- Use bounded optimal-string-alignment Damerau–Levenshtein distance, including adjacent transpositions. Allow one edit for 4–7 code points and two for longer queries, capped at 25% of query length. Rank by edit distance with stable source-order ties.
|
||||||
- Empty queries return the eligible collection. Show the localized close-match notice only when fuzzy fallback returns results.
|
- Empty queries return the eligible collection. Display fuzzy results without an explanatory message.
|
||||||
|
|
||||||
## Reuse the project implementation
|
## Reuse the project implementation
|
||||||
|
|
||||||
Use `searchItems` from `lib/search.ts`, returning `{ items, fuzzy }`. Supply separate selectors for existing direct-search text and fuzzy candidate names. Use `SearchNotice` from `components/search-notice.tsx` for fallback feedback.
|
Use `searchItems` from `lib/search.ts`, returning `{ items, fuzzy }`. Supply separate selectors for existing direct-search text and fuzzy candidate names.
|
||||||
|
|
||||||
Use `searchTeams` from `lib/guides/team-search.ts` for collection-level team searches. Apply active groups, permissions, and category restrictions before matching. Explicit DPS sorting takes precedence over fuzzy ranking. Direct team queries must match within one candidate name, not span multiple members.
|
Use `searchTeams` from `lib/guides/team-search.ts` for collection-level team searches. Apply active groups, permissions, and category restrictions before matching. Explicit DPS sorting takes precedence over fuzzy ranking. Direct team queries must match within one candidate name, not span multiple members.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { searchItems } from "@/lib/search";
|
import { searchItems } from "@/lib/search";
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -80,7 +79,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
|
|||||||
<InputGroupAddon><SearchIcon /></InputGroupAddon>
|
<InputGroupAddon><SearchIcon /></InputGroupAddon>
|
||||||
<InputGroupInput value={query} onChange={(event) => setQuery(event.target.value)} placeholder="ค้นหาตัวละคร…" aria-label="ค้นหาตัวละคร" />
|
<InputGroupInput value={query} onChange={(event) => setQuery(event.target.value)} placeholder="ค้นหาตัวละคร…" aria-label="ค้นหาตัวละคร" />
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<SearchNotice fuzzy={search.fuzzy} />
|
|
||||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{filtered.map((character) => {
|
{filtered.map((character) => {
|
||||||
const disabled = existing.has(character.key);
|
const disabled = existing.has(character.key);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { searchItems } from "@/lib/search";
|
import { searchItems } from "@/lib/search";
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { SearchIcon, Trash2Icon } from "lucide-react";
|
import { SearchIcon, Trash2Icon } from "lucide-react";
|
||||||
@@ -146,7 +145,7 @@ export function GlossaryManager({
|
|||||||
<Input id="glossary-search" className="pl-9" value={query} onChange={(event) => setQuery(event.target.value)} />
|
<Input id="glossary-search" className="pl-9" value={query} onChange={(event) => setQuery(event.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
<SearchNotice fuzzy={search.fuzzy} />
|
|
||||||
{visibleAliases.map((entry) => {
|
{visibleAliases.map((entry) => {
|
||||||
const name = itemNames.get(`${entry.kind}:${entry.catalogKey}`);
|
const name = itemNames.get(`${entry.kind}:${entry.catalogKey}`);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { searchItems } from "@/lib/search";
|
import { searchItems } from "@/lib/search";
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -3340,7 +3339,7 @@ function TeamEditor({
|
|||||||
<FieldGroup className="mt-4">
|
<FieldGroup className="mt-4">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel htmlFor="team-search">ค้นหา Team</FieldLabel>
|
<FieldLabel htmlFor="team-search">ค้นหา Team</FieldLabel>
|
||||||
<SearchNotice fuzzy={teamSearch.fuzzy} />
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<SearchIcon
|
<SearchIcon
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -4094,7 +4093,7 @@ function TeamEditor({
|
|||||||
<ToggleGroupItem value="artifact">อาร์ติแฟกต์</ToggleGroupItem>
|
<ToggleGroupItem value="artifact">อาร์ติแฟกต์</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
<div className="min-h-0 overflow-y-auto pr-1">
|
<div className="min-h-0 overflow-y-auto pr-1">
|
||||||
<SearchNotice fuzzy={importTeamSearch.fuzzy} />
|
|
||||||
{filteredImportTeams.length ? (
|
{filteredImportTeams.length ? (
|
||||||
<div className="grid gap-4 lg:grid-cols-2">
|
<div className="grid gap-4 lg:grid-cols-2">
|
||||||
{filteredImportTeams.map((team) => {
|
{filteredImportTeams.map((team) => {
|
||||||
@@ -4203,7 +4202,7 @@ function TeamEditor({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 overflow-y-auto pr-1">
|
<div className="min-h-0 overflow-y-auto pr-1">
|
||||||
<SearchNotice fuzzy={importGuideSearch.fuzzy} />
|
|
||||||
{filteredImportGuides.length ? (
|
{filteredImportGuides.length ? (
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{filteredImportGuides.map((sourceGuide) => (
|
{filteredImportGuides.map((sourceGuide) => (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { searchItems } from "@/lib/search";
|
import { searchItems } from "@/lib/search";
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -213,7 +212,7 @@ export function StructuredDashboard({
|
|||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
) : null}
|
) : null}
|
||||||
<SearchNotice fuzzy={search.fuzzy} />
|
|
||||||
{filteredGuides.length ? (
|
{filteredGuides.length ? (
|
||||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
|
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
|
||||||
{filteredGuides.map((guide) => (
|
{filteredGuides.map((guide) => (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { searchItems } from "@/lib/search";
|
import { searchItems } from "@/lib/search";
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -59,7 +58,7 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
|
|||||||
aria-label="ค้นหาไกด์"
|
aria-label="ค้นหาไกด์"
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<SearchNotice fuzzy={search.fuzzy} />
|
|
||||||
{filtered.length ? (
|
{filtered.length ? (
|
||||||
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{filtered.map((guide, index) => (
|
{filtered.map((guide, index) => (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SearchNotice } from "@/components/search-notice";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -395,7 +394,7 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-0 flex-col gap-4">
|
<div className="flex min-w-0 flex-col gap-4">
|
||||||
<SearchNotice fuzzy={search.fuzzy} />
|
|
||||||
{groups.length > 1 ? (
|
{groups.length > 1 ? (
|
||||||
<ScrollableNav ariaLabel="เลือกเงื่อนไขทีม" className="w-full min-w-0">
|
<ScrollableNav ariaLabel="เลือกเงื่อนไขทีม" className="w-full min-w-0">
|
||||||
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[activeCondition]} onValueChange={(values) => {
|
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[activeCondition]} onValueChange={(values) => {
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export function SearchNotice({ fuzzy }: { fuzzy: boolean }) {
|
|
||||||
return fuzzy ? <p role="status" className="px-2 py-1 text-sm text-muted-foreground">แสดงผลลัพธ์ที่ใกล้เคียง</p> : null;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { searchItems } from "@/lib/search"
|
import { searchItems } from "@/lib/search"
|
||||||
import { SearchNotice } from "@/components/search-notice"
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
@@ -16,7 +15,6 @@ import {
|
|||||||
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
|
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
const Combobox = ComboboxPrimitive.Root
|
const Combobox = ComboboxPrimitive.Root
|
||||||
const FuzzyContext = React.createContext(false)
|
|
||||||
|
|
||||||
function FuzzyCombobox<Value, Multiple extends boolean | undefined = false>({
|
function FuzzyCombobox<Value, Multiple extends boolean | undefined = false>({
|
||||||
items = [], searchNames, searchText, children, onInputValueChange, ...props
|
items = [], searchNames, searchText, children, onInputValueChange, ...props
|
||||||
@@ -30,14 +28,12 @@ function FuzzyCombobox<Value, Multiple extends boolean | undefined = false>({
|
|||||||
searchText ?? ((item) => searchNames(item).join(" ")), searchNames),
|
searchText ?? ((item) => searchNames(item).join(" ")), searchNames),
|
||||||
[items, query, searchNames, searchText])
|
[items, query, searchNames, searchText])
|
||||||
return (
|
return (
|
||||||
<FuzzyContext.Provider value={result.fuzzy}>
|
|
||||||
<ComboboxPrimitive.Root {...props} items={result.items} filter={null}
|
<ComboboxPrimitive.Root {...props} items={result.items} filter={null}
|
||||||
onInputValueChange={(value, details) => {
|
onInputValueChange={(value, details) => {
|
||||||
setQuery(details.reason === "input-change" ? value : "")
|
setQuery(details.reason === "input-change" ? value : "")
|
||||||
onInputValueChange?.(value, details)
|
onInputValueChange?.(value, details)
|
||||||
}}
|
}}
|
||||||
>{children}</ComboboxPrimitive.Root>
|
>{children}</ComboboxPrimitive.Root>
|
||||||
</FuzzyContext.Provider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +120,6 @@ function ComboboxContent({
|
|||||||
ComboboxPrimitive.Positioner.Props,
|
ComboboxPrimitive.Positioner.Props,
|
||||||
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
||||||
>) {
|
>) {
|
||||||
const fuzzy = React.useContext(FuzzyContext)
|
|
||||||
return (
|
return (
|
||||||
<ComboboxPrimitive.Portal>
|
<ComboboxPrimitive.Portal>
|
||||||
<ComboboxPrimitive.Positioner
|
<ComboboxPrimitive.Positioner
|
||||||
@@ -141,7 +136,7 @@ function ComboboxContent({
|
|||||||
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SearchNotice fuzzy={fuzzy} />
|
|
||||||
{children}
|
{children}
|
||||||
</ComboboxPrimitive.Popup>
|
</ComboboxPrimitive.Popup>
|
||||||
</ComboboxPrimitive.Positioner>
|
</ComboboxPrimitive.Positioner>
|
||||||
|
|||||||
Reference in New Issue
Block a user