diff --git a/app/assets/stylesheets/_garden_cards.scss b/app/assets/stylesheets/_garden_cards.scss index 731ed3c06..daf12e093 100644 --- a/app/assets/stylesheets/_garden_cards.scss +++ b/app/assets/stylesheets/_garden_cards.scss @@ -220,3 +220,46 @@ $garden-card-line: #e6e6e6; } } } + +// Star rating in the planting edit dialog. The radios are visually hidden, so +// show a ring on the star that has keyboard focus. +.star-rating { + border: 0; + padding: 0; + + legend { + float: none; + width: auto; + font-size: inherit; + } + + .star-rating-stars { + display: flex; + align-items: center; + min-height: 2.25rem; + } + + .star-rating-star { + padding: 0 0.1rem; + color: #9e9e9e; + font-size: 1.6rem; + line-height: 1; + cursor: pointer; + } + + .star-rating-star--lit { + color: #f5a623; + } + + input:focus-visible + .star-rating-star { + outline: 2px solid var(--bs-link-color, #0a58ca); + outline-offset: 2px; + border-radius: 0.25rem; + } + + .star-rating-value { + margin-left: 0.75rem; + color: $garden-card-muted; + font-size: 0.9rem; + } +} diff --git a/app/javascript/components/EditPlantingModal.jsx b/app/javascript/components/EditPlantingModal.jsx index 668cc2371..766cbe139 100644 --- a/app/javascript/components/EditPlantingModal.jsx +++ b/app/javascript/components/EditPlantingModal.jsx @@ -3,8 +3,7 @@ import React, {useEffect, useState} from 'react'; import {getJson, patchJson} from '../api'; import CropPicker from './CropPicker'; import Modal from './Modal'; - -const RATINGS = [[1, 'Poor'], [2, ''], [3, ''], [4, ''], [5, 'Great']]; +import StarRating from './StarRating'; function humanize(field) { return field.replace(/_/g, ' ').replace(/^./, (c) => c.toUpperCase()); @@ -148,11 +147,12 @@ export default function EditPlantingModal({planting, onClose, onSaved}) {
- - + setValues({...values, overall_rating: rating})} + />
diff --git a/app/javascript/components/StarRating.jsx b/app/javascript/components/StarRating.jsx new file mode 100644 index 000000000..f9ff7d5b3 --- /dev/null +++ b/app/javascript/components/StarRating.jsx @@ -0,0 +1,46 @@ +import React, {useState} from 'react'; + +const STARS = [1, 2, 3, 4, 5]; +const WORDS = {1: 'Poor', 5: 'Great'}; + +// A rating out of five as stars. They are real radio buttons underneath, so +// the arrow keys and screen readers work as they do for any radio group; the +// stars just light up as far as the one hovered, or chosen. `value` is a number +// from 1 to 5, or null for no rating, and Clear puts it back to that. +export default function StarRating({id, label, value, onChange}) { + const [hovered, setHovered] = useState(null); + const lit = hovered ?? value ?? 0; + + return ( +
+ {label} +
setHovered(null)}> + {STARS.map((number) => ( + + onChange(number)} + /> +