Skip to content
Closed
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
1 change: 1 addition & 0 deletions apps/mantine.dev/src/mdx/data/mdx-hooks-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,5 @@ export const MDX_HOOKS_DATA: Record<string, Frontmatter> = {
'Track scroll position and detect which heading is currently in the viewport, can be used for table of contents'
),
useFileDialog: hDocs('useFileDialog', 'Capture one or more files from the user'),
useSelection: hDocs('useSelection', 'Handles selection state'),
};
1 change: 1 addition & 0 deletions apps/mantine.dev/src/mdx/mdx-nav-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ const HOOKS_PAGES_GROUP: MdxPagesCategory[] = sortCategoriesPages([
MDX_DATA.useLocalStorage,
MDX_DATA.usePrevious,
MDX_DATA.useQueue,
MDX_DATA.useSelection,
MDX_DATA.useSetState,
MDX_DATA.useToggle,
MDX_DATA.useUncontrolled,
Expand Down
32 changes: 32 additions & 0 deletions apps/mantine.dev/src/pages/hooks/use-selection.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HooksDemos } from '@docs/demos';
import { Layout } from '@/layout';
import { MDX_DATA } from '@/mdx';

export default Layout(MDX_DATA.useSelection);

## Usage

The `use-selection` hook manages the selection state of multiple items from a given dataset.
It returns an array of currently selected items and handler functions to manipulate the selection state.

<Demo data={HooksDemos.useSelectionDemo} />

## Definition

```ts
function useSelection<T>(
data: T[],
initialSelection?: T[]
): [
T[], // Currently selected items
{
select: (item: T) => void;
deselect: (item: T) => void;
toggle: (item: T) => void;
isAllSelected: () => boolean;
isSomeSelected: () => boolean;
setSelection: (items: T[]) => void;
resetSelection: () => void;
}
];
```
5 changes: 5 additions & 0 deletions packages/@docs/demos/src/demos/hooks/Hooks.demos.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export const Demo_useLoggerDemo = {
render: renderDemo(demos.useLoggerDemo),
};

export const Demo_useSelectedDemo = {
name: '⭐ Demo: useSelectedDemo',
render: renderDemo(demos.useSelectionDemo),
};

export const Demo_useMediaQueryDemo = {
name: '⭐ Demo: useMediaQueryDemo',
render: renderDemo(demos.useMediaQueryDemo),
Expand Down
1 change: 1 addition & 0 deletions packages/@docs/demos/src/demos/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ export { useIsFirstRenderUsage } from './use-is-first-render.demo.usage';
export { useRadialMoveUsage } from './use-radial-move.demo.usage';
export { useScrollSpyUsage } from './use-scroll-spy.demo.usage';
export { useScrollSpySelector } from './use-scroll-spy.demo.selector';
export { useSelectionDemo } from './use-selection.demo.usage';
export { useFileDialogUsage } from './use-file-dialog.demo';
187 changes: 187 additions & 0 deletions packages/@docs/demos/src/demos/hooks/use-selection.demo.usage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React from 'react';
import { Button, Checkbox, Group, Table, Text } from '@mantine/core';
import { useSelection } from '@mantine/hooks';
import { MantineDemo } from '@mantinex/demo';

const code = `
import { Button, Checkbox, Group, Table, Text } from '@mantine/core';
import { useSelection } from '@mantine/hooks';

const elements = [
{ id: '1', position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
{ id: '2', position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
{ id: '3', position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
{ id: '4', position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
{ id: '5', position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
// Initialize with the first item selected as an example
const [selected, handlers] = useSelection(elements, [elements[0]]);

const allSelected = handlers.isAllSelected();
const someSelected = handlers.isSomeSelected();

const rows = elements.map((item) => {
// Check if item (by id) is in the selected array
const isSelected = selected.some((s) => s.id === item.id);
const bg = isSelected ? 'var(--mantine-color-blue-light)' : undefined;
return (
<Table.Tr key={item.id} bg={bg}>
<Table.Td>
<Checkbox
checked={isSelected}
onChange={() => handlers.toggle(item)}
/>
</Table.Td>
<Table.Td>{item.position}</Table.Td>
<Table.Td>{item.name}</Table.Td>
<Table.Td>{item.symbol}</Table.Td>
<Table.Td>{item.mass}</Table.Td>
</Table.Tr>
);
});

const handleSelectAllToggle = () => {
if (allSelected) {
handlers.resetSelection();
} else {
handlers.setSelection(elements);
}
};

return (
<>
<Group mb="md">
<Button onClick={handleSelectAllToggle}>
{allSelected ? 'Deselect all' : 'Select all'}
</Button>
<Button onClick={() => handlers.setSelection([elements[1], elements[3]])} variant="light">
Select Nitrogen & Barium
</Button>
<Button onClick={handlers.resetSelection} variant="outline" color="red">
Reset selection
</Button>
</Group>

<Text mb="xs" size="sm">
Selected items: {selected.map(s => s.name).join(', ') || 'None'} (Total: {selected.length})
</Text>

<Table miw={700} verticalSpacing="sm">
<Table.Thead>
<Table.Tr>
<Table.Th style={{ width: '2rem' }}>
<Checkbox
checked={allSelected}
indeterminate={someSelected && !allSelected}
onChange={handleSelectAllToggle}
aria-label="Select all rows"
/>
</Table.Th>
<Table.Th>Element position</Table.Th>
<Table.Th>Element name</Table.Th>
<Table.Th>Symbol</Table.Th>
<Table.Th>Atomic mass</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
</>
);
}
`;

const elements = [
{ id: '1', position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
{ id: '2', position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
{ id: '3', position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
{ id: '4', position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
{ id: '5', position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
// Initialize with the first item selected as an example
const [selected, handlers] = useSelection(elements, [elements[0]]);

const allSelected = handlers.isAllSelected();
const someSelected = handlers.isSomeSelected();

const rows = elements.map((item) => {
const bg = selected.some((s) => s.id === item.id)
? 'var(--mantine-color-blue-light)'
: undefined;
// Check if item (by id) is in the selected array
const isSelected = selected.some((s) => s.id === item.id);
return (
<Table.Tr key={item.id} bg={bg}>
<Table.Td>
<Checkbox
checked={isSelected}
onChange={() => handlers.toggle(item)}
aria-label={`Select row ${item.name}`}
/>
</Table.Td>
<Table.Td>{item.position}</Table.Td>
<Table.Td>{item.name}</Table.Td>
<Table.Td>{item.symbol}</Table.Td>
<Table.Td>{item.mass}</Table.Td>
</Table.Tr>
);
});

const handleSelectAllToggle = () => {
if (allSelected) {
handlers.resetSelection();
} else {
handlers.setSelection(elements);
}
};

return (
<>
<Group mb="md">
<Button onClick={handleSelectAllToggle}>
{allSelected ? 'Deselect all' : 'Select all'}
</Button>
<Button onClick={() => handlers.setSelection([elements[1], elements[3]])} variant="light">
Select Nitrogen & Barium
</Button>
<Button onClick={handlers.resetSelection} variant="outline" color="red">
Reset selection
</Button>
</Group>

<Text mb="xs" size="sm">
Selected items: {selected.map((s) => s.name).join(', ') || 'None'} (Total: {selected.length}
)
</Text>

<Table miw="auto" verticalSpacing="sm">
<Table.Thead>
<Table.Tr>
<Table.Th>
<Checkbox
checked={allSelected}
indeterminate={someSelected && !allSelected}
onChange={handleSelectAllToggle}
aria-label="Select all rows"
/>
</Table.Th>
<Table.Th>Element position</Table.Th>
<Table.Th>Element name</Table.Th>
<Table.Th>Symbol</Table.Th>
<Table.Th>Atomic mass</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
</>
);
}

export const useSelectionDemo: MantineDemo = {
type: 'code',
component: Demo,
code,
};
1 change: 1 addition & 0 deletions packages/@mantine/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export { useFetch } from './use-fetch/use-fetch';
export { useRadialMove, normalizeRadialValue } from './use-radial-move/use-radial-move';
export { useScrollSpy } from './use-scroll-spy/use-scroll-spy';
export { useFileDialog } from './use-file-dialog/use-file-dialog';
export { useSelection } from './use-selection/use-selection';

export type { UseMovePosition } from './use-move/use-move';
export type { OS } from './use-os/use-os';
Expand Down
Loading
Loading