DataTableViewSelect

Toolbar select for switching DataTable list and card views.

Recipe Source

components/ui/data-table-view-select.tsx
"use client";import { Button } from "@giddaa-housing/ui/button";import {	Select,	SelectContent,	SelectGroup,	SelectItem,	SelectTrigger,	SelectValue,} from "@giddaa-housing/ui/select";import { LayoutGridIcon, ListIcon } from "lucide-react";import { useState } from "react";import { useIsMobile } from "./lib/use-mobile";type DataTableView = "list" | "card" | "kanban";type SelectableDataTableView = Extract<DataTableView, "list" | "card">;const VIEW_OPTIONS: {	icon: typeof ListIcon;	label: string;	value: SelectableDataTableView;}[] = [	{ icon: ListIcon, label: "List View", value: "list" },	{ icon: LayoutGridIcon, label: "Card View", value: "card" },];type DataTableViewSelectProps = {	onViewChange: (view: SelectableDataTableView) => void;	view: SelectableDataTableView;};function DataTableViewSelect({ onViewChange, view }: DataTableViewSelectProps) {	return (		<Select			items={VIEW_OPTIONS}			value={view}			onValueChange={(value) => onViewChange(value ?? "list")}		>			<SelectTrigger				className="w-full max-w-40 *:text-inherit"				unstyled				render={<Button variant="tertiary-outline" />}			>				<SelectValue>					{(value) => {						const option =							VIEW_OPTIONS.find((item) => item.value === value) ??							VIEW_OPTIONS[0];						const Icon = option.icon;						return (							<span className="flex items-center gap-2">								<Icon className="size-3" />								<span>{option.label}</span>							</span>						);					}}				</SelectValue>			</SelectTrigger>			<SelectContent>				<SelectGroup>					{VIEW_OPTIONS.map((item) => (						<SelectItem key={item.value} value={item.value}>							{item.label}						</SelectItem>					))}				</SelectGroup>			</SelectContent>		</Select>	);}type UseDataTableViewOptions = {	canRenderCardView?: boolean;	defaultView?: DataTableView;	storageKey: string;};function useDataTableView({	canRenderCardView = true,	defaultView = "list",	storageKey,}: UseDataTableViewOptions) {	const isMobile = useIsMobile();	const [view, setViewState] = useState<DataTableView>(() => {		const storedView = localStorage.getItem(storageKey) as DataTableView | null;		return storedView ?? defaultView;	});	const setView = (nextView: DataTableView) => {		localStorage.setItem(storageKey, nextView);		setViewState(nextView);	};	return {		resolvedView: isMobile && canRenderCardView ? "card" : view,		setView,		view,	} as const;}export {	DataTableViewSelect,	useDataTableView,	type DataTableView,	type DataTableViewSelectProps,	type SelectableDataTableView,	type UseDataTableViewOptions,};

Usage

import { useState } from "react";
import {
  DataTableViewSelect,
  type SelectableDataTableView,
} from "@/components/recipes/data-table-view-select";

const [view, setView] = useState<SelectableDataTableView>("list");

<DataTableViewSelect view={view} onViewChange={setView} />;

Examples

Default View Select

Use this in a DataTableToolbar when a screen supports both list and card layouts.

Current table view: list

Props

Prop/APITypeDefaultDescription
view"list" | "card"-Current selected view.
onViewChange(view) => void-Called with the next selected view.
useDataTableViewhook-Optional local-storage helper that can resolve mobile screens to card view.
storageKeystring-Required key for the optional hook. Keep it app-specific.

Accessibility

  • Keep the visible labels clear: "List View" and "Card View" are easier to scan than icons alone.
  • Persist the view only when it helps the user return to the same table context.
  • Keep mobile fallback logic in the app or optional hook; the select only changes explicit user choice.

On this page