@manusiakemos/laravel-tanstack-react 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +284 -33
- package/dist/index.cjs +638 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +364 -4
- package/dist/index.d.ts +364 -4
- package/dist/index.js +623 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
- package/src/components/DataTable.tsx +166 -0
- package/src/components/DataTableFilter.tsx +217 -0
- package/src/components/DataTablePagination.tsx +271 -0
- package/src/components/DataTableSearch.tsx +173 -0
- package/src/components/layouts/DataTableSplitLayout.tsx +153 -0
- package/src/components/ui/button.tsx +49 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/select.tsx +27 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/hooks/useDataTable.ts +1 -1
- package/src/index.ts +60 -2
- package/src/lib/cn.ts +10 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/buildQueryString.ts","../src/types/index.ts","../src/utils/defaultFetcher.ts","../src/utils/useDebouncedValue.ts","../src/hooks/useDataTable.ts"],"names":["useState","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ColumnFiltersState,\n PaginationState,\n SortingState,\n} from '@tanstack/react-table'\n\n/**\n * Build a query string from TanStack Table state, matching the protocol\n * expected by `manusiakemos/laravel-tanstack`:\n *\n * ?page=1&per_page=25&sort=name:asc,id:desc&search=foo\n * &filter[status]=active&filter[role][]=admin&filter[role][]=editor\n *\n * Pagination is 0-indexed in TanStack (pageIndex) but 1-indexed in the\n * backend (page); this function handles the conversion.\n */\nexport interface BuildQueryStringInput {\n pagination: PaginationState\n sorting?: SortingState\n columnFilters?: ColumnFiltersState\n globalFilter?: string\n /** Extra static params merged in (e.g. tenant_id, view preset). */\n extra?: Record<string, string | string[]>\n}\n\nexport function buildQueryString(input: BuildQueryStringInput): string {\n const { pagination, sorting = [], columnFilters = [], globalFilter, extra } =\n input\n const params = new URLSearchParams()\n\n // Pagination — convert 0-indexed pageIndex to 1-indexed page.\n params.set('page', String(pagination.pageIndex + 1))\n params.set('per_page', String(pagination.pageSize))\n\n // Global search.\n if (globalFilter && globalFilter.trim() !== '') {\n params.set('search', globalFilter.trim())\n }\n\n // Sort — collapse multi-sort into one comma-separated value.\n if (sorting.length > 0) {\n const sortString = sorting\n .map((s) => `${s.id}:${s.desc ? 'desc' : 'asc'}`)\n .join(',')\n params.set('sort', sortString)\n }\n\n // Column filters — array values become `filter[key][]=v1&filter[key][]=v2`,\n // single values become `filter[key]=v`.\n for (const filter of columnFilters) {\n const value = filter.value\n\n if (value === null || value === undefined || value === '') {\n continue\n }\n\n if (Array.isArray(value)) {\n for (const v of value) {\n if (v === null || v === undefined || v === '') continue\n params.append(`filter[${filter.id}][]`, String(v))\n }\n } else {\n params.set(`filter[${filter.id}]`, String(value))\n }\n }\n\n // Extras (passed through verbatim).\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (Array.isArray(value)) {\n for (const v of value) params.append(`${key}[]`, v)\n } else {\n params.set(key, value)\n }\n }\n }\n\n return params.toString()\n}\n","/**\n * Response shape from a `manusiakemos/laravel-tanstack` backend endpoint.\n *\n * Backend returns:\n * { data: T[], meta: { page, per_page, total, filtered, last_page } }\n */\nexport interface DataTableResponse<TData> {\n data: TData[]\n meta: DataTableMeta\n}\n\nexport interface DataTableMeta {\n page: number\n per_page: number\n /** May be null when backend has skipTotal() enabled. */\n total: number | null\n filtered: number\n last_page: number\n}\n\n/**\n * Request query parameters sent to the backend.\n * Built from TanStack Table state by the request adapter.\n */\nexport interface DataTableRequestParams {\n page: number\n per_page: number\n /** Comma-separated `column:direction` pairs. */\n sort?: string\n /** Global search term. */\n search?: string\n /** Per-column filters: `filter[col]=val` or `filter[col][]=val1&filter[col][]=val2`. */\n filter?: Record<string, string | string[]>\n}\n\n/**\n * Optional fetcher signature. Defaults to `fetch` but allows axios, ky,\n * Inertia's router, or any custom transport (e.g. with CSRF headers).\n */\nexport type DataTableFetcher = (\n url: string,\n init?: RequestInit,\n) => Promise<unknown>\n\n/**\n * Error thrown or returned when a request fails.\n */\nexport class DataTableError extends Error {\n public readonly status: number\n public readonly response?: unknown\n\n constructor(message: string, status: number, response?: unknown) {\n super(message)\n this.name = 'DataTableError'\n this.status = status\n this.response = response\n }\n}\n","import { DataTableError, type DataTableResponse } from '../types'\n\n/**\n * Default JSON fetcher. Reads body, throws DataTableError on non-2xx.\n * Sends standard headers for Laravel session/Sanctum compatibility\n * (X-Requested-With makes Laravel treat the request as AJAX).\n */\nexport async function defaultFetcher<TData>(\n url: string,\n init?: RequestInit,\n): Promise<DataTableResponse<TData>> {\n const response = await fetch(url, {\n credentials: 'same-origin',\n headers: {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n ...(init?.headers ?? {}),\n },\n ...init,\n })\n\n let body: unknown = null\n const text = await response.text()\n if (text) {\n try {\n body = JSON.parse(text)\n } catch {\n body = text\n }\n }\n\n if (!response.ok) {\n throw new DataTableError(\n `Request failed with status ${response.status}`,\n response.status,\n body,\n )\n }\n\n if (!isDataTableResponse<TData>(body)) {\n throw new DataTableError(\n 'Invalid response shape: expected { data, meta }',\n response.status,\n body,\n )\n }\n\n return body\n}\n\nfunction isDataTableResponse<TData>(\n value: unknown,\n): value is DataTableResponse<TData> {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return Array.isArray(v.data) && typeof v.meta === 'object' && v.meta !== null\n}\n","import { useEffect, useState } from 'react'\n\n/**\n * Debounce a value. Useful for search inputs to avoid one HTTP request\n * per keystroke. Default delay is 300ms.\n */\nexport function useDebouncedValue<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delay)\n return () => clearTimeout(handle)\n }, [value, delay])\n\n return debounced\n}\n","import {\n type ColumnDef,\n type ColumnFiltersState,\n type PaginationState,\n type SortingState,\n type Table,\n type Updater,\n getCoreRowModel,\n useReactTable,\n} from '@tanstack/react-table'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport { buildQueryString } from '../adapters/buildQueryString'\nimport type {\n DataTableError,\n DataTableFetcher,\n DataTableMeta,\n DataTableResponse,\n} from '../types'\nimport { defaultFetcher } from '../utils/defaultFetcher'\nimport { useDebouncedValue } from '../utils/useDebouncedValue'\n\nexport interface UseDataTableOptions<TData> {\n /** Backend endpoint, e.g. '/datatable/users'. */\n endpoint: string\n /** TanStack column definitions. */\n columns: ColumnDef<TData, unknown>[]\n /** Initial page size. Defaults to 25. */\n initialPageSize?: number\n /** Initial sort state. */\n initialSorting?: SortingState\n /** Initial column filters. */\n initialColumnFilters?: ColumnFiltersState\n /** Debounce delay for the global search, in ms. Defaults to 300. */\n searchDebounceMs?: number\n /** Custom fetcher (axios, ky, inertia, etc.). */\n fetcher?: DataTableFetcher\n /** Extra static query params merged into every request. */\n extraParams?: Record<string, string | string[]>\n /** Called when a fetch fails. */\n onError?: (error: DataTableError | Error) => void\n /** Skip the first fetch on mount (e.g. defer until user interacts). */\n manual?: boolean\n}\n\nexport interface UseDataTableResult<TData> {\n table: Table<TData>\n data: TData[]\n meta: DataTableMeta | null\n loading: boolean\n error: DataTableError | Error | null\n /** Trigger a refetch with current state. */\n refetch: () => void\n}\n\n/**\n * Hook that wires TanStack Table state to a Laravel server-side endpoint\n * implementing the `manusiakemos/laravel-tanstack` protocol.\n *\n * @example\n * const { table, loading } = useDataTable<User>({\n * endpoint: '/datatable/users',\n * columns: [\n * { accessorKey: 'name', header: 'Nama' },\n * { accessorKey: 'email', header: 'Email' },\n * ],\n * })\n */\nexport function useDataTable<TData>(\n options: UseDataTableOptions<TData>,\n): UseDataTableResult<TData> {\n const {\n endpoint,\n columns,\n initialPageSize = 25,\n initialSorting = [],\n initialColumnFilters = [],\n searchDebounceMs = 300,\n fetcher = defaultFetcher,\n extraParams,\n onError,\n manual = false,\n } = options\n\n const [data, setData] = useState<TData[]>([])\n const [meta, setMeta] = useState<DataTableMeta | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<DataTableError | Error | null>(null)\n\n const [pagination, setPagination] = useState<PaginationState>({\n pageIndex: 0,\n pageSize: initialPageSize,\n })\n const [sorting, setSorting] = useState<SortingState>(initialSorting)\n const [columnFilters, setColumnFilters] =\n useState<ColumnFiltersState>(initialColumnFilters)\n const [globalFilter, setGlobalFilter] = useState('')\n\n // Debounce only the global search; pagination/sort/filter fire immediately.\n const debouncedSearch = useDebouncedValue(globalFilter, searchDebounceMs)\n\n // Reset to page 0 whenever search/filter/sort changes so the user doesn't\n // land on an empty page that no longer exists in the filtered result set.\n const isFirstRender = useRef(true)\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n setPagination((p) => ({ ...p, pageIndex: 0 }))\n }, [debouncedSearch, sorting, columnFilters])\n\n // Cancel in-flight requests when a new one fires.\n const abortRef = useRef<AbortController | null>(null)\n const [refetchToken, setRefetchToken] = useState(0)\n const refetch = useCallback(() => setRefetchToken((t) => t + 1), [])\n\n useEffect(() => {\n if (manual && refetchToken === 0) return\n\n abortRef.current?.abort()\n const controller = new AbortController()\n abortRef.current = controller\n\n const query = buildQueryString({\n pagination,\n sorting,\n columnFilters,\n globalFilter: debouncedSearch,\n extra: extraParams,\n })\n\n const url = `${endpoint}${endpoint.includes('?') ? '&' : '?'}${query}`\n\n setLoading(true)\n setError(null)\n\n fetcher(url, { signal: controller.signal })\n .then((response) => {\n if (controller.signal.aborted) return\n const typed = response as DataTableResponse<TData>\n setData(typed.data)\n setMeta(typed.meta)\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return\n if (err instanceof Error && err.name === 'AbortError') return\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n if (!controller.signal.aborted) setLoading(false)\n })\n\n return () => controller.abort()\n }, [\n endpoint,\n pagination,\n sorting,\n columnFilters,\n debouncedSearch,\n extraParams,\n fetcher,\n onError,\n manual,\n refetchToken,\n ])\n\n const pageCount = useMemo(() => {\n if (!meta) return -1\n return meta.last_page\n }, [meta])\n\n const table = useReactTable<TData>({\n data,\n columns,\n pageCount,\n state: { pagination, sorting, columnFilters, globalFilter },\n onPaginationChange: setPagination as (updater: Updater<PaginationState>) => void,\n onSortingChange: setSorting as (updater: Updater<SortingState>) => void,\n onColumnFiltersChange: setColumnFilters as (\n updater: Updater<ColumnFiltersState>,\n ) => void,\n onGlobalFilterChange: setGlobalFilter as (updater: Updater<string>) => void,\n manualPagination: true,\n manualSorting: true,\n manualFiltering: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n return { table, data, meta, loading, error, refetch }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/buildQueryString.ts","../src/types/index.ts","../src/utils/defaultFetcher.ts","../src/utils/useDebouncedValue.ts","../src/hooks/useDataTable.ts","../src/lib/cn.ts","../src/components/ui/table.tsx","../src/components/DataTable.tsx","../src/components/ui/button.tsx","../src/components/ui/input.tsx","../src/components/DataTableSearch.tsx","../src/components/ui/select.tsx","../src/components/DataTablePagination.tsx","../src/components/DataTableFilter.tsx","../src/components/layouts/DataTableSplitLayout.tsx"],"names":["useState","useEffect","jsx","forwardRef","jsxs","SearchIcon","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ColumnFiltersState,\n PaginationState,\n SortingState,\n} from '@tanstack/react-table'\n\n/**\n * Build a query string from TanStack Table state, matching the protocol\n * expected by `manusiakemos/laravel-tanstack`:\n *\n * ?page=1&per_page=25&sort=name:asc,id:desc&search=foo\n * &filter[status]=active&filter[role][]=admin&filter[role][]=editor\n *\n * Pagination is 0-indexed in TanStack (pageIndex) but 1-indexed in the\n * backend (page); this function handles the conversion.\n */\nexport interface BuildQueryStringInput {\n pagination: PaginationState\n sorting?: SortingState\n columnFilters?: ColumnFiltersState\n globalFilter?: string\n /** Extra static params merged in (e.g. tenant_id, view preset). */\n extra?: Record<string, string | string[]>\n}\n\nexport function buildQueryString(input: BuildQueryStringInput): string {\n const { pagination, sorting = [], columnFilters = [], globalFilter, extra } =\n input\n const params = new URLSearchParams()\n\n // Pagination — convert 0-indexed pageIndex to 1-indexed page.\n params.set('page', String(pagination.pageIndex + 1))\n params.set('per_page', String(pagination.pageSize))\n\n // Global search.\n if (globalFilter && globalFilter.trim() !== '') {\n params.set('search', globalFilter.trim())\n }\n\n // Sort — collapse multi-sort into one comma-separated value.\n if (sorting.length > 0) {\n const sortString = sorting\n .map((s) => `${s.id}:${s.desc ? 'desc' : 'asc'}`)\n .join(',')\n params.set('sort', sortString)\n }\n\n // Column filters — array values become `filter[key][]=v1&filter[key][]=v2`,\n // single values become `filter[key]=v`.\n for (const filter of columnFilters) {\n const value = filter.value\n\n if (value === null || value === undefined || value === '') {\n continue\n }\n\n if (Array.isArray(value)) {\n for (const v of value) {\n if (v === null || v === undefined || v === '') continue\n params.append(`filter[${filter.id}][]`, String(v))\n }\n } else {\n params.set(`filter[${filter.id}]`, String(value))\n }\n }\n\n // Extras (passed through verbatim).\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (Array.isArray(value)) {\n for (const v of value) params.append(`${key}[]`, v)\n } else {\n params.set(key, value)\n }\n }\n }\n\n return params.toString()\n}\n","/**\n * Response shape from a `manusiakemos/laravel-tanstack` backend endpoint.\n *\n * Backend returns:\n * { data: T[], meta: { page, per_page, total, filtered, last_page } }\n */\nexport interface DataTableResponse<TData> {\n data: TData[]\n meta: DataTableMeta\n}\n\nexport interface DataTableMeta {\n page: number\n per_page: number\n /** May be null when backend has skipTotal() enabled. */\n total: number | null\n filtered: number\n last_page: number\n}\n\n/**\n * Request query parameters sent to the backend.\n * Built from TanStack Table state by the request adapter.\n */\nexport interface DataTableRequestParams {\n page: number\n per_page: number\n /** Comma-separated `column:direction` pairs. */\n sort?: string\n /** Global search term. */\n search?: string\n /** Per-column filters: `filter[col]=val` or `filter[col][]=val1&filter[col][]=val2`. */\n filter?: Record<string, string | string[]>\n}\n\n/**\n * Optional fetcher signature. Defaults to `fetch` but allows axios, ky,\n * Inertia's router, or any custom transport (e.g. with CSRF headers).\n */\nexport type DataTableFetcher = (\n url: string,\n init?: RequestInit,\n) => Promise<unknown>\n\n/**\n * Error thrown or returned when a request fails.\n */\nexport class DataTableError extends Error {\n public readonly status: number\n public readonly response?: unknown\n\n constructor(message: string, status: number, response?: unknown) {\n super(message)\n this.name = 'DataTableError'\n this.status = status\n this.response = response\n }\n}\n","import { DataTableError, type DataTableResponse } from '../types'\n\n/**\n * Default JSON fetcher. Reads body, throws DataTableError on non-2xx.\n * Sends standard headers for Laravel session/Sanctum compatibility\n * (X-Requested-With makes Laravel treat the request as AJAX).\n */\nexport async function defaultFetcher<TData>(\n url: string,\n init?: RequestInit,\n): Promise<DataTableResponse<TData>> {\n const response = await fetch(url, {\n credentials: 'same-origin',\n headers: {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n ...(init?.headers ?? {}),\n },\n ...init,\n })\n\n let body: unknown = null\n const text = await response.text()\n if (text) {\n try {\n body = JSON.parse(text)\n } catch {\n body = text\n }\n }\n\n if (!response.ok) {\n throw new DataTableError(\n `Request failed with status ${response.status}`,\n response.status,\n body,\n )\n }\n\n if (!isDataTableResponse<TData>(body)) {\n throw new DataTableError(\n 'Invalid response shape: expected { data, meta }',\n response.status,\n body,\n )\n }\n\n return body\n}\n\nfunction isDataTableResponse<TData>(\n value: unknown,\n): value is DataTableResponse<TData> {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return Array.isArray(v.data) && typeof v.meta === 'object' && v.meta !== null\n}\n","import { useEffect, useState } from 'react'\n\n/**\n * Debounce a value. Useful for search inputs to avoid one HTTP request\n * per keystroke. Default delay is 300ms.\n */\nexport function useDebouncedValue<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delay)\n return () => clearTimeout(handle)\n }, [value, delay])\n\n return debounced\n}\n","import {\n type ColumnDef,\n type ColumnFiltersState,\n type PaginationState,\n type SortingState,\n type Table,\n type Updater,\n getCoreRowModel,\n useReactTable,\n} from '@tanstack/react-table'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport { buildQueryString } from '../adapters/buildQueryString'\nimport type {\n DataTableError,\n DataTableFetcher,\n DataTableMeta,\n DataTableResponse,\n} from '../types'\nimport { defaultFetcher } from '../utils/defaultFetcher'\nimport { useDebouncedValue } from '../utils/useDebouncedValue'\n\nexport interface UseDataTableOptions<TData> {\n /** Backend endpoint, e.g. '/datatable/users'. */\n endpoint: string\n /** TanStack column definitions. */\n columns: ColumnDef<TData, unknown>[]\n /** Initial page size. Defaults to 25. */\n initialPageSize?: number\n /** Initial sort state. */\n initialSorting?: SortingState\n /** Initial column filters. */\n initialColumnFilters?: ColumnFiltersState\n /** Debounce delay for the global search, in ms. Defaults to 300. */\n searchDebounceMs?: number\n /** Custom fetcher (axios, ky, inertia, etc.). */\n fetcher?: DataTableFetcher\n /** Extra static query params merged into every request. */\n extraParams?: Record<string, string | string[]>\n /** Called when a fetch fails. */\n onError?: (error: DataTableError | Error) => void\n /** Skip the first fetch on mount (e.g. defer until user interacts). */\n manual?: boolean\n}\n\nexport interface UseDataTableResult<TData> {\n table: Table<TData>\n data: TData[]\n meta: DataTableMeta | null\n loading: boolean\n error: DataTableError | Error | null\n /** Trigger a refetch with current state. */\n refetch: () => void\n}\n\n/**\n * Hook that wires TanStack Table state to a Laravel server-side endpoint\n * implementing the `manusiakemos/laravel-tanstack` protocol.\n *\n * @example\n * const { table, loading } = useDataTable<User>({\n * endpoint: '/datatable/users',\n * columns: [\n * { accessorKey: 'name', header: 'Name' },\n * { accessorKey: 'email', header: 'Email' },\n * ],\n * })\n */\nexport function useDataTable<TData>(\n options: UseDataTableOptions<TData>,\n): UseDataTableResult<TData> {\n const {\n endpoint,\n columns,\n initialPageSize = 25,\n initialSorting = [],\n initialColumnFilters = [],\n searchDebounceMs = 300,\n fetcher = defaultFetcher,\n extraParams,\n onError,\n manual = false,\n } = options\n\n const [data, setData] = useState<TData[]>([])\n const [meta, setMeta] = useState<DataTableMeta | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<DataTableError | Error | null>(null)\n\n const [pagination, setPagination] = useState<PaginationState>({\n pageIndex: 0,\n pageSize: initialPageSize,\n })\n const [sorting, setSorting] = useState<SortingState>(initialSorting)\n const [columnFilters, setColumnFilters] =\n useState<ColumnFiltersState>(initialColumnFilters)\n const [globalFilter, setGlobalFilter] = useState('')\n\n // Debounce only the global search; pagination/sort/filter fire immediately.\n const debouncedSearch = useDebouncedValue(globalFilter, searchDebounceMs)\n\n // Reset to page 0 whenever search/filter/sort changes so the user doesn't\n // land on an empty page that no longer exists in the filtered result set.\n const isFirstRender = useRef(true)\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n setPagination((p) => ({ ...p, pageIndex: 0 }))\n }, [debouncedSearch, sorting, columnFilters])\n\n // Cancel in-flight requests when a new one fires.\n const abortRef = useRef<AbortController | null>(null)\n const [refetchToken, setRefetchToken] = useState(0)\n const refetch = useCallback(() => setRefetchToken((t) => t + 1), [])\n\n useEffect(() => {\n if (manual && refetchToken === 0) return\n\n abortRef.current?.abort()\n const controller = new AbortController()\n abortRef.current = controller\n\n const query = buildQueryString({\n pagination,\n sorting,\n columnFilters,\n globalFilter: debouncedSearch,\n extra: extraParams,\n })\n\n const url = `${endpoint}${endpoint.includes('?') ? '&' : '?'}${query}`\n\n setLoading(true)\n setError(null)\n\n fetcher(url, { signal: controller.signal })\n .then((response) => {\n if (controller.signal.aborted) return\n const typed = response as DataTableResponse<TData>\n setData(typed.data)\n setMeta(typed.meta)\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return\n if (err instanceof Error && err.name === 'AbortError') return\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n if (!controller.signal.aborted) setLoading(false)\n })\n\n return () => controller.abort()\n }, [\n endpoint,\n pagination,\n sorting,\n columnFilters,\n debouncedSearch,\n extraParams,\n fetcher,\n onError,\n manual,\n refetchToken,\n ])\n\n const pageCount = useMemo(() => {\n if (!meta) return -1\n return meta.last_page\n }, [meta])\n\n const table = useReactTable<TData>({\n data,\n columns,\n pageCount,\n state: { pagination, sorting, columnFilters, globalFilter },\n onPaginationChange: setPagination as (updater: Updater<PaginationState>) => void,\n onSortingChange: setSorting as (updater: Updater<SortingState>) => void,\n onColumnFiltersChange: setColumnFilters as (\n updater: Updater<ColumnFiltersState>,\n ) => void,\n onGlobalFilterChange: setGlobalFilter as (updater: Updater<string>) => void,\n manualPagination: true,\n manualSorting: true,\n manualFiltering: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n return { table, data, meta, loading, error, refetch }\n}\n","import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/**\n * Tailwind-aware className combinator (shadcn convention).\n * Resolves conflicting Tailwind utilities so later inputs win.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs))\n}\n","import {\n forwardRef,\n type HTMLAttributes,\n type TdHTMLAttributes,\n type ThHTMLAttributes,\n} from 'react'\n\nimport { cn } from '../../lib/cn'\n\n/**\n * Shadcn-style Table primitives. These mirror the components produced by\n * `shadcn add table` so users get identical visual output to a hand-rolled\n * shadcn table in their app.\n */\n\nexport const Table = forwardRef<\n HTMLTableElement,\n HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn('w-full caption-bottom text-sm', className)}\n {...props}\n />\n </div>\n))\nTable.displayName = 'Table'\n\nexport const TableHeader = forwardRef<\n HTMLTableSectionElement,\n HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />\n))\nTableHeader.displayName = 'TableHeader'\n\nexport const TableBody = forwardRef<\n HTMLTableSectionElement,\n HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn('[&_tr:last-child]:border-0', className)}\n {...props}\n />\n))\nTableBody.displayName = 'TableBody'\n\nexport const TableFooter = forwardRef<\n HTMLTableSectionElement,\n HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n 'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n className,\n )}\n {...props}\n />\n))\nTableFooter.displayName = 'TableFooter'\n\nexport const TableRow = forwardRef<\n HTMLTableRowElement,\n HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n 'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',\n className,\n )}\n {...props}\n />\n))\nTableRow.displayName = 'TableRow'\n\nexport const TableHead = forwardRef<\n HTMLTableCellElement,\n ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n 'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',\n className,\n )}\n {...props}\n />\n))\nTableHead.displayName = 'TableHead'\n\nexport const TableCell = forwardRef<\n HTMLTableCellElement,\n TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n 'p-4 align-middle [&:has([role=checkbox])]:pr-0',\n className,\n )}\n {...props}\n />\n))\nTableCell.displayName = 'TableCell'\n\nexport const TableCaption = forwardRef<\n HTMLTableCaptionElement,\n HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn('mt-4 text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nTableCaption.displayName = 'TableCaption'\n","import {\n type Table as TanStackTable,\n flexRender,\n} from '@tanstack/react-table'\nimport { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'\nimport type { ReactNode } from 'react'\n\nimport { cn } from '../lib/cn'\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from './ui/table'\n\nexport interface DataTableClassNames {\n root?: string\n table?: string\n header?: string\n headerRow?: string\n head?: string\n body?: string\n row?: string\n cell?: string\n empty?: string\n loading?: string\n}\n\nexport interface DataTableProps<TData> {\n table: TanStackTable<TData>\n /** Loading flag from `useDataTable`. */\n loading?: boolean\n /** Slot rendered inside the empty <tbody> when there are no rows. */\n emptyMessage?: ReactNode\n /** Slot rendered inside the loading <tbody>. */\n loadingMessage?: ReactNode\n /** Class for the outer wrapper. */\n className?: string\n /** Fine-grained class names for table parts. */\n classNames?: DataTableClassNames\n /** Called when a row is clicked. */\n onRowClick?: (row: TData) => void\n}\n\n/**\n * Shadcn-styled table component wired to a TanStack `Table` instance.\n * Renders header (with sort indicators), body, empty state, and loading state.\n *\n * @example\n * const { table, loading } = useDataTable<User>({ ... })\n * <DataTable table={table} loading={loading} />\n */\nexport function DataTable<TData>(props: DataTableProps<TData>) {\n const {\n table,\n loading = false,\n emptyMessage = 'No results.',\n loadingMessage = 'Loading...',\n className,\n classNames,\n onRowClick,\n } = props\n\n const columnCount = table.getAllColumns().length\n const rows = table.getRowModel().rows\n\n return (\n <div className={cn('rounded-md border', className, classNames?.root)}>\n <Table className={classNames?.table}>\n <TableHeader className={classNames?.header}>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id} className={classNames?.headerRow}>\n {headerGroup.headers.map((header) => {\n const canSort = header.column.getCanSort()\n const sortDir = header.column.getIsSorted()\n return (\n <TableHead\n key={header.id}\n className={cn(\n canSort && 'cursor-pointer select-none',\n classNames?.head,\n )}\n onClick={\n canSort\n ? header.column.getToggleSortingHandler()\n : undefined\n }\n >\n {header.isPlaceholder ? null : (\n <span className=\"inline-flex items-center gap-1\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {canSort && (\n <span className=\"text-muted-foreground\">\n {sortDir === 'asc' ? (\n <ArrowUp className=\"h-3.5 w-3.5\" />\n ) : sortDir === 'desc' ? (\n <ArrowDown className=\"h-3.5 w-3.5\" />\n ) : (\n <ChevronsUpDown className=\"h-3.5 w-3.5 opacity-50\" />\n )}\n </span>\n )}\n </span>\n )}\n </TableHead>\n )\n })}\n </TableRow>\n ))}\n </TableHeader>\n\n <TableBody className={classNames?.body}>\n {loading ? (\n <TableRow>\n <TableCell\n colSpan={columnCount}\n className={cn(\n 'h-24 text-center text-muted-foreground',\n classNames?.loading,\n )}\n >\n {loadingMessage}\n </TableCell>\n </TableRow>\n ) : rows.length === 0 ? (\n <TableRow>\n <TableCell\n colSpan={columnCount}\n className={cn(\n 'h-24 text-center text-muted-foreground',\n classNames?.empty,\n )}\n >\n {emptyMessage}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => (\n <TableRow\n key={row.id}\n className={cn(\n onRowClick && 'cursor-pointer',\n classNames?.row,\n )}\n onClick={\n onRowClick ? () => onRowClick(row.original) : undefined\n }\n >\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} className={classNames?.cell}>\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </TableCell>\n ))}\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n </div>\n )\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport { type ButtonHTMLAttributes, forwardRef } from 'react'\n\nimport { cn } from '../../lib/cn'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, type = 'button', ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n ),\n)\nButton.displayName = 'Button'\n","import { forwardRef, type InputHTMLAttributes } from 'react'\n\nimport { cn } from '../../lib/cn'\n\nexport type InputProps = InputHTMLAttributes<HTMLInputElement>\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n ({ className, type = 'text', ...props }, ref) => (\n <input\n ref={ref}\n type={type}\n className={cn(\n 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n />\n ),\n)\nInput.displayName = 'Input'\n","import { Search as SearchIcon } from 'lucide-react'\nimport type { Table } from '@tanstack/react-table'\nimport {\n type FormEvent,\n type InputHTMLAttributes,\n type ReactNode,\n useEffect,\n useState,\n} from 'react'\n\nimport { cn } from '../lib/cn'\nimport { useDebouncedValue } from '../utils/useDebouncedValue'\nimport { Button } from './ui/button'\nimport { Input } from './ui/input'\n\nexport interface DataTableSearchRenderProps {\n /** Current uncommitted input value (pre-debounce / pre-submit). */\n value: string\n /** Update the input value (debounced commit if `debounce` is on). */\n setValue: (next: string) => void\n /** Immediately commit the current value to the table's global filter. */\n submit: () => void\n /** The value currently applied to the table's global filter. */\n appliedValue: string\n}\n\nexport type DataTableSearchDebounce = boolean | number\n\nexport interface DataTableSearchProps<TData> {\n table: Table<TData>\n /**\n * Debounce behavior:\n * - `true` → debounced with default delay (300ms)\n * - number → debounced with the given delay in ms\n * - `false` → no debounce; instead the component renders a Search button\n * and only commits to the table on submit (button click / Enter)\n *\n * Defaults to `true`.\n */\n debounce?: DataTableSearchDebounce\n /** Placeholder text for the default input. */\n placeholder?: string\n /** Label for the search submit button (when `debounce` is `false`). */\n submitLabel?: ReactNode\n /** Class for the root wrapper element (ignored when `render` is provided). */\n className?: string\n /** Class merged into the underlying shadcn Input. */\n inputClassName?: string\n /** Class merged into the submit Button (when `debounce` is `false`). */\n buttonClassName?: string\n /** Extra props spread onto the underlying Input. */\n inputProps?: Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'value' | 'onChange' | 'type' | 'className'\n >\n /** Fired whenever the search value is committed to the table. */\n onSearch?: (value: string) => void\n /**\n * Full render override. Receives the controlled value, setter, and a manual\n * `submit()` so you can drop in any custom UI.\n */\n render?: (props: DataTableSearchRenderProps) => ReactNode\n}\n\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Debounced (or manual-submit) global search wired to a TanStack `Table`.\n *\n * @example Debounced (default)\n * <DataTableSearch table={table} placeholder=\"Search users...\" />\n *\n * @example Manual submit (search button)\n * <DataTableSearch table={table} debounce={false} placeholder=\"Search...\" />\n *\n * @example Custom UI\n * <DataTableSearch\n * table={table}\n * render={({ value, setValue, submit }) => (\n * <MyCombobox value={value} onChange={setValue} onSubmit={submit} />\n * )}\n * />\n */\nexport function DataTableSearch<TData>(props: DataTableSearchProps<TData>) {\n const {\n table,\n debounce = true,\n placeholder = 'Search...',\n submitLabel,\n className,\n inputClassName,\n buttonClassName,\n inputProps,\n onSearch,\n render,\n } = props\n\n const isDebounced = debounce !== false\n const debounceMs =\n typeof debounce === 'number' ? debounce : DEFAULT_DEBOUNCE_MS\n\n const appliedValue = (table.getState().globalFilter as string | undefined) ?? ''\n const [value, setValue] = useState(appliedValue)\n const debounced = useDebouncedValue(value, debounceMs)\n\n const commit = (next: string) => {\n if (next === appliedValue) return\n table.setGlobalFilter(next)\n onSearch?.(next)\n }\n\n // Debounced auto-commit. Disabled entirely when `debounce={false}`.\n useEffect(() => {\n if (!isDebounced) return\n commit(debounced)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debounced, isDebounced, table])\n\n // Keep local state in sync if the table's filter is changed elsewhere.\n useEffect(() => {\n setValue(appliedValue)\n }, [appliedValue])\n\n const submit = () => commit(value)\n\n if (render) {\n return <>{render({ value, setValue, submit, appliedValue })}</>\n }\n\n const handleFormSubmit = (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n submit()\n }\n\n // Debounced mode → just an input, no form / button needed.\n if (isDebounced) {\n return (\n <Input\n type=\"search\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={placeholder}\n className={cn(className, inputClassName)}\n {...inputProps}\n />\n )\n }\n\n // Manual mode → input + submit button, wrapped in a form so Enter submits.\n return (\n <form\n onSubmit={handleFormSubmit}\n className={cn('flex w-full max-w-md items-center gap-2', className)}\n >\n <Input\n type=\"search\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={placeholder}\n className={inputClassName}\n {...inputProps}\n />\n <Button type=\"submit\" className={buttonClassName}>\n {submitLabel ?? (\n <>\n <SearchIcon className=\"h-4 w-4\" aria-hidden=\"true\" />\n <span>Search</span>\n </>\n )}\n </Button>\n </form>\n )\n}\n","import { forwardRef, type SelectHTMLAttributes } from 'react'\n\nimport { cn } from '../../lib/cn'\n\nexport type SelectProps = SelectHTMLAttributes<HTMLSelectElement>\n\n/**\n * Shadcn-styled native <select>. We use the native element (not Radix) to\n * keep the dependency footprint minimal; consumers who want a Radix-based\n * popover select can swap it in via the `render` prop on the parent\n * component.\n */\nexport const Select = forwardRef<HTMLSelectElement, SelectProps>(\n ({ className, multiple, ...props }, ref) => (\n <select\n ref={ref}\n multiple={multiple}\n className={cn(\n 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n multiple ? 'min-h-[6rem]' : 'h-10',\n className,\n )}\n {...props}\n />\n ),\n)\nSelect.displayName = 'Select'\n","import type { Table } from '@tanstack/react-table'\nimport {\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n} from 'lucide-react'\nimport type { ButtonHTMLAttributes, ReactNode, SelectHTMLAttributes } from 'react'\n\nimport { cn } from '../lib/cn'\nimport type { DataTableMeta } from '../types'\nimport { Button } from './ui/button'\nimport { Select } from './ui/select'\n\nexport interface DataTablePaginationLabels {\n previous?: ReactNode\n next?: ReactNode\n first?: ReactNode\n last?: ReactNode\n page?: string\n of?: string\n rowsPerPage?: string\n /** Renderer for the row-count summary. */\n summary?: (info: {\n pageIndex: number\n pageCount: number\n pageSize: number\n filtered: number\n total: number | null\n }) => ReactNode\n}\n\nexport interface DataTablePaginationClassNames {\n root?: string\n info?: string\n controls?: string\n button?: string\n select?: string\n pageSize?: string\n}\n\nexport interface DataTablePaginationRenderProps<TData> {\n table: Table<TData>\n meta: DataTableMeta | null\n pageIndex: number\n pageCount: number\n pageSize: number\n canPreviousPage: boolean\n canNextPage: boolean\n goToFirst: () => void\n goToPrevious: () => void\n goToNext: () => void\n goToLast: () => void\n setPageSize: (size: number) => void\n}\n\nexport interface DataTablePaginationProps<TData> {\n table: Table<TData>\n /** Meta from `useDataTable`. Used for the row-count summary. */\n meta?: DataTableMeta | null\n /** Show the page-size selector. Defaults to false. */\n showPageSize?: boolean\n /** Available page size options when `showPageSize` is true. */\n pageSizeOptions?: number[]\n /** Show first/last page buttons. Defaults to false. */\n showFirstLast?: boolean\n /** Hide the \"Page X of Y · N rows\" summary. */\n hideSummary?: boolean\n /** Override individual labels / the summary renderer. */\n labels?: DataTablePaginationLabels\n /** Class for the root container. */\n className?: string\n /** Fine-grained class names for sub-elements. */\n classNames?: DataTablePaginationClassNames\n /** Extra props for the prev/next/first/last buttons. */\n buttonProps?: Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'onClick' | 'disabled' | 'className' | 'type'\n >\n /** Extra props for the page-size <select>. */\n selectProps?: Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n 'value' | 'onChange' | 'className'\n >\n /**\n * Full render override. Receives the pagination API so any custom UI can\n * still drive the table.\n */\n render?: (props: DataTablePaginationRenderProps<TData>) => ReactNode\n}\n\nconst DEFAULT_PAGE_SIZES = [10, 25, 50, 100]\n\n/**\n * Shadcn-styled pagination controls (prev/next, optional first/last, optional\n * page-size selector, row-count summary) wired to a TanStack `Table`.\n */\nexport function DataTablePagination<TData>(props: DataTablePaginationProps<TData>) {\n const {\n table,\n meta = null,\n showPageSize = false,\n pageSizeOptions = DEFAULT_PAGE_SIZES,\n showFirstLast = false,\n hideSummary = false,\n labels,\n className,\n classNames,\n buttonProps,\n selectProps,\n render,\n } = props\n\n const state = table.getState().pagination\n const pageIndex = state.pageIndex\n const pageSize = state.pageSize\n const pageCount = table.getPageCount()\n const canPreviousPage = table.getCanPreviousPage()\n const canNextPage = table.getCanNextPage()\n\n const goToFirst = () => table.setPageIndex(0)\n const goToPrevious = () => table.previousPage()\n const goToNext = () => table.nextPage()\n const goToLast = () => table.setPageIndex(Math.max(0, pageCount - 1))\n const setPageSize = (size: number) => table.setPageSize(size)\n\n if (render) {\n return (\n <>\n {render({\n table,\n meta,\n pageIndex,\n pageCount,\n pageSize,\n canPreviousPage,\n canNextPage,\n goToFirst,\n goToPrevious,\n goToNext,\n goToLast,\n setPageSize,\n })}\n </>\n )\n }\n\n const filtered = meta?.filtered ?? 0\n const total = meta?.total ?? null\n\n const summaryNode = labels?.summary\n ? labels.summary({ pageIndex, pageCount, pageSize, filtered, total })\n : (\n <>\n {labels?.page ?? 'Page'} {pageIndex + 1} {labels?.of ?? 'of'}{' '}\n {Math.max(1, pageCount)}\n {meta ? ` · ${filtered} rows` : null}\n </>\n )\n\n return (\n <div\n className={cn(\n 'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between',\n className,\n classNames?.root,\n )}\n >\n {!hideSummary && (\n <div\n className={cn(\n 'text-sm text-muted-foreground',\n classNames?.info,\n )}\n >\n {summaryNode}\n </div>\n )}\n\n <div\n className={cn(\n 'flex items-center gap-2',\n classNames?.controls,\n )}\n >\n {showPageSize && (\n <label\n className={cn(\n 'flex items-center gap-2 text-sm text-muted-foreground',\n classNames?.pageSize,\n )}\n >\n <span>{labels?.rowsPerPage ?? 'Rows per page:'}</span>\n <Select\n value={pageSize}\n onChange={(e) => setPageSize(Number(e.target.value))}\n className={cn('h-9 w-[5rem]', classNames?.select)}\n {...selectProps}\n >\n {pageSizeOptions.map((size) => (\n <option key={size} value={size}>\n {size}\n </option>\n ))}\n </Select>\n </label>\n )}\n\n {showFirstLast && (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={goToFirst}\n disabled={!canPreviousPage}\n className={classNames?.button}\n aria-label=\"Go to first page\"\n {...buttonProps}\n >\n {labels?.first ?? <ChevronsLeft className=\"h-4 w-4\" />}\n </Button>\n )}\n\n <Button\n variant=\"outline\"\n onClick={goToPrevious}\n disabled={!canPreviousPage}\n className={classNames?.button}\n aria-label=\"Go to previous page\"\n {...buttonProps}\n >\n {labels?.previous ?? (\n <>\n <ChevronLeft className=\"h-4 w-4\" />\n <span>Previous</span>\n </>\n )}\n </Button>\n\n <Button\n variant=\"outline\"\n onClick={goToNext}\n disabled={!canNextPage}\n className={classNames?.button}\n aria-label=\"Go to next page\"\n {...buttonProps}\n >\n {labels?.next ?? (\n <>\n <span>Next</span>\n <ChevronRight className=\"h-4 w-4\" />\n </>\n )}\n </Button>\n\n {showFirstLast && (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={goToLast}\n disabled={!canNextPage}\n className={classNames?.button}\n aria-label=\"Go to last page\"\n {...buttonProps}\n >\n {labels?.last ?? <ChevronsRight className=\"h-4 w-4\" />}\n </Button>\n )}\n </div>\n </div>\n )\n}\n","import type { Column, Table } from '@tanstack/react-table'\nimport type {\n InputHTMLAttributes,\n ReactNode,\n SelectHTMLAttributes,\n} from 'react'\n\nimport { cn } from '../lib/cn'\nimport { Input } from './ui/input'\nimport { Select } from './ui/select'\n\nexport type DataTableFilterValue = string | string[] | undefined\n\nexport interface DataTableFilterOption {\n label: string\n value: string\n}\n\nexport interface DataTableFilterRenderProps<TData> {\n /** The current filter value for this column. */\n value: DataTableFilterValue\n /** Commit a new filter value (use `undefined` or `''` to clear). */\n setValue: (next: DataTableFilterValue) => void\n /** The TanStack column instance. */\n column: Column<TData, unknown>\n /** The parent table instance. */\n table: Table<TData>\n}\n\ninterface BaseFilterProps<TData> {\n table: Table<TData>\n /** The column id this filter applies to. */\n columnId: string\n /** Class for the root element (when not using `render`). */\n className?: string\n /**\n * Full render override. You get the raw column filter value + setter, so\n * any UI (popover, multi-select combobox, date picker, …) can be plugged\n * in while still feeding the same TanStack column filter pipeline.\n */\n render?: (props: DataTableFilterRenderProps<TData>) => ReactNode\n}\n\ninterface SelectFilterProps<TData> extends BaseFilterProps<TData> {\n type?: 'select'\n options: DataTableFilterOption[]\n /** Placeholder option label (renders an empty value option). */\n placeholder?: string\n /** Whether to render the empty/placeholder option. Defaults to true. */\n includeEmptyOption?: boolean\n selectProps?: Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n 'value' | 'onChange' | 'className'\n >\n}\n\ninterface MultiSelectFilterProps<TData> extends BaseFilterProps<TData> {\n type: 'multiselect'\n options: DataTableFilterOption[]\n selectProps?: Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n 'value' | 'onChange' | 'multiple' | 'className'\n >\n}\n\ninterface InputFilterProps<TData> extends BaseFilterProps<TData> {\n type: 'input'\n placeholder?: string\n inputProps?: Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'value' | 'onChange' | 'type' | 'className'\n >\n}\n\ninterface CustomFilterProps<TData> extends BaseFilterProps<TData> {\n type?: 'custom'\n /** Required when `type` is `\"custom\"` (or when no `options` given). */\n render: (props: DataTableFilterRenderProps<TData>) => ReactNode\n}\n\nexport type DataTableFilterProps<TData> =\n | SelectFilterProps<TData>\n | MultiSelectFilterProps<TData>\n | InputFilterProps<TData>\n | CustomFilterProps<TData>\n\n/**\n * Shadcn-styled per-column filter wired to a TanStack `Table`.\n *\n * Supports four modes:\n * - `type=\"select\"` — single-select (default when `options` given)\n * - `type=\"multiselect\"` — multi-select\n * - `type=\"input\"` — text input\n * - `type=\"custom\"` (or `render`) — fully custom UI\n *\n * @example Select\n * <DataTableFilter\n * table={table}\n * columnId=\"status\"\n * options={[\n * { label: 'Active', value: 'active' },\n * { label: 'Inactive', value: 'inactive' },\n * ]}\n * placeholder=\"All statuses\"\n * />\n *\n * @example Custom render\n * <DataTableFilter\n * table={table}\n * columnId=\"status\"\n * render={({ value, setValue }) => (\n * <MyCustomFilter value={value} onChange={setValue} />\n * )}\n * />\n */\nexport function DataTableFilter<TData>(props: DataTableFilterProps<TData>) {\n const { table, columnId, className, render } = props\n const column = table.getColumn(columnId)\n\n if (!column) {\n if (typeof console !== 'undefined') {\n console.warn(\n `[DataTableFilter] Column \"${columnId}\" not found on table. ` +\n `Make sure a column with this id exists.`,\n )\n }\n return null\n }\n\n const value = column.getFilterValue() as DataTableFilterValue\n const setValue = (next: DataTableFilterValue) => {\n if (\n next === undefined ||\n next === '' ||\n (Array.isArray(next) && next.length === 0)\n ) {\n column.setFilterValue(undefined)\n } else {\n column.setFilterValue(next)\n }\n }\n\n if (render) {\n return <>{render({ value, setValue, column, table })}</>\n }\n\n // Multi-select\n if (props.type === 'multiselect') {\n const arrayValue = Array.isArray(value)\n ? value\n : value\n ? [String(value)]\n : []\n return (\n <Select\n multiple\n value={arrayValue}\n onChange={(e) => {\n const selected = Array.from(e.target.selectedOptions).map((o) => o.value)\n setValue(selected)\n }}\n className={cn('w-auto min-w-[10rem]', className)}\n {...props.selectProps}\n >\n {props.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </Select>\n )\n }\n\n // Text input\n if (props.type === 'input') {\n return (\n <Input\n type=\"text\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => setValue(e.target.value)}\n placeholder={props.placeholder}\n className={cn('w-auto min-w-[10rem]', className)}\n {...props.inputProps}\n />\n )\n }\n\n // Default: single-select. Only valid if `options` was provided.\n if ('options' in props && props.options) {\n const includeEmpty = props.includeEmptyOption ?? true\n return (\n <Select\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => setValue(e.target.value)}\n className={cn('w-auto min-w-[10rem]', className)}\n {...props.selectProps}\n >\n {includeEmpty && (\n <option value=\"\">{props.placeholder ?? 'All'}</option>\n )}\n {props.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </Select>\n )\n }\n\n if (typeof console !== 'undefined') {\n console.warn(\n `[DataTableFilter] No \"options\" or \"render\" prop provided for column ` +\n `\"${columnId}\". Nothing will render.`,\n )\n }\n return null\n}\n","import type { Table as TanStackTable } from '@tanstack/react-table'\nimport type { ReactNode } from 'react'\n\nimport { cn } from '../../lib/cn'\nimport type { DataTableMeta } from '../../types'\nimport { DataTable, type DataTableProps } from '../DataTable'\nimport {\n DataTablePagination,\n type DataTablePaginationProps,\n} from '../DataTablePagination'\nimport {\n DataTableSearch,\n type DataTableSearchProps,\n} from '../DataTableSearch'\n\nexport interface DataTableSplitLayoutClassNames {\n root?: string\n toolbar?: string\n toolbarLeft?: string\n toolbarRight?: string\n body?: string\n footer?: string\n}\n\nexport interface DataTableSplitLayoutProps<TData> {\n table: TanStackTable<TData>\n meta?: DataTableMeta | null\n loading?: boolean\n\n /**\n * Right-side toolbar slot — typically one or more `<DataTableFilter />`s.\n * Rendered to the right of the search input on the same row.\n */\n filters?: ReactNode\n /**\n * Optional far-right toolbar slot for page-level actions (e.g. an\n * \"+ Add\" button). Appears after `filters`.\n */\n actions?: ReactNode\n\n /**\n * Override the entire search slot. By default, a `<DataTableSearch />`\n * wired to `table` is rendered with sensible defaults.\n */\n search?: ReactNode\n\n /** Forwarded to the auto-rendered `<DataTableSearch />`. */\n searchProps?: Omit<DataTableSearchProps<TData>, 'table'>\n /** Forwarded to the auto-rendered `<DataTable />`. */\n tableProps?: Omit<DataTableProps<TData>, 'table' | 'loading'>\n /** Forwarded to the auto-rendered `<DataTablePagination />`. */\n paginationProps?: Omit<DataTablePaginationProps<TData>, 'table' | 'meta'>\n\n className?: string\n classNames?: DataTableSplitLayoutClassNames\n}\n\n/**\n * Pre-built \"split toolbar\" layout: search on the left, filters/actions on\n * the right; pagination info on the left, controls on the right.\n *\n * @example\n * <DataTableSplitLayout\n * table={table}\n * meta={meta}\n * loading={loading}\n * searchProps={{ placeholder: 'Search users...' }}\n * filters={\n * <>\n * <DataTableFilter table={table} columnId=\"status\" options={statusOpts} />\n * <DataTableFilter table={table} columnId=\"role\" type=\"multiselect\" options={roleOpts} />\n * </>\n * }\n * actions={<Button>+ Add user</Button>}\n * paginationProps={{ showPageSize: true, showFirstLast: true }}\n * />\n */\nexport function DataTableSplitLayout<TData>(\n props: DataTableSplitLayoutProps<TData>,\n) {\n const {\n table,\n meta = null,\n loading = false,\n filters,\n actions,\n search,\n searchProps,\n tableProps,\n paginationProps,\n className,\n classNames,\n } = props\n\n const searchNode = search ?? (\n <DataTableSearch\n table={table}\n placeholder=\"Search...\"\n className=\"w-full max-w-md\"\n {...searchProps}\n />\n )\n\n return (\n <div\n className={cn(\n 'flex flex-col gap-4',\n className,\n classNames?.root,\n )}\n >\n <div\n className={cn(\n 'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between',\n classNames?.toolbar,\n )}\n >\n <div\n className={cn(\n 'flex-1 min-w-0',\n classNames?.toolbarLeft,\n )}\n >\n {searchNode}\n </div>\n\n {(filters || actions) && (\n <div\n className={cn(\n 'flex flex-wrap items-center gap-2',\n classNames?.toolbarRight,\n )}\n >\n {filters}\n {actions}\n </div>\n )}\n </div>\n\n <div className={classNames?.body}>\n <DataTable table={table} loading={loading} {...tableProps} />\n </div>\n\n <div className={classNames?.footer}>\n <DataTablePagination\n table={table}\n meta={meta}\n {...paginationProps}\n />\n </div>\n </div>\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manusiakemos/laravel-tanstack-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "React hooks for server-side TanStack Table powered by Laravel. Companion to manusiakemos/laravel-tanstack.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"laravel",
|
|
@@ -81,5 +81,11 @@
|
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=18.0.0"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"class-variance-authority": "^0.7.1",
|
|
87
|
+
"clsx": "^2.1.1",
|
|
88
|
+
"lucide-react": "^1.16.0",
|
|
89
|
+
"tailwind-merge": "^3.6.0"
|
|
84
90
|
}
|
|
85
91
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Table as TanStackTable,
|
|
3
|
+
flexRender,
|
|
4
|
+
} from '@tanstack/react-table'
|
|
5
|
+
import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'
|
|
6
|
+
import type { ReactNode } from 'react'
|
|
7
|
+
|
|
8
|
+
import { cn } from '../lib/cn'
|
|
9
|
+
import {
|
|
10
|
+
Table,
|
|
11
|
+
TableBody,
|
|
12
|
+
TableCell,
|
|
13
|
+
TableHead,
|
|
14
|
+
TableHeader,
|
|
15
|
+
TableRow,
|
|
16
|
+
} from './ui/table'
|
|
17
|
+
|
|
18
|
+
export interface DataTableClassNames {
|
|
19
|
+
root?: string
|
|
20
|
+
table?: string
|
|
21
|
+
header?: string
|
|
22
|
+
headerRow?: string
|
|
23
|
+
head?: string
|
|
24
|
+
body?: string
|
|
25
|
+
row?: string
|
|
26
|
+
cell?: string
|
|
27
|
+
empty?: string
|
|
28
|
+
loading?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DataTableProps<TData> {
|
|
32
|
+
table: TanStackTable<TData>
|
|
33
|
+
/** Loading flag from `useDataTable`. */
|
|
34
|
+
loading?: boolean
|
|
35
|
+
/** Slot rendered inside the empty <tbody> when there are no rows. */
|
|
36
|
+
emptyMessage?: ReactNode
|
|
37
|
+
/** Slot rendered inside the loading <tbody>. */
|
|
38
|
+
loadingMessage?: ReactNode
|
|
39
|
+
/** Class for the outer wrapper. */
|
|
40
|
+
className?: string
|
|
41
|
+
/** Fine-grained class names for table parts. */
|
|
42
|
+
classNames?: DataTableClassNames
|
|
43
|
+
/** Called when a row is clicked. */
|
|
44
|
+
onRowClick?: (row: TData) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shadcn-styled table component wired to a TanStack `Table` instance.
|
|
49
|
+
* Renders header (with sort indicators), body, empty state, and loading state.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const { table, loading } = useDataTable<User>({ ... })
|
|
53
|
+
* <DataTable table={table} loading={loading} />
|
|
54
|
+
*/
|
|
55
|
+
export function DataTable<TData>(props: DataTableProps<TData>) {
|
|
56
|
+
const {
|
|
57
|
+
table,
|
|
58
|
+
loading = false,
|
|
59
|
+
emptyMessage = 'No results.',
|
|
60
|
+
loadingMessage = 'Loading...',
|
|
61
|
+
className,
|
|
62
|
+
classNames,
|
|
63
|
+
onRowClick,
|
|
64
|
+
} = props
|
|
65
|
+
|
|
66
|
+
const columnCount = table.getAllColumns().length
|
|
67
|
+
const rows = table.getRowModel().rows
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={cn('rounded-md border', className, classNames?.root)}>
|
|
71
|
+
<Table className={classNames?.table}>
|
|
72
|
+
<TableHeader className={classNames?.header}>
|
|
73
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
74
|
+
<TableRow key={headerGroup.id} className={classNames?.headerRow}>
|
|
75
|
+
{headerGroup.headers.map((header) => {
|
|
76
|
+
const canSort = header.column.getCanSort()
|
|
77
|
+
const sortDir = header.column.getIsSorted()
|
|
78
|
+
return (
|
|
79
|
+
<TableHead
|
|
80
|
+
key={header.id}
|
|
81
|
+
className={cn(
|
|
82
|
+
canSort && 'cursor-pointer select-none',
|
|
83
|
+
classNames?.head,
|
|
84
|
+
)}
|
|
85
|
+
onClick={
|
|
86
|
+
canSort
|
|
87
|
+
? header.column.getToggleSortingHandler()
|
|
88
|
+
: undefined
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
{header.isPlaceholder ? null : (
|
|
92
|
+
<span className="inline-flex items-center gap-1">
|
|
93
|
+
{flexRender(
|
|
94
|
+
header.column.columnDef.header,
|
|
95
|
+
header.getContext(),
|
|
96
|
+
)}
|
|
97
|
+
{canSort && (
|
|
98
|
+
<span className="text-muted-foreground">
|
|
99
|
+
{sortDir === 'asc' ? (
|
|
100
|
+
<ArrowUp className="h-3.5 w-3.5" />
|
|
101
|
+
) : sortDir === 'desc' ? (
|
|
102
|
+
<ArrowDown className="h-3.5 w-3.5" />
|
|
103
|
+
) : (
|
|
104
|
+
<ChevronsUpDown className="h-3.5 w-3.5 opacity-50" />
|
|
105
|
+
)}
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</TableHead>
|
|
111
|
+
)
|
|
112
|
+
})}
|
|
113
|
+
</TableRow>
|
|
114
|
+
))}
|
|
115
|
+
</TableHeader>
|
|
116
|
+
|
|
117
|
+
<TableBody className={classNames?.body}>
|
|
118
|
+
{loading ? (
|
|
119
|
+
<TableRow>
|
|
120
|
+
<TableCell
|
|
121
|
+
colSpan={columnCount}
|
|
122
|
+
className={cn(
|
|
123
|
+
'h-24 text-center text-muted-foreground',
|
|
124
|
+
classNames?.loading,
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
{loadingMessage}
|
|
128
|
+
</TableCell>
|
|
129
|
+
</TableRow>
|
|
130
|
+
) : rows.length === 0 ? (
|
|
131
|
+
<TableRow>
|
|
132
|
+
<TableCell
|
|
133
|
+
colSpan={columnCount}
|
|
134
|
+
className={cn(
|
|
135
|
+
'h-24 text-center text-muted-foreground',
|
|
136
|
+
classNames?.empty,
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{emptyMessage}
|
|
140
|
+
</TableCell>
|
|
141
|
+
</TableRow>
|
|
142
|
+
) : (
|
|
143
|
+
rows.map((row) => (
|
|
144
|
+
<TableRow
|
|
145
|
+
key={row.id}
|
|
146
|
+
className={cn(
|
|
147
|
+
onRowClick && 'cursor-pointer',
|
|
148
|
+
classNames?.row,
|
|
149
|
+
)}
|
|
150
|
+
onClick={
|
|
151
|
+
onRowClick ? () => onRowClick(row.original) : undefined
|
|
152
|
+
}
|
|
153
|
+
>
|
|
154
|
+
{row.getVisibleCells().map((cell) => (
|
|
155
|
+
<TableCell key={cell.id} className={classNames?.cell}>
|
|
156
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
157
|
+
</TableCell>
|
|
158
|
+
))}
|
|
159
|
+
</TableRow>
|
|
160
|
+
))
|
|
161
|
+
)}
|
|
162
|
+
</TableBody>
|
|
163
|
+
</Table>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { Column, Table } from '@tanstack/react-table'
|
|
2
|
+
import type {
|
|
3
|
+
InputHTMLAttributes,
|
|
4
|
+
ReactNode,
|
|
5
|
+
SelectHTMLAttributes,
|
|
6
|
+
} from 'react'
|
|
7
|
+
|
|
8
|
+
import { cn } from '../lib/cn'
|
|
9
|
+
import { Input } from './ui/input'
|
|
10
|
+
import { Select } from './ui/select'
|
|
11
|
+
|
|
12
|
+
export type DataTableFilterValue = string | string[] | undefined
|
|
13
|
+
|
|
14
|
+
export interface DataTableFilterOption {
|
|
15
|
+
label: string
|
|
16
|
+
value: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DataTableFilterRenderProps<TData> {
|
|
20
|
+
/** The current filter value for this column. */
|
|
21
|
+
value: DataTableFilterValue
|
|
22
|
+
/** Commit a new filter value (use `undefined` or `''` to clear). */
|
|
23
|
+
setValue: (next: DataTableFilterValue) => void
|
|
24
|
+
/** The TanStack column instance. */
|
|
25
|
+
column: Column<TData, unknown>
|
|
26
|
+
/** The parent table instance. */
|
|
27
|
+
table: Table<TData>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface BaseFilterProps<TData> {
|
|
31
|
+
table: Table<TData>
|
|
32
|
+
/** The column id this filter applies to. */
|
|
33
|
+
columnId: string
|
|
34
|
+
/** Class for the root element (when not using `render`). */
|
|
35
|
+
className?: string
|
|
36
|
+
/**
|
|
37
|
+
* Full render override. You get the raw column filter value + setter, so
|
|
38
|
+
* any UI (popover, multi-select combobox, date picker, …) can be plugged
|
|
39
|
+
* in while still feeding the same TanStack column filter pipeline.
|
|
40
|
+
*/
|
|
41
|
+
render?: (props: DataTableFilterRenderProps<TData>) => ReactNode
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SelectFilterProps<TData> extends BaseFilterProps<TData> {
|
|
45
|
+
type?: 'select'
|
|
46
|
+
options: DataTableFilterOption[]
|
|
47
|
+
/** Placeholder option label (renders an empty value option). */
|
|
48
|
+
placeholder?: string
|
|
49
|
+
/** Whether to render the empty/placeholder option. Defaults to true. */
|
|
50
|
+
includeEmptyOption?: boolean
|
|
51
|
+
selectProps?: Omit<
|
|
52
|
+
SelectHTMLAttributes<HTMLSelectElement>,
|
|
53
|
+
'value' | 'onChange' | 'className'
|
|
54
|
+
>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface MultiSelectFilterProps<TData> extends BaseFilterProps<TData> {
|
|
58
|
+
type: 'multiselect'
|
|
59
|
+
options: DataTableFilterOption[]
|
|
60
|
+
selectProps?: Omit<
|
|
61
|
+
SelectHTMLAttributes<HTMLSelectElement>,
|
|
62
|
+
'value' | 'onChange' | 'multiple' | 'className'
|
|
63
|
+
>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface InputFilterProps<TData> extends BaseFilterProps<TData> {
|
|
67
|
+
type: 'input'
|
|
68
|
+
placeholder?: string
|
|
69
|
+
inputProps?: Omit<
|
|
70
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
71
|
+
'value' | 'onChange' | 'type' | 'className'
|
|
72
|
+
>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface CustomFilterProps<TData> extends BaseFilterProps<TData> {
|
|
76
|
+
type?: 'custom'
|
|
77
|
+
/** Required when `type` is `"custom"` (or when no `options` given). */
|
|
78
|
+
render: (props: DataTableFilterRenderProps<TData>) => ReactNode
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type DataTableFilterProps<TData> =
|
|
82
|
+
| SelectFilterProps<TData>
|
|
83
|
+
| MultiSelectFilterProps<TData>
|
|
84
|
+
| InputFilterProps<TData>
|
|
85
|
+
| CustomFilterProps<TData>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Shadcn-styled per-column filter wired to a TanStack `Table`.
|
|
89
|
+
*
|
|
90
|
+
* Supports four modes:
|
|
91
|
+
* - `type="select"` — single-select (default when `options` given)
|
|
92
|
+
* - `type="multiselect"` — multi-select
|
|
93
|
+
* - `type="input"` — text input
|
|
94
|
+
* - `type="custom"` (or `render`) — fully custom UI
|
|
95
|
+
*
|
|
96
|
+
* @example Select
|
|
97
|
+
* <DataTableFilter
|
|
98
|
+
* table={table}
|
|
99
|
+
* columnId="status"
|
|
100
|
+
* options={[
|
|
101
|
+
* { label: 'Active', value: 'active' },
|
|
102
|
+
* { label: 'Inactive', value: 'inactive' },
|
|
103
|
+
* ]}
|
|
104
|
+
* placeholder="All statuses"
|
|
105
|
+
* />
|
|
106
|
+
*
|
|
107
|
+
* @example Custom render
|
|
108
|
+
* <DataTableFilter
|
|
109
|
+
* table={table}
|
|
110
|
+
* columnId="status"
|
|
111
|
+
* render={({ value, setValue }) => (
|
|
112
|
+
* <MyCustomFilter value={value} onChange={setValue} />
|
|
113
|
+
* )}
|
|
114
|
+
* />
|
|
115
|
+
*/
|
|
116
|
+
export function DataTableFilter<TData>(props: DataTableFilterProps<TData>) {
|
|
117
|
+
const { table, columnId, className, render } = props
|
|
118
|
+
const column = table.getColumn(columnId)
|
|
119
|
+
|
|
120
|
+
if (!column) {
|
|
121
|
+
if (typeof console !== 'undefined') {
|
|
122
|
+
console.warn(
|
|
123
|
+
`[DataTableFilter] Column "${columnId}" not found on table. ` +
|
|
124
|
+
`Make sure a column with this id exists.`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const value = column.getFilterValue() as DataTableFilterValue
|
|
131
|
+
const setValue = (next: DataTableFilterValue) => {
|
|
132
|
+
if (
|
|
133
|
+
next === undefined ||
|
|
134
|
+
next === '' ||
|
|
135
|
+
(Array.isArray(next) && next.length === 0)
|
|
136
|
+
) {
|
|
137
|
+
column.setFilterValue(undefined)
|
|
138
|
+
} else {
|
|
139
|
+
column.setFilterValue(next)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (render) {
|
|
144
|
+
return <>{render({ value, setValue, column, table })}</>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Multi-select
|
|
148
|
+
if (props.type === 'multiselect') {
|
|
149
|
+
const arrayValue = Array.isArray(value)
|
|
150
|
+
? value
|
|
151
|
+
: value
|
|
152
|
+
? [String(value)]
|
|
153
|
+
: []
|
|
154
|
+
return (
|
|
155
|
+
<Select
|
|
156
|
+
multiple
|
|
157
|
+
value={arrayValue}
|
|
158
|
+
onChange={(e) => {
|
|
159
|
+
const selected = Array.from(e.target.selectedOptions).map((o) => o.value)
|
|
160
|
+
setValue(selected)
|
|
161
|
+
}}
|
|
162
|
+
className={cn('w-auto min-w-[10rem]', className)}
|
|
163
|
+
{...props.selectProps}
|
|
164
|
+
>
|
|
165
|
+
{props.options.map((opt) => (
|
|
166
|
+
<option key={opt.value} value={opt.value}>
|
|
167
|
+
{opt.label}
|
|
168
|
+
</option>
|
|
169
|
+
))}
|
|
170
|
+
</Select>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Text input
|
|
175
|
+
if (props.type === 'input') {
|
|
176
|
+
return (
|
|
177
|
+
<Input
|
|
178
|
+
type="text"
|
|
179
|
+
value={typeof value === 'string' ? value : ''}
|
|
180
|
+
onChange={(e) => setValue(e.target.value)}
|
|
181
|
+
placeholder={props.placeholder}
|
|
182
|
+
className={cn('w-auto min-w-[10rem]', className)}
|
|
183
|
+
{...props.inputProps}
|
|
184
|
+
/>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Default: single-select. Only valid if `options` was provided.
|
|
189
|
+
if ('options' in props && props.options) {
|
|
190
|
+
const includeEmpty = props.includeEmptyOption ?? true
|
|
191
|
+
return (
|
|
192
|
+
<Select
|
|
193
|
+
value={typeof value === 'string' ? value : ''}
|
|
194
|
+
onChange={(e) => setValue(e.target.value)}
|
|
195
|
+
className={cn('w-auto min-w-[10rem]', className)}
|
|
196
|
+
{...props.selectProps}
|
|
197
|
+
>
|
|
198
|
+
{includeEmpty && (
|
|
199
|
+
<option value="">{props.placeholder ?? 'All'}</option>
|
|
200
|
+
)}
|
|
201
|
+
{props.options.map((opt) => (
|
|
202
|
+
<option key={opt.value} value={opt.value}>
|
|
203
|
+
{opt.label}
|
|
204
|
+
</option>
|
|
205
|
+
))}
|
|
206
|
+
</Select>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (typeof console !== 'undefined') {
|
|
211
|
+
console.warn(
|
|
212
|
+
`[DataTableFilter] No "options" or "render" prop provided for column ` +
|
|
213
|
+
`"${columnId}". Nothing will render.`,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
return null
|
|
217
|
+
}
|