Skip to content

ktsstudio/frontend-hw-2

Repository files navigation

Начинающий React разработчик. Домашнее задание №2

В данном домашнем задании Вам необходимо реализовать React-компоненты для вашего будущего проекта.

Макеты

Требования:

  1. Все цвета и отступы должны быть заданы в переменных (указаны в макете) Переменные

Запуск тестов

Для запуска всех тестов выполняйте команду:

yarn test

Для запуска теста отдельного компонента:

yarn test:single <название компонента>

Например:

yarn test:single Card

Как запустить скриншотный тест для отдельного компонента

  • В WebStorm есть опция запуска теста внутри файла теста (иконка play слева от кода).
  • В VSCode можно установить расширение Jest, которое позволяет запускать отдельные скриншотные тесты из меню расширения Jest. После установки зависимостей перезапустите VSCode, чтобы расширение проиндексировало файлы тестов.

При этом необходимо на время закомментировать те тест-кейсы, которые не требуется прогонять (вызовы функции screenshotTesting). Важно всё раскомментировать перед финальной общей проверкой и пушем в репозиторий.

1. Loader

Реализуйте компонент Лоадер

type LoaderProps = {
  /** Размер */
  size?: 's' | 'm' | 'l';
  /** Дополнительный класс */
  className?: string;
};

Примеры использования:

<Loader />  // стандартный лоадер

<Loader size="l" />  // лоадер размера L

2. Text

Реализуйте компонент Text

Требования:

  1. По умолчанию должен иметь цвет родителя
  2. Пропс weight имеет больший приоритет чем view
  3. При указании 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>

3. Icons

Реализуйте компоненты иконок. (Назвать их CheckIcon и ArrowDownIcon)

  1. Все компоненты иконок лежат в директории components/icons/ (Пример импорта: import CheckIcon from 'components/icons/CheckIcon')
  2. Все компоненты иконок имеют одинаковый базовый набор пропсов IconProps. (Лучше всего сделать компонент-обертку Icon и использовать в компонентах иконок, но это не обязательно).
  3. По умолчанию имеют цвет родителя а при указании color, красятся в указанный стиль цвета.
  4. Можно указать ширину и высоту иконки (по умолчанию 24px)
type IconProps = React.SVGAttributes<SVGElement> & {
  className?: string;
  color?: 'primary' | 'secondary' | 'accent';
};

Примеры использования:

<CheckIcon width={40} height={40} />
<ArrowDownIcon color="accent" />

4. Button

Реализуйте компонент Кнопка

Требования:

  1. Кнопка использует html-тег button и принимает все его пропсы
  2. Кнопка принимает пропсы ButtonProps и удовлетворяет их требованиям, описанным ниже
  3. Текст кнопки/дочерний элемент передается в качестве children
  4. При передаче дополнительного className не должны сбрасываться внутренние (описанные вами в стилях) классы кнопки
  5. Компонент должен быть реактивным, то есть реагировать на изменение любых пропсов
  6. Для управления классами необходимо использовать библиотеку classnames
  7. При 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>

5. Card

Реализуйте компонент Карточка (Элемент списка)

Требования:

  1. Для изображения используется html-тег img
  2. В заголовке может быть максимум 2 строки
  3. В описании может быть максимум 3 строки
  4. Контент над заголовком необязательный (кол-во строк не ограничено)
  5. При клике на карточку должен выполняться onClick
  6. Для текстов используется компонент Text
  7. При расширении/сужении карточки, изображение должно сохранять пропорции
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>}
/>

7. Input

Реализуйте компонент Поле ввода

Требования:

  1. Необходимо использовать html-тег input
  2. Должен быть слот для иконки справа
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" />
/>

8. MultiDropdown

Реализуйте компонент Выпадающий список с множественным выбором (Фильтр).

Требования:

  1. Должен использовать компонент Input
  2. При вводе в поле, опции должны фильтроваться
  3. Опции должны пропадать из 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}`}
/>

9. CheckBox

Реализуйте компонент Чекбокс

Требования:

  1. Необходимо использовать html-тег input с типом "чекбокс"
type CheckBoxProps = Omit<
  React.InputHTMLAttributes<HTMLInputElement>,
  'onChange'
> & {
  /** Вызывается при клике на чекбокс */
  onChange: (checked: boolean) => void;
};

Примеры использования:

// Простой чекбокс
<CheckBox
  checked={checked}
  onChange={setChecked}
/>

// Заблокированный чекбокс
<CheckBox
  disabled
  checked={checked}
  onChange={setChecked}
/>

Перед отправкой ДЗ на проверку

  1. Укажите личный ключ user_token в файле config.yml. Пример config.yml:
user_token: e3631261-c636-42458-ab0b-g8e534e984ee
  1. Выполните команду запуска тестов
yarn test
  1. Если не прошел визуальный тест screenshot.test.ts
  • Обновите Google Chrome до последне стабильной версии (>=110.0.5478.0)
  • Убедитесь что не меняли файлы *.stories.tsx
  • Посмотрите различия в папке src/__test__/__image_snapshots__/<НАЗВАНИЕ КОМПОНЕНТА>/__diff_output__ и исправьте их
  1. При успешном прохождении тестов, отправьте изменения в свой репозиторий

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published