a tool for shared writing and social publishing

styling mention with scoped to publicaiton

+84 -25
+21
components/Icons/GoBackTiny.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const GoBackTiny = (props: Props) => { 4 + return ( 5 + <svg 6 + width="16" 7 + height="16" 8 + viewBox="0 0 16 16" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M7.40426 3L2.19592 8M2.19592 8L7.40426 13M2.19592 8H13.8041" 14 + stroke="currentColor" 15 + strokeWidth="2" 16 + strokeLinecap="round" 17 + strokeLinejoin="round" 18 + /> 19 + </svg> 20 + ); 21 + };
+63 -25
components/Mention.tsx
··· 9 9 import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 10 10 import { GoBackSmall } from "components/Icons/GoBackSmall"; 11 11 import { SearchTiny } from "components/Icons/SearchTiny"; 12 + import { CloseTiny } from "./Icons/CloseTiny"; 13 + import { GoToArrow } from "./Icons/GoToArrow"; 14 + import { GoBackTiny } from "./Icons/GoBackTiny"; 12 15 13 16 export function MentionAutocomplete(props: { 14 17 open: boolean; ··· 18 21 coords: { top: number; left: number } | null; 19 22 }) { 20 23 const [searchQuery, setSearchQuery] = useState(""); 24 + const [noResults, setNoResults] = useState(false); 21 25 const inputRef = useRef<HTMLInputElement>(null); 22 26 const contentRef = useRef<HTMLDivElement>(null); 23 27 ··· 48 52 setSearchQuery(""); 49 53 setScope({ type: "default" }); 50 54 setSuggestionIndex(0); 55 + setNoResults(false); 51 56 } 52 57 }, [props.open, setScope, setSuggestionIndex]); 58 + 59 + // Handle timeout for showing "No results found" 60 + useEffect(() => { 61 + if (searchQuery && suggestions.length === 0) { 62 + setNoResults(false); 63 + const timer = setTimeout(() => { 64 + setNoResults(true); 65 + }, 2000); 66 + return () => clearTimeout(timer); 67 + } else { 68 + setNoResults(false); 69 + } 70 + }, [searchQuery, suggestions.length]); 53 71 54 72 // Handle keyboard navigation 55 73 const handleKeyDown = (e: React.KeyboardEvent) => { ··· 120 138 121 139 if (!props.open || !props.coords) return null; 122 140 123 - const getHeader = (type: Mention["type"]) => { 141 + const getHeader = (type: Mention["type"], scope?: MentionScope) => { 124 142 switch (type) { 125 143 case "did": 126 144 return "People"; 127 145 case "publication": 128 146 return "Publications"; 129 147 case "post": 130 - return "Posts"; 148 + if (scope) { 149 + return ( 150 + <ScopeHeader 151 + scope={scope} 152 + handleScopeChange={() => { 153 + handleScopeChange({ type: "default" }); 154 + }} 155 + /> 156 + ); 157 + } else return "Posts"; 131 158 } 132 159 }; 133 160 ··· 164 191 max-h-(--radix-popover-content-available-height) 165 192 overflow-y-scroll`} 166 193 > 167 - {/* Search input */} 168 - <div className="flex items-center gap-2 px-2 py-1 border-b group-data-[side=top]/mention-menu:border-b-0 group-data-[side=top]/mention-menu:border-t border-border-light"> 169 - {scope.type === "publication" && ( 170 - <button 171 - className="p-1 rounded hover:bg-accent-light text-tertiary hover:text-accent-contrast shrink-0" 172 - onClick={() => handleScopeChange({ type: "default" })} 173 - onMouseDown={(e) => e.preventDefault()} 174 - > 175 - <GoBackSmall className="w-4 h-4" /> 176 - </button> 177 - )} 178 - {scope.type === "publication" && ( 179 - <span className="text-sm font-bold text-secondary truncate shrink-0 max-w-[100px]"> 180 - {scope.name} 181 - </span> 182 - )} 194 + {/* Dropdown Header */} 195 + <div className="flex flex-col items-center gap-2 px-2 py-1 border-b group-data-[side=top]/mention-menu:border-b-0 group-data-[side=top]/mention-menu:border-t border-border-light"> 183 196 <div className="flex items-center gap-1 flex-1 min-w-0"> 184 197 <SearchTiny className="w-4 h-4 text-tertiary shrink-0" /> 185 198 <input 186 199 ref={inputRef} 200 + size={100} 187 201 type="text" 188 202 value={searchQuery} 189 203 onChange={(e) => { ··· 197 211 ? "Search posts..." 198 212 : "Search people & publications..." 199 213 } 200 - className="flex-1 min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary" 214 + className="flex-1 w-full min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary" 201 215 /> 202 216 </div> 203 217 </div> 204 218 {sortedSuggestions.length === 0 ? ( 205 - <div className="text-sm text-tertiary italic px-3 py-2"> 219 + <div className="text-sm text-tertiary italic px-3 py-1 text-center"> 206 220 {searchQuery 207 - ? "No results found" 221 + ? noResults 222 + ? "No results found..." 223 + : "Searching..." 208 224 : scope.type === "publication" 209 - ? "Type to search posts" 210 - : "Type to search"} 225 + ? "Start typing to search posts" 226 + : "Start typing to search people and publications"} 211 227 </div> 212 228 ) : ( 213 229 <ul className="list-none p-0 text-sm flex flex-col group-data-[side=top]/mention-menu:flex-col-reverse"> 214 230 {sortedSuggestions.map((result, index) => { 215 231 const prevResult = sortedSuggestions[index - 1]; 216 232 const showHeader = 217 - prevResult && prevResult.type !== result.type; 233 + index === 0 || 234 + (prevResult && prevResult.type !== result.type); 218 235 219 236 return ( 220 237 <Fragment ··· 226 243 <hr className="border-border-light mx-1 my-1" /> 227 244 )} 228 245 <div className="text-xs text-tertiary font-bold pt-1 px-2"> 229 - {getHeader(result.type)} 246 + {getHeader(result.type, scope)} 230 247 </div> 231 248 </> 232 249 )} ··· 387 404 selected={props.selected} 388 405 /> 389 406 ); 407 + }; 408 + 409 + const ScopeHeader = (props: { 410 + scope: MentionScope; 411 + handleScopeChange: () => void; 412 + }) => { 413 + if (props.scope.type === "default") return; 414 + if (props.scope.type === "publication") 415 + return ( 416 + <button 417 + className="w-full flex flex-row gap-2 pt-1 rounded text-tertiary hover:text-accent-contrast shrink-0 text-xs" 418 + onClick={() => props.handleScopeChange()} 419 + onMouseDown={(e) => e.preventDefault()} 420 + > 421 + <GoBackTiny className="shrink-0 " /> 422 + 423 + <div className="grow w-full truncate text-left"> 424 + Posts from {props.scope.name} 425 + </div> 426 + </button> 427 + ); 390 428 }; 391 429 392 430 export type Mention =