feat(ui): support disabling pagination in useTable complete mode
Add `CompletePaginationOptions` type extending `PaginationOptions`
with a `type` field supporting `'page'` (default) and `'none'`.
When using `mode: 'complete'` with `type: 'none'`, `useTable` skips
data slicing and produces `pagination: { type: 'none' }` in
`tableProps` directly.
Also sync `pageSize` state when `paginationOptions.pageSize` changes
dynamically, fixing cases where the initial value became stale.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added support for disabling pagination in `useTable` complete mode by setting `paginationOptions: { type: 'none' }`. This skips data slicing and produces `pagination: { type: 'none' }` in `tableProps`, removing the need for consumers to manually override the pagination prop on `Table`. Also fixed complete mode not reacting to dynamic changes in `paginationOptions.pageSize`.
|
||||
|
||||
**Affected components:** `useTable`
|
||||
@@ -119,7 +119,7 @@ With `mode: 'complete'`, sorting happens client-side. Provide a `sortFn` that re
|
||||
|
||||
### Pagination
|
||||
|
||||
Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically.
|
||||
Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically. In `complete` mode, set `type: 'none'` to disable pagination and show all rows.
|
||||
|
||||
<CodeBlock code={tablePaginationSnippet} />
|
||||
|
||||
|
||||
@@ -45,15 +45,44 @@ export const useTableOptionsPropDefs: Record<string, PropDef> = {
|
||||
'The data for the table. Only applicable for "complete" mode, and either this or `getData` must be provided.',
|
||||
},
|
||||
paginationOptions: {
|
||||
type: 'enum',
|
||||
values: ['object'],
|
||||
description: (
|
||||
<>
|
||||
Pagination configuration including <Chip>pageSize</Chip>,{' '}
|
||||
<Chip>pageSizeOptions</Chip>, <Chip>initialOffset</Chip>, and{' '}
|
||||
<Chip>showPaginationLabel</Chip>.
|
||||
</>
|
||||
),
|
||||
type: 'complex',
|
||||
description: 'Pagination configuration.',
|
||||
complexType: {
|
||||
name: 'PaginationOptions',
|
||||
properties: {
|
||||
type: {
|
||||
type: "'page' | 'none'",
|
||||
description:
|
||||
"Pagination mode. Set to 'none' to disable pagination and show all rows (complete mode only). Defaults to 'page'.",
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
description: 'Number of items per page. Defaults to 20.',
|
||||
},
|
||||
pageSizeOptions: {
|
||||
type: 'number[]',
|
||||
description: 'Available page size options for the dropdown.',
|
||||
},
|
||||
initialOffset: {
|
||||
type: 'number',
|
||||
description: 'Starting offset for the first page.',
|
||||
},
|
||||
showPageSizeOptions: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether to show the page size dropdown. Defaults to true.',
|
||||
},
|
||||
showPaginationLabel: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
"Whether to display the pagination label (e.g., '1 - 20 of 150').",
|
||||
},
|
||||
getLabel: {
|
||||
type: '(props) => string',
|
||||
description: 'Custom function to generate the pagination label text.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Uncontrolled state
|
||||
initialSort: {
|
||||
|
||||
@@ -906,6 +906,12 @@ export type Columns =
|
||||
| '12'
|
||||
| 'auto';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CompletePaginationOptions extends PaginationOptions {
|
||||
// (undocumented)
|
||||
type?: 'page' | 'none';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Container: ForwardRefExoticComponent<
|
||||
ContainerProps & RefAttributes<HTMLDivElement>
|
||||
@@ -3162,7 +3168,17 @@ export const useBreakpoint: () => {
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableOptions<T, TFilter>,
|
||||
options: UseTableCompleteOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableOffsetOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableCursorOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -3171,7 +3187,7 @@ export type UseTableCompleteOptions<
|
||||
TFilter = unknown,
|
||||
> = QueryOptions<TFilter> & {
|
||||
mode: 'complete';
|
||||
paginationOptions?: PaginationOptions;
|
||||
paginationOptions?: CompletePaginationOptions;
|
||||
sortFn?: (data: T[], sort: SortDescriptor) => T[];
|
||||
filterFn?: (data: T[], filter: TFilter) => T[];
|
||||
searchFn?: (data: T[], search: string) => T[];
|
||||
|
||||
@@ -62,6 +62,11 @@ export interface PaginationOptions
|
||||
initialOffset?: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface CompletePaginationOptions extends PaginationOptions {
|
||||
type?: 'page' | 'none';
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface OffsetParams<TFilter> {
|
||||
offset: number;
|
||||
@@ -102,7 +107,7 @@ export type UseTableCompleteOptions<
|
||||
TFilter = unknown,
|
||||
> = QueryOptions<TFilter> & {
|
||||
mode: 'complete';
|
||||
paginationOptions?: PaginationOptions;
|
||||
paginationOptions?: CompletePaginationOptions;
|
||||
sortFn?: (data: T[], sort: SortDescriptor) => T[];
|
||||
filterFn?: (data: T[], filter: TFilter) => T[];
|
||||
searchFn?: (data: T[], search: string) => T[];
|
||||
|
||||
@@ -38,8 +38,11 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
searchFn,
|
||||
} = options;
|
||||
const hasGetData = 'getData' in options;
|
||||
const noPagination = paginationOptions.type === 'none';
|
||||
const { initialOffset = 0 } = paginationOptions;
|
||||
const defaultPageSize = getEffectivePageSize(paginationOptions);
|
||||
const defaultPageSize = noPagination
|
||||
? Infinity
|
||||
: getEffectivePageSize(paginationOptions);
|
||||
|
||||
const getData = useStableCallback(getDataProp);
|
||||
const { sort, filter, search } = query;
|
||||
@@ -52,6 +55,12 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
const [offset, setOffset] = useState(initialOffset);
|
||||
const [pageSize, setPageSize] = useState(defaultPageSize);
|
||||
|
||||
// Sync pageSize when the caller changes paginationOptions.pageSize
|
||||
useEffect(() => {
|
||||
setPageSize(defaultPageSize);
|
||||
setOffset(0);
|
||||
}, [defaultPageSize]);
|
||||
|
||||
// Load data on mount and when loadCount changes (reload trigger)
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@@ -121,12 +130,15 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
|
||||
// Paginate the processed data
|
||||
const paginatedData = useMemo(
|
||||
() => processedData?.slice(offset, offset + pageSize),
|
||||
[processedData, offset, pageSize],
|
||||
() =>
|
||||
noPagination
|
||||
? processedData
|
||||
: processedData?.slice(offset, offset + pageSize),
|
||||
[processedData, offset, pageSize, noPagination],
|
||||
);
|
||||
|
||||
const hasNextPage = offset + pageSize < totalCount;
|
||||
const hasPreviousPage = offset > 0;
|
||||
const hasNextPage = !noPagination && offset + pageSize < totalCount;
|
||||
const hasPreviousPage = !noPagination && offset > 0;
|
||||
|
||||
const onNextPage = useCallback(() => {
|
||||
if (offset + pageSize < totalCount) {
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import type { SortState, TableItem, TableProps } from '../types';
|
||||
import type {
|
||||
PaginationOptions,
|
||||
PaginationResult,
|
||||
UseTableCompleteOptions,
|
||||
UseTableCursorOptions,
|
||||
UseTableOffsetOptions,
|
||||
UseTableOptions,
|
||||
UseTableResult,
|
||||
} from './types';
|
||||
@@ -29,7 +31,7 @@ import { useOffsetPagination } from './useOffsetPagination';
|
||||
function useTableProps<T extends TableItem>(
|
||||
paginationResult: PaginationResult<T>,
|
||||
sortState: SortState,
|
||||
paginationOptions: PaginationOptions = {},
|
||||
paginationOptions: UseTableCompleteOptions<T>['paginationOptions'] = {},
|
||||
): Omit<
|
||||
TableProps<T>,
|
||||
'columnConfig' | 'rowConfig' | 'selection' | 'emptyState'
|
||||
@@ -52,8 +54,11 @@ function useTableProps<T extends TableItem>(
|
||||
const displayData = paginationResult.data ?? previousDataRef.current;
|
||||
const isStale = paginationResult.loading && displayData !== undefined;
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
const pagination = useMemo(() => {
|
||||
if (paginationOptions.type === 'none') {
|
||||
return { type: 'none' as const };
|
||||
}
|
||||
return {
|
||||
type: 'page' as const,
|
||||
pageSize: paginationResult.pageSize,
|
||||
pageSizeOptions,
|
||||
@@ -76,25 +81,25 @@ function useTableProps<T extends TableItem>(
|
||||
showPageSizeOptions,
|
||||
getLabel,
|
||||
showPaginationLabel,
|
||||
}),
|
||||
[
|
||||
paginationResult.pageSize,
|
||||
pageSizeOptions,
|
||||
paginationResult.offset,
|
||||
paginationResult.totalCount,
|
||||
paginationResult.hasNextPage,
|
||||
paginationResult.hasPreviousPage,
|
||||
paginationResult.onNextPage,
|
||||
paginationResult.onPreviousPage,
|
||||
paginationResult.onPageSizeChange,
|
||||
onNextPageCallback,
|
||||
onPreviousPageCallback,
|
||||
onPageSizeChangeCallback,
|
||||
showPageSizeOptions,
|
||||
getLabel,
|
||||
showPaginationLabel,
|
||||
],
|
||||
);
|
||||
};
|
||||
}, [
|
||||
paginationOptions.type,
|
||||
paginationResult.pageSize,
|
||||
pageSizeOptions,
|
||||
paginationResult.offset,
|
||||
paginationResult.totalCount,
|
||||
paginationResult.hasNextPage,
|
||||
paginationResult.hasPreviousPage,
|
||||
paginationResult.onNextPage,
|
||||
paginationResult.onPreviousPage,
|
||||
paginationResult.onPageSizeChange,
|
||||
onNextPageCallback,
|
||||
onPreviousPageCallback,
|
||||
onPageSizeChangeCallback,
|
||||
showPageSizeOptions,
|
||||
getLabel,
|
||||
showPaginationLabel,
|
||||
]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
@@ -117,6 +122,17 @@ function useTableProps<T extends TableItem>(
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableCompleteOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
/** @public */
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableOffsetOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
/** @public */
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableCursorOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter>;
|
||||
export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
options: UseTableOptions<T, TFilter>,
|
||||
): UseTableResult<T, TFilter> {
|
||||
|
||||
@@ -69,6 +69,7 @@ export type {
|
||||
SearchState,
|
||||
QueryOptions,
|
||||
PaginationOptions,
|
||||
CompletePaginationOptions,
|
||||
} from './hooks/types';
|
||||
|
||||
export { TableDefinition } from './definition';
|
||||
|
||||
Reference in New Issue
Block a user