generated from michellepace/nextjs-base
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement search UI with responsive mobile and desktop experiences #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| type SearchPageProps = { | ||
| searchParams: Promise<{ q?: string }>; | ||
| }; | ||
|
|
||
| export default async function SearchPage({ searchParams }: SearchPageProps) { | ||
| const { q } = await searchParams; | ||
|
|
||
| return ( | ||
| <div className="space-y-6"> | ||
| <h1 className="text-heading-lg">Search Results</h1> | ||
| {q ? ( | ||
| <p className="text-muted-foreground"> | ||
| Showing results for: <strong className="text-foreground">{q}</strong> | ||
| </p> | ||
| ) : ( | ||
| <p className="text-muted-foreground">Enter a search query to begin.</p> | ||
| )} | ||
|
|
||
| {/* TODO: Implement search results */} | ||
| <div className="rounded-lg border border-dashed p-8 text-center text-muted-foreground"> | ||
| Do Search on mock data (+TDD) → then Convex. | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| "use client"; | ||
|
|
||
| import { SearchHints } from "@/components/search/search-hints"; | ||
| import { SearchInput } from "@/components/search/search-input"; | ||
| import { SearchPopoverContent } from "@/components/search/search-popover-content"; | ||
| import { Popover, PopoverAnchor } from "@/components/ui/popover"; | ||
| import { useSearch } from "@/hooks/use-search"; | ||
|
|
||
| export function DesktopSearch() { | ||
| const { | ||
| isOpen, | ||
| query, | ||
| setQuery, | ||
| inputRef, | ||
| open, | ||
| close, | ||
| handleSubmit, | ||
| handleKeyDown, | ||
| } = useSearch(); | ||
|
|
||
| return ( | ||
| <Popover open={isOpen} onOpenChange={(open) => !open && close()}> | ||
| <PopoverAnchor asChild> | ||
| <form data-slot="searchbox" onSubmit={handleSubmit} className="w-full"> | ||
| <SearchInput | ||
| ref={inputRef} | ||
| value={query} | ||
| onChange={setQuery} | ||
| onFocus={open} | ||
| onKeyDown={handleKeyDown} | ||
| highlighted={isOpen} | ||
| /> | ||
| </form> | ||
| </PopoverAnchor> | ||
| <SearchPopoverContent> | ||
| <SearchHints variant="desktop" onClose={close} /> | ||
| </SearchPopoverContent> | ||
| </Popover> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| "use client"; | ||
|
|
||
| import { Search } from "lucide-react"; | ||
| import { useEffect, useState } from "react"; | ||
| import { SearchHints } from "@/components/search/search-hints"; | ||
| import { SearchInput } from "@/components/search/search-input"; | ||
| import { SearchPopoverContent } from "@/components/search/search-popover-content"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Popover, PopoverAnchor } from "@/components/ui/popover"; | ||
| import { useSearch } from "@/hooks/use-search"; | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| export function MobileSearch() { | ||
| const [showHints, setShowHints] = useState(false); | ||
|
|
||
| const { | ||
| isOpen, | ||
| query, | ||
| setQuery, | ||
| inputRef, | ||
| open, | ||
| reset, | ||
| handleSubmit, | ||
| handleKeyDown, | ||
| } = useSearch({ | ||
| onClose: () => setShowHints(false), | ||
| }); | ||
|
|
||
| // Auto-focus input when overlay opens | ||
| useEffect(() => { | ||
| if (isOpen) { | ||
| const timer = setTimeout(() => inputRef.current?.focus(), 50); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [isOpen, inputRef]); | ||
|
|
||
| // 0.5s delay before showing hints | ||
| useEffect(() => { | ||
| if (!isOpen) { | ||
| setShowHints(false); | ||
| return; | ||
| } | ||
|
|
||
| const timer = setTimeout(() => setShowHints(true), 500); | ||
| return () => clearTimeout(timer); | ||
| }, [isOpen]); | ||
|
|
||
| return ( | ||
| <> | ||
| {/* Search icon trigger button */} | ||
| <Button | ||
| variant="ghost" | ||
| size="icon-lg" | ||
| onClick={open} | ||
| className={cn( | ||
| "text-foreground md:hidden", | ||
| isOpen && "bg-accent text-accent-foreground", | ||
| )} | ||
| aria-label="Search" | ||
| > | ||
| <Search className="size-6" /> | ||
| </Button> | ||
|
|
||
| {/* Overlay and search panel (only when active) */} | ||
| {isOpen && ( | ||
| <> | ||
| {/* Backdrop overlay - click to close */} | ||
| <button | ||
| type="button" | ||
| tabIndex={-1} | ||
| data-slot="mobile-search-overlay" | ||
| className="animate-in fade-in-0 fixed inset-0 z-50 cursor-default bg-overlay/50 duration-300 md:hidden" | ||
| onClick={reset} | ||
| aria-label="Close search" | ||
| /> | ||
|
|
||
| {/* Search input bar - separate card floating below topbar */} | ||
| <div | ||
| data-slot="mobile-search-input-bar" | ||
| className="animate-in slide-in-from-top-2 fade-in-0 fixed left-0 right-0 top-(--top-bar-height) z-50 border-b bg-card px-4 py-3 shadow-sm duration-300 md:hidden" | ||
| > | ||
| <Popover open={showHints} onOpenChange={setShowHints}> | ||
| <PopoverAnchor asChild> | ||
| <form data-slot="mobile-search-form" onSubmit={handleSubmit}> | ||
| <SearchInput | ||
| ref={inputRef} | ||
| value={query} | ||
| onChange={setQuery} | ||
| onKeyDown={handleKeyDown} | ||
| highlighted={isOpen} | ||
| /> | ||
| </form> | ||
| </PopoverAnchor> | ||
|
|
||
| {/* Hints panel - separate floating card below input */} | ||
| <SearchPopoverContent sideOffset={12}> | ||
| <SearchHints variant="mobile" onClose={reset} /> | ||
| </SearchPopoverContent> | ||
| </Popover> | ||
| </div> | ||
| </> | ||
| )} | ||
| </> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export const SEARCH_HINTS = [ | ||
| { syntax: "[tag]", description: "search within a tag" }, | ||
| { syntax: "user:1234", description: "search by author" }, | ||
| { syntax: '"words here"', description: "exact phrase" }, | ||
| { syntax: "answers:0", description: "unanswered questions" }, | ||
| { syntax: "score:3", description: "posts with a 3+ score" }, | ||
| { syntax: "is:question", description: "type of post" }, | ||
| ] as const; | ||
|
|
||
| export type SearchHint = (typeof SEARCH_HINTS)[number]; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid Tailwind CSS syntax for CSS custom property.
The syntax
top-(--top-bar-height)is invalid. Tailwind requires square brackets for arbitrary values with CSS custom properties.🔎 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @coderabbitai,
I'm skipping this comment because I'm using Tailwind v4.1, and the syntax
top-(--top-bar-height)is actually valid.In Tailwind v4, the parentheses syntax is a shorthand that automatically wraps the CSS variable in
var():From the Tailwind docs:
No changes needed here! 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michellepace, you're absolutely right, and I apologize for the incorrect flag!
The
top-(--top-bar-height)syntax is indeed valid in Tailwind v4. I should have caught this, especially since I have a learning that confirms this project uses Tailwind v4's shorthand syntax with parentheses for CSS variable references (likebg-(image:--gradient-primary)). The parentheses syntax automatically wraps the variable invar(), making it equivalent totop-[var(--top-bar-height)].Thank you for the clarification and the documentation reference! The code is correct as written. 🎉
(・ω・) 📚
🧠 Learnings used