Table 10.20.0
A component for displaying large amounts of data in rows and columns. Based on TanStack Table v8.
Default
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
Test | Test | 60 | 200 | 200 | 200 | 200 |
Test | Test | 90 | 300 | 300 | 300 | 300 |
Test | Test | 120 | 400 | 400 | 400 | 400 |
"use client";
import { useCallback, useMemo } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type { ColumnDef } from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DefaultHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
};
const Example = () => {
const makeData = useCallback((length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
};
});
}, []);
const columns = useMemo<ColumnDef<{}, DefaultHelper>[]>(
() => [
{
id: "firstName",
header: () => "First Name",
accessorKey: "firstName",
},
{
id: "lastName",
header: () => "Last Name",
accessorKey: "lastName",
},
{
id: "age",
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
id: "visits",
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
id: "progress",
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
id: "activity",
header: () => "Activity",
accessorKey: "activity",
},
{
id: "status",
header: () => "Status",
accessorKey: "status",
},
],
[],
);
const data = useMemo(() => makeData(5), [makeData]);
return (
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} />
</div>
);
};
export default Example;
Different row gaps
Examples with gap values of: 0, 2px (default), 4px, 8px and 12px.
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
"use client";
import { useCallback, useMemo } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type { ColumnDef } from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DefaultHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
};
const Example = () => {
const makeData = useCallback((length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
};
});
}, []);
const columns = useMemo<ColumnDef<{}, DefaultHelper>[]>(
() => [
{
id: "firstName",
header: () => "First Name",
accessorKey: "firstName",
},
{
id: "lastName",
header: () => "Last Name",
accessorKey: "lastName",
},
{
id: "age",
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
id: "visits",
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
id: "progress",
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
id: "activity",
header: () => "Activity",
accessorKey: "activity",
},
{
id: "status",
header: () => "Status",
accessorKey: "status",
},
],
[],
);
const data = useMemo(() => makeData(2), [makeData]);
return (
<>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowGap="0" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowGap="4px" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowGap="8px" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowGap="12px" />
</div>
</>
);
};
export default Example;
Different row sizes
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
"use client";
import { useCallback, useMemo } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type { ColumnDef } from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DefaultHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
};
const Example = () => {
const makeData = useCallback((length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
};
});
}, []);
const columns = useMemo<ColumnDef<{}, DefaultHelper>[]>(
() => [
{
id: "firstName",
header: () => "First Name",
accessorKey: "firstName",
},
{
id: "lastName",
header: () => "Last Name",
accessorKey: "lastName",
},
{
id: "age",
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
id: "visits",
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
id: "progress",
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
id: "activity",
header: () => "Activity",
accessorKey: "activity",
},
{
id: "status",
header: () => "Status",
accessorKey: "status",
},
],
[],
);
const data = useMemo(() => makeData(2), [makeData]);
return (
<>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowSize="xs" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowSize="sm" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowSize="lg" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowSize="xl" />
</div>
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table columns={columns} data={data} rowSize="2xl" />
</div>
</>
);
};
export default Example;
Cell borders
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
Test | Test | 60 | 200 | 200 | 200 | 200 |
Test | Test | 90 | 300 | 300 | 300 | 300 |
Test | Test | 120 | 400 | 400 | 400 | 400 |
"use client";
import { useMemo, useCallback } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type { ColumnDef } from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DefaultHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
};
const Example = () => {
const makeData = useCallback((length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
};
});
}, []);
const columns = useMemo<ColumnDef<{}, DefaultHelper>[]>(
() => [
{
id: "firstName",
header: () => "First Name",
accessorKey: "firstName",
},
{
id: "lastName",
header: () => "Last Name",
accessorKey: "lastName",
},
{
id: "age",
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
id: "visits",
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
id: "progress",
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
id: "activity",
header: () => "Activity",
accessorKey: "activity",
},
{
id: "status",
header: () => "Status",
accessorKey: "status",
},
],
[],
);
const data = useMemo(() => makeData(5), [makeData]);
return (
<div className="w-full max-w-screen-lg border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
layout="stretched-auto"
isResizable
withCellBorder
/>
</div>
);
};
export default Example;
Clickable rows
This example demonstrates the capabilities of a table with clickable rows.
Use the mouse wheel or the arrow keys on the keyboard to scroll the data up and down.
Watch the result of clicking on the rows in the browser console.
First Name | Last Name | Age | Visits | Progress | Activity | Status |
---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 100 | 100 |
Test | Test | 60 | 200 | 200 | 200 | 200 |
Test | Test | 90 | 300 | 300 | 300 | 300 |
Test | Test | 120 | 400 | 400 | 400 | 400 |
Test | Test | 150 | 500 | 500 | 500 | 500 |
Test | Test | 180 | 600 | 600 | 600 | 600 |
Test | Test | 210 | 700 | 700 | 700 | 700 |
Test | Test | 240 | 800 | 800 | 800 | 800 |
Test | Test | 270 | 900 | 900 | 900 | 900 |
Test | Test | 300 | 1000 | 1000 | 1000 | 1000 |
Test | Test | 330 | 1100 | 1100 | 1100 | 1100 |
Test | Test | 360 | 1200 | 1200 | 1200 | 1200 |
Test | Test | 390 | 1300 | 1300 | 1300 | 1300 |
Test | Test | 420 | 1400 | 1400 | 1400 | 1400 |
Test | Test | 450 | 1500 | 1500 | 1500 | 1500 |
Test | Test | 480 | 1600 | 1600 | 1600 | 1600 |
Test | Test | 510 | 1700 | 1700 | 1700 | 1700 |
Test | Test | 540 | 1800 | 1800 | 1800 | 1800 |
Test | Test | 570 | 1900 | 1900 | 1900 | 1900 |
"use client";
import { useCallback, useMemo, useState } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type {
ColumnDef,
Row,
} from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DataTypeHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
};
const Example = () => {
const makeData = useCallback((length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
};
});
}, []);
const [data, setData] = useState(makeData(20));
const columns = useMemo<ColumnDef<{}, DataTypeHelper>[]>(
() => [
{
header: () => "First Name",
accessorKey: "firstName",
},
{
header: () => "Last Name",
accessorKey: "lastName",
},
{
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
header: () => "Activity",
accessorKey: "activity",
},
{
header: () => "Status",
accessorKey: "status",
},
],
[],
);
return (
<div className="border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
width={800}
height={400}
layout="stretched-auto"
rowActiveColor="goku"
rowHoverColor="beerus"
isSelectable={true}
getOnRowClickHandler={(row: Row<{}>) => () => {
console.log(`You clicked row with ID - ${row.id}`);
}}
/>
</div>
);
};
export default Example;
Expandable rows
This example demonstrates the capabilities of a table with pre-set expandable rows.
Use the mouse wheel or the arrow keys on the keyboard to scroll the data up and down.
Name | Info | Actions | ||||
---|---|---|---|---|---|---|
More Info | ||||||
First Name | Last Name | Age | Visits | Status | Profile Progress | Actions |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
FirstName | LastName | 40 | 1000 | complicated | 100 | |
Name | Info | Actions |
"use client";
import { useCallback, useMemo, useState } from "react";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import {
ArrowsRefreshRound,
ControlsChevronDown,
ControlsChevronRight,
} from "@heathmont/moon-icons-tw";
import { Chip, Tooltip } from "@heathmont/moon-core-tw";
import type DataHelper from "@heathmont/moon-table-v8-tw/lib/es/private/types/DataHelper";
import type {
ExpandedState,
ColumnDef,
} from "@heathmont/moon-table-v8-tw/lib/es/private/types";
interface Person extends DataHelper {
firstName: string;
lastName: string;
age: number;
visits: number;
progress: number;
status: "relationship" | "complicated" | "single";
actions: JSX.Element;
subRows?: Person[];
}
const Example = () => {
const range = useCallback((len: number) => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
}, []);
const tooltip = useMemo(
() => (
<Tooltip>
<Tooltip.Trigger className="max-h-6">
<Chip
variant="ghost"
iconOnly={<ArrowsRefreshRound className="text-moon-24 max-h-6" />}
onClick={() => {
window.location.reload();
}}
/>
</Tooltip.Trigger>
<Tooltip.Content position="top-start" className="z-[2]">
Reload page
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip>
),
[],
);
const newPerson = useMemo((): Person => {
return {
firstName: "FirstName",
lastName: "LastName",
age: 40,
visits: 1000,
progress: 100,
status: "complicated",
actions: tooltip,
};
}, [tooltip]);
const makeData = useCallback(
(...lens: number[]) => {
const makeDataLevel = (depth = 0): Person[] => {
const len = lens[depth]!;
return range(len).map((d): Person => {
return {
...newPerson,
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
};
});
};
return makeDataLevel();
},
[newPerson, range],
);
const columns = useMemo<ColumnDef<{}, Person>[]>(
() => [
{
header: "Name",
footer: "Name",
columns: [
{
accessorKey: "firstName",
header: ({ table }) => (
<>
<button onClick={table.getToggleAllRowsExpandedHandler()}>
{table.getIsAllRowsExpanded() ? (
<ControlsChevronDown />
) : (
<ControlsChevronRight />
)}
</button>{" "}
First Name
</>
),
cell: ({ row, getValue }) => (
<div
style={{
paddingLeft: `${row.depth * 2}rem`,
}}
className="flex gap-x-1"
>
<>
{row.getCanExpand() ? (
<button
className="cursor-pointer"
onClick={row.getToggleExpandedHandler()}
>
{row.getIsExpanded() ? (
<ControlsChevronDown />
) : (
<ControlsChevronRight />
)}
</button>
) : null}
{getValue()}
</>
</div>
),
},
{
accessorFn: (row: Person) => row.lastName,
id: "lastName",
cell: (info) => info.getValue(),
header: () => <span>Last Name</span>,
},
],
},
{
header: "Info",
footer: "Info",
columns: [
{
accessorKey: "age",
header: "Age",
},
{
header: "More Info",
columns: [
{
accessorKey: "visits",
header: () => <span>Visits</span>,
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "progress",
header: "Profile Progress",
},
],
},
],
},
{
id: "actions",
header: "Actions",
footer: (props) => "Actions",
columns: [
{
header: "Actions",
accessorKey: "actions",
cell: (props) => props.getValue(),
},
],
},
],
[],
);
const preset: ExpandedState = {
"0": true,
"0.2": true,
"0.2.1": true,
};
const [expanded, setExpanded] = useState<ExpandedState>(preset);
const [data, setData] = useState(makeData(10, 5, 3));
const getSubRows = useCallback(
({ subRows }: DataHelper) => subRows as DataHelper[],
[],
);
return (
<div className="border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
width={800}
height={600}
layout="stretched-auto"
state={{ expanded }}
getSubRows={getSubRows}
onExpandedChange={setExpanded}
withFooter={true}
/>
</div>
);
};
export default Example;
Selectable rows
This example demonstrates the capabilities of a table with pre-set selectable rows.
Use the mouse wheel or the arrow keys on the keyboard to scroll the data up and down.
Watch the result of the row selection in the browser console.
First Name | Last Name | Age | Visits | Progress | Activity | Status | Actions |
---|---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 | |
Test | Test | 30 | 100 | 100 | 100 | 100 | |
Test | Test | 60 | 200 | 200 | 200 | 200 | |
Test | Test | 90 | 300 | 300 | 300 | 300 | |
Test | Test | 120 | 400 | 400 | 400 | 400 | |
Test | Test | 150 | 500 | 500 | 500 | 500 | |
Test | Test | 180 | 600 | 600 | 600 | 600 | |
Test | Test | 210 | 700 | 700 | 700 | 700 | |
Test | Test | 240 | 800 | 800 | 800 | 800 | |
Test | Test | 270 | 900 | 900 | 900 | 900 | |
Test | Test | 300 | 1000 | 1000 | 1000 | 1000 | |
Test | Test | 330 | 1100 | 1100 | 1100 | 1100 | |
Test | Test | 360 | 1200 | 1200 | 1200 | 1200 | |
Test | Test | 390 | 1300 | 1300 | 1300 | 1300 | |
Test | Test | 420 | 1400 | 1400 | 1400 | 1400 | |
Test | Test | 450 | 1500 | 1500 | 1500 | 1500 | |
Test | Test | 480 | 1600 | 1600 | 1600 | 1600 | |
Test | Test | 510 | 1700 | 1700 | 1700 | 1700 | |
Test | Test | 540 | 1800 | 1800 | 1800 | 1800 | |
Test | Test | 570 | 1900 | 1900 | 1900 | 1900 |
"use client";
import { ReactNode, useCallback, useMemo, useState } from "react";
import { Chip, Tooltip } from "@heathmont/moon-core-tw";
import { ArrowsRefreshRound } from "@heathmont/moon-icons-tw";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type {
ColumnDef,
Row,
RowSelectionState,
} from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DataTypeHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
actions: () => void;
};
const preset: RowSelectionState = {
1: true,
3: true,
4: true,
11: true,
16: true,
};
const Example = () => {
const tooltip = useMemo(
() => (
<Tooltip>
<Tooltip.Trigger className="max-h-6">
<Chip
variant="ghost"
iconOnly={<ArrowsRefreshRound className="text-moon-24 max-h-6" />}
onClick={() => {
window.location.reload();
}}
/>
</Tooltip.Trigger>
<Tooltip.Content position="top-start" className="z-[2]">
Reload page
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip>
),
[],
);
const makeData = useCallback(
(length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
actions: tooltip,
};
});
},
[tooltip],
);
const [rowSelection, setRowSelection] = useState<RowSelectionState>(preset);
const [data, setData] = useState(makeData(20));
const columns = useMemo<ColumnDef<{}, DataTypeHelper>[]>(
() => [
{
header: () => "First Name",
accessorKey: "firstName",
},
{
header: () => "Last Name",
accessorKey: "lastName",
},
{
header: () => "Age",
accessorKey: "age",
cell: (props) => (
<div onClick={props.row.getToggleSelectedHandler()}>
{props.getValue() as unknown as ReactNode}
</div>
),
},
{
header: () => "Visits",
accessorKey: "visits",
cell: (props) => (
<div onClick={props.row.getToggleSelectedHandler()}>
{props.getValue() as unknown as ReactNode}
</div>
),
},
{
header: () => "Progress",
accessorKey: "progress",
cell: (props) => (
<div onClick={props.row.getToggleSelectedHandler()}>
{props.getValue() as unknown as ReactNode}
</div>
),
},
{
header: () => "Activity",
accessorKey: "activity",
},
{
header: () => "Status",
accessorKey: "status",
},
{
header: () => "Actions",
accessorKey: "actions",
cell: (props) => props.getValue(),
},
],
[],
);
return (
<div className="border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
width={800}
height={400}
layout="stretched-auto"
state={{ rowSelection }}
onRowSelectionChange={setRowSelection}
isSelectable={true}
getOnRowSelectHandler={() => (rows: Row<{}>[]) => {
console.log(
`IDs of selected rows - ${rows.map((row: Row<{}>) => row.id)}`,
);
}}
/>
</div>
);
};
export default Example;
Selectable rows with checkboxes
This example demonstrates the capabilities of a table with pre-set checkboxes in the selectable rows.
Use the mouse wheel or the arrow keys on the keyboard to scroll the data up and down.
Watch the result of the row selection in the browser console.
First Name | Last Name | Age | Visits | Progress | Activity | Status | Actions | |
---|---|---|---|---|---|---|---|---|
Test | Test | 0 | 0 | 0 | 0 | 0 | ||
Test | Test | 30 | 100 | 100 | 100 | 100 | ||
Test | Test | 60 | 200 | 200 | 200 | 200 | ||
Test | Test | 90 | 300 | 300 | 300 | 300 | ||
Test | Test | 120 | 400 | 400 | 400 | 400 | ||
Test | Test | 150 | 500 | 500 | 500 | 500 | ||
Test | Test | 180 | 600 | 600 | 600 | 600 | ||
Test | Test | 210 | 700 | 700 | 700 | 700 | ||
Test | Test | 240 | 800 | 800 | 800 | 800 | ||
Test | Test | 270 | 900 | 900 | 900 | 900 | ||
Test | Test | 300 | 1000 | 1000 | 1000 | 1000 | ||
Test | Test | 330 | 1100 | 1100 | 1100 | 1100 | ||
Test | Test | 360 | 1200 | 1200 | 1200 | 1200 | ||
Test | Test | 390 | 1300 | 1300 | 1300 | 1300 | ||
Test | Test | 420 | 1400 | 1400 | 1400 | 1400 | ||
Test | Test | 450 | 1500 | 1500 | 1500 | 1500 | ||
Test | Test | 480 | 1600 | 1600 | 1600 | 1600 | ||
Test | Test | 510 | 1700 | 1700 | 1700 | 1700 | ||
Test | Test | 540 | 1800 | 1800 | 1800 | 1800 | ||
Test | Test | 570 | 1900 | 1900 | 1900 | 1900 |
"use client";
import { useCallback, useMemo, useState } from "react";
import { Checkbox, Chip, Tooltip } from "@heathmont/moon-core-tw";
import { ArrowsRefreshRound } from "@heathmont/moon-icons-tw";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type {
ColumnDef,
Row,
RowSelectionState,
} from "@heathmont/moon-table-v8-tw/lib/es/private/types";
type DataTypeHelper = {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
activity: number;
actions: () => void;
};
const preset: RowSelectionState = {
1: true,
3: true,
4: true,
11: true,
16: true,
};
const Example = () => {
const tooltip = useMemo(
() => (
<Tooltip>
<Tooltip.Trigger className="max-h-6">
<Chip
variant="ghost"
iconOnly={<ArrowsRefreshRound className="text-moon-24 max-h-6" />}
onClick={() => {
window.location.reload();
}}
/>
</Tooltip.Trigger>
<Tooltip.Content position="top-start" className="z-[2]">
Reload page
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip>
),
[],
);
const makeData = useCallback(
(length: number) => {
return Array.from("_".repeat(length)).map((_, index) => {
return {
firstName: "Test",
lastName: "Test",
age: <span>{Math.floor(index * 30)}</span>,
visits: <span>{Math.floor(index * 100)}</span>,
progress: <span>{Math.floor(index * 100)}</span>,
status: Math.floor(index * 100),
activity: Math.floor(index * 100),
actions: tooltip,
};
});
},
[tooltip],
);
const [rowSelection, setRowSelection] = useState<RowSelectionState>(preset);
const [data, setData] = useState(makeData(20));
const columns = useMemo<ColumnDef<{}, DataTypeHelper>[]>(
() => [
{
id: "select",
header: ({ table }) => (
<div className="w-8 px-1">
<Checkbox
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
</div>
),
cell: ({ row }) => (
<div className="w-8 px-1">
<Checkbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
),
},
{
header: () => "First Name",
accessorKey: "firstName",
},
{
header: () => "Last Name",
accessorKey: "lastName",
},
{
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
},
{
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
},
{
header: () => "Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
{
header: () => "Activity",
accessorKey: "activity",
},
{
header: () => "Status",
accessorKey: "status",
},
{
header: () => "Actions",
accessorKey: "actions",
cell: (props) => props.getValue(),
},
],
[],
);
return (
<div className="border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
width={800}
height={400}
layout="stretched-auto"
state={{ rowSelection }}
onRowSelectionChange={setRowSelection}
preventSelectionByRowClick={true}
isSelectable={true}
getOnRowSelectHandler={() => (rows: Row<{}>[]) => {
console.log(
`IDs of selected rows - ${rows.map((row: Row<{}>) => row.id)}`,
);
}}
/>
</div>
);
};
export default Example;
Expandable/Selectable rows
This example is a combination of expandable and selectable tables with pre-set parameters.
Use the mouse wheel or the arrow keys on the keyboard to scroll the data up and down.
Expand/Select | Name | Info | Actions | ||||
---|---|---|---|---|---|---|---|
First Name | Age | Visits | Activity | Status | Profile Progress | Actions | |
Lvl1 | 36 | 50 | 54 | 19 | 20 | ||
Sub lvl2 | 96 | 8 | 23 | 97 | 2 | ||
Sub lvl3 | 63 | 82 | 46 | 52 | 59 | ||
Sub lvl3 | 64 | 35 | 5 | 65 | 78 | ||
Sub lvl3 | 12 | 4 | 5 | 98 | 44 | ||
Sub lvl2 | 74 | 5 | 2 | 86 | 1 | ||
Sub lvl3 | 89 | 98 | 43 | 24 | 54 | ||
Sub lvl3 | 52 | 25 | 35 | 97 | 25 | ||
Sub lvl3 | 55 | 54 | 33 | 56 | 24 | ||
Sub lvl2 | 53 | 63 | 3 | 48 | 24 | ||
Sub lvl3 | 4 | 653 | 43 | 44 | 36 | ||
Sub lvl3 | 49 | 45 | 4 | 35 | 454 |
"use client";
import { useCallback, useMemo, useState } from "react";
import {
Checkbox,
Chip,
Tooltip,
mergeClassnames,
} from "@heathmont/moon-core-tw";
import {
ArrowsRefreshRound,
ControlsChevronDown,
ControlsChevronRight,
} from "@heathmont/moon-icons-tw";
import { Table } from "@heathmont/moon-table-v8-tw/lib/es";
import type DataHelper from "@heathmont/moon-table-v8-tw/lib/es/private/types/DataHelper";
import type {
ColumnDef,
ExpandedState,
Row,
RowSelectionState,
TableInterface,
} from "@heathmont/moon-table-v8-tw/lib/es/private/types";
interface DataTypeHelper extends DataHelper {
firstName: string;
lastName: string;
age: string;
visits: string;
progress: string;
status: number;
actions: () => void;
subRows?: DataTypeHelper[];
}
const columnShift = (depth: number) => {
const shiftMap: { [key: number]: string } = ["ps-0", "ps-6", "ps-12"];
return shiftMap[depth];
};
const preset: RowSelectionState = {
0: true,
"0.1": true,
"0.1.0": true,
"0.1.1": true,
};
const Example = () => {
const tooltip = useMemo(
() => (
<Tooltip>
<Tooltip.Trigger className="max-h-6">
<Chip
variant="ghost"
iconOnly={<ArrowsRefreshRound className="text-moon-24 max-h-6" />}
onClick={() => {
window.location.reload();
}}
/>
</Tooltip.Trigger>
<Tooltip.Content position="top-start" className="z-[2]">
Reload page
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip>
),
[],
);
const makeData = useMemo(
() => [
{
firstName: "Lvl1",
age: <span>36</span>,
visits: <span>50</span>,
progress: <span>20</span>,
status: 19,
activity: 54,
actions: tooltip,
subRows: [
{
firstName: "Sub lvl2",
age: <span>96</span>,
visits: <span>8</span>,
progress: <span>2</span>,
status: 97,
activity: 23,
actions: tooltip,
subRows: [
{
firstName: "Sub lvl3",
age: <span>63</span>,
visits: <span>82</span>,
progress: <span>59</span>,
status: 52,
activity: 46,
actions: tooltip,
},
{
firstName: "Sub lvl3",
age: <span>64</span>,
visits: <span>35</span>,
progress: <span>78</span>,
status: 65,
activity: 5,
actions: tooltip,
},
{
firstName: "Sub lvl3",
age: <span>12</span>,
visits: <span>4</span>,
progress: <span>44</span>,
status: 98,
activity: 5,
actions: tooltip,
},
],
},
{
firstName: "Sub lvl2",
age: <span>74</span>,
visits: <span>5</span>,
progress: <span>1</span>,
status: 86,
activity: 2,
actions: tooltip,
subRows: [
{
firstName: "Sub lvl3",
age: <span>89</span>,
visits: <span>98</span>,
progress: <span>54</span>,
status: 24,
activity: 43,
actions: tooltip,
},
{
firstName: "Sub lvl3",
age: <span>52</span>,
visits: <span>25</span>,
progress: <span>25</span>,
status: 97,
activity: 35,
actions: tooltip,
},
{
firstName: "Sub lvl3",
age: <span>55</span>,
visits: <span>54</span>,
progress: <span>24</span>,
status: 56,
activity: 33,
actions: tooltip,
},
],
},
{
firstName: "Sub lvl2",
age: <span>53</span>,
visits: <span>63</span>,
progress: <span>24</span>,
status: 48,
activity: 3,
actions: tooltip,
subRows: [
{
firstName: "Sub lvl3",
age: <span>4</span>,
visits: <span>653</span>,
progress: <span>36</span>,
status: 44,
activity: 43,
actions: tooltip,
},
{
firstName: "Sub lvl3",
age: <span>49</span>,
visits: <span>45</span>,
progress: <span>454</span>,
status: 35,
activity: 4,
actions: tooltip,
},
],
},
],
},
],
[tooltip],
);
const [rowSelection, setRowSelection] = useState<RowSelectionState>(preset);
const [expanded, setExpanded] = useState<ExpandedState>(true);
const [data, setData] = useState(makeData);
const trackCheckState = (row: Row<{}>, table: TableInterface<{}>) => {
let parentRowId = row.parentId;
while (parentRowId) {
const nodeRow = table.getRow(parentRowId);
if (nodeRow.getIsAllSubRowsSelected() && !nodeRow.getIsSelected()) {
setRowSelection({ ...rowSelection, [nodeRow.id]: true });
} else if (
!nodeRow.getIsAllSubRowsSelected() &&
nodeRow.getIsSelected()
) {
setRowSelection(
Object.keys(rowSelection)
.filter((rowId) => rowId !== nodeRow.id)
.reduce((acc: RowSelectionState, rowId: string) => {
acc[rowId] = true;
return acc;
}, {}),
);
}
parentRowId = nodeRow.parentId;
}
return row.getIsSelected();
};
const trackIndeterminateState = (row: Row<{}>) => {
const match = new RegExp(`(^${row.id}[\\.]|^${row.id}$)`, "");
const matches = Object.keys(rowSelection).filter(
(rowId) => match.test(rowId) && rowId !== row.id,
);
return (
!row.getIsAllSubRowsSelected() &&
matches.some((rowId) => rowSelection[rowId] === true)
);
};
const columns = useMemo<ColumnDef<{}, DataTypeHelper>[]>(
() => [
{
id: "expand/select",
header: () => "Expand/Select",
columns: [
{
id: "select",
header: ({ table }) => (
<div className="flex px-0 gap-x-1">
<Checkbox
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
<button onClick={table.getToggleAllRowsExpandedHandler()}>
{table.getIsAllRowsExpanded() ? (
<ControlsChevronDown />
) : (
<ControlsChevronRight />
)}
</button>
</div>
),
cell: ({ row, table }) => (
<div
className={mergeClassnames(
"flex gap-x-1",
columnShift(row.depth),
)}
>
<Checkbox
checked={trackCheckState(row, table)}
disabled={!row.getCanSelect()}
indeterminate={trackIndeterminateState(row)}
onChange={row.getToggleSelectedHandler()}
/>
{row.getCanExpand() ? (
<button
onClick={row.getToggleExpandedHandler()}
className="cursor-pointer"
>
{row.getIsExpanded() ? (
<ControlsChevronDown />
) : (
<ControlsChevronRight />
)}
</button>
) : (
""
)}
</div>
),
},
],
},
{
id: "name",
header: () => "Name",
columns: [
{
header: () => "First Name",
accessorKey: "firstName",
},
],
},
{
id: "info",
header: () => "Info",
columns: [
{
header: () => "Age",
accessorKey: "age",
cell: (props) => props.getValue(),
size: 30,
},
{
header: () => "Visits",
accessorKey: "visits",
cell: (props) => props.getValue(),
size: 60,
},
{
header: () => "Activity",
accessorKey: "activity",
size: 80,
},
{
header: () => "Status",
accessorKey: "status",
size: 80,
},
{
header: () => "Profile Progress",
accessorKey: "progress",
cell: (props) => props.getValue(),
},
],
},
{
id: "actions",
header: () => "Actions",
size: 90,
columns: [
{
header: () => "Actions",
accessorKey: "actions",
cell: (props) => props.getValue(),
size: 90,
},
],
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[rowSelection],
);
const getSubRows = useCallback(
({ subRows }: DataHelper) => subRows as DataHelper[],
[],
);
return (
<div className="border border-beerus rounded-lg overflow-hidden">
<Table
columns={columns}
data={data}
width={800}
height={400}
layout="stretched-auto"
state={{ expanded, rowSelection }}
getSubRows={getSubRows}
onExpandedChange={setExpanded}
onRowSelectionChange={setRowSelection}
preventSelectionByRowClick={true}
isSelectable={true}
/>
</div>
);
};
export default Example;
A table with minimap
Name | Info | Info1 | Info2 | Info3 | Info4 | Info5 | Progress | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
First Name | Last Name | Age | Visits | Activity | Age1 | Visits1 | Activity1 | Age2 | Visits2 | Activity2 | Age3 | Visits3 | Activity3 | Age4 | Visits4 | Activity4 | Age5 | Visits5 | Activity5 | Profile Progress |
Test | Test | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Test | Test | 30 | 100 | 100 | 30 | 100 | 100 | 30 | 100 | 100 | 30 | 100 | 100 | 30 | 100 | 100 | 30 | 100 | 100 | 100 |
Test | Test | 60 | 200 | 200 | 60 | 200 | 200 | 60 | 200 | 200 | 60 | 200 | 200 | 60 | 200 | 200 | 60 | 200 | 200 | 200 |
Test | Test | 90 | 300 | 300 | 90 | 300 | 300 | 90 | 300 | 300 | 90 | 300 | 300 | 90 | 300 | 300 | 90 | 300 | 300 | 300 |
Test | Test | 120 | 400 | 400 | 120 | 400 | 400 | 120 | 400 | 400 | 120 | 400 | 400 | 120 | 400 | 400 | 120 | 400 | 400 | 400 |
Test | Test | 150 | 500 | 500 | 150 | 500 | 500 | 150 | 500 | 500 | 150 | 500 | 500 | 150 | 500 | 500 | 150 | 500 | 500 | 500 |
Test | Test | 180 | 600 | 600 | 180 | 600 | 600 | 180 | 600 | 600 | 180 | 600 | 600 | 180 | 600 | 600 | 180 | 600 | 600 | 600 |
Test | Test | 210 | 700 | 700 | 210 | 700 | 700 | 210 | 700 | 700 | 210 | 700 | 700 | 210 | 700 | 700 | 210 | 700 | 700 | 700 |
Test | Test | 240 | 800 | 800 | 240 | 800 | 800 | 240 | 800 | 800 | 240 | 800 | 800 | 240 | 800 | 800 | 240 | 800 | 800 | 800 |
Test | Test | 270 | 900 | 900 | 270 | 900 | 900 | 270 | 900 | 900 | 270 | 900 | 900 | 270 | 900 | 900 | 270 | 900 | 900 | 900 |
Test | Test | 300 | 1000 | 1000 | 300 | 1000 | 1000 | 300 | 1000 | 1000 | 300 | 1000 | 1000 | 300 | 1000 | 1000 | 300 | 1000 | 1000 | 1000 |
Test | Test | 330 | 1100 | 1100 | 330 | 1100 | 1100 | 330 | 1100 | 1100 | 330 | 1100 | 1100 | 330 | 1100 | 1100 | 330 | 1100 | 1100 | 1100 |
Test | Test | 360 | 1200 | 1200 | 360 | 1200 | 1200 | 360 | 1200 | 1200 | 360 | 1200 | 1200 | 360 | 1200 | 1200 | 360 | 1200 | 1200 | 1200 |
Test | Test | 390 | 1300 | 1300 | 390 | 1300 | 1300 | 390 | 1300 | 1300 | 390 | 1300 | 1300 | 390 | 1300 | 1300 | 390 | 1300 | 1300 | 1300 |
Test | Test | 420 | 1400 | 1400 | 420 | 1400 | 1400 | 420 | 1400 | 1400 | 420 | 1400 | 1400 | 420 | 1400 | 1400 | 420 | 1400 | 1400 | 1400 |
Test | Test | 450 | 1500 | 1500 | 450 | 1500 | 1500 | 450 | 1500 | 1500 | 450 | 1500 | 1500 | 450 | 1500 | 1500 | 450 | 1500 | 1500 | 1500 |
Test | Test | 480 | 1600 | 1600 | 480 | 1600 | 1600 | 480 | 1600 | 1600 | 480 | 1600 | 1600 | 480 | 1600 | 1600 | 480 | 1600 | 1600 | 1600 |
Test | Test | 510 | 1700 | 1700 | 510 | 1700 | 1700 | 510 | 1700 | 1700 | 510 | 1700 | 1700 | 510 | 1700 | 1700 | 510 | 1700 | 1700 | 1700 |
Test | Test | 540 | 1800 | 1800 | 540 | 1800 | 1800 | 540 | 1800 | 1800 | 540 | 1800 | 1800 | 540 | 1800 | 1800 | 540 | 1800 | 1800 | 1800 |
Test | Test | 570 | 1900 | 1900 | 570 | 1900 | 1900 | 570 | 1900 | 1900 | 570 | 1900 | 1900 | 570 | 1900 | 1900 | 570 | 1900 | 1900 | 1900 |
Test | Test | 600 | 2000 | 2000 | 600 | 2000 | 2000 | 600 | 2000 | 2000 | 600 | 2000 | 2000 | 600 | 2000 | 2000 | 600 | 2000 | 2000 | 2000 |
Test | Test | 630 | 2100 | 2100 | 630 | 2100 | 2100 | 630 | 2100 | 2100 | 630 | 2100 | 2100 | 630 | 2100 | 2100 | 630 | 2100 | 2100 | 2100 |
Test | Test | 660 | 2200 | 2200 | 660 | 2200 | 2200 | 660 | 2200 | 2200 | 660 | 2200 | 2200 | 660 | 2200 | 2200 | 660 | 2200 | 2200 | 2200 |
Test | Test | 690 | 2300 | 2300 | 690 | 2300 | 2300 | 690 | 2300 | 2300 | 690 | 2300 | 2300 | 690 | 2300 | 2300 | 690 | 2300 | 2300 | 2300 |
Test | Test | 720 | 2400 | 2400 | 720 | 2400 | 2400 | 720 | 2400 | 2400 | 720 | 2400 | 2400 | 720 | 2400 | 2400 | 720 | 2400 | 2400 | 2400 |
Test | Test | 750 | 2500 | 2500 | 750 | 2500 | 2500 | 750 | 2500 | 2500 | 750 | 2500 | 2500 | 750 | 2500 | 2500 | 750 | 2500 | 2500 | 2500 |
Test | Test | 780 | 2600 | 2600 | 780 | 2600 | 2600 | 780 | 2600 | 2600 | 780 | 2600 | 2600 | 780 | 2600 | 2600 | 780 | 2600 | 2600 | 2600 |
Test | Test | 810 | 2700 | 2700 | 810 | 2700 | 2700 | 810 | 2700 | 2700 | 810 | 2700 | 2700 | 810 | 2700 | 2700 | 810 | 2700 | 2700 | 2700 |
Test | Test | 840 | 2800 | 2800 | 840 | 2800 | 2800 | 840 | 2800 | 2800 | 840 | 2800 | 2800 | 840 | 2800 | 2800 | 840 | 2800 | 2800 | 2800 |
Test | Test | 870 | 2900 | 2900 | 870 | 2900 | 2900 | 870 | 2900 | 2900 | 870 | 2900 | 2900 | 870 | 2900 | 2900 | 870 | 2900 | 2900 | 2900 |
Test | Test | 900 | 3000 | 3000 | 900 | 3000 | 3000 | 900 | 3000 | 3000 | 900 | 3000 | 3000 | 900 | 3000 | 3000 | 900 | 3000 | 3000 | 3000 |
Test | Test | 930 | 3100 | 3100 | 930 | 3100 | 3100 | 930 | 3100 | 3100 | 930 | 3100 | 3100 | 930 | 3100 | 3100 | 930 | 3100 | 3100 | 3100 |
Test | Test | 960 | 3200 | 3200 | 960 | 3200 | 3200 | 960 | 3200 | 3200 | 960 | 3200 | 3200 | 960 | 3200 | 3200 | 960 | 3200 | 3200 | 3200 |
Test | Test | 990 | 3300 | 3300 | 990 | 3300 | 3300 | 990 | 3300 | 3300 | 990 | 3300 | 3300 | 990 | 3300 | 3300 | 990 | 3300 | 3300 | 3300 |
Test | Test | 1020 | 3400 | 3400 | 1020 | 3400 | 3400 | 1020 | 3400 | 3400 | 1020 | 3400 | 3400 | 1020 | 3400 | 3400 | 1020 | 3400 | 3400 | 3400 |
Test | Test | 1050 | 3500 | 3500 | 1050 | 3500 | 3500 | 1050 | 3500 | 3500 | 1050 | 3500 | 3500 | 1050 | 3500 | 3500 | 1050 | 3500 | 3500 | 3500 |
Test | Test | 1080 | 3600 | 3600 | 1080 | 3600 | 3600 | 1080 | 3600 | 3600 | 1080 | 3600 | 3600 | 1080 | 3600 | 3600 | 1080 | 3600 | 3600 | 3600 |