Polish filters combobox alignment
This commit is contained in:
parent
feca5dd4a7
commit
59a94744b3
2 changed files with 45 additions and 26 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue