В данном домашнем задании вам необходимо реализовать React-компоненты для вашего будущего проекта.
Общие требования:
- Если в требованиях к компоненту указано, что он должен принимать пропс
className
, в таком случае переданные css-классы должны дополнять текущие css-классы, но не заменять их. - При реализации className необходимо воспользоваться библиотекой classnames
- Именование css-классов должно осуществляться согласно Методологии БЭМ
- Компоненты должны быть реактивными, то есть реагировать на изменение любых пропсов
- Компоненты, которые принимают в качестве пропсов
React.InputHTMLAttributes<HTMLElement>
должны уметь их пробрасывать в нативные элементы
Реализуйте компонент Лоадер
Макеты лоадера: GitHub, E-commerce, Food, Crypto
Требования:
/** Возможные значения размера лоадера */
enum LoaderSize {
s = 's',
m = 'm',
l = 'l'
}
/** Пропсы, которые принимает компонент Loader */
type LoaderProps = {
/**
* Идет ли загрузка.
* По умолчанию - true, для удобства использования
* Если false, то лоадер не должен отображаться
*/
loading?: boolean;
/**
* Размер лоадера. При передаче данного пропса, должен добавляться css-класс loader_size-{size}
* По умолчанию: размер - LoaderSize.m, css-класс - loader_size-m
*/
size?: LoaderSize;
/**
* Дополнительные CSS-классы.
*/
className?: string;
};
Примеры использования:
<Loader /> // стандартный лоадер
<Loader size={LoaderSize.l} /> // лоадер размера L
<Loader loading={isLoading} /> // лоадер, который отображается только при isLoading=false
Реализуйте компонент WithLoader.
Требования:
- Всегда отображает вложенные элементы (children)
- При передаче loading=true поверх контента отображает Loader
- Должен переиспользовать компонент Loader
/** Пропсы, которые принимает компонент WithLoader */
export type WithLoaderProps = React.PropsWithChildren<{
loading: boolean;
}>;
Примеры использования:
<WithLoader loading={isLoading}>
<div>Семантическая верстка наше все!</div>
</WithLoader>
Реализуйте компонент Кнопка.
Макеты кнопки: GitHub, E-commerce, Food, Crypto
Требования:
- Кнопка использует html-тег button и принимает все его нативные html-атрибуты
- Кнопка принимает пропсы ButtonProps и удовлетворяет их требованиям, описанным ниже
- Текст кнопки/дочерний элемент передается в качестве children
- Именование css-классов:
- Базовый класс кнопки:
.button
- Класс-модификатор заблокированной кнопки:
.button_disabled
- Класс-модификатор, отвечающий за раскраску кнопки:
.button_color-primary
,.button_color-secondary
- Базовый класс кнопки:
/** Возможные раскраски кнопки */
enum ButtonColor {
/** Основная, акцентная кнопка */
primary = 'primary',
/** Второстепенная кнопка */
secondary = 'secondary'
}
/** Пропсы, который принимает компонент Button */
export type ButtonProps = React.PropsWithChildren<{
/**
* Если true, то внутри кнопки вместе с children отображается компонент Loader
* Также кнопка должна переходить в состояние disabled
* По умолчанию - false
*/
loading?: boolean;
/** Цвет кнопки, по умолчанию - ButtonColor.primary*/
color?: ButtonColor;
}> & React.ButtonHTMLAttributes<HTMLButtonElement>;
Примеры использования:
// Кнопка с текстом "Отправить", логирующая в консоль "Письмо отправлено" при клике
<Button onClick={() => console.log('Письмо отправлено')}>
Отправить
</Button>
// Второстепенная кнопка, отображающая компонент Loader при загрузке каких-то данных
<Button
color={ButtonColor.secondary}
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 */
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
/>
Реализуйте компонент Выпадающий список с множественным выбором (Фильтр).
Макеты мультидропдауна: 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 */
type CheckBoxProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange'
> & {
/** Вызывается при клике на чекбокс */
onChange: (value: boolean) => void;
};
Примеры использования:
// Простой чекбокс
<CheckBox
checked={checked}
onChange={setChecked}
/>
// Заблокированный чекбокс
<CheckBox
disabled
checked={checked}
onChange={setChecked}
/>