Something went wrong. Try again.
leaflet fork for copy edit / bug fixes forked from leaflet.pub/leaflet
Something went wrong. Try again.
6.4 kB · 185 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186"use client";import { Input } from "components/Input";import { Combobox, ComboboxResult } from "components/Combobox";import { useActorTypeahead, type ActorSuggestion,} from "src/hooks/useActorTypeahead";import { DotLoader } from "components/utils/DotLoader";import { Avatar } from "components/Avatar";import { theme } from "tailwind.config";import { AtmosphereAccount } from "components/Icons/AtmosphereAccount";import { INPUT_HIGHLIGHT_CLASS } from "./Subscribe/inputHighlight";
export const HandleSearchInput = (props: { autoFocus?: boolean; action?: React.ReactNode; // For callers that need a real button component (not the plain wrapper the // `action` node gets); receives the submit callback and the current value. renderAction?: (submit: () => void, value: string) => React.ReactNode; // `null` renders no leading slot; undefined falls back to the Atmosphere icon. leading?: React.ReactNode | null; placeholder?: string; className?: string; triggerClassName?: string; large?: boolean; compact?: boolean; loading?: boolean; // Called with the typed or selected handle; `actor` is present when the value // came from a suggestion. Resolving `true` clears the input (e.g. after a // successful invite). onSubmit?: ( handle: string, actor?: ActorSuggestion, ) => void | boolean | Promise<void | boolean>; // Fires on every keystroke and on suggestion select, for callers that stash // the handle instead of submitting (e.g. the paid join modal). onChange?: (value: string) => void; // Flags the input as needing attention (e.g. a tier was picked before a // handle was entered); cleared on focus via onFocus. highlight?: boolean; onFocus?: () => void;}) => { let { handleValue, setHandleValue, suggestions, setSuggestions, dropdownOpen, setDropdownOpen, highlighted, setHighlighted, } = useActorTypeahead();
const handleSelect = async (handle?: string) => { const selected = handle ?? handleValue; if (!selected) return; const actor = suggestions.find((s) => s.handle === selected); setHandleValue(selected); setDropdownOpen(false); setSuggestions([]); setHighlighted(undefined); props.onChange?.(selected); let submitted = await props.onSubmit?.(selected, actor); if (submitted) setHandleValue(""); };
const handles = suggestions.map((s) => s.handle); const leading = props.leading === undefined ? <AtmosphereAccount /> : props.leading;
return ( <Combobox open={dropdownOpen && !props.loading} onOpenChange={(open) => { if (!open) { setDropdownOpen(false); setHighlighted(undefined); } }} results={handles} highlighted={highlighted} setHighlighted={setHighlighted} onSelect={() => handleSelect(highlighted)} zIndex={60} sideOffset={4} triggerClassName={props.triggerClassName} className="w-(--radix-popover-trigger-width)!" trigger={ <div className={`handleInput input-with-border relative py-0! flex items-center gap-2 w-full ${props.large && "px-2!"} ${props.highlight ? INPUT_HIGHLIGHT_CLASS : ""} ${props.className}`} style={ props.loading ? { backgroundColor: theme.colors["border-light"], color: theme.colors.tertiary, } : { backgroundColor: theme.colors["bg-page"], color: theme.colors.primary, } } > {leading && ( <div className="text-tertiary text-center shrink-0 flex justify-end h-full items-center"> {leading} </div> )} <Input autoFocus={props.autoFocus} className={`appearance-none! grow outline-none! min-w-0 ${props.large ? "py-1!" : props.compact ? "py-0!" : "py-0.5"}`} placeholder={props.placeholder ?? "atmosphere.handle"} size={0} value={handleValue} onChange={(e) => { setHandleValue(e.target.value); props.onChange?.(e.target.value); }} onFocus={props.onFocus} onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); // handle Enter here (with the current handleValue) instead of // relying on the Combobox's window listener, which only submits // when a suggestion is highlighted. stopPropagation keeps that // listener from also firing with a stale value. e.stopPropagation(); handleSelect(highlighted); } }} autoComplete="off" disabled={props.loading} /> {props.renderAction ? ( props.renderAction(() => handleSelect(), handleValue) ) : props.loading ? ( <DotLoader /> ) : props.onSubmit && props.action ? ( <button type="button" onClick={(e) => { e.stopPropagation(); handleSelect(); }} disabled={!handleValue} > {props.action} </button> ) : ( props.action )} </div> } > {suggestions.map((actor) => ( <ComboboxResult key={actor.did} result={actor.handle} highlighted={highlighted} setHighlighted={setHighlighted} onSelect={() => handleSelect(actor.handle)} className=" flex-row! gap-2! leading-snug text-sm" > <Avatar src={actor.avatar} displayName={actor.displayName || actor.handle} size="medium" className="mr-2" /> <div className="flex flex-col min-w-0 flex-1 text-left"> <div className="truncate font-bold"> {actor.displayName || actor.handle} </div> {actor.displayName && ( <div className="text-tertiary text-xs italic truncate"> @{actor.handle} </div> )} </div> </ComboboxResult> ))} </Combobox> );};