В данном домашнем задании вам необходимо реализовать React-компоненты для вашего будущего проекта.
Реализуйте компонент Лоадер
Макеты лоадера: GitHub, E-commerce, Food, Crypto
Требования:
- Использовать border
/** Возможные значения размера лоадера */
enum LoaderSize {
s = 's',
m = 'm',
l = 'l'
}
/** Пропсы, которые принимает компонент Loader */
type LoaderProps = {
/**
* Идет ли загрузка.
* По умолчанию - true, для удобства использования
* Если false, то лоадер не должен отображаться
*/
loading?: boolean;
/**
* Размер лоадера.
* По умолчанию - LoaderSize.m
*/
size?: LoaderSize;
/**
* Дополнительный класс лоадера.
*/
className?: string;
};
Примеры использования:
<Loader /> // стандартный лоадер
<Loader size={LoaderSize.l} /> // лоадер размера L
<Loader loading={isLoading} /> // лоадер, который отображается только при isLoading=true
Реализуйте компонент WithLoader.
Требования:
- Всегда отображает вложенные элементы (children)
- При передаче loading=true поверх контента, по центру отображается лоадер
- Должен переиспользовать компонент Loader
/** Пропсы, которые принимает компонент WithLoader */
export type WithLoaderProps = React.PropsWithChildren<{
loading: boolean;
}>;
Примеры использования:
//
<WithLoader loading={isLoading}>
<div>Семантическая верстка наше все!</div>
</WithLoader>
Реализуйте компонент Кнопка.
Макеты кнопки: GitHub, E-commerce, Food, Crypto
Требования:
- Кнопка использует html-тег button и принимает все его пропсы
- Кнопка принимает пропсы ButtonProps и удовлетворяет их требованиям, описанным ниже
- Текст кнопки/дочерний элемент передается в качестве children
- При передаче дополнительного className не должны сбрасываться внутренние (описанные вами в стилях) классы кнопки
- Компонент должен быть реактивным, то есть реагировать на изменение любых пропсов
- css-классы должны быть названы согласно Методологии БЭМ:
- Базовый класс кнопки:
.button
- Класс-модификатор заблокированной кнопки:
.button_disabled
- Базовый класс кнопки:
- Для управления классами необходимо использовать библиотеку classnames
/** Пропсы, который принимает компонент Button */
export type ButtonProps = React.PropsWithChildren<{
/**
* Если true, то внутри кнопки вместе с children отображается компонент Loader
* Также кнопка должна переходить в состояние disabled
*/
loading?: boolean;
}> & React.ButtonHTMLAttributes<HTMLButtonElement>;
Примеры использования:
// Кнопка с текстом "Отправить", логирующая в консоль "Письмо отправлено" при клике
<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>
Реализуйте компонент Карточка (Элемент списка).
Макеты карточки: GitHub, E-commerce, Food, Crypto
Требования:
- Для изображения используется html-тег img
- Карточка должна сужаться при меньшем экране
- Изображение при сужении должно сохранять пропорции
/** Пропсы, которые принимает компонент Card */
type CardProps = {
/** URL изображения */
image: string;
/** Заголовок карточки */
title: React.ReactNode;
/** Подзаголовок карточки */
subtitle: React.ReactNode;
/** Содержимое карточки (футер/боковая часть), может быть пустым */
content?: React.ReactNode;
/** Клик на карточку */
onClick?: React.MouseEventHandler;
};
Примеры использования:
// Простая карточка
<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>}
content={<span><b>299р</b><i>5 отзывов</i><span>}
/>
Реализуйте компонент Поле ввода.
Макеты инпута: GitHub, E-commerce, Food, Crypto
Требования:
- Необходимо использовать html-тег input
- Класс корня компонента должен быть input
/** Пропсы, которые принимает компонент Input */
export type InputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange'
> & {
/** Значение поля */
value: string;
/** Callback, вызываемый при вводе данных в поле */
onChange: (value: string) => void;
};
Примеры использования:
// Простое поле
<Input
value="Кто такой биткоин?"
onChange={(value: string) => console.log(value)}
/>
// Заблокированное поле с плейсхолдером
<Input
value=""
onChange={(value: string) => console.log(value)}
placeholder="Начните набирать свой вопрос"
disabled
/>
Реализуйте компонент Выпадающий список с множественным выбором (Фильтр).
Требования:
- Класс корня компонента должен быть multi-dropdown
Макеты мультидропдауна: GitHub, E-commerce, Food, Crypto,
Требования:
/** Вариант для выбора в фильтре */
type Option = {
/** Ключ варианта, используется для отправки на бек/использования в коде */
key: string;
/** Значение варианта, отображается пользователю */
value: string;
};
/** Пропсы, которые принимает компонент Dropdown */
type MultiDropdownProps = {
/** Массив возможных вариантов для выбора */
options: Option[];
/** Текущие выбранные значения поля, может быть пустым */
value: Option[];
/** Callback, вызываемый при выборе варианта */
onChange: (value: Option[]) => void;
/** Заблокирован ли дропдаун */
disabled?: boolean;
/** Преобразовать выбранные значения в строку. Отображается в дропдауне в качестве выбранного значения */
pluralizeOptions: (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)}
pluralizeOptions={() => ''}
/>
// Заблокированный фильтр
<MultiDropdown
disabled
options={someOptions}
value={currentValue}
onChange={onChange}
pluralizeOptions={(values: Option[]) => values.length === 0 ? 'Выберите город' : `Выбрано: ${values.length}`}
/>
// Фильтр, отображающий количество выбранных вариантов
<MultiDropdown
options={someOptions}
value={currentValue}
onChange={onChange}
pluralizeOptions={(values: Option[]) => `Выбрано: ${values.length}`}
/>
Реализуйте компонент Чекбокс.
Макеты чекбокса: GitHub, E-commerce, Food, Crypto
Требования:
- Необходимо использовать html-тег input с типом "чекбокс"
- Класс корня компонента должен быть checkbox
/** Пропсы, которые принимает компонент CheckBox */
type CheckBoxProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange'
> & {
/** Вызывается при клике на чекбокс */
onChange: (value: boolean) => void;
};
Примеры использования:
// Простой чекбокс
<CheckBox
checked={checked}
onChange={setChecked}
/>
// Заблокированный чекбокс
<CheckBox
disabled
checked={checked}
onChange={setChecked}
/>
- Укажите личный ключ
user_token
в файле config.yml - Укажите тип проекта в поле
project_type
в файле config.yml. Доступные варианты:
'github' | 'e-commerce' |'food' | 'crypto'
Пример config.yml:
user_token: e3631261-c636-42458-ab0b-g8e534e984ee
project_type: food
- Выполните команду запуска тестов
yarn test
- Если не прошел визуальный тест
screenshot.test.ts
- Обновите Google Chrome до последней стабильной версии (>=110.0.5478.0)
- Убедитесь что не меняли файлы *.stories.tsx
- Посмотрите различия в папке
src/__test__/__image_snapshots__/<НАЗВАНИЕ ПРОЕКТА>/<НАЗВАНИЕ КОМПОНЕНТА>/__diff_output__
и исправьте их
- Отправьте изменения в свой репозиторий