bigbullui logobigbullui
Componentdata

Column Toggle

Column show/hide dropdown menu for data grids.

Preview

Live Preview

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add column-toggle

Or install the full npm package:

npm install bigbullui

Usage

import { ColumnToggle } from "@/components/ui/column-toggle";

<ColumnToggle />

Source code

column-toggle.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface ColumnToggleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onToggle"> {
  columns: { id: string; label: string; visible?: boolean }[];
  onToggle?: (id: string, visible: boolean) => void;
}

/** Column visibility toggle dropdown menu for data grids. */
export function ColumnToggle({ columns, onToggle, className, ...props }: ColumnToggleProps) {
  const [open, setOpen] = React.useState(false);
  const [state, setState] = React.useState(
    () => new Map(columns.map((c) => [c.id, c.visible !== false]))
  );
  React.useEffect(() => {
    setState(new Map(columns.map((c) => [c.id, c.visible !== false])));
  }, [columns]);

  const toggle = (id: string) => {
    const next = new Map(state);
    const val = !(next.get(id) ?? true);
    next.set(id, val);
    setState(next);
    onToggle?.(id, val);
  };

  const visibleCount = [...state.values()].filter(Boolean).length;

  return (
    <div className={cn("relative inline-block", className)} {...props}>
      <button
        onClick={() => setOpen((o) => !o)}
        aria-expanded={open}
        className={cn(
          "inline-flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 font-mono text-[10px] uppercase tracking-wider text-muted-foreground",
          "transition-colors hover:border-foreground/40 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
        )}
      >
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
          <rect x="3" y="4" width="18" height="16" rx="2" /><path d="M10 4v16" />
        </svg>
        Kolonlar ({visibleCount}/{columns.length})
      </button>
      {open && (
        <>
          <div className="fixed inset-0 z-30" onClick={() => setOpen(false)} aria-hidden="true" />
          <ul className="absolute right-0 z-40 mt-1 w-48 rounded-md border border-border bg-card p-1 shadow-lg animate-[fade-in-up_0.2s_ease-out_both] motion-reduce:animate-none" role="menu">
            {columns.map((col) => {
              const checked = state.get(col.id) ?? true;
              return (
                <li key={col.id}>
                  <label className="flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-secondary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none">
                    <input type="checkbox" checked={checked} onChange={() => toggle(col.id)} className="size-3.5 accent-accent" />
                    {col.label}
                  </label>
                </li>
              );
            })}
          </ul>
        </>
      )}
    </div>
  );
}

Props

PropTypeDescription
columns{ id: string; label: string; visible?: boolean }[]
onToggle(id: string, visible: boolean) => void