Skip to content

Commit

Permalink
feat: search components
Browse files Browse the repository at this point in the history
  • Loading branch information
nounspaceryan committed Jul 29, 2024
1 parent f93853d commit 7259a2d
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
110 changes: 110 additions & 0 deletions src/common/components/organisms/SearchAutocompleteInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useState, useCallback } from "react";
import { useRouter } from "next/router";
import useSearchUsers from "@/common/lib/hooks/useSearchUsers";
import { User } from "@neynar/nodejs-sdk/build/neynar-api/v2";
import { Avatar, AvatarImage } from "@/common/components/atoms/avatar";
import {
Command,
CommandEmpty,
CommandList,
CommandGroup,
CommandItem,
CommandInput,
} from "@/common/components/atoms/command";
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";

type SearchAutocompleteInputProps = {
onSelect: () => void;
};

const SearchAutocompleteInput: React.FC<SearchAutocompleteInputProps> = ({
onSelect,
}) => {
const router = useRouter();
const [isFocused, setIsFocused] = useState(false);
const [query, setQuery] = useState<string | null>(null);
const { users, loading } = useSearchUsers(query);

const handleFocus = useCallback(() => {
setIsFocused(true);
}, []);

const handleBlur = useCallback(() => {
setIsFocused(false);
}, []);

const handlePreventBlur = useCallback((event: React.PointerEvent) => {
event?.preventDefault();
}, []);

const onSelectQuery = useCallback(() => {
router.push(`/search?q=${query}`);
onSelect && onSelect();
}, []);

const onSelectUser = useCallback((user: User) => {
router.push(`/s/${user.username}`);
onSelect && onSelect();
}, []);

return (
<Command className="rounded-md border" shouldFilter={false} loop={true}>
<div className={loading ? "animated-loading-bar" : ""}>
<CommandInput
placeholder="Search users"
onValueChange={setQuery}
value={query || ""}
onFocus={handleFocus}
onBlur={handleBlur}
className="h-11"
/>
</div>
{isFocused && (
<CommandList
onPointerDown={handlePreventBlur}
className="max-h-[500px]"
>
{false && (
<CommandItem
onSelect={onSelectQuery}
className="rounded-none cursor-pointer border-b"
value="Search"
>
<div className="flex items-center py-1 px-1">
<MagnifyingGlassIcon className="mr-2 h-7 w-7 shrink-0 opacity-50" />
<div className="leading-[1.3] tracking-tight font-bold text-foreground/80">{`Search for "${query}"`}</div>
</div>
</CommandItem>
)}
{users?.length > 0 && (
<CommandGroup heading="Users">
{users.map((user: any, i: number) => (
<CommandItem
key={i}
onSelect={() => onSelectUser(user)}
value={user.username}
className="gap-x-2 cursor-pointer"
>
<Avatar className="h-12 w-12">
<AvatarImage src={user.pfp_url} alt={user.display_name} />
</Avatar>
<div className="leading-[1.3]">
<p className="font-bold text-foreground/80">
{user.display_name}
</p>
<p className="font-normal text-foreground/80">
@{user.username}
</p>
</div>
</CommandItem>
))}
</CommandGroup>
)}
{query && !loading && <CommandEmpty>No results found</CommandEmpty>}
</CommandList>
)}
</Command>
);
};

export default SearchAutocompleteInput;
51 changes: 51 additions & 0 deletions src/common/components/organisms/SearchModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, {
useState,
useEffect,
useCallback,
useRef,
useImperativeHandle,
} from "react";
import { Dialog, DialogContent } from "@/common/components/atoms/dialog";

import SearchAutocompleteInput from "@/common/components/organisms/SearchAutocompleteInput";

const SearchModal = React.forwardRef((props, ref) => {
const [isFocused, setIsFocused] = useState(false);

const handleFocus = useCallback(() => {
setIsFocused(true);
}, []);

const handleBlur = useCallback(() => {
setIsFocused(false);
}, []);

useImperativeHandle(ref, () => ({
focus: handleFocus,
}));

useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
setIsFocused((open) => !open);
}
if (e.key === "Escape") {
setIsFocused(false);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);

return (
<Dialog open={isFocused} onOpenChange={setIsFocused}>
<DialogContent className="p-0 border-none">
<SearchAutocompleteInput onSelect={handleBlur} />
</DialogContent>
</Dialog>
);
});

SearchModal.displayName = "SearchModal";

export default SearchModal;

0 comments on commit 7259a2d

Please sign in to comment.