Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
6.1 kB · 191 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192"use client";import { useRef } from "react";import { isValidHandle } from "@atproto/syntax";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; renderAction?: (submit: () => void, value: string) => React.ReactNode; leading?: React.ReactNode | null; placeholder?: string; className?: string; triggerClassName?: string; large?: boolean; compact?: boolean; loading?: boolean; onSubmit?: ( handle: string, actor?: ActorSuggestion, ) => void | boolean | Promise<void | boolean>; onChange?: (value: string) => void; highlight?: boolean; onFocus?: () => void; validateHandle?: boolean;}) => { let { handleValue, setHandleValue, suggestions, setSuggestions, dropdownOpen, setDropdownOpen, highlighted, setHighlighted, } = useActorTypeahead();
let triggerRef = useRef<HTMLDivElement>(null);
const handleSelect = async (handle?: string) => { let selected = handle ?? handleValue; if (!selected) return; const actor = suggestions.find((s) => s.handle === selected); if (props.validateHandle && !actor) { selected = selected.trim().replace(/^@/, ""); if (!isValidHandle(selected)) { let input = triggerRef.current?.querySelector("input"); input?.setCustomValidity( "Please enter a full handle, like name.bsky.social", ); input?.reportValidity(); return; } } 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 ref={triggerRef} 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 inputMode="url" 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) => { e.currentTarget.setCustomValidity(""); 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(); 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" className="shrink-0 whitespace-nowrap" 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> );};