Skip to content
Merged
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', 'Manages selection state of given dataset'),
};
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 @@ -182,6 +182,7 @@ const HOOKS_PAGES_GROUP: MdxPagesCategory[] = sortCategoriesPages([
MDX_DATA.useStateHistory,
MDX_DATA.useMap,
MDX_DATA.useSet,
MDX_DATA.useSelection,
],
},

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

`use-selection` hook manages **selection state** for a list of items.
It provides a flexible API to `select`, `deselect` or `toggle` individual items,
and offers `reset` and `setSelection` to manipulate selection state,
additionally offers convenient flags like `isAllSelected` and `isSomeSelected` to reflect the current selection status.
This hook is ideal for implementing features like selectable lists, data grid row selection, or multi-select components.

<Demo data={HooksDemos.useSelectionDemo} />

## Definition

```tsx
function useSelection<T>(
dataset: T[],
defaultSelection?: T[]
): readonly [T[], {
select: (selection: T) => void,
deselect: (selection: T) => void,
toggle: (selection: T) => void,
isAllSelected: boolean,
isSomeSelected: boolean,
setSelection: (selection: 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 @@ -347,3 +347,8 @@ export const Demo_useFileDialogUsage = {
name: '⭐ Demo: useFileDialogUsage',
render: renderDemo(demos.useFileDialogUsage),
};

export const Demo_useSelectionDemo = {
name: '⭐ Demo: useSelectionDemo',
render: renderDemo(demos.useSelectionDemo),
};
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 @@ -69,3 +69,4 @@ 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 { useFileDialogUsage } from './use-file-dialog.demo';
export { useSelectionDemo } from './use-selection.demo.usage';
151 changes: 151 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,151 @@
import { Checkbox, Table } from '@mantine/core';
import { useSelection } from '@mantine/hooks';
import { MantineDemo } from '@mantinex/demo';

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

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

function Demo() {
const [selection, handlers] = useSelection(elements)

const rows = elements.map((element) => {
const isSelected = selection.includes(element)
return (
<Table.Tr
key={element.name}
bg={isSelected ? 'var(--mantine-color-blue-light)' : undefined}
>
<Table.Td>
<Checkbox
aria-label="Select row"
checked={isSelected}
onChange={(event) => {
if (event.target.checked) {
handlers.select(element)
} else {
handlers.deselect(element)
}
}}
/>
</Table.Td>
<Table.Td>{element.position}</Table.Td>
<Table.Td>{element.name}</Table.Td>
<Table.Td>{element.symbol}</Table.Td>
<Table.Td>{element.mass}</Table.Td>
</Table.Tr>
)
});

return (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>
<Checkbox
aria-label="Select deselect all rows"
indeterminate={handlers.isSomeSelected}
checked={handlers.isAllSelected}
onChange={(event) => {
if (handlers.isSomeSelected) {
handlers.resetSelection()
} else if (event.target.checked) {
handlers.setSelection(elements)
} else {
handlers.resetSelection()
}
}}
/>
</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 = [
{ position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
{ position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
{ position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
{ position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
{ position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
const [selection, handlers] = useSelection(elements);

const rows = elements.map((element) => {
const isSelected = selection.includes(element);
return (
<Table.Tr key={element.name} bg={isSelected ? 'var(--mantine-color-blue-light)' : undefined}>
<Table.Td>
<Checkbox
aria-label="Select row"
checked={isSelected}
onChange={(event) => {
if (event.target.checked) {
handlers.select(element);
} else {
handlers.deselect(element);
}
}}
/>
</Table.Td>
<Table.Td>{element.position}</Table.Td>
<Table.Td>{element.name}</Table.Td>
<Table.Td>{element.symbol}</Table.Td>
<Table.Td>{element.mass}</Table.Td>
</Table.Tr>
);
});

return (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>
<Checkbox
aria-label="Select deselect all rows"
indeterminate={handlers.isSomeSelected}
checked={handlers.isAllSelected}
onChange={(event) => {
if (handlers.isSomeSelected) {
handlers.resetSelection();
} else if (event.target.checked) {
handlers.setSelection(elements);
} else {
handlers.resetSelection();
}
}}
/>
</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