Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Добавляет рецепт рейтинга 5 ⭐️ #5592

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

solarrust
Copy link
Member

Попалась мне тут задачка на собеседовании и я решила написать по её следам рецепт. Вариантов решения этой задачи в интернете много. Но этот показался мне самым оптимальным.

@TatianaFokina очень интересно будет узнать твои рекомендации по доступности такого элемента 🙏

@skorobaeus приходи завалидировать демки =)

@solarrust solarrust added статья Расширенный материал рецепт Контент для Рецептов labels Dec 11, 2024
Copy link

github-actions bot commented Dec 11, 2024

Чек-лист для новой статьи

Метаданные

  • Код в поле title завёрнут в бэктики
  • Есть поле description с описанием
  • Проставлен тег article
  • Поле keywords содержит ключевые слова, которых нет в тексте статьи
  • В поле authors указан автор. Файл автора есть в папке people
  • В поле related есть ссылки на материалы Доки, интересные в контексте данного (не более трёх)

Статья

  • Структура совпадает с шаблоном
  • Демки написаны по рекомендациям
  • У картинок есть описание в alt, у фреймов демок название в title
  • Статья добавлена в содержание раздела
  • Для статьи указаны материалы Доки, интересные для дальнейшего чтения

Copy link
Member

@Inventoris Inventoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет, красота 🤩

Оставил несколько комментов, и будет космическая ракета!

recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/index.md Outdated Show resolved Hide resolved
Copy link
Member

@Inventoris Inventoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Микро-правочка

recipes/star-rating/index.md Outdated Show resolved Hide resolved
recipes/star-rating/demos/js-css/index.html Outdated Show resolved Hide resolved
@skorobaeus

This comment was marked as resolved.

Comment on lines +117 to +130
const stars = document.querySelectorAll('.star')

stars.forEach((star, index) => {
star.addEventListener('click', (event) => {
const activeStar = event.currentTarget

if (activeStar.classList.contains('active')) {
activeStar.classList.remove('active')
rating.dataset.ratingValue = ''
} else {
stars.forEach(star => star.classList.remove('active'))
activeStar.classList.add('active')
rating.dataset.ratingValue = parseInt(activeStar.textContent.trim())
}
Copy link
Contributor

@vitya-ne vitya-ne Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет,
Классные звёздочки ! Ставлю: ⭐⭐⭐⭐⭐

В скрипте можно немножко сэкономить:

    const rating = document.querySelector('.star-rating')
    // дочерние элименты ищем локально
    const starsContainer = rating.querySelector('.stars')
    const stars = rating.querySelectorAll('.star')
    
    // добавляем один EventListener к элементу ul
    starsContainer.addEventListener('click', (event) => {
      // получаем звезду на которой произошёл клик
      const activeStar = event.target.closest('.star')
      ...

@TatianaFokina

This comment was marked as outdated.

@solarrust

This comment was marked as outdated.

Copy link

github-actions bot commented Jan 7, 2025

Превью контента из 1b51203 опубликовано.

Copy link
Member

@Inventoris Inventoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\((( ̄( ̄( ̄▽ ̄) ̄) ̄)))/

@solarrust
Copy link
Member Author

Надо будет переписать рецепт на чекбоксы/радио-кнопки. Пока переведу в драфт

@solarrust solarrust marked this pull request as draft January 21, 2025 18:51
@TatianaFokina
Copy link
Member

Я в комментах, пожалуй, предложу правочки по коду.

Вот так NVDA читает текущие звёздочки. Порядок фокуса с клавиатуры начинается с последней звезды (потому что в коде они расположены в таком порядке, а визуально нет). Выбрана кнопка или нет не понятно, поэтому сложно сказать, какой рейтинг ты поставил. Про <fieldset> без <form>, как я поняла, с точки зрения спеки проблем нет, но, кажется, логично было бы запихнуть всё в формочку и отправлять итоговый рейтинг на сервер. Также, при фокусе с клавы, хотелось бы тоже выделять предыдущие звёзды.

nvda-before.mp4

@TatianaFokina
Copy link
Member

TatianaFokina commented Jan 22, 2025

Что я предлагаю.

HTML:

<form class="star-rating" role="radiogroup" data-rating-value="">
  <fieldset class="star-group">
    <legend>Оцените товар:</legend>
    <ul class="stars">
      <li class="star">
        <button class="star-button" role="radio" aria-checked="false" aria-label="1 звезда">
          <span aria-hidden="true">⭐️</span>
        </button>
      </li>
      <li class="star">
        <button class="star-button" role="radio" aria-checked="false" aria-label="2 звезды">
          <span aria-hidden="true">⭐️</span>
        </button>
      </li>
      <li class="star">
        <button class="star-button" role="radio" aria-checked="false" aria-label="3 звезды">
          <span aria-hidden="true">⭐️</span>
        </button>
      </li>
      <li class="star">
        <button class="star-button" role="radio" aria-checked="false" aria-label="4 звезды">
          <span aria-hidden="true">⭐️</span>
        </button>
      </li>
      <li class="star">
        <button class="star-button" role="radio" aria-checked="false" aria-label="5 звёзд">
          <span aria-hidden="true">⭐️</span>
        </button>
      </li>
    </ul>
  </fieldset>
</form>

CSS:

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  color-scheme: dark;
}

body {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  margin: 0;
  padding: 50px;
  background-color: #18191c;
  color: #ffffff;
  font-family: "Roboto", sans-serif;
}

legend {
  font-size: 24px;
  font-weight: 500;
}

.star-button {
  background-color: transparent;
  border: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  padding: 0;
}

.star-group {
  text-align: center;
  border: none;
}

.stars {
  list-style: none;
  display: flex;
  margin-top: 20px;
  gap: 1rem;
  font-size: 5rem;
}

.star {
  line-height: 1;
  opacity: 0.5;
  cursor: pointer;
  transition: opacity 0.2s;
}

.active,
.star:has(~ .active) {
  opacity: 1;
}

.stars:hover .star {
  opacity: 0.5;
}

.stars .star:hover,
.star:has(~ .star:hover) {
  opacity: 1;
}

.stars .star:has(.star-button:focus-visible),
.stars .star:has(~ .star .star-button:focus-visible) {
  opacity: 1;
}

.stars .star:has(.star-button:focus-visible) ~ .star {
  opacity: 0.5;
}

JS:

const rating = document.querySelector('.star-rating')
const stars = rating.querySelectorAll('.star')
const activeStarsClass = ('active')
const starsButtonClass = ('.star-button')

function checkStars(activeStar, activeButton, value) {
  stars.forEach(star => {
    star.classList.remove(activeStarsClass)
    star.querySelector(starsButtonClass).setAttribute('aria-checked', 'false')
  })
  if (activeStar) {
    activeStar.classList.add(activeStarsClass)
    activeButton.setAttribute('aria-checked', 'true')
  }
  rating.dataset.ratingValue = value
}

rating.addEventListener('submit', (event) => {
  event.preventDefault()
})

stars.forEach((star, index) => {
  const starButton = star.querySelector(starsButtonClass)

  starButton.addEventListener('focus', () => {
    checkStars(star, starButton, index + 1)
  })

  starButton.addEventListener('keydown', (event) => {
    let nextIndex

    switch (event.key) {
      case 'ArrowRight':
      case 'ArrowUp':
        event.preventDefault()
        nextIndex = index < stars.length - 1 ? index + 1 : 0
        break
      case 'ArrowLeft':
      case 'ArrowDown':
        event.preventDefault()
        nextIndex = index > 0 ? index - 1 : stars.length - 1
        break
      default:
        return
    }

    const nextButton = stars[nextIndex].querySelector(starsButtonClass)
    nextButton.focus()
    checkStars(stars[nextIndex], nextButton, nextIndex + 1)
  })

  star.addEventListener('click', (event) => {
    const activeStar = event.currentTarget
    const activeStarButton = activeStar.querySelector(starsButtonClass)

    if (activeStar.classList.contains(activeStarsClass)) {
      checkStars(null, null, '')
    } else {
      checkStars(activeStar, activeStarButton, index + 1)
    }
  })
})

Так NVDA зачитывает этот код:

nvda-after.mp4

@TatianaFokina
Copy link
Member

Объяснения, почему предлагаю так.

Если мы оставляем кнопки и избегаем радиокнопок, то надо руками прописать нативное поведение группы радиобаттонов. По умолчанию по ним можно туда-сюда перемещаться стрелочками, не только табом.

У кнопок нет по умолчанию состояния выбранности, поэтому надо добавить aria-checked, чтобы его передать.

Так как кнопки в формочке, надо поработать над сбросом данных при отправке.

В HTML-коде лучше поменять порядок кнопок, а их визуальное состояние выделенности решить без flex-direction. Там я наворотила конструкцию с :has, но может есть более элегантное решение.

Названия кнопок засунула в aria-label (это норм), а эмодзи скрыла от вспомогательных технологий, так как они объявляются. А, ну и перезаписала роль кнопочек на radio.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
рецепт Контент для Рецептов статья Расширенный материал
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants