elements
lifecycle
browser
- useAudio
- useBattery
- useBluetooth
- useBreakpoints
- useBroadcastChannel
- useBrowserLocation
- useClipboard
- useCopy
- useCssVar
- useDisplayMedia
- useDocumentEvent
- useDocumentTitle
- useDocumentVisibility
- useEventListener
- useEventSource
- useEyeDropper
- useFavicon
- useFileSystemAccess
- useFps
- useFullscreen
- useGamepad
- useGeolocation
- useMeasure
- useMediaControls
- useMediaQuery
- useMemory
- useNetwork
- useObjectUrl
- useOnline
- useOtpCredential
- usePermission
- usePictureInPicture
- usePointerLock
- usePostMessage
- useRaf
- useShare
- useSpeechRecognition
- useSpeechSynthesis
- useSticky
utilities
state
user
sensors
import type { Line } from '@siberiacancode/reactuse';
import { useHotkeys, usePaint } from '@siberiacancode/reactuse';
import { Redo2Icon, Undo2Icon } from 'lucide-react';
import { cn } from '@/utils/lib';
const SIZES = [
{ label: 'S', value: 4 },
{ label: 'M', value: 10 },
{ label: 'L', value: 18 }
];
const LINES_KEY = 'paint-lines';
const getStoredLines = () => {
if (typeof localStorage === 'undefined') return '[]' as unknown as Line[];
JSON.parse(localStorage.getItem(LINES_KEY) ?? '[]') as Line[];
};
const Demo = () => {
const paint = usePaint(
{ color: '#3b82f6', radius: 10, lines: getStoredLines() },
{
smooth: true,
onMouseUp: (_event, instance) =>
localStorage.setItem(LINES_KEY, JSON.stringify(instance.lines))
}
);
const onUndo = () => {
paint.undo();
localStorage.setItem(LINES_KEY, JSON.stringify(paint.lines));
};
const onRedo = () => {
paint.redo();
localStorage.setItem(LINES_KEY, JSON.stringify(paint.lines));
};
const onClear = () => {
paint.clear();
localStorage.removeItem(LINES_KEY);
};
useHotkeys('control+keyz', (event) => {
event.preventDefault();
if (event.shiftKey) return onRedo();
onUndo();
});
useHotkeys('meta+keyz', (event) => {
event.preventDefault();
if (event.shiftKey) return onRedo();
onUndo();
});
return (
<section className='flex w-full max-w-md flex-col gap-3 p-4'>
<div className='flex items-center justify-between gap-3'>
<div className='flex items-center gap-2'>
<label
className='border-border relative flex size-5 cursor-pointer items-center justify-center overflow-hidden rounded-full! border'
style={{ backgroundColor: paint.color }}
>
<input
className='absolute inset-0 cursor-pointer p-0! opacity-0'
type='color'
value={paint.color}
onChange={(event) => paint.changeColor(event.target.value)}
/>
</label>
<div className='bg-muted flex items-center gap-0.5 rounded-lg p-0.5'>
{SIZES.map((size) => (
<button
key={size.value}
className={cn(
'flex h-7! items-center justify-center rounded-md! p-2!',
paint.radius === size.value ? 'bg-background' : 'text-muted-foreground'
)}
data-variant='ghost'
type='button'
onClick={() => paint.changeRadius(size.value)}
>
{size.label}
</button>
))}
</div>
</div>
<div className='flex items-center gap-1'>
<button
aria-label='Undo'
data-size='icon-sm'
data-variant='outline'
disabled={!paint.canUndo}
type='button'
onClick={onUndo}
>
<Undo2Icon className='size-4' />
</button>
<button
aria-label='Redo'
data-size='icon-sm'
data-variant='outline'
disabled={!paint.canRedo}
type='button'
onClick={onRedo}
>
<Redo2Icon className='size-4' />
</button>
<button data-size='sm' data-variant='outline' type='button' onClick={onClear}>
Clear
</button>
</div>
</div>
<div className='border-border bg-card relative overflow-hidden rounded-xl border'>
<canvas ref={paint.ref} className='h-[300px] w-full cursor-crosshair touch-none' />
{!paint.lines.length && !paint.drawing && (
<div className='pointer-events-none absolute inset-0 flex items-center justify-center'>
<span className='text-muted-foreground text-sm'>Draw something here</span>
</div>
)}
</div>
</section>
);
};
export default Demo;
Installation
pnpm add @siberiacancode/reactuseUsage
const paint = usePaint(canvasRef, { color: 'red', radius: 10 });
// or
const { ref, draw, clear, undo, redo, changeColor } = usePaint({ color: 'red', radius: 10 }, { smooth: true });Type Declarations
import type { HookTarget } from '@/utils/helpers';
import type { StateRef } from '../useRefState/useRefState';
export interface Point {
x: number;
y: number;
}
export interface Line {
color: string;
opacity: number;
points: Point[];
radius: number;
}
export interface UsePaintInitialValue {
/** The initial brush color */
color?: string;
/** The initial lines */
lines?: Line[];
/** The initial brush opacity */
opacity?: number;
/** The initial brush radius */
radius?: number;
}
export interface UsePaintOptions {
/** Whether the brush movement is smooth */
smooth?: boolean;
/** The callback when the mouse is down */
onMouseDown?: (event: MouseEvent, paint: Paint) => void;
/** The callback when the mouse is moved */
onMouseMove?: (event: MouseEvent, paint: Paint) => void;
/** The callback when the mouse is up */
onMouseUp?: (event: MouseEvent, paint: Paint) => void;
}
export interface UsePaintReturn {
/** Whether redo is available */
canRedo: boolean;
/** Whether undo is available */
canUndo: boolean;
/** The current brush color */
color: string;
/** Whether the user is drawing */
drawing: boolean;
/** The current lines */
lines: Line[];
/** The current brush opacity */
opacity: number;
/** The current brush radius */
radius: number;
/** Changes the brush color */
changeColor: (color: string) => void;
/** Changes the brush opacity */
changeOpacity: (opacity: number) => void;
/** Changes the brush radius */
changeRadius: (radius: number) => void;
/** Clears the canvas */
clear: () => void;
/** Draws a line */
draw: (line: Line) => void;
/** Redoes the last undone line */
redo: () => void;
/** Undoes the last line */
undo: () => void;
}
export interface UsePaint {
(
target: HookTarget,
initialValue?: UsePaintInitialValue,
options?: UsePaintOptions
): UsePaintReturn;
<Target extends HTMLCanvasElement>(
initialValue?: UsePaintInitialValue,
options?: UsePaintOptions
): UsePaintReturn & { ref: StateRef<Target> };
}API
Parameters
| Name | Type | Default | Note |
|---|---|---|---|
| target | HookTarget | - | The target element to be painted |
| initialValue | UsePaintInitialValue | - | The initial value of the paint |
| options | UsePaintOptions | - | The options to be used |
Returns
UsePaintReturnParameters
| Name | Type | Default | Note |
|---|---|---|---|
| initialValue | UsePaintInitialValue | - | The initial value of the paint |
| options | UsePaintOptions | - | The options to be used |
Returns
UsePaintReturn & { ref: StateRef<HTMLCanvasElement> }Contributors
ddebabinhhywaxVVladPorvin
Last updated on