Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 8x 40x 40x 40x 8x 8x 9x 9x 90x 90x 8x 6x | import { memo, type ReactNode, type HTMLAttributes } from 'react';
import cn from 'classnames';
import Loader from './loader';
import useDataCheckboxes from '../hooks/useDataCheckboxes';
import withDataLoader, { type WrapperProps } from './data-loader';
export type Props<Datum> = {
/**
* The data to be displayed
*/
data: Datum[];
/**
* Flag saying that data is loading, so we might be showing stale data
*/
loading?: boolean;
/**
* A function that returns a unique ID for each of the data objects.
* Same function signature as a map function.
*/
getIdKey: (datum: Datum, index: number, data: Datum[]) => string;
/**
* A callback that is called whenever a user selects or unselects a row.
*/
onSelectionChange?: (event: MouseEvent | KeyboardEvent) => void;
/**
* A renderer function for each item of the list.
* Make sure that it doesn't change unecessarily by wrapping it in useCallback
*/
dataRenderer: (datum: Datum) => ReactNode;
};
type BasicDatum = Record<string, unknown>;
type DataListItemProps<Datum> = {
datum: Datum;
id: string;
dataRenderer: (datum: Datum) => ReactNode;
loading: boolean;
firstItem: boolean;
};
const DataListItem = <Datum extends BasicDatum>({
datum,
id,
dataRenderer,
loading,
firstItem,
}: DataListItemProps<Datum>) => {
let rendered: ReactNode;
try {
rendered = dataRenderer(datum);
} catch (error) {
/**
* We get here only if the renderer fails. If the renderer returns null of
* undefined because of a lack of data, then it will no throw and will not
* display the loader at all
*/
if (!loading) {
throw error;
} else {
rendered = firstItem && <Loader />;
}
}
return <li key={id}>{rendered}</li>;
};
const MemoizedDataListItem = memo(DataListItem) as typeof DataListItem;
export const DataList = <Datum extends BasicDatum>({
data,
getIdKey,
dataRenderer,
loading = false,
onSelectionChange,
className,
...props
}: Props<Datum> & HTMLAttributes<HTMLUListElement>) => {
const { checkboxContainerRef } = useDataCheckboxes(onSelectionChange);
return (
<ul
{...props}
className={cn('data-list', 'no-bullet', className)}
ref={checkboxContainerRef}
>
{data.map((datum, index) => {
const id = getIdKey(datum, index, data);
return (
<MemoizedDataListItem
key={id}
datum={datum}
id={id}
dataRenderer={dataRenderer}
loading={loading}
firstItem={index === 0}
/>
);
})}
</ul>
);
};
export const DataListWithLoader = <Datum extends BasicDatum>(
props: WrapperProps<Datum> & Props<Datum> & HTMLAttributes<HTMLElement>
) => <>{withDataLoader<Datum, typeof props>(DataList)(props)}</>;
|