diff --git a/content/intro-to-storybook/react/ru/composite-component.md b/content/intro-to-storybook/react/ru/composite-component.md
new file mode 100644
index 000000000..ee0878155
--- /dev/null
+++ b/content/intro-to-storybook/react/ru/composite-component.md
@@ -0,0 +1,227 @@
+---
+title: 'Создание составного компонента'
+tocTitle: 'Составной компонент'
+description: 'Создание составного компонента из простых компонентов'
+commit: '73d7821'
+---
+
+В прошлой главе мы создали наш первый компонент; в этой главе мы расширим полученные знания, чтобы создать компонент `TaskList` - список задач. Давайте объединим компоненты вместе и посмотрим, что произойдет, когда мы добавим больше сложности.
+
+## Компонент TaskList
+
+`Taskbox` выделяет закреплённые задачи, размещая их над обычными задачами. Это даёт два варианта отображения компонента `TaskList`, для которых нужно создавать истории: обычный и с закреплёнными элементами.
+
+
+
+Поскольку данные для `Task` могут быть отправлены асинхронно, нам **также** необходимо состояние загрузки для рендеринга в отсутствие соединения. Кроме того, нам нужно пустое состояние, когда нет задач.
+
+
+
+## Начинаем подготовку
+
+Составной компонент мало чем отличается от простых компонентов, которые он содержит. Создайте компонент `TaskList` и сопроводительный файл историй: `src/components/TaskList.js` и `src/components/TaskList.stories.js`.
+
+Начните с грубой реализации `TaskList`. Вам нужно будет импортировать компонент `Task` и передать атрибуты и действия в качестве входных данных.
+
+```js:title=src/components/TaskList.js
+import React from 'react';
+
+import Task from './Task';
+
+export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) {
+ const events = {
+ onPinTask,
+ onArchiveTask,
+ };
+
+ if (loading) {
+ return
loading
;
+ }
+
+ if (tasks.length === 0) {
+ return
empty
;
+ }
+
+ return (
+
+ {tasks.map(task => (
+
+ ))}
+
+ );
+}
+```
+
+Затем создайте тестовые состояния `TaskList` в файле истории.
+
+```js:title=src/components/TaskList.stories.js
+import React from 'react';
+
+import TaskList from './TaskList';
+import * as TaskStories from './Task.stories';
+
+export default {
+ component: TaskList,
+ title: 'TaskList',
+ decorators: [story =>
+💡 Декораторы это способ создания произвольных обёрток для историй. В данном случае мы используем декоратор для того, чтобы добавить padding вокруг отрисованного компонента. Их также можно использовать для обёртывания историй в "провайдеры" - т.е. библиотечные компоненты, которые устанавливают контекст React.
+
+
+Импортировав `TaskStories`, мы смогли [составить](https://storybook.js.org/docs/react/writing-stories/args#args-composition) аргументы (сокращенно args) в наших историях с минимальными усилиями. Таким образом, данные и действия (замоканные коллбеки), ожидаемые обоими компонентами, сохраняются.
+
+Теперь давайте проверим Storybook на наличие новых историй для `TaskList`.
+
+
+
+## Реализуем состояния
+
+Наш компонент все ещё сырой, но теперь у нас есть представление о том, над чем нужно работать. Вы можете подумать, что обёртка `.list-items` слишком упрощена. Вы правы - в большинстве случаев мы не стали бы создавать новый компонент только для того, чтобы добавить обёртку. Но **реальная сложность** компонента `TaskList` проявляется в граничных случаях `withPinnedTasks`, `load` и `empty`.
+
+```js:title=src/components/TaskList.js
+import React from 'react';
+
+import Task from './Task';
+
+export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) {
+ const events = {
+ onPinTask,
+ onArchiveTask,
+ };
+ const LoadingRow = (
+
+ );
+}
+```
+
+Результат добавленной разметки следующий:
+
+
+
+Обратите внимание на положение закреплённого элемента в списке. Мы хотим, чтобы закреплённый элемент отображался в верхней части списка, чтобы сделать его приоритетным для наших пользователей.
+
+## Требования к данным и свойствам
+
+По мере роста компонента растут и входные требования. Определите требования к свойствам `TaskList`. Поскольку `Task` является дочерним компонентом, убедитесь, что данные имеют соответствующую структуру для его рендеринга. Чтобы сэкономить время и головную боль, повторно используйте `propTypes`, которые вы определили в `Task` ранее.
+
+```diff:title=src/components/TaskList.js
+import React from 'react';
++ import PropTypes from 'prop-types';
+
+import Task from './Task';
+
+export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) {
+ ...
+}
+
++ TaskList.propTypes = {
++ /** Checks if it's in loading state */
++ loading: PropTypes.bool,
++ /** The list of tasks */
++ tasks: PropTypes.arrayOf(Task.propTypes.task).isRequired,
++ /** Event to change the task to pinned */
++ onPinTask: PropTypes.func,
++ /** Event to change the task to archived */
++ onArchiveTask: PropTypes.func,
++ };
++ TaskList.defaultProps = {
++ loading: false,
++ };
+```
+
+
+💡 Не забудьте зафиксировать свои изменения с помощью git!
+
diff --git a/content/intro-to-storybook/react/ru/data.md b/content/intro-to-storybook/react/ru/data.md
new file mode 100644
index 000000000..a826bce9c
--- /dev/null
+++ b/content/intro-to-storybook/react/ru/data.md
@@ -0,0 +1,304 @@
+---
+title: 'Передача данных'
+tocTitle: 'Данные'
+description: 'Узнайте как передать данные UI-компоненту'
+commit: '94b134e'
+---
+
+До сих пор мы создавали изолированные компоненты без статического состояния - это хорошо для Storybook, но в конечном счете бесполезно, пока мы не предоставим им какие-либо данные в нашем приложении.
+
+В этом руководстве не рассматриваются особенности создания приложения, поэтому мы не будем углубляться в эти детали. Но мы уделим немного времени рассмотрению общего шаблона для подключения данных к подключаемым компонентам.
+
+## Присоединённые компоненты
+
+На данный момент, наш компонент `TaskList`, является "презентационным", т.е. он не взаимодействует ни с чем внешним, кроме своей собственной реализации. Нам нужно подключить его к поставщику данных, чтобы получить данные.
+
+Чтобы построить простую модель данных для нашего приложения мы будем использовать [Redux Toolkit](https://redux-toolkit.js.org/). Это эффективный набор инструментов для разработки приложений которые хранят данные с помощью [Redux](https://redux.js.org/). Однако используемый здесь паттерн также применим и к другим библиотекам управления данными, таким как [Apollo](https://www.apollographql.com/client/) и [MobX](https://mobx.js.org/).
+
+Добавьте необходимые зависимости в ваш проект с помощью команды:
+
+```bash
+yarn add @reduxjs/toolkit react-redux
+```
+
+Сначала мы создадим простое хранилище Redux, которое реагирует на действия, изменяющие состояние задачи, в файле `store.js` в каталоге `src/lib` (намеренно оставленном простым):
+
+```js:title=src/lib/store.js
+/* Простая имплементация redux store/actions/reducer.
+ * В настоящем приложении хранилище будет сложнее и разбито на отдельные файлы.
+ */
+import { configureStore, createSlice } from '@reduxjs/toolkit';
+
+/*
+ * Начальное состояние нашего приложения, когда оно будет загружено.
+ * Обычно, вы будете получать данные от сервера. Давайте не беспокоиться об этом сейчас.
+ */
+const defaultTasks = [
+ { id: '1', title: 'Something', state: 'TASK_INBOX' },
+ { id: '2', title: 'Something more', state: 'TASK_INBOX' },
+ { id: '3', title: 'Something else', state: 'TASK_INBOX' },
+ { id: '4', title: 'Something again', state: 'TASK_INBOX' },
+];
+const TaskBoxData = {
+ tasks: defaultTasks,
+ status: 'idle',
+ error: null,
+};
+
+/*
+ * Хранилище создано тут.
+ * Вы можете узнать больше о Redux Toolkit слайсах в документации:
+ * https://redux-toolkit.js.org/api/createSlice
+ */
+const TasksSlice = createSlice({
+ name: 'taskbox',
+ initialState: TaskBoxData,
+ reducers: {
+ updateTaskState: (state, action) => {
+ const { id, newTaskState } = action.payload;
+ const task = state.tasks.findIndex((task) => task.id === id);
+ if (task >= 0) {
+ state.tasks[task].state = newTaskState;
+ }
+ },
+ },
+});
+
+// Экшены, содержащиеся в слайсах экспортированы для использования в наших компонентах.
+export const { updateTaskState } = TasksSlice.actions;
+
+/*
+ * Конфигурация хранилища нашего приложения начинается отсюда.
+ * Узнать больше о configureStore можно в документации Redux:
+ * https://redux-toolkit.js.org/api/configureStore
+ */
+const store = configureStore({
+ reducer: {
+ taskbox: TasksSlice.reducer,
+ },
+});
+
+export default store;
+```
+
+Затем мы изменим наш компонент `TaskList`, подключив его к хранилищу Redux и отрендерим задачи, в которых мы заинтересованы:
+
+```js:title=src/components/TaskList.js
+import React from 'react';
+import Task from './Task';
+import { useDispatch, useSelector } from 'react-redux';
+import { updateTaskState } from '../lib/store';
+
+export default function TaskList() {
+ // Получаем наше состояние из хранилища
+ const tasks = useSelector((state) => {
+ const tasksInOrder = [
+ ...state.taskbox.tasks.filter((t) => t.state === 'TASK_PINNED'),
+ ...state.taskbox.tasks.filter((t) => t.state !== 'TASK_PINNED'),
+ ];
+ const filteredTasks = tasksInOrder.filter(
+ (t) => t.state === 'TASK_INBOX' || t.state === 'TASK_PINNED'
+ );
+ return filteredTasks;
+ });
+
+ const { status } = useSelector((state) => state.taskbox);
+
+ const dispatch = useDispatch();
+
+ const pinTask = (value) => {
+ // Отправляем событие Pinned обратно в наше хранилище
+ dispatch(updateTaskState({ id: value, newTaskState: 'TASK_PINNED' }));
+ };
+ const archiveTask = (value) => {
+ // Отправляем событие Archive обратно в наше хранилище
+ dispatch(updateTaskState({ id: value, newTaskState: 'TASK_ARCHIVED' }));
+ };
+ const LoadingRow = (
+
+ );
+}
+```
+
+Теперь, когда у нас есть некоторые фактические данные, заполняющие наш компонент, полученные из хранилища Redux, мы могли бы подключить их к `src/App.js` и отрендерить компонент там. Но пока давайте воздержимся от этого и продолжим наш путь, основанный на компонентах.
+
+Не беспокойтесь об этом. Мы позаботимся об этом в следующей главе.
+
+## Supplying context with decorators
+
+С этим изменением, наши истории Storybook перестали работать, потому что наш `Tasklist` теперь подключен, и полагается на хранилище Redux для получения и обновления наших задач.
+
+
+
+Мы можем использовать различные подходы для решения этой проблемы. Поскольку наше приложение довольно простое, мы можем использовать декоратор, подобный тому, что мы делали в [предыдущей главе](/intro-to-storybook/react/en/composite-component), и предоставить замоканное хранилище в наших историях Storybook:
+
+```js:title=src/components/TaskList.stories.js
+import React from 'react';
+
+import TaskList from './TaskList';
+import * as TaskStories from './Task.stories';
+
+import { Provider } from 'react-redux';
+
+import { configureStore, createSlice } from '@reduxjs/toolkit';
+
+// Суперпростой мок состояния нашего хранилища.
+export const MockedState = {
+ tasks: [
+ { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' },
+ { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' },
+ { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' },
+ { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' },
+ { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' },
+ { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' },
+ ],
+ status: 'idle',
+ error: null,
+};
+
+// Суперпростой мок redux-хранилища.
+const Mockstore = ({ taskboxState, children }) => (
+ {
+ const { id, newTaskState } = action.payload;
+ const task = state.tasks.findIndex((task) => task.id === id);
+ if (task >= 0) {
+ state.tasks[task].state = newTaskState;
+ }
+ },
+ },
+ }).reducer,
+ },
+ })}
+ >
+ {children}
+
+);
+
+export default {
+ component: TaskList,
+ title: 'TaskList',
+ decorators: [(story) =>
+💡 excludeStories это поле конфигурации Storybook, которое запрещает рассматривать наше замоканное состояние как историю. Подробнее об этом поле вы можете прочитать в документации Storybook.
+
+
+
+
+
+💡 Не забудьте зафиксировать свои изменения с помощью git!
+
+
+Успех! Мы вернулись к тому, с чего начали, наш Storybook теперь работает, и мы можем видеть, как мы можем поставлять данные в подключенный компонент. В следующей главе мы возьмем то, чему научились здесь, и применим это к экрану.
diff --git a/content/intro-to-storybook/react/ru/get-started.md b/content/intro-to-storybook/react/ru/get-started.md
new file mode 100644
index 000000000..71122b6a7
--- /dev/null
+++ b/content/intro-to-storybook/react/ru/get-started.md
@@ -0,0 +1,81 @@
+---
+title: 'Руководство по Storybook для React'
+tocTitle: 'Введение'
+description: 'Настройка Storybook в вашей среде разработки'
+commit: '9245261'
+---
+
+Storybook работает параллельно с вашим приложением в режиме разработки. Он помогает создавать UI-компоненты, изолированные от бизнес-логики и контекста вашего приложения. Данное руководство предназначено для React; существуют и другие руководства для [React Native](/intro-to-storybook/react-native/en/get-started), [Vue](/intro-to-storybook/vue/en/get-started), [Angular](/intro-to-storybook/angular/en/get-started), [Svelte](/intro-to-storybook/svelte/en/get-started) и [Ember](/intro-to-storybook/ember/en/get-started).
+
+
+
+## Настройка React Storybook
+
+Нам нужно выполнить несколько шагов, чтобы настроить процесс сборки. Для начала, мы используем [degit](https://github.com/Rich-Harris/degit) для настройки нашей системы сборки.Используя этот пакет, вы можете загрузить «шаблоны» (частично созданные приложения с некоторой конфигурацией по умолчанию), которые помогут вам ускорить процесс разработки.
+
+Давайте выполним следующие команды:
+
+```bash
+# Клонируем шаблон
+npx degit chromaui/intro-storybook-react-template taskbox
+
+cd taskbox
+
+# Устанавливаем зависимости
+yarn
+```
+
+
+💡 Этот шаблон содержит необходимые стили, ассеты и базовые конфигурации для этой версии руководства.
+
+
+Теперь мы можем проверить, правильно ли работают различные среды нашего приложения:
+
+```bash
+# Запускаем Jest:
+yarn test --watchAll
+
+# Запускаем обозреватель компонентов на порту 6006:
+yarn storybook
+
+# Запускаем приложение на порту 3000:
+yarn start
+```
+
+
+💡 Обратите внимание на флаг --watchAll, включение этого флага обеспечивает выполнение всех тестов. По мере прохождения этого руководства вы познакомитесь с различными тестовыми сценариями. Возможно, вы захотите соответствующим образом скорректировать скрипты вашего package.json.
+
+
+Три модальности нашего фронтенд-приложения: автоматизированное тестирование (Jest), разработка компонентов (Storybook) и само приложение.
+
+
+
+В зависимости от того, над какой частью приложения вы работаете, вы можете запустить одну или несколько из них одновременно. Поскольку сейчас мы сосредоточены на создании одного UI-компонента, мы остановимся на запуске Storybook.
+
+## Сохраним изменения
+
+На данном этапе нам лучше добавлять наши файлы в локальный репозиторий. Выполните следующие команды, чтобы создать локальный репозиторий, добавить и зафиксировать изменения, которые мы уже сделали.
+
+```shell
+$ git init
+```
+
+Добавим файлы:
+
+```shell
+$ git add .
+```
+
+Зафиксируем изменения:
+
+```shell
+$ git commit -m "first commit"
+```
+
+И наконец:
+
+```shell
+$ git branch -M main
+```
+
+А теперь, давайте создадим наш первый компонент!
diff --git a/content/intro-to-storybook/react/ru/simple-component.md b/content/intro-to-storybook/react/ru/simple-component.md
new file mode 100644
index 000000000..f0d9200b9
--- /dev/null
+++ b/content/intro-to-storybook/react/ru/simple-component.md
@@ -0,0 +1,328 @@
+---
+title: 'Создание простого компонента'
+tocTitle: 'Простой компонент'
+description: 'Создание простого компонента в изоляции'
+commit: 'efa06ff'
+---
+
+Мы создадим наш пользовательский интерфейс, следуя методологии [Component-Driven Development](https://www.componentdriven.org/) (CDD). Это процесс, при котором пользовательский интерфейс создается «снизу вверх», начиная с компонентов и заканчивая экранами. CDD помогает вам масштабировать уровень сложности, с которым вы сталкиваетесь при создании пользовательского интерфейса.
+
+## Task
+
+
+
+`Task` это основной компонент нашего приложения. Каждая задача отображается по-разному в зависимости от того, в каком именно состоянии она находится. Мы отображаем отмеченным (или не отмеченным) чекбокс, некоторую информацию о задаче и кнопку "прикрепить", позволяющую перемещать задачи вверх и вниз по списку. Чтобы собрать всё это вместе, нам понадобятся следующие параметры:
+
+- `title` – строка, описывающая задачу
+- `state` – в каком списке находится задание в данный момент, и отмечено ли оно?
+
+Начиная создавать компонент `Task`, мы сначала пишем наши тестовые состояния, которые соответствуют различным типам задач, описанным выше. Затем мы используем Storybook для создания компонента в изоляции с использованием замоканных данных. Мы будем вручную тестировать внешний вид компонента в каждом состоянии по ходу работы.
+
+## Начинаем подготовку
+
+Сначала создадим компонент `Task` и сопровождающий его файл истории: `src/components/Task.js` and `src/components/Task.stories.js`.
+
+Мы начнём с базовой реализации `Task`, просто взяв атрибуты, которые нам понадобятся, и два действия, которые можно выполнить над задачей (чтобы переместить её между списками):
+
+```js:title=src/components/Task.js
+import React from 'react';
+
+export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
+ return (
+
+
+
+ );
+}
+```
+
+Выше мы сделали разметку для `Task` на основе существующей HTML-структуры приложения Todos.
+
+Ниже мы создадим три тестовых состояния `Task` в файле истории:
+
+```js:title=src/components/Task.stories.js
+import React from 'react';
+
+import Task from './Task';
+
+export default {
+ component: Task,
+ title: 'Task',
+};
+
+const Template = args => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ task: {
+ id: '1',
+ title: 'Test Task',
+ state: 'TASK_INBOX',
+ },
+};
+
+export const Pinned = Template.bind({});
+Pinned.args = {
+ task: {
+ ...Default.args.task,
+ state: 'TASK_PINNED',
+ },
+};
+
+export const Archived = Template.bind({});
+Archived.args = {
+ task: {
+ ...Default.args.task,
+ state: 'TASK_ARCHIVED',
+ },
+};
+```
+
+В Storybook есть два основных уровня организации: компонент и его дочерние истории. Считайте, что каждая история – это один из вариантов компонента. Вы можете иметь столько историй на компонент, сколько вам нужно.
+
+- **Component**
+ - Story
+ - Story
+ - Story
+
+Чтобы рассказать Storybook о компоненте, который мы документируем, мы создаем экспорт по умолчанию (`export default`), который содержит:
+
+- `component` – сам компонент
+- `title` – как ссылаться на компонент в боковой панели Storybook
+
+Чтобы определить наши истории, мы экспортируем функцию для каждого из наших тестовых состояний для создания истории. История – это функция, которая возвращает отрендеренный элемент (т.е. компонент с набором параметров) в данном состоянии – точно так же, как [Functional Component](https://ru.reactjs.org/docs/components-and-props.html#function-and-class-components).
+
+Поскольку у нас есть несколько вариантов нашего компонента, удобно присвоить его переменной Template. Внедрение этого паттерна в ваши истории сократит количество кода, который вам нужно писать и поддерживать.
+
+
+💡 Template.bind({}) это стандартный способ JavaScript для создания копии функции. Мы используем этот способ, чтобы позволить каждой экспортируемой истории устанавливать свои собственные свойства, но использовать одну и ту же реализацию.
+
+
+Аргументы или кратко [`args`](https://storybook.js.org/docs/react/writing-stories/args), позволят нам редактировать наши компоненты в реальном времени с помощью аддона `controls` без перезапуска Storybook. Как только значение [`args`](https://storybook.js.org/docs/react/writing-stories/args) изменяется, изменяется и компонент.
+
+При создании истории мы используем базовый аргумент `task` для построения структуры задачи, которую ожидает компонент. Обычно она моделируется на основе того, как выглядят фактические данные. Опять же, экпортирование этой структуры позволит нам повторно использовать её в последующих историях, как мы увидим.
+
+
+💡 Actions помогут вам проверить взаимодействие при изолированном создании компонентов пользовательского интерфейса. Зачастую у вас не будет доступа к функциям и состояниям, которые есть в контексте приложения. Используйте action(), чтобы сымитировать их.
+
+
+## Конфигурация
+
+Нам потребуется внести пару изменений в конфигурационные файлы Storybook, чтобы он обнаруживал не только наши недавно созданные истории, но и позволил нам использовать CSS-файл приложения (расположенный в `src/index.css`).
+
+Начните с изменения конфигурационного файла Storybook (`.storybook/main.js`) на следующий:
+
+```diff:title=.storybook/main.js
+module.exports = {
+- stories: [
+- '../src/**/*.stories.mdx',
+- '../src/**/*.stories.@(js|jsx|ts|tsx)'
+- ],
++ stories: ['../src/components/**/*.stories.js'],
+ staticDirs: ['../public'],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/preset-create-react-app',
+ '@storybook/addon-interactions',
+ ],
+ framework: '@storybook/react',
+ core: {
+ builder: '@storybook/builder-webpack5',
+ },
+ features: {
+ interactionsDebugger: true,
+ },
+};
+```
+
+После выполнения вышеуказанных изменений, внутри папки `.storybook` внесите следующие изменения в файл `preview.js`:
+
+```diff:title=.storybook/preview.js
++ import '../src/index.css';
+
+//👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI.
+export const parameters = {
+ actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+};
+```
+
+[`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) обычно используются для управления поведением функций и аддонов Storybook. В нашем случае мы будем использовать их для настройки того, как будут обрабатываться `actions` (замоканные функции).
+
+`actions` позволяет нам создавать коллбэки, которые появляются на панели **actions** пользовательского интерфейса Storybook при нажатии на кнопку. Таким образом, когда мы создадим кнопку `pin`, мы сможем определить, было ли нажатие кнопки успешным в пользовательском интерфейсе.
+
+После этого перезапуск Storybook должен привести к появлению тестовых примеров для трех состояний задачи:
+
+
+
+## Реализуем состояния
+
+Теперь, когда Storybook настроен, стили импортированы, а тестовые примеры созданы, мы можем быстро приступить к реализации HTML компонента в соответствии с дизайном.
+
+На данный момент этот компонент всё ещё находится в зачаточном состоянии. Сначала напишите код, который соответствует дизайну, не вдаваясь в излишние подробности:
+
+```js:title=src/components/Task.js
+import React from 'react';
+
+export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
+ return (
+
+ );
+}
+```
+
+Дополнительная разметка сверху в сочетании с CSS, который мы импортировали ранее, даёт следующий пользовательский интерфейс:
+
+
+
+## Определяем требования к данным
+
+Лучшей практикой является использование `propTypes` в React для указания структуры данных, которые ожидает компонент. Это не только самодокументирование, но и помогает выявить проблемы на ранней стадии.
+
+```diff:title=src/components/Task.js
+import React from 'react';
++ import PropTypes from 'prop-types';
+
+export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
+ // ...
+}
+
++ Task.propTypes = {
++ /** Composition of the task */
++ task: PropTypes.shape({
++ /** Id of the task */
++ id: PropTypes.string.isRequired,
++ /** Title of the task */
++ title: PropTypes.string.isRequired,
++ /** Current state of the task */
++ state: PropTypes.string.isRequired,
++ }),
++ /** Event to change the task to archived */
++ onArchiveTask: PropTypes.func,
++ /** Event to change the task to pinned */
++ onPinTask: PropTypes.func,
++ };
+```
+
+Теперь при неправильном использовании компонента Task в режиме разработки будет появляться предупреждение.
+
+
+💡 Альтернативным способом достижения той же цели является использование системы типов JavaScript, например TypeScript, для создания типа для свойств компонента.
+
+
+## Компонент готов!
+
+Мы успешно создали компонент без необходимости использования сервера или запуска всего фронтенд-приложения. Следующим шагом будет создание остальных компонентов Taskbox аналогичным образом.
+
+Как видите, начать создавать изолированные компоненты легко и быстро. Мы можем рассчитывать на создание более качественного пользовательского интерфейса с меньшим количеством ошибок и большей доработкой, потому что можно покопаться и протестировать все возможные состояния.
+
+## Устраняем проблемы с доступностью
+
+Тесты доступности относятся к практике аудита визуализации DOM с помощью автоматизированных инструментов на основе набора эвристик, основанных на правилах [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) и других принятых в отрасли лучших практик. Они выступают в качестве первой линии QA для выявления вопиющих нарушений доступности, гарантируя, что приложение будет пригодно для использования как можно большим количеством людей, включая людей с ограниченными возможностями, такими как нарушения зрения, проблемы со слухом и когнитивные заболевания.
+
+Storybook имеет официальный [аддон доступности](https://storybook.js.org/addons/@storybook/addon-a11y). Он работает на основе [axe-core](https://github.com/dequelabs/axe-core) от Deque и позволяет решить [57% проблем WCAG](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/).
+
+Давайте посмотрим, как это работает! Выполните следующую команду для установки аддона:
+
+```bash
+yarn add --dev @storybook/addon-a11y
+```
+
+Затем обновите файл конфигурации Storybook (`.storybook/main.js`), чтобы включить его:
+
+```diff:title=.storybook/main.js
+module.exports = {
+ stories: ['../src/components/**/*.stories.js'],
+ staticDirs: ['../public'],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/preset-create-react-app',
+ '@storybook/addon-interactions',
++ '@storybook/addon-a11y',
+ ],
+ framework: '@storybook/react',
+ core: {
+ builder: '@storybook/builder-webpack5',
+ },
+ features: {
+ interactionsDebugger: true,
+ },
+};
+```
+
+
+
+Просматривая наши истории, мы видим, что аддон обнаружил проблему доступности в одном из наших тестовых состояний. Сообщение [**"Элементы должны иметь достаточный цветовой контраст "**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI) означает, что между заголовком задачи и фоном недостаточно контраста. Мы можем быстро исправить это, изменив цвет текста на более тёмный серый в CSS нашего приложения (находится в `src/index.css`).
+
+```diff:title=src/index.css
+.list-item.TASK_ARCHIVED input[type="text"] {
+- color: #a0aec0;
++ color: #4a5568;
+ text-decoration: line-through;
+}
+```
+
+Вот и всё! Мы сделали первый шаг к тому, чтобы сделать пользовательский интерфейс доступным. По мере дальнейшего усложнения нашего приложения мы сможем повторить этот процесс для всех остальных компонентов без необходимости в дополнительных инструментах или средах тестирования.
+
+
+💡 Не забудьте зафиксировать свои изменения с помощью git!
+