483

useLockScroll

Hook that locks scroll on an element or document body

elementsmediumtest coverage

Lock the page scroll

Open the dialog below — while it is visible, the page behind it cannot be scrolled. Close the dialog and scrolling becomes available again.

import { useDisclosure, useLockScroll } from '@siberiacancode/reactuse';
import { XIcon } from 'lucide-react';

const Demo = () => {
  const dialog = useDisclosure();
  useLockScroll({ enabled: dialog.opened });

  return (
    <section className='flex w-full max-w-md flex-col gap-3 p-4'>
      <h2 className='text-foreground text-base font-semibold'>Lock the page scroll</h2>
      <p className='text-muted-foreground text-sm leading-relaxed'>
        Open the dialog below — while it is visible, the page behind it cannot be scrolled. Close
        the dialog and scrolling becomes available again.
      </p>

      <button className='self-start' data-size='sm' type='button' onClick={dialog.open}>
        Open dialog
      </button>

      {dialog.opened && (
        <div
          className='animate-in fade-in fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm duration-150'
          onClick={dialog.close}
        >
          <div
            className='animate-in fade-in zoom-in-95 border-border bg-card flex w-full max-w-sm flex-col gap-3 rounded-xl border p-5 shadow-2xl duration-200'
            onClick={(event) => event.stopPropagation()}
          >
            <div className='flex items-start justify-between gap-2'>
              <h3 className='text-foreground text-sm font-semibold'>Delete this project?</h3>
              <button
                aria-label='Close'
                data-size='icon'
                data-variant='ghost'
                type='button'
                onClick={dialog.close}
              >
                <XIcon className='size-3.5' />
              </button>
            </div>

            <p className='text-muted-foreground text-xs leading-relaxed'>
              This will permanently remove all data, comments, and history associated with this
              project. This action cannot be undone.
            </p>

            <div className='mt-2 flex items-center justify-end gap-2'>
              <button data-size='sm' data-variant='outline' type='button' onClick={dialog.close}>
                Cancel
              </button>
              <button
                data-size='sm'
                data-variant='destructive'
                type='button'
                onClick={dialog.close}
              >
                Delete project
              </button>
            </div>
          </div>
        </div>
      )}
    </section>
  );
};

export default Demo;

Installation

pnpm add @siberiacancode/reactuse

Usage

const { lock, unlock, value, toggle } = useLockScroll(ref);
// or
const { ref, lock, unlock, value, toggle } = useLockScroll();

Type Declarations

import type { HookTarget } from '@/utils/helpers';

import type { StateRef } from '../useRefState/useRefState';

export interface UseLockScrollOptions {
  /** Enable or disable scroll locking. Default: true */
  enabled?: boolean;
}

export interface UseLockScrollReturn<Target extends Element> {
  /** The ref to attach to the element */
  ref: StateRef<Target>;
  /** The value of the lock state */
  value: boolean;
  /** Lock the scroll */
  lock: () => void;
  /** Toggle the scroll lock */
  toggle: (value?: boolean) => void;
  /** Unlock the scroll */
  unlock: () => void;
}

export interface UseLockScroll {
  (target: HookTarget, options?: UseLockScrollOptions): UseLockScrollReturn<Element>;

  <Target extends Element>(
    options?: UseLockScrollOptions,
    target?: never
  ): UseLockScrollReturn<Target> & { ref: StateRef<Target> };
}

API

Parameters

NameTypeDefaultNote
targetHookTargetdocument.bodyThe target element to lock scroll on
optionsUseLockScrollOptions-The options for scroll locking

Parameters

NameTypeDefaultNote
optionsUseLockScrollOptions-The options for scroll locking

Returns

StateRef<Target>

Contributors

ddebabin

Last updated on