Open4

Reactでシンプルなスターウィジェット

ピン留めされたアイテム
convers39convers39

オリジナルはこちら。具体的な要求は以下となりる。

Create a star rating widget that allows users to select a rating value.

Requirements

  • The widget accepts two parameters: the maximum number of stars and the number of currently filled stars.
  • When a star is clicked, it is filled along with all the stars to its left.
  • When the user hovers over the stars, all the stars under the cursor and its left are filled.
    • The stars which need to be filled during hover take priority over existing filled state.
    • If the cursor leaves the widget and no new selection is made, the appropriate stars revert to the filled state before the hovering.
  • Make the star rating widget reusable such that multiple instances can be rendered within the same page.

The star icons, both empty and filled, are provided to you as SVGs.

convers39convers39

import {useState} from 'react'
import './styles.css';

export default function App() {
  return (
    <div>
      <StarRating />
      <StarRating />
    </div>
  );
}

function StarRating({ maxCount = 5, filledCount = 0 }) {
  const [hoverIdx, setHoverIdx] = useState(-1)
  const [clickIdx, setClickIdx] = useState(filledCount - 1)

  return (
    <div>
      {[...Array(maxCount).keys()].map((idx) => {
        const shouldBeFilled = idx <= hoverIdx || (hoverIdx === -1 && idx <= clickIdx)
        return (
          <Star 
            onClick={() => setClickIdx(idx)} 
            onMouseEnter={() => setHoverIdx(idx)}
            onMouseLeave={() => setHoverIdx(-1)}
            isFilled={shouldBeFilled}
          />
        )
      })}
    </div>
  );
}

function Star({onMouseEnter, onMouseLeave, onClick, isFilled}) {
  const iconClass = `star-icon ${isFilled && 'star-icon-filled'}`.trim()
  return (
    <span 
      onClick={onClick} 
      onMouseEnter={onMouseEnter} 
      onMouseLeave={onMouseLeave} 
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        className={iconClass}
        fill="none"
        viewBox="0 0 24 24"
        stroke="currentColor"
        strokeWidth="2">
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
        />
      </svg>
    </span>
  )
}
body {
  font-family: sans-serif;
}

.star-icon {
  --icon-size: 32px;
  height: var(--icon-size);
  width: var(--icon-size);
}

.star-icon-filled {
  fill: yellow;
}
convers39convers39

Notes

The Star Rating widget can be improved in the following ways:

  • Allow the value to be part of a form submit event data by embedding an <input>.
  • Add keyboard support for better a11y.
  • Add RTL (right-to-left) support.

Test Cases

  • Click on each star and move the cursor away, see that the highlighted state is correct.
  • Hover over each star, see that every star under the cursor and to its left are highlighted.
    • Remove cursor over widget, see that the highlighted state is back to before the hovering.
  • Render multiple components, ensure that each can maintain its own state and interacting with a widget does not affect other onscreen components.
convers39convers39

a11y(accessibility)のことはあまり考慮したことなかった気がするな。。キーボードにも使えるようにするにはtabでフォーカス、アローキーとエンターキーの挙動も有効にする必要があるってことか。。