-
Notifications
You must be signed in to change notification settings - Fork 210
fix(rating): address inconsistent hover behavior #3196
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
Changes from all commits
494769c
da7d7a3
1407ea5
0bc67fe
6d98fa8
bb9e22e
7af0245
7c2d326
08a62c5
f27e7a6
eaf9568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| "@spectrum-css/rating": major | ||
| --- | ||
|
|
||
| Provides more granular control over the hover behavior of child stars within the rating component to prevent hovering in space adjacent to the component from highlighting all stars. | ||
|
|
||
| - `.is-focused` has been renamed to `.is-keyboardFocused` to better reflect its intended use. Clicking the rating component no longer renders the focus ring. | ||
| - `.is-hoverSelection` has been added to the rating component and may be leveraged to update the hover and select state of the star icons the component contains. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,7 +44,8 @@ | |
| } | ||
|
|
||
| .spectrum-Rating { | ||
| &.is-focused { | ||
|
marissahuysentruyt marked this conversation as resolved.
|
||
| &:has(:focus-visible), | ||
| &.is-keyboardFocused { | ||
| box-shadow: 0 0 0 var(--mod-rating-focus-indicator-thickness, var(--spectrum-rating-focus-indicator-thickness)) var(--highcontrast-rating-focus-indicator-color, var(--mod-rating-focus-indicator-color, var(--spectrum-rating-focus-indicator-color))); | ||
|
|
||
| .spectrum-Rating-icon { | ||
|
|
@@ -98,17 +99,6 @@ | |
| cursor: default; | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| /* When the entire component is hovered, show all solid icons */ | ||
| &:hover { | ||
| .spectrum-Rating-starActive { | ||
| display: block; | ||
| } | ||
|
|
||
| .spectrum-Rating-starInactive { | ||
| display: none; | ||
| } | ||
| } | ||
|
Comment on lines
-102
to
-111
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we need to double check with design or anybody else that we can completely remove this? I agree with the removal, but it certainly seems like it was intentional. Also because I'm nosy I'd love to get the context around that decision hahahaha |
||
| } | ||
|
|
||
| .spectrum-Rating-input { | ||
|
|
@@ -158,14 +148,14 @@ | |
| position: absolute; | ||
| } | ||
|
|
||
| /* All stars following the hovered star */ | ||
| &:hover ~ .spectrum-Rating-icon { | ||
| &:hover, | ||
| &.is-hoverSelection { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the intention here that this new Why does that change reverse the |
||
| .spectrum-Rating-starActive { | ||
| display: none; | ||
| display: block; | ||
| } | ||
|
|
||
| .spectrum-Rating-starInactive { | ||
| display: block; | ||
| display: none; | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -191,7 +181,7 @@ | |
| } | ||
|
|
||
| .spectrum-Rating--emphasized { | ||
| &.is-focused { | ||
| &.is-keyboardFocused { | ||
| .spectrum-Rating-icon.is-selected { | ||
| color: var(--highcontrast-rating-emphasized-icon-color-key-focus, var(--mod-rating-emphasized-icon-color-key-focus, var(--spectrum-rating-emphasized-icon-color-key-focus))); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { disableDefaultModes } from "@spectrum-css/preview/modes"; | ||
| import { isDisabled, isEmphasized, isFocused, isReadOnly } from "@spectrum-css/preview/types"; | ||
| import { isDisabled, isEmphasized, isKeyboardFocused, isReadOnly } from "@spectrum-css/preview/types"; | ||
| import metadata from "../metadata/metadata.json"; | ||
| import packageJson from "../package.json"; | ||
| import { RatingGroup } from "./rating.test.js"; | ||
|
|
@@ -8,17 +8,18 @@ import { Template } from "./template.js"; | |
| /** | ||
| * The rating component is used to display or collect a user's rating of an item as represented by a number of stars. | ||
| * | ||
| * ### Usage notes | ||
| * ## Usage notes | ||
| * - All active stars have the class `is-selected` applied. | ||
| * - The current value (the last active star) has the class `is-currentValue` applied. | ||
| * - When the rating receives focus, the class `is-focused` should be added to the component's root element (`.spectrum-Rating`). | ||
| * - When the rating receives keyboard focus, the class `.is-keyboardFocused` should be added to the component's root element (`.spectrum-Rating`). | ||
| * - When the rating is hovered, the class `.is-hoverSelection` should be added to the `.spectrum-Rating-icon` being hovered over. | ||
| */ | ||
| export default { | ||
| title: "Rating", | ||
| component: "Rating", | ||
| argTypes: { | ||
| isEmphasized, | ||
| isFocused, | ||
| isKeyboardFocused, | ||
| isDisabled, | ||
| isReadOnly, | ||
| max: { | ||
|
|
@@ -49,6 +50,7 @@ export default { | |
| isDisabled: false, | ||
| isEmphasized: false, | ||
| isReadOnly: false, | ||
| isKeyboardFocused: false, | ||
| max: 5, | ||
| value: 3, | ||
| }, | ||
|
|
@@ -59,7 +61,7 @@ export default { | |
| }; | ||
|
|
||
| /** | ||
| * A initial value of three is used for the following examples, to demonstrate both active and inactive stars. | ||
| * An initial value of three is used for the following examples, to demonstrate both active and inactive stars. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏 love a typo fix |
||
| * When hovering over a rating component that has a previously entered value, an underline appears under the | ||
| * current selection to provide context. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,8 @@ export const RatingGroup = Variants({ | |
| isDisabled: true, | ||
| }, | ||
| { | ||
| testHeading: "Focused", | ||
| isFocused: true, | ||
| testHeading: "Keyboard focused", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just personal preference here- I'd probably hyphenate this. I'm not sure we have a very strong convention for that either way though, so you can disregard if you want. |
||
| isKeyboardFocused: true, | ||
| }, | ||
| { | ||
| testHeading: "Read-only", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,30 +12,89 @@ export const Template = ({ | |
| max = 5, | ||
| value = 0, | ||
| isReadOnly = false, | ||
| isFocused = false, | ||
| isKeyboardFocused = false, | ||
| isDisabled = true, | ||
| isEmphasized = false, | ||
| customClasses = [], | ||
| id = getRandomId("rating"), | ||
| } = {}, context = {}) => { | ||
| const { updateArgs } = context; | ||
| document.addEventListener("DOMContentLoaded", function() { | ||
| const rating = document.getElementById(id); | ||
| if (!rating) return; | ||
| if (rating.classList.contains("is-disabled") || rating.classList.contains("is-readOnly")) return; | ||
| const icons = Array.from(rating.getElementsByClassName("spectrum-Rating-icon")); | ||
| let hoverIndex = -1; | ||
| let selectedIndex = -1; | ||
|
|
||
| const updateHoverState = () => { | ||
| icons.forEach((icon, index) => { | ||
| const activeStar = icon.querySelector(".spectrum-Rating-starActive"); | ||
| const inactiveStar = icon.querySelector(".spectrum-Rating-starInactive"); | ||
|
|
||
| if (index <= hoverIndex || | ||
| (index <= selectedIndex && hoverIndex === -1) | ||
| ) { | ||
| icon.classList.add("is-hoverSelection"); | ||
| activeStar.style.display = "block"; | ||
| inactiveStar.style.display = "none"; | ||
| } | ||
|
Comment on lines
+35
to
+41
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't |
||
| else { | ||
| icon.classList.remove("is-hoverSelection"); | ||
| activeStar.style.display = "none"; | ||
| inactiveStar.style.display = "block"; | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| rating.addEventListener("mouseleave", function() { | ||
| icons.forEach(icon => { | ||
| icon.classList.remove("is-hoverSelection"); | ||
| icon.querySelector(".spectrum-Rating-starActive").style.display = ""; | ||
| icon.querySelector(".spectrum-Rating-starInactive").style.display = ""; | ||
| }); | ||
| }); | ||
|
|
||
| icons.forEach((icon, index) => { | ||
| if (icon.classList.contains("is-selected")) selectedIndex = index; | ||
|
|
||
| icon.addEventListener("mouseover", function() { | ||
| hoverIndex = index; | ||
| updateHoverState(); | ||
| }); | ||
|
|
||
| icon.addEventListener("mouseleave", function(event) { | ||
| if (!rating.contains(event.relatedTarget)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I've seen |
||
| hoverIndex = -1; | ||
| updateHoverState(); | ||
| } | ||
| }); | ||
|
|
||
| icon.addEventListener("click", function() { | ||
| selectedIndex = index; | ||
| updateHoverState(); | ||
| }); | ||
| }); | ||
|
|
||
| updateHoverState(); | ||
| }); | ||
|
|
||
| return html` | ||
| <div | ||
| class=${classMap({ | ||
| [rootClass]: true, | ||
| "is-disabled": isDisabled, | ||
| "is-readOnly": isReadOnly, | ||
| "is-focused": isFocused, | ||
| "is-keyboardFocused": isKeyboardFocused, | ||
|
marissahuysentruyt marked this conversation as resolved.
|
||
| [`${rootClass}--emphasized`]: isEmphasized, | ||
| ...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}), | ||
| })} | ||
| id=${ifDefined(id)} | ||
| @focusin=${function() { | ||
| updateArgs({ isFocused: true }); | ||
| updateArgs({ isKeyboardFocused: true }); | ||
| }} | ||
| @focusout=${function() { | ||
| updateArgs({ isFocused: false }); | ||
| updateArgs({ isKeyboardFocused: false }); | ||
| }} | ||
| > | ||
| <input | ||
|
|
@@ -69,7 +128,7 @@ export const Template = ({ | |
| "is-currentValue": idx === value - 1, | ||
| })} | ||
| @click=${function() { | ||
| updateArgs({ value: idx + 1, isFocused: true }); | ||
| updateArgs({ value: idx + 1 }); | ||
| }} | ||
| > | ||
| ${Icon({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pfulton I'm a bit anxious about introducing such a large bump to an S1 component with the current progress on foundations work. What do you think? Should we try to scale back the scope of this work, hold it until after foundations, or move forward as-is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on the anxiousness around S1/S2 timing. On the plus side, though, this is a component that is not currently available in SWC, so the downstream impact of a big change might not be as large as it could have been if it was already implemented there. That said: it's still a major, and we want to respect what that means for all potential consumers.
I think we should pause on this one for now until after Foundations goes in. After that, we can work through rebasing this one and getting it merged in. The scope of changes are only within this particular component, so hopefully any potential conflicts won't be too terrible to resolve when the time comes.