deepmoon

Filter Bar

Status: OpenAssignee: MeLabel: bug

Installation

$ npx shadcn@latest add https://deepmoon.dev/r/filter-bar.json

npm dependencies: lucide-react

Props

FilterBarProps
PropTypeRequired
filtersFilterChip[]Yes
onRemove(id: string) => voidYes
onClearAll() => voidNo

Code

filter-bar.tsx
import * as React from "react";
import { X } from "lucide-react";

import { cn } from "@/lib/utils";

export interface FilterChip {
  id: string;
  label: string;
}

export interface FilterBarProps extends React.ComponentPropsWithoutRef<"div"> {
  filters: FilterChip[];
  onRemove: (id: string) => void;
  onClearAll?: () => void;
}

export function FilterBar({ filters, onRemove, onClearAll, className, ...props }: FilterBarProps) {
  if (filters.length === 0) return null;

  return (
    <div role="group" aria-label="Active filters" className={cn("flex flex-wrap items-center gap-2", className)} {...props}>
      {filters.map((filter) => (
        <span
          key={filter.id}
          className="flex items-center gap-1 rounded-full border border-border bg-secondary py-1 pl-3 pr-1.5 text-xs font-medium text-secondary-foreground"
        >
          {filter.label}
          <button
            type="button"
            onClick={() => onRemove(filter.id)}
            aria-label={`Remove ${filter.label} filter`}
            className="rounded-full p-0.5 text-secondary-foreground/70 outline-none hover:bg-secondary-foreground/10 hover:text-secondary-foreground focus-visible:ring-1 focus-visible:ring-ring"
          >
            <X className="size-3" aria-hidden="true" />
          </button>
        </span>
      ))}
      {onClearAll ? (
        <button
          type="button"
          onClick={onClearAll}
          className="text-xs font-medium text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
        >
          Clear all
        </button>
      ) : null}
    </div>
  );
}