Учебник из четырех частей по созданию расширенного, доступного и многоразового модального окна ReactJS.

Если вы веб-разработчик или разработчик приложений, скорее всего, вам приходилось интегрировать модальное окно в свое приложение. Вам повезло, эта серия посвящена созданию этого модального окна! Это будет вторая часть моей серии статей Как создать модальное окно в ReactJS. Если вы не читали Часть первая, настоятельно рекомендую это сделать!

Я закончил первую часть с разработки простого модального окна, которое реагирует на события щелчка, чтобы показать или скрыть и позволяет родительским компонентам определять свое внутреннее содержимое. В этой части учений я расскажу о следующем:

  1. Включить настройку по родительским компонентам
  2. События клавиатуры
  3. Обсуждение специальных возможностей (реализация рассматривается в третьей части)

Начнем с включения настройки модального окна. Подпишитесь на эту песочницу здесь!

Почему важна персонализация?

Это позволяет легко интегрировать наш компонент в любую часть нашего приложения без особых усилий со стороны наших разработчиков. Модальное окно должно легко справляться со всеми вариантами использования, не ожидая слишком многого от нашего родительского компонента и/или от разработчиков, пытающихся его использовать. Представьте, что этот компонент должен был стать, так сказать, следующим реагирующим модалом. Вы же не хотите, чтобы разработчики, решившие использовать ваш пакет в своем проекте, разочаровались в том, что им нужно настроить на своей стороне, чтобы заставить его работать. ! Имея это в виду, мы должны подумать о том, какие важные детали, возможно , мы хотели бы, чтобы эти другие разработчики имели возможность указывать.

При этом я думаю, что наиболееважными деталями являются размер, позиционирование (возможно, люди не хотят, чтобы это было по центру!), aria и метки role для доступности. По мере появления вариантов использования, конечно, компонент может развиваться, но это стандартные ожидаемые детали, чтобы сдвинуться с мертвой точки.

Для изменения размера все, что нам нужно сделать, это принять ширину и высоту нашего модального окна и уважать их, если они указаны! Мы можем установить значения по умолчанию в случае, если наши пользователи довольны нашими спецификациями.

Нам нужно удалить принудительную ширину и высоту из нашего CSS в предыдущем примере и использовать их в качестве свойств, для которых мы включим значения по умолчанию.

innerModal CSS, который имел ширину и высоту, теперь становится:

И наш компонент теперь выглядит так

render() {
   const { show = false, width = 250, height = 300, children } = this.props;
   ...
   return (
      <div className="outerModal flex centerFlex" onClick={this.onOutsideClick}>
         <div
            style={{
               width: `${width}px`,
               height: `${height}px`
             }}
             ...
          >
              {children}
          </div>
      </div>
   );
}

Мы переходим к рассмотрению позиционирования. Нашим пользователям может понадобиться модальное окно сверху, слева, снизу, справа или по центру! Есть несколько способов сделать это, самый простой способ - позволить нашим пользователям настраивать свои модальные окна и размещать их там, где они хотят, в противном случае они могут передать центрированную опору, чтобы сказать, что они хотят, чтобы модальные окна были горизонтальными и вертикальными. по центру.

Чтобы настроить наш код, во-первых, давайте уважать булево значение centered. Нам нужны следующие классы из нашего самого внешнего div в Modal, если пользователь не проходит в centered prop.

flex centerFlex

Чтобы применить классы на основе логического значения centered, я импортирую и использую невероятно полезный пакет — classnames. Это позволяет нам условно объединять имена классов интуитивно понятным способом.

В Modal.js мы делаем

// Dependencies
import cx from 'classnames';
...
render() {
   const { show = false, centered = false, width = 250, height = 300, children } = this.props;
   ...
   return (
<div className={cx("outerModal", { "flex centerFlex": centered })}
onClick={this.onOutsideClick}>
         <div
             style={{
                width: `${width}px`,
                height: `${height}px`
             }}
             ...
         >
            {children}
         </div>
      </div>
   );
}

Мы не должны забыть обновить GreetingsModal.js, чтобы указать модальное окно, которое должно быть центрировано.

<Modal show={show} onClose={onClose} centered>
   ...
</Modal>

Отлично, теперь мы используем центрированное логическое значение. Если пользователь не хочет, чтобы модальное окно располагалось по центру, нам нужно расставить приоритеты в его спецификациях className.

Мы вернемся к Modal.js и добавим реквизит positionClassName, который мы будем использовать в случае, если centered имеет значение false.

// Dependencies
...
import cx from "classnames";
...
render() {
   const { show = false, positionClassNames = '', centered = false, width = 250, height = 300, children } = this.props;
   ...
   return (
      <div className={cx("outerModal", { "flex centerFlex": centered, [positionClassNames]: !centered })} onClick={this.onOutsideClick}>
         ...
      </div>
   );
}
...

Обратите внимание: поскольку positionClassNames — это переменная, содержащая строку, а не жестко заданную строку, я поместил имена наших классов в скобки, чтобы понять различие. Разработчики теперь могут определять positionClassNames, если они хотят свое собственное позиционирование для модального окна!

Наконец, мы хотим рассмотреть доступность для нашего модального раздела настройки. Метки ролей и арий помогают пользователям определить, на что они смотрят при использовании инструментов преобразования текста в речь (TTS).

Сначала мы берем роль для нашего модального окна, затем добавляем арию, объект, который мы будем распространять в качестве дополнительной опоры. При этом мы даем нашим разработчикам полный и точный контроль над тем, какие свойства они хотят указать для своих требований к доступности.

// Dependencies
...
import cx from "classnames";
...
render() {
   const { show = false, aria = {}, role = 'presentation', positionClassNames = '', centered = false, width = 250, height = 300, children } = this.props;
   ...
   return (
      <div role={role} {...aria} className={cx("outerModal", { "flex centerFlex": centered, [positionClassNames]: !centered })} onClick={this.onOutsideClick}>
         ...
      </div>
   );
}
...

На этом мы завершаем раздел о настройке модального окна! Мы, конечно, всегда можем добавить больше. Например, мы можем справиться с тем, чтобы наше модальное окно занимало весь экран, или, может быть, отключить ключевые события (это будет обсуждаться в следующем разделе). Тем не менее, эти детали, возможно, относятся к сфере проекта и должны добавляться по мере необходимости, чтобы наш модальный режим был обобщенным.

Давайте перейдем к обработке события клавиатуры!

Некоторые пользователи особенно привыкли нажимать кнопку escape при отображении модального окна. Нам нужно убедиться, что это взаимодействие правильно обрабатывается нашим компонентом.

Мы делаем это, добавляя прослушиватель событий в document, специально для событий типа keydown.

Помните, всякий раз, когда мы добавляем прослушиватели событий, мы должны помнить, что их также всегда нужно удалять при размонтировании, чтобы избежать утечек памяти.

Мы настроили это так, скажем, мы намерены создать обработчик, который будет искать событие типа управляющей клавиши, скажем, onKeyPress.

export default class Modal extends React.PureComponent {
   componentWillUnmount() {
      document.removeEventListener('keydown', this.onKeyPress, false);
   }
   componentDidMount () {
      document.addEventListener('keydown', this.onKeyPress, false);
   }
   onKeyPress = (e) => {}
   ...
   render() {
      ...
   }

Мы видим, что я добавил прослушиватель событий при монтировании, удалил его при размонтировании и мой обработчик onKeyPress, который пока ничего не делает.

Давайте теперь завершим наш обработчик onKeyPress. Есть много разных ключевых событий, поэтому я предпочитаю создавать отдельную папку для их отслеживания, это помогает избежать дублирования и защищает от ошибок.

Создайте папку utils и внутри мы создадим keys.js.

Внутри keys.js я стремлюсь отслеживать коды клавиш и создавать вспомогательные функции для оценки того, содержало ли событие правильное взаимодействие с клавиатурой.

const KEY_CODES = {
   ESCAPE: 'Escape'
};
export const isEscapeKey = (event = {}) => {
   event = event || {};
   return KEY_CODES['ESCAPE'] === event.key;
};
export default KEY_CODES;

Таким образом, клавиша события будет Escape в нашем случае, и мы будем использовать isEscapeKey, чтобы проверить, было ли наше событие нажатием клавиши escape! Давайте применим нашу утилиту к действию.

Вернувшись в Modal.js, мы делаем следующее

// Utils
import { isEscapeKey } from '../../utils/keys';
export default class Modal extends React.PureComponent {
   ...
   onKeyPress = (e) => {
      const { onClose } = this.props;
      if (isEscapeKey(e)) {
         onClose && onClose();
      }
   }
   ...
   render() {
      ...
   }

Это должно быть так! Теперь, когда мы нажмем клавишу выхода, наш модальный модуль закроется.

Есть несколько других ключевых событий, для которых мы можем добавить обработку, например, нажатие клавиши и нажатие клавиши для прокрутки. Учитывая, что это немного сложнее, это тема для другой статьи.

Наш последний раздел посвящен доступности. Возможно, вы сейчас думаете: что вы имеете в виду под специальными возможностями? Мы только что добавили роль и обработку арии для нашего модального окна!

Преобразование текста в речь (TTS) не охватывает все случаи доступности, особенно когда речь идет о модальных окнах. Чтобы уточнить, когда пользователи не видят экран, они полагаются на вкладку на клавиатуре. Вкладка фокусирует целевые элементы, то есть элементы, с которыми можно взаимодействовать (вводы, кнопки, раскрывающиеся списки…). Проблема в том, что когда всплывает наше модальное окно и вы пытаетесь нажать вкладку, наблюдайте, как элементы за пределами нашего модального окна тоже фокусируются!

Такое поведение нежелательно. Нашим пользователям было представлено модальное окно, которое пытается передать информацию, требующую немедленного внимания. Это означает, что нам нужно, чтобы элементы в нашем модальном окне были первыми в фокусе при нажатии этой клавиши табуляции! Нам нужно убедиться, что фокус также остается в модальном окне, пока оно открыто. Под этим я подразумеваю, что когда пользователь просматривает все элементы, с которыми можно взаимодействовать, нам нужно снова начать цикл с первого наиболее доступного для фокуса элемента в модальном окне.

Я довольно визуальный человек, так что вот первый образец, который я нашел, что табирование Conta только в контексте модального окна. Обратите внимание, что когда я переключаюсь, кнопка закрытия и кнопка действия — единственные, которые получают фокус. Это то, что мы хотим сделать и с нашим модальным окном!

Думайте об этом как о круге всех элементов, на которые можно сфокусироваться, или как о начале массива после достижения конца... ну, надеюсь, я понял. Если я ничего не понимаю, не волнуйтесь! Часть 3 будет полностью посвящена этому, и мы приступим к реализации.

Я бы сказал, что наш модальный скоро появится! Мы почти у цели создать продвинутый модальный режим, который можно использовать в любом проекте. Следите за моими будущими статьями, которые продолжат работу над этим модальным окном, а наше следующее обсуждение будет посвящено более продвинутой обработке специальных возможностей. Не стесняйтесь обращаться ко мне с любыми вопросами, я буду рад помочь, чем смогу!

Всем привет! Если вам понравилась эта статья, пожалуйста, поддержите меня, подписавшись на меня! Я новичок в медиа и буду очень часто публиковать свои открытия и знания в технологической индустрии и за ее пределами.

Спасибо, что прочитали еще раз! ❤️