Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ export const ROUTES: RouteInfo[] = [
title: 'Pinning the first column',
description: `Example: how to pin the first column on ${PRODUCT_NAME}`,
},
{
href: '/examples/pinning-arbitrary-columns',
title: 'Pinning arbitrary columns',
description: `Example: how to pin arbitrary columns on ${PRODUCT_NAME}`,
},
{
href: '/examples/rtl-support',
title: 'RTL support',
Expand Down
227 changes: 227 additions & 0 deletions app/examples/pinning-arbitrary-columns/PinArbitraryColumnsExamples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
'use client';

import { ActionIcon, Box, Button, DirectionProvider, Group, SegmentedControl, Stack, Text } from '@mantine/core';
import { IconEdit, IconEye, IconTrash } from '@tabler/icons-react';
import { DataTable, useDataTableColumns } from '__PACKAGE__';
import { useState } from 'react';
import { employees, type Employee } from '~/data';

const records = employees.slice(0, 5);

// example-start multiple-left-pinned
export function PinMultipleLeftColumnsExample() {
return (
<DataTable
withTableBorder
columns={[
{ accessor: 'firstName', noWrap: true, pinned: 'left' },
{ accessor: 'lastName', noWrap: true, pinned: 'left' },
{ accessor: 'department.name', title: 'Department' },
{ accessor: 'department.company.name', title: 'Company', noWrap: true },
{ accessor: 'department.company.city', title: 'City', noWrap: true },
{ accessor: 'department.company.state', title: 'State' },
{ accessor: 'department.company.streetAddress', title: 'Address', noWrap: true },
{ accessor: 'department.company.missionStatement', title: 'Mission statement', noWrap: true },
]}
records={records}
/>
);
}
// example-end

// example-start left-and-right-pinned
export function PinLeftAndRightColumnsExample() {
const [selectedRecords, setSelectedRecords] = useState<Employee[]>([]);

return (
<DataTable
withTableBorder
columns={[
{ accessor: 'firstName', noWrap: true, pinned: 'left' },
{ accessor: 'lastName', noWrap: true, pinned: 'left' },
{ accessor: 'department.name', title: 'Department' },
{ accessor: 'department.company.name', title: 'Company', noWrap: true },
{ accessor: 'department.company.city', title: 'City', noWrap: true },
{ accessor: 'department.company.state', title: 'State' },
{ accessor: 'department.company.streetAddress', title: 'Address', noWrap: true },
{ accessor: 'department.company.missionStatement', title: 'Mission statement', noWrap: true },
{
accessor: 'actions',
title: <Box mr={6}>Row actions</Box>,
textAlign: 'right',
pinned: 'right',
render: () => (
<Group gap={4} justify="right" wrap="nowrap">
<ActionIcon size="sm" variant="subtle" color="green">
<IconEye size={16} />
</ActionIcon>
<ActionIcon size="sm" variant="subtle" color="blue">
<IconEdit size={16} />
</ActionIcon>
<ActionIcon size="sm" variant="subtle" color="red">
<IconTrash size={16} />
</ActionIcon>
</Group>
),
},
]}
records={records}
selectedRecords={selectedRecords}
onSelectedRecordsChange={setSelectedRecords}
/>
);
}
// example-end

// example-start interactive-pinning
export function InteractivePinningExample() {
const key = 'interactive-pinning-example';
const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr');

const { effectiveColumns, resetColumnsPinning } = useDataTableColumns<Employee>({
key,
columns: [
{ accessor: 'firstName', noWrap: true, pinnable: true, pinned: 'left' },
{ accessor: 'lastName', noWrap: true, pinnable: true },
{ accessor: 'department.name', title: 'Department', pinnable: true },
{ accessor: 'department.company.name', title: 'Company', noWrap: true, pinnable: true },
{ accessor: 'department.company.city', title: 'City', noWrap: true, pinnable: true },
{ accessor: 'department.company.state', title: 'State', pinnable: true },
{ accessor: 'department.company.streetAddress', title: 'Address', noWrap: true, pinnable: true },
{ accessor: 'department.company.missionStatement', title: 'Mission statement', noWrap: true, pinnable: true },
],
});

return (
<Stack gap="xs">
<Group justify="space-between">
<Group gap="xs">
<Text size="sm" fw={500}>
Direction:
</Text>
<SegmentedControl
size="xs"
value={direction}
onChange={(value) => setDirection(value as 'ltr' | 'rtl')}
data={[
{ label: 'LTR', value: 'ltr' },
{ label: 'RTL', value: 'rtl' },
]}
/>
</Group>
<Button size="xs" variant="light" onClick={resetColumnsPinning}>
Reset pinning
</Button>
</Group>
<DirectionProvider key={`interactive-pinning-${direction}`} initialDirection={direction} detectDirection={false}>
<Box dir={direction}>
<DataTable withTableBorder storeColumnsKey={key} columns={effectiveColumns} records={records} />
</Box>
</DirectionProvider>
</Stack>
);
}
// example-end

// example-start pinning-and-toggling
export function PinningAndTogglingExample() {
const key = 'pinning-and-toggling-example';

const { effectiveColumns, resetColumnsPinning, resetColumnsToggle } = useDataTableColumns<Employee>({
key,
columns: [
{ accessor: 'firstName', noWrap: true, pinnable: true, toggleable: true, pinned: 'left' },
{ accessor: 'lastName', noWrap: true, pinnable: true, toggleable: true },
{ accessor: 'email', noWrap: true, pinnable: true, toggleable: true },
{ accessor: 'birthDate', title: 'Birth date', pinnable: true, toggleable: true },
{ accessor: 'department.name', title: 'Department', pinnable: true, toggleable: true },
{ accessor: 'department.company.name', title: 'Company', noWrap: true, pinnable: true, toggleable: true },
{ accessor: 'department.company.city', title: 'City', noWrap: true, pinnable: true, toggleable: true },
{ accessor: 'department.company.state', title: 'State', pinnable: true, toggleable: true },
{
accessor: 'department.company.streetAddress',
title: 'Address',
noWrap: true,
pinnable: true,
toggleable: true,
},
{
accessor: 'department.company.missionStatement',
title: 'Mission statement',
noWrap: true,
width: 300,
pinnable: true,
toggleable: true,
},
],
});

return (
<>
<DataTable withTableBorder storeColumnsKey={key} columns={effectiveColumns} records={records} />
<Group justify="right" mt="xs">
<Button size="xs" variant="light" onClick={resetColumnsToggle}>
Reset visibility
</Button>
<Button size="xs" variant="light" onClick={resetColumnsPinning}>
Reset pinning
</Button>
</Group>
</>
);
}
// example-end

// example-start pinning-with-column-groups
export function PinningWithColumnGroupsExample() {
const key = 'pinning-with-column-groups-example';

const { effectiveColumns, resetColumnsPinning } = useDataTableColumns<Employee>({
key,
columns: [
{ accessor: 'firstName', noWrap: true, pinnable: true, pinned: 'left' },
{ accessor: 'lastName', noWrap: true, pinnable: true, pinned: 'left' },
{ accessor: 'department.name', title: 'Department', pinnable: true },
{ accessor: 'department.company.name', title: 'Company', noWrap: true, pinnable: true },
{ accessor: 'department.company.city', title: 'City', noWrap: true, pinnable: true },
{ accessor: 'department.company.state', title: 'State', pinnable: true },
{ accessor: 'department.company.streetAddress', title: 'Address', noWrap: true, pinnable: true },
{ accessor: 'department.company.missionStatement', title: 'Mission statement', noWrap: true, pinnable: true },
],
});

// Build groups from effectiveColumns, preserving pinning state
const employeeColumns = effectiveColumns.filter(
(c) => c.accessor === 'firstName' || c.accessor === 'lastName'
);
const workplaceColumns = effectiveColumns.filter(
(c) =>
c.accessor === 'department.name' ||
c.accessor === 'department.company.name' ||
c.accessor === 'department.company.city' ||
c.accessor === 'department.company.state' ||
c.accessor === 'department.company.streetAddress' ||
c.accessor === 'department.company.missionStatement'
);

return (
<Stack gap="xs">
<DataTable
withTableBorder
withColumnBorders
storeColumnsKey={key}
groups={[
{ id: 'employee', title: 'Employee', columns: employeeColumns },
{ id: 'workplace', title: 'Workplace', columns: workplaceColumns },
]}
records={records}
/>
<Group justify="right">
<Button size="xs" variant="light" onClick={resetColumnsPinning}>
Reset pinning
</Button>
</Group>
</Stack>
);
}
// example-end
114 changes: 114 additions & 0 deletions app/examples/pinning-arbitrary-columns/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Code } from '@mantine/core';
import type { Route } from 'next';
import { CodeBlock } from '~/components/CodeBlock';
import { InternalLink } from '~/components/InternalLink';
import { PageNavigation } from '~/components/PageNavigation';
import { PageSubtitle } from '~/components/PageSubtitle';
import { PageTitle } from '~/components/PageTitle';
import { Txt } from '~/components/Txt';
import { readCodeFile } from '~/lib/code';
import { getRouteMetadata } from '~/lib/utils';
import {
InteractivePinningExample,
PinLeftAndRightColumnsExample,
PinMultipleLeftColumnsExample,
PinningAndTogglingExample,
PinningWithColumnGroupsExample,
} from './PinArbitraryColumnsExamples';

const PATH: Route = '/examples/pinning-arbitrary-columns';

export const metadata = getRouteMetadata(PATH);

export default async function PinArbitraryColumnsExamplePage() {
const code = await readCodeFile<
Record<
| 'multiple-left-pinned'
| 'left-and-right-pinned'
| 'interactive-pinning'
| 'pinning-and-toggling'
| 'pinning-with-column-groups',
string
>
>(`${PATH}/PinArbitraryColumnsExamples.tsx`);

return (
<>
<PageTitle of={PATH} />
<Code hidden>pin, pinned, fix, fixed, affix, sticky, arbitrary, multiple, columns</Code>
<Txt>
In addition to <InternalLink to="/examples/pinning-the-first-column">pinning the first column</InternalLink> and{' '}
<InternalLink to="/examples/pinning-the-last-column">pinning the last column</InternalLink> using the{' '}
<Code>pinFirstColumn</Code> and <Code>pinLastColumn</Code> DataTable props, you can pin{' '}
<strong>arbitrary columns</strong> to the left or right side of the table by setting the <Code>pinned</Code>{' '}
property on individual column definitions.
</Txt>
<PageSubtitle value="Pinning multiple columns to the left" />
<Txt>
Set <Code>{'pinned: \'left\''}</Code> on any columns you want pinned to the left side of the table:
</Txt>
<PinMultipleLeftColumnsExample />
<Txt>Here is the code:</Txt>
<CodeBlock code={code['multiple-left-pinned']} />
<PageSubtitle value="Pinning columns to both sides" />
<Txt>
You can combine left-pinned and right-pinned columns. In this example, the first two columns are pinned to the
left, and the actions column is pinned to the right. This also works with{' '}
<InternalLink to="/examples/records-selection">row selection</InternalLink>:
</Txt>
<PinLeftAndRightColumnsExample />
<Txt>Here is the code:</Txt>
<CodeBlock code={code['left-and-right-pinned']} />
<PageSubtitle value="Interactive pinning" />
<Txt>
You can allow users to pin and unpin columns interactively by setting <Code>{'pinnable: true'}</Code> on columns.
When enabled, a pin icon appears in the column header on hover. Clicking it reveals a dropdown where users can
choose to pin the column to the left, right, or unpin it.
</Txt>
<Txt>
Use <Code>pinned</Code> to set the initial pinning state for pinnable columns. The pinning state is persisted in
localStorage when you provide a <Code>storeColumnsKey</Code> prop to the DataTable. You can also use the{' '}
<Code>resetColumnsPinning</Code> function from the <Code>useDataTableColumns</Code> hook to reset pinning to
default values.
</Txt>
<InteractivePinningExample />
<Txt>Here is the code:</Txt>
<CodeBlock code={code['interactive-pinning']} />
<PageSubtitle value="Combining pinning with column toggling" />
<Txt>
You can combine <Code>pinnable</Code> with <Code>toggleable</Code> on the same columns. This allows users to both
pin/unpin columns and show/hide them. Right-click on the table header to open the column visibility menu.
</Txt>
<Txt>
In this example, some columns are hidden by default using <Code>{'defaultToggle: false'}</Code>. Use the{' '}
<Code>resetColumnsToggle</Code> and <Code>resetColumnsPinning</Code> functions to reset visibility and pinning
independently.
</Txt>
<PinningAndTogglingExample />
<Txt>Here is the code:</Txt>
<CodeBlock code={code['pinning-and-toggling']} />
<PageSubtitle value="Pinning with column groups" />
<Txt>
When using <InternalLink to="/examples/column-grouping">column groups</InternalLink> with interactive pinning,
you can pin individual columns within groups. The group header will be pinned only when{' '}
<strong>all columns in the group</strong> are pinned to the same side. If columns within a group have different
pinning states (some pinned left, some unpinned, or mixed), the group header will not be pinned.
</Txt>
<Txt idea>
<strong>Important:</strong> Pinning works within the group&apos;s visual position in the table. If a group is
positioned on the left side of the table (like the &ldquo;Employee&rdquo; group below), its columns can only be
effectively pinned to the left. Pinning them to the right will have no visual effect because pinning uses CSS{' '}
<Code>position: sticky</Code> which doesn&apos;t reorder columns — it only makes them &ldquo;stick&rdquo; when
scrolling. Similarly, columns in a middle-positioned group will only stick from their group&apos;s starting
position.
</Txt>
<Txt>
Try pinning and unpinning columns in the example below to see how the group headers behave:
</Txt>
<PinningWithColumnGroupsExample />
<Txt>Here is the code:</Txt>
<CodeBlock code={code['pinning-with-column-groups']} />
<PageNavigation of={PATH} />
</>
);
}
Loading