andreivictor
andreivictor

Reputation: 8481

React + Headless UI Dialog - Navigation with keyboard

In a React app, I use HeadlessUI Dialog to display an item preview from a list. Using keyboard arrows left / right should trigger the display of the next / prev item from the list.

For that, I used the following code:

// Keyboard navigation
// ************************************
useEffect(() => {
  const keyDownHandler = (e: KeyboardEvent) => {
      
    // Right arrow => 
    if (e.keyCode === 39 && nextItem) {
      // Go to next item
      goToItem(nextItem);
    }

    // Left arrow => 
    if (e.keyCode === 37 && prevItem) {
      // Go to prev item
      goToItem(prevItem);
    };
  }

  document.addEventListener('keydown', keyDownHandler);

  return () => {
    document.removeEventListener('keydown', keyDownHandler);
  };
}, [nextItem, prevItem, goToItem]);

Now, the thing is - within the item preview dialog - some other dialogs can also be opened (eg: Share Item Dialog, Report content, etc). This dialogs are opened from different components, so I can not add some open callback to each of them.

If one of these dialogs are opened, I want to disable the keyboard navigation.

The code I've tried is to add the following code to the keydown listener:

// If multiple modals are open, the navigation should be disabled.
// HeadlessUI renders modals within a container element marked with the data-headlessui-portal attribute.
// If multiple data-headlessui-portal elements are present, it indicates that more than one modal is open.
  if (document.querySelectorAll('div[data-headlessui-portal]').length > 1) {
     return;
   }

Is there are approach that I could take?

Upvotes: 0

Views: 169

Answers (1)

Mladen Milosavljevic
Mladen Milosavljevic

Reputation: 1810

Create a state that follows active dialog, and set state should update with each dialog. Add isActiveDialog() inside of useEffect, it should return boolean.

useEffect(() => {

  if (isActiveDialog(thisDialog )) {
  const keyDownHandler = (e: KeyboardEvent) => {
      
    // Right arrow => 
    if (e.keyCode === 39 && nextItem) {
      // Go to next item
      goToItem(nextItem);
    }

    // Left arrow => 
    if (e.keyCode === 37 && prevItem) {
      // Go to prev item
      goToItem(prevItem);
    };
  }

  document.addEventListener('keydown', keyDownHandler);
  }

  return () => {
    document.removeEventListener('keydown', keyDownHandler);
  };
}, [nextItem, prevItem, goToItem, activeDialog]);

And isActive dialog shoud compare state of dialog with dialog id

const isActiveDialog = () => {
   return activeDialog === thisDialog 
}

Upvotes: 1

Related Questions