Skip to content

Commit

Permalink
Make the search input better.
Browse files Browse the repository at this point in the history
  • Loading branch information
ivasilov committed Jan 7, 2025
1 parent 9b292d4 commit a3267d3
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 57 deletions.
2 changes: 1 addition & 1 deletion apps/web/src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CircleUser } from 'lucide-react'
import Link from 'next/link'
import { PropsWithChildren, Suspense } from 'react'
import { LayoutDropdownMenuContent } from './LayoutDropdownMenuContent'
import SearchInput from './search-input'
import { SearchInput } from './search-input'

export default async function RootLayout({ children }: PropsWithChildren<{}>) {
return (
Expand Down
53 changes: 0 additions & 53 deletions apps/web/src/app/(dashboard)/search-input.tsx

This file was deleted.

24 changes: 24 additions & 0 deletions apps/web/src/app/(dashboard)/search-input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use server'

import { createClient } from '@/src/utils/supabase/server'
import { cookies } from 'next/headers'
import { SearchInputInner } from './inner'

export const SearchInput = async () => {
const cookieStore = cookies()
const supabase = createClient(cookieStore)

const { data: tags } = await supabase.from('tags').select('*').order('name', { ascending: false })

if (!tags) {
return null
}

return (
<div className="w-full flex-1">
<div className="relative">
<SearchInputInner tags={tags} />
</div>
</div>
)
}
114 changes: 114 additions & 0 deletions apps/web/src/app/(dashboard)/search-input/inner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use client'

import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
Popover,
PopoverContent,
PopoverTrigger,
cn,
} from '@rememr/ui'
import { useRouter } from 'next/navigation'
import { useQueryState } from 'nuqs'
import { useCallback, useEffect, useRef, useState } from 'react'

export const SearchInputInner = ({ tags }: { tags: { id: string; name: string }[] }) => {
const router = useRouter()
const [searchQuery, setSearchQuery] = useQueryState('q')
const [searchTerm, setSearchTerm] = useState(searchQuery)
const [open, setOpen] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const appendSearchParam = useCallback(
(value: string | null) => {
if (value === searchQuery) {
return
}

if (value && value.length > 0) {
setSearchQuery(value, { shallow: false, scroll: false })
} else {
setSearchQuery(null, { shallow: false, scroll: false })
}
},
[searchQuery, setSearchQuery],
)

const filteredTags = tags.filter(tag => tag.name.includes(searchTerm ?? '')).slice(0, 4)

// Focus the input when the user presses cmd+k
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
inputRef.current?.focus()
}
}

document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [])

return (
<div className="w-full flex-1">
<div className="relative">
<Command
shouldFilter={false}
className={cn(`transition-width duration-300 ease-in-out`, open ? 'md:w-full lg:w-2/3' : `md:w-2/3 lg:w-1/3`)}
>
<CommandInput
placeholder="Search bookmarks..."
ref={inputRef}
className="shadow-none"
onValueChange={setSearchTerm}
value={searchTerm ?? ''}
onFocus={() => setOpen(true)}
onBlur={() => setOpen(false)}
/>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger></PopoverTrigger>
<PopoverContent className="w-[200px] p-0" onOpenAutoFocus={e => e.preventDefault()} sameWidthAsTrigger>
<CommandList>
{(searchTerm || '').length > 0 && (
<CommandGroup heading="Bookmarks">
<CommandItem
onSelect={() => {
appendSearchParam(searchTerm)
setSearchTerm('')
setOpen(false)
}}
>
Search the bookmarks for &ldquo;{searchTerm}&rdquo;...
</CommandItem>
</CommandGroup>
)}
{filteredTags.length > 0 && (
<>
<CommandSeparator />
<CommandGroup heading="Tags">
{filteredTags.map(tag => (
<CommandItem
key={tag.id}
onSelect={() => {
router.push(`/tags/${tag.id}`)
setSearchTerm('')
setOpen(false)
}}
>
{tag.name}
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</PopoverContent>
</Popover>
</Command>
</div>
</div>
)
}
5 changes: 5 additions & 0 deletions packages/ui/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@
@apply bg-background text-foreground;
}
}

/* make the popover content width the same as the trigger width */
.popover-trigger-width {
width: var(--radix-popover-trigger-width);
}
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
Expand Down
37 changes: 37 additions & 0 deletions packages/ui/src/components/popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

import * as PopoverPrimitive from '@radix-ui/react-popover'
import * as React from 'react'

import { cn } from '../utils'

const Popover = PopoverPrimitive.Root

const PopoverTrigger = PopoverPrimitive.Trigger

const PopoverAnchor = PopoverPrimitive.Anchor

type PopoverContentProps = {
sameWidthAsTrigger?: boolean
} & React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>

const PopoverContent = React.forwardRef<React.ElementRef<typeof PopoverPrimitive.Content>, PopoverContentProps>(
({ className, align = 'center', sideOffset = 4, sameWidthAsTrigger = false, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
sameWidthAsTrigger ? 'popover-trigger-width' : '',
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
),
)
PopoverContent.displayName = PopoverPrimitive.Content.displayName

export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './components/dropdown-menu'
export * from './components/form'
export * from './components/input'
export * from './components/label'
export * from './components/popover'
export * from './components/progress'
export * from './components/sheet'
export * from './components/sidebar'
Expand Down
Loading

0 comments on commit a3267d3

Please sign in to comment.