use-move
Handles move behavior over given element, can be used to build custom sliders
Import
Source
Docs
Package
Usage
use-move
handles move behavior over any element:
Values { x: 20, y: 60 }
import { useState } from 'react';
import { Group, Text, Code, rem } from '@mantine/core';
import { useMove } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState({ x: 0.2, y: 0.6 });
const { ref, active } = useMove(setValue);
return (
<>
<Group justify="center">
<div
ref={ref}
style={{
width: rem(400),
height: rem(120),
backgroundColor: 'var(--mantine-color-blue-light)',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
left: `calc(${value.x * 100}% - ${rem(8)})`,
top: `calc(${value.y * 100}% - ${rem(8)})`,
width: rem(16),
height: rem(16),
backgroundColor: active ? 'var(--mantine-color-teal-7)' : 'var(--mantine-color-blue-7)',
}}
/>
</div>
</Group>
<Text ta="center" mt="sm">
Values <Code>{`{ x: ${Math.round(value.x * 100)}, y: ${Math.round(value.y * 100)} }`}</Code>
</Text>
</>
);
}
API
The hook accepts a callback that is called when user moves pressed mouse over the given element
and returns an object with ref
and active state:
import { useMove } from '@mantine/hooks';
const {
ref, // -> pass ref to target element
active, // -> is user changing something right now?
} = useMove(({ x, y }) => console.log({ x, y }));
x
and y
values are always between 0
and 1
, you can use them to calculate value in your boundaries.
Horizontal slider
You can ignore changes for one of the axis:
Value: 20
import { useState } from 'react';
import { Group, Text, rem } from '@mantine/core';
import { useMove } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState(0.2);
const { ref } = useMove(({ x }) => setValue(x));
return (
<>
<Group justify="center">
<div
ref={ref}
style={{
width: rem(400),
height: rem(16),
backgroundColor: 'var(--mantine-color-blue-light)',
position: 'relative',
}}
>
{/* Filled bar */}
<div
style={{
width: `${value * 100}%`,
height: rem(16),
backgroundColor: 'var(--mantine-color-blue-filled)',
opacity: 0.7,
}}
/>
{/* Thumb */}
<div
style={{
position: 'absolute',
left: `calc(${value * 100}% - ${rem(8)})`,
top: 0,
width: rem(16),
height: rem(16),
backgroundColor: 'var(--mantine-color-blue-7)',
}}
/>
</div>
</Group>
<Text ta="center" mt="sm">
Value: {Math.round(value * 100)}
</Text>
</>
);
}
Horizontal slider with styles
import { useState } from 'react';
import { IconGripVertical } from '@tabler/icons-react';
import { clamp, useMove } from '@mantine/hooks';
import classes from './Demo.module.css';
function Demo() {
const [value, setValue] = useState(0.3);
const { ref } = useMove(({ x }) => setValue(clamp(x, 0.1, 0.9)));
const labelFloating = value < 0.2 || value > 0.8;
return (
<div className={classes.root}>
<div className={classes.track} ref={ref}>
<div
className={classes.filled}
style={{
width: `calc(${value * 100}% - var(--thumb-width) / 2 - var(--thumb-offset) / 2)`,
}}
>
<span className={classes.label} data-floating={labelFloating || undefined} data-filled>
{(value * 100).toFixed(0)}
</span>
</div>
<div
className={classes.empty}
style={{
width: `calc(${(1 - value) * 100}% - var(--thumb-width) / 2 - var(--thumb-offset) / 2)`,
}}
>
<span className={classes.label} data-floating={labelFloating || undefined}>
{((1 - value) * 100).toFixed(0)}
</span>
</div>
<div
className={classes.thumb}
style={{ left: `calc(${value * 100}% - var(--thumb-width) / 2)` }}
>
<IconGripVertical stroke={1.5} />
</div>
</div>
</div>
);
}
Vertical slider
Moving the slider down increases the value, to reverse that set value to 1 - y
in your setValue
function:
Value: 20
import { useState } from 'react';
import { Group, Text, rem } from '@mantine/core';
import { useMove } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState(0.2);
const { ref } = useMove(({ y }) => setValue(1 - y));
return (
<>
<Group justify="center">
<div
ref={ref}
style={{
width: rem(16),
height: rem(120),
backgroundColor: 'var(--mantine-color-blue-light)',
position: 'relative',
}}
>
{/* Filled bar */}
<div
style={{
position: 'absolute',
bottom: 0,
height: `${value * 100}%`,
width: rem(16),
backgroundColor: 'var(--mantine-color-blue-filled)',
opacity: 0.7,
}}
/>
{/* Thumb */}
<div
style={{
position: 'absolute',
bottom: `calc(${value * 100}% - ${rem(8)})`,
left: 0,
width: rem(16),
height: rem(16),
backgroundColor: 'var(--mantine-color-blue-7)',
}}
/>
</div>
</Group>
<Text ta="center" mt="sm">
Value: {Math.round(value * 100)}
</Text>
</>
);
}
Color picker
import { useState } from 'react';
import { rem } from '@mantine/core';
import { useMove } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState({ x: 0.2, y: 0.6 });
const { ref } = useMove(setValue);
return (
<div>
<div
ref={ref}
style={{
width: rem(300),
height: rem(150),
backgroundColor: 'red',
position: 'relative',
}}
>
{/* Gradient overlays */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: 'linear-gradient(90deg, #fff, transparent)',
}}
/>
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: 'linear-gradient(0deg, #000, transparent)',
}}
/>
{/* Thumb */}
<div
style={{
position: 'absolute',
left: `calc(${value.x * 100}% - ${rem(8)})`,
top: `calc(${value.y * 100}% - ${rem(8)})`,
width: rem(16),
height: rem(16),
border: `${rem(2)} solid #fff`,
borderRadius: rem(16),
}}
/>
</div>
</div>
);
}
clampUseMovePosition
clampUseMovePosition
function can be used to clamp x
and y
values to 0-1
range.
It is useful when you want to use external events to change the value, for example changing value with keyboard arrows:
import { clampUseMovePosition } from '@mantine/hooks';
clampUseMovePosition({ x: 0.5, y: 0.5 }); // -> { x: 0.5, y: 0.5 }
clampUseMovePosition({ x: 1.5, y: 0.5 }); // -> { x: 1, y: 0.5 }
clampUseMovePosition({ x: -0.5, y: 0.5 }); // -> { x: 0, y: 0.5 }
UseMovePosition
@mantine/hooks
exports UseMovePosition
type, it can be used as a type parameter for useState
:
import { useState } from 'react';
import { UseMovePosition } from '@mantine/hooks';
const [value, setValue] = useState<UseMovePosition>({
x: 0.5,
y: 0.5,
});
Definition
interface UseMovePosition {
x: number;
y: number;
}
interface useMoveHandlers {
onScrubStart?: () => void;
onScrubEnd?: () => void;
}
function useMove<T extends HTMLElement = HTMLDivElement>(
onChange: (value: UseMovePosition) => void,
handlers?: useMoveHandlers,
dir?: 'ltr' | 'rtl'
): {
ref: React.RefObject<T>;
active: boolean;
};
Build fully functional accessible web applications faster than ever