В данном домашнем задании Вам необходимо реализовать React-компоненты для вашего будущего проекта.
Требования:
Для запуска всех тестов выполняйте команду:
yarn test
Для запуска теста отдельного компонента:
yarn test:single <название компонента>
Например:
yarn test:single Card
- В WebStorm есть опция запуска теста внутри файла теста (иконка play слева от кода).
- В VSCode можно установить расширение Jest, которое позволяет запускать отдельные скриншотные тесты из меню расширения Jest. После установки зависимостей перезапустите VSCode, чтобы расширение проиндексировало файлы тестов.
При этом необходимо на время закомментировать те тест-кейсы, которые не требуется прогонять (вызовы функции
screenshotTesting
). Важно всё раскомментировать перед финальной общей проверкой и пушем в репозиторий.
Реализуйте компонент Лоадер
type LoaderProps = {
/** Размер */
size?: 's' | 'm' | 'l';
/** Дополнительный класс */
className?: string;
};
Примеры использования:
<Loader /> // стандартный лоадер
<Loader size="l" /> // лоадер размера L
Реализуйте компонент Text
Требования:
- По умолчанию должен иметь цвет родителя
- Пропс
weight
имеет больший приоритет чемview
- При указании
tag
рендерится соответствующий тег, по умолчаниюp
type TextProps = {
/** Дополнительный класс */
className?: string;
/** Стиль отображения */
view?: 'title' | 'button' | 'p-20' | 'p-18' | 'p-16' | 'p-14';
/** Html-тег */
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' | 'p' | 'span';
/** Начертание шрифта */
weight?: 'normal' | 'medium' | 'bold';
/** Контент */
children: React.ReactNode;
/** Цвет */
color?: 'primary' | 'secondary' | 'accent';
/** Максимальное кол-во строк */
maxLines?: number;
};
Примеры использования:
<Text tag="p" weight="medium">Жирный текст</Text>
<Text tag="h1" view="title" maxLines={2}>Заголовок</Text>
Реализуйте компоненты иконок. (Назвать их CheckIcon и ArrowDownIcon)
- Все компоненты иконок лежат в директории
components/icons/
(Пример импорта:import CheckIcon from 'components/icons/CheckIcon'
) - Все компоненты иконок имеют одинаковый базовый набор пропсов
IconProps
. (Лучше всего сделать компонент-оберткуIcon
и использовать в компонентах иконок, но это не обязательно). - По умолчанию имеют цвет родителя а при указании
color
, красятся в указанный стиль цвета. - Можно указать ширину и высоту иконки (по умолчанию 24px)
type IconProps = React.SVGAttributes<SVGElement> & {
className?: string;
color?: 'primary' | 'secondary' | 'accent';
};
Примеры использования:
<CheckIcon width={40} height={40} />
<ArrowDownIcon color="accent" />
Реализуйте компонент Кнопка
Требования:
- Кнопка использует html-тег button и принимает все его пропсы
- Кнопка принимает пропсы ButtonProps и удовлетворяет их требованиям, описанным ниже
- Текст кнопки/дочерний элемент передается в качестве
children
- При передаче дополнительного
className
не должны сбрасываться внутренние (описанные вами в стилях) классы кнопки - Компонент должен быть реактивным, то есть реагировать на изменение любых пропсов
- Для управления классами необходимо использовать библиотеку
classnames
- При loading=true, на кнопке должен появляться атрибут disabled
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
/** Состояние загрузки */
loading?: boolean;
/** Текст кнопки */
children: React.ReactNode;
};
Примеры использования:
// Кнопка с текстом "Отправить", логирующая в консоль "Письмо отправлено" при клике
<Button onClick={() => console.log('Письмо отправлено')}>
Отправить
</Button>
// Кнопка, отображающая компонент Loader при загрузке каких-то данных
<Button
loading={isLoading}
>
Отправить
</Button>
// Кнопка с элементом в качестве содержимого
<Button>
<span>Модная кнопка</span>
</Button>
// Заблокированная кнопка с дополнительным классом
<Button className="some-outer-class" disabled>
Отправить
</Button>
// Кнопка с пропсом нативной кнопки
<Button onMouseOver={() => console.log('Убери от меня курсор!')}>
Отправить
</Button>
Реализуйте компонент Карточка (Элемент списка)
Требования:
- Для изображения используется html-тег
img
- В заголовке может быть максимум 2 строки
- В описании может быть максимум 3 строки
- Контент над заголовком необязательный (кол-во строк не ограничено)
- При клике на карточку должен выполняться
onClick
- Для текстов используется компонент
Text
- При расширении/сужении карточки, изображение должно сохранять пропорции
type CardProps = {
/** Дополнительный classname */
className?: string;
/** URL изображения */
image: string;
/** Слот над заголовком */
captionSlot?: React.ReactNode;
/** Заголовок карточки */
title: React.ReactNode;
/** Описание карточки */
subtitle: React.ReactNode;
/** Содержимое карточки (футер/боковая часть), может быть пустым */
contentSlot?: React.ReactNode;
/** Клик на карточку */
onClick?: React.MouseEventHandler;
/** Слот для действия */
actionSlot?: React.ReactNode;
};
Примеры использования:
<Card
image="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
title="Мандарин"
subtitle="Марокко"
onClick={() => console.log('Мандарин куплен!')}
/>
<Card
image="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
title="Мандарин"
subtitle={<a href="/morocco">Марокко</a>}
contentSlot={<>299р</>}
actionSlot={<Button>В корзину</Button>}
/>
Реализуйте компонент Поле ввода
Требования:
- Необходимо использовать html-тег
input
- Должен быть слот для иконки справа
type InputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange' | 'value'
> & {
/** Значение поля */
value: string;
/** Callback, вызываемый при вводе данных в поле */
onChange: (value: string) => void;
/** Слот для иконки справа */
afterSlot?: React.ReactNode;
};
Примеры использования:
// Простое поле
<Input
value="Кто такой биткоин?"
onChange={(value: string) => console.log(value)}
/>
// Заблокированное поле с плейсхолдером
<Input
value=""
onChange={(value: string) => console.log(value)}
placeholder="Начните набирать свой вопрос"
disabled
/>
// Поле с иконкой
<Input
value="Кто такой биткоин?"
onChange={(value: string) => console.log(value)}
afterSlot={<ArrowDownIcon color="secondary" />
/>
Реализуйте компонент Выпадающий список с множественным выбором (Фильтр).
Требования:
- Должен использовать компонент
Input
- При вводе в поле, опции должны фильтроваться
- Опции должны пропадать из DOM-дерева при клике вне поля
type Option = {
/** Ключ варианта, используется для отправки на бек/использования в коде */
key: string;
/** Значение варианта, отображается пользователю */
value: string;
};
/** Пропсы, которые принимает компонент Dropdown */
type MultiDropdownProps = {
className?: string;
/** Массив возможных вариантов для выбора */
options: Option[];
/** Текущие выбранные значения поля, может быть пустым */
value: Option[];
/** Callback, вызываемый при выборе варианта */
onChange: (value: Option[]) => void;
/** Заблокирован ли дропдаун */
disabled?: boolean;
/** Возвращает строку которая будет выводится в инпуте. В случае если опции не выбраны, строка должна отображаться как placeholder. */
getTitle: (value: Option[]) => string;
};
Примеры использования:
// Простой фильтр
<MultiDropdown
options={[
{ key: 'msk', value: 'Москва' },
{ key: 'spb', value: 'Санкт-Петербург' },
{ key: 'ekb', value: 'Екатеринбург' }
]}
value={[{ key: 'msk', value: 'Москва' }]}
onChange={({ key, value }: Option) => console.log('Выбрано:', key, value)}
getTitle={() => ''}
/>
// Заблокированный фильтр
<MultiDropdown
disabled
options={someOptions}
value={currentValue}
onChange={onChange}
getTitle={(values: Option[]) => values.length === 0 ? 'Выберите город' : `Выбрано: ${values.length}`}
/>
// Фильтр, отображающий количество выбранных вариантов
<MultiDropdown
options={someOptions}
value={currentValue}
onChange={onChange}
getTitle={(values: Option[]) => `Выбрано: ${values.length}`}
/>
Реализуйте компонент Чекбокс
Требования:
- Необходимо использовать html-тег
input
с типом "чекбокс"
type CheckBoxProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange'
> & {
/** Вызывается при клике на чекбокс */
onChange: (checked: boolean) => void;
};
Примеры использования:
// Простой чекбокс
<CheckBox
checked={checked}
onChange={setChecked}
/>
// Заблокированный чекбокс
<CheckBox
disabled
checked={checked}
onChange={setChecked}
/>
- Укажите личный ключ
user_token
в файлеconfig.yml
. Примерconfig.yml
:
user_token: e3631261-c636-42458-ab0b-g8e534e984ee
- Выполните команду запуска тестов
yarn test
- Если не прошел визуальный тест
screenshot.test.ts
- Обновите Google Chrome до последне стабильной версии (>=110.0.5478.0)
- Убедитесь что не меняли файлы *.stories.tsx
- Посмотрите различия в папке
src/__test__/__image_snapshots__/<НАЗВАНИЕ КОМПОНЕНТА>/__diff_output__
и исправьте их
- При успешном прохождении тестов, отправьте изменения в свой репозиторий