DataTableViewSelect
Toolbar select for switching DataTable list and card views.
Recipe Source
"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.
Props
| Prop/API | Type | Default | Description |
|---|---|---|---|
view | "list" | "card" | - | Current selected view. |
onViewChange | (view) => void | - | Called with the next selected view. |
useDataTableView | hook | - | Optional local-storage helper that can resolve mobile screens to card view. |
storageKey | string | - | 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.