responsiveness and ui fixers
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
import { ArrowLeft, Bell, Boxes, Building2, FileText, LogOut, Receipt, Search, Settings, User } from "lucide-react"
|
||||
import { ArrowLeft, Bell, Boxes, Building2, FileText, LogOut, Receipt, Search, Settings, User, X } from "lucide-react"
|
||||
|
||||
import { authApi } from "@/lib/api/auth"
|
||||
import { searchApi } from "@/lib/api/search"
|
||||
@@ -201,7 +201,9 @@ export function Header() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [searchResults, setSearchResults] = useState<GlobalSearchResult[]>([])
|
||||
const [searchOpen, setSearchOpen] = useState(false)
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false)
|
||||
const searchBoxRef = useRef<HTMLDivElement>(null)
|
||||
const mobileSearchBoxRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const term = searchQuery.trim()
|
||||
@@ -221,6 +223,9 @@ export function Header() {
|
||||
if (searchBoxRef.current && !searchBoxRef.current.contains(e.target as Node)) {
|
||||
setSearchOpen(false)
|
||||
}
|
||||
if (mobileSearchBoxRef.current && !mobileSearchBoxRef.current.contains(e.target as Node)) {
|
||||
setMobileSearchOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", onClickOutside)
|
||||
return () => document.removeEventListener("mousedown", onClickOutside)
|
||||
@@ -228,6 +233,7 @@ export function Header() {
|
||||
|
||||
function goToSearchResult(result: GlobalSearchResult) {
|
||||
setSearchOpen(false)
|
||||
setMobileSearchOpen(false)
|
||||
setSearchQuery("")
|
||||
setSearchResults([])
|
||||
router.push(SEARCH_TYPE_META[result.type].href(result.id))
|
||||
@@ -263,7 +269,8 @@ export function Header() {
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="print:hidden mx-3 mt-3 mb-3 flex items-center justify-between gap-2 rounded-3xl bg-card py-4 pr-3 pl-12 shadow-sm ring-1 ring-foreground/10 sm:mx-6 sm:pr-4 sm:pl-14 lg:mx-8 lg:mt-4 lg:mb-4 lg:gap-4 lg:p-4">
|
||||
<header className="print:hidden mx-3 mt-3 mb-3 flex flex-col gap-2 rounded-3xl bg-card py-4 pr-3 pl-12 shadow-sm ring-1 ring-foreground/10 sm:mx-6 sm:pr-4 sm:pl-14 lg:mx-8 lg:mt-4 lg:mb-4 lg:p-4">
|
||||
<div className="flex items-center justify-between gap-2 lg:gap-4">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 sm:gap-3">
|
||||
{showBackButton && (
|
||||
<button
|
||||
@@ -279,6 +286,18 @@ export function Header() {
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-1.5 sm:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileSearchOpen((v) => !v)}
|
||||
aria-label="Search"
|
||||
className={cn(
|
||||
"flex size-10 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground sm:hidden",
|
||||
mobileSearchOpen && "bg-muted text-foreground"
|
||||
)}
|
||||
>
|
||||
<Search className="size-5" />
|
||||
</button>
|
||||
|
||||
{branchingEnabled && (isAdmin ? (
|
||||
<Select value={activeBranchId ? String(activeBranchId) : ALL_BRANCHES} onValueChange={handleBranchChange}>
|
||||
<SelectTrigger
|
||||
@@ -375,10 +394,7 @@ export function Header() {
|
||||
<Badge className="absolute top-1.5 right-1.5 size-2 rounded-full bg-primary p-0" />
|
||||
)}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="w-[calc(100vw-2rem)] max-w-sm sm:w-96"
|
||||
>
|
||||
<PopoverContent align="end" sideOffset={10} className="w-[calc(100vw-1.5rem)] max-w-[22rem] sm:w-96">
|
||||
<div className="flex items-center justify-between border-b border-foreground/10 px-4 py-3">
|
||||
<p className="text-sm font-semibold text-foreground">Notifications</p>
|
||||
{unreadCount > 0 && (
|
||||
@@ -477,6 +493,68 @@ export function Header() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mobileSearchOpen && (
|
||||
<div ref={mobileSearchBoxRef} className="relative sm:hidden">
|
||||
<Search className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search item, PO, invoice, employee…"
|
||||
className="h-10 w-full rounded-full border border-transparent bg-muted pr-9 pl-9 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setMobileSearchOpen(false)
|
||||
setSearchQuery("")
|
||||
setSearchResults([])
|
||||
}}
|
||||
aria-label="Close search"
|
||||
className="absolute top-1/2 right-2 flex size-6 -translate-y-1/2 items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
|
||||
{searchQuery.trim().length >= 2 && (
|
||||
<div className="absolute top-full right-0 left-0 z-50 mt-2 max-h-96 overflow-y-auto rounded-xl bg-popover py-1.5 text-sm shadow-md ring-1 ring-foreground/10">
|
||||
{searchResults.length === 0 ? (
|
||||
<p className="p-4 text-center text-sm text-muted-foreground">No matches found.</p>
|
||||
) : (
|
||||
searchGrouped.map((group) => {
|
||||
const meta = SEARCH_TYPE_META[group.type]
|
||||
const Icon = meta.icon
|
||||
return (
|
||||
<div key={group.type}>
|
||||
<p className="px-4 pt-1 pb-1 text-xs font-medium text-muted-foreground">{meta.label}</p>
|
||||
{group.items.map((item) => (
|
||||
<button
|
||||
key={`${item.type}-${item.id}`}
|
||||
type="button"
|
||||
onClick={() => goToSearchResult(item)}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-left hover:bg-primary/10"
|
||||
>
|
||||
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-foreground">{item.title}</p>
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{item.number}
|
||||
{item.subtitle ? ` · ${item.subtitle}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user