Polish filters combobox alignment

This commit is contained in:
Esdras Renan 2025-11-13 21:08:34 -03:00
parent feca5dd4a7
commit 59a94744b3
2 changed files with 45 additions and 26 deletions

View file

@ -319,14 +319,14 @@ export function TicketsFilters({
</div> </div>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{canUseAdvancedFilters && ( {canUseAdvancedFilters && (
<div className="relative min-w-[220px] flex-1"> <div className={fieldWrap}>
<IconList className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<Select <Select
value={filters.queue ?? ALL_VALUE} value={filters.queue ?? ALL_VALUE}
onValueChange={(value) => setPartial({ queue: value === ALL_VALUE ? null : value })} onValueChange={(value) => setPartial({ queue: value === ALL_VALUE ? null : value })}
> >
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 pl-9 focus:ring-neutral-300"> <SelectTrigger className={fieldTrigger}>
<SelectValue placeholder="Fila" /> <IconList className="size-4 text-neutral-400" />
<SelectValue placeholder="Fila" className="flex-1 text-left" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value={ALL_VALUE}>Todas as filas</SelectItem> <SelectItem value={ALL_VALUE}>Todas as filas</SelectItem>
@ -340,8 +340,7 @@ export function TicketsFilters({
</div> </div>
)} )}
{canUseAdvancedFilters && ( {canUseAdvancedFilters && (
<div className="relative min-w-[220px] flex-1"> <div className={fieldWrap}>
<IconBuilding className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<SearchableCombobox <SearchableCombobox
value={filters.company} value={filters.company}
onValueChange={(value) => setPartial({ company: value })} onValueChange={(value) => setPartial({ company: value })}
@ -349,18 +348,19 @@ export function TicketsFilters({
placeholder="Empresa" placeholder="Empresa"
allowClear allowClear
clearLabel="Todas as empresas" clearLabel="Todas as empresas"
className="min-h-[40px] w-full rounded-2xl border border-slate-300 bg-slate-50/80 pl-9 text-left text-sm font-semibold text-neutral-700" triggerClassName={fieldTrigger}
prefix={<IconBuilding className="size-4 text-neutral-400" />}
/> />
</div> </div>
)} )}
<div className="relative min-w-[220px] flex-1"> <div className={fieldWrap}>
<IconTags className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<Select <Select
value={filters.categoryId ?? ALL_VALUE} value={filters.categoryId ?? ALL_VALUE}
onValueChange={(value) => setPartial({ categoryId: value === ALL_VALUE ? null : value })} onValueChange={(value) => setPartial({ categoryId: value === ALL_VALUE ? null : value })}
> >
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 pl-9 focus:ring-neutral-300"> <SelectTrigger className={fieldTrigger}>
<SelectValue placeholder="Categoria" /> <IconTags className="size-4 text-neutral-400" />
<SelectValue placeholder="Categoria" className="flex-1 text-left" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value={ALL_VALUE}>Todas as categorias</SelectItem> <SelectItem value={ALL_VALUE}>Todas as categorias</SelectItem>
@ -373,14 +373,14 @@ export function TicketsFilters({
</Select> </Select>
</div> </div>
{canUseAdvancedFilters && ( {canUseAdvancedFilters && (
<div className="relative min-w-[220px] flex-1"> <div className={fieldWrap}>
<IconUser className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<Select <Select
value={filters.assigneeId ?? ALL_VALUE} value={filters.assigneeId ?? ALL_VALUE}
onValueChange={(value) => setPartial({ assigneeId: value === ALL_VALUE ? null : value })} onValueChange={(value) => setPartial({ assigneeId: value === ALL_VALUE ? null : value })}
> >
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 pl-9 focus:ring-neutral-300"> <SelectTrigger className={fieldTrigger}>
<SelectValue placeholder="Responsável" /> <IconUser className="size-4 text-neutral-400" />
<SelectValue placeholder="Responsável" className="flex-1 text-left" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem> <SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem>
@ -472,3 +472,6 @@ function formatDateValue(value: string | null) {
"flex h-10 min-w-[220px] flex-1 items-stretch rounded-full border border-slate-200 bg-slate-50/70 p-1 gap-1" "flex h-10 min-w-[220px] flex-1 items-stretch rounded-full border border-slate-200 bg-slate-50/70 p-1 gap-1"
const segmentedItem = const segmentedItem =
"inline-flex h-full flex-1 items-center justify-center rounded-full first:rounded-full last:rounded-full px-4 text-sm font-semibold text-neutral-600 transition-colors hover:bg-slate-100 data-[state=on]:bg-neutral-900 data-[state=on]:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300" "inline-flex h-full flex-1 items-center justify-center rounded-full first:rounded-full last:rounded-full px-4 text-sm font-semibold text-neutral-600 transition-colors hover:bg-slate-100 data-[state=on]:bg-neutral-900 data-[state=on]:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
const fieldWrap = "min-w-[220px] flex-1"
const fieldTrigger =
"h-10 w-full rounded-2xl border border-slate-300 bg-slate-50/80 px-3 text-left text-sm font-semibold text-neutral-700 focus:ring-neutral-300 flex items-center gap-2"

View file

@ -1,9 +1,7 @@
"use client" "use client"
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState, useId, type ReactNode } from "react"
import { ChevronsUpDown, Check, X } from "lucide-react" import { ChevronsUpDown, Check, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { ScrollArea } from "@/components/ui/scroll-area" import { ScrollArea } from "@/components/ui/scroll-area"
@ -26,6 +24,7 @@ type SearchableComboboxProps = {
searchPlaceholder?: string searchPlaceholder?: string
emptyText?: string emptyText?: string
className?: string className?: string
triggerClassName?: string
disabled?: boolean disabled?: boolean
allowClear?: boolean allowClear?: boolean
clearLabel?: string clearLabel?: string
@ -34,6 +33,7 @@ type SearchableComboboxProps = {
contentClassName?: string contentClassName?: string
scrollClassName?: string scrollClassName?: string
scrollProps?: React.HTMLAttributes<HTMLDivElement> scrollProps?: React.HTMLAttributes<HTMLDivElement>
prefix?: ReactNode
} }
export function SearchableCombobox({ export function SearchableCombobox({
@ -44,6 +44,7 @@ export function SearchableCombobox({
searchPlaceholder = "Buscar...", searchPlaceholder = "Buscar...",
emptyText = "Nenhuma opção encontrada.", emptyText = "Nenhuma opção encontrada.",
className, className,
triggerClassName,
disabled, disabled,
allowClear = false, allowClear = false,
clearLabel = "Limpar seleção", clearLabel = "Limpar seleção",
@ -52,9 +53,11 @@ export function SearchableCombobox({
contentClassName, contentClassName,
scrollClassName, scrollClassName,
scrollProps, scrollProps,
prefix,
}: SearchableComboboxProps) { }: SearchableComboboxProps) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const listId = useId()
const selected = useMemo(() => { const selected = useMemo(() => {
if (value === null) return null if (value === null) return null
@ -92,24 +95,37 @@ export function SearchableCombobox({
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <button
type="button" type="button"
variant="outline"
role="combobox" role="combobox"
aria-expanded={open} aria-expanded={open}
aria-controls={listId}
disabled={disabled} disabled={disabled}
className={cn( className={cn(
"flex min-h-[46px] w-full items-center justify-between rounded-full border border-input bg-background px-3 py-2.5 text-sm font-medium text-foreground shadow-sm transition focus-visible:ring-2 focus-visible:ring-ring", "flex h-full w-full items-center justify-between gap-2 rounded-full border border-input bg-background px-3 text-sm font-semibold text-foreground shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60",
className, className,
triggerClassName,
)} )}
> >
<span className="truncate text-left"> <span className="flex flex-1 items-center gap-2">
{renderValue ? renderValue(selected) : selected?.label ?? <span className="text-muted-foreground">{placeholder}</span>} {prefix ? <span className="inline-flex items-center text-neutral-400">{prefix}</span> : null}
<span className="flex-1 truncate text-left">
{renderValue ? (
renderValue(selected)
) : selected?.label ? (
selected.label
) : (
<span className="text-muted-foreground">{placeholder}</span>
)}
</span>
</span> </span>
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 size-4 shrink-0 text-neutral-500 opacity-70" />
</Button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className={cn("z-50 w-[var(--radix-popover-trigger-width)] max-w-[480px] p-0", contentClassName)}> <PopoverContent
id={listId}
className={cn("z-50 w-[var(--radix-popover-trigger-width)] max-w-[480px] p-0", contentClassName)}
>
<div className="border-b border-border/80 p-2"> <div className="border-b border-border/80 p-2">
<Input <Input
autoFocus autoFocus