-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
[Chip] Replace :focus with :focus-visible #22386
Comments
@cakirpro Thanks for raising, this comes from the use of the pseudo class It seems that we should introduce the usage of the useIsFocusVisible helper. Do you want to work on it? :) |
Also worth mentioning that #20470 could make the implementation here simpler. |
@oliviertassinari I've been having a go at this and think I managed to get the desired behaviour working. When you click on a chip it no longer appears as selected, but when you navigate through them using the keyboard you can clearly see which one is highlighted. This solution was adapted from the ButtonBase file. I am new to this code base so my approach might not be perfect but I can work on improvements. What's your take on these changes? diff --git a/packages/material-ui/src/Chip/Chip.js b/packages/material-ui/src/Chip/Chip.js
index 2cb69af4b..791b48ffb 100644
--- a/packages/material-ui/src/Chip/Chip.js
+++ b/packages/material-ui/src/Chip/Chip.js
@@ -8,6 +8,8 @@ import { emphasize, fade } from '../styles/colorManipulator';
import useForkRef from '../utils/useForkRef';
import unsupportedProp from '../utils/unsupportedProp';
import capitalize from '../utils/capitalize';
+import useEventCallback from '../utils/useEventCallback';
+import useIsFocusVisible from '../utils/useIsFocusVisible';
import ButtonBase from '../ButtonBase';
export const styles = (theme) => {
@@ -87,7 +89,7 @@ export const styles = (theme) => {
userSelect: 'none',
WebkitTapHighlightColor: 'transparent',
cursor: 'pointer',
- '&:hover, &:focus': {
+ '&$focusVisible, &:hover': {
backgroundColor: emphasize(backgroundColor, 0.08),
},
'&:active': {
@@ -96,31 +98,31 @@ export const styles = (theme) => {
},
/* Styles applied to the root element if `onClick` and `color="primary"` is defined or `clickable={true}`. */
clickableColorPrimary: {
- '&:hover, &:focus': {
+ '&$focusVisible, &:hover': {
backgroundColor: emphasize(theme.palette.primary.main, 0.08),
},
},
/* Styles applied to the root element if `onClick` and `color="secondary"` is defined or `clickable={true}`. */
clickableColorSecondary: {
- '&:hover, &:focus': {
+ '&$focusVisible, &:hover': {
backgroundColor: emphasize(theme.palette.secondary.main, 0.08),
},
},
/* Styles applied to the root element if `onDelete` is defined. */
deletable: {
- '&:focus': {
+ '&$focusVisible': {
backgroundColor: emphasize(backgroundColor, 0.08),
},
},
/* Styles applied to the root element if `onDelete` and `color="primary"` is defined. */
deletableColorPrimary: {
- '&:focus': {
+ '&$focusVisible': {
backgroundColor: emphasize(theme.palette.primary.main, 0.2),
},
},
/* Styles applied to the root element if `onDelete` and `color="secondary"` is defined. */
deletableColorSecondary: {
- '&:focus': {
+ '&$focusVisible': {
backgroundColor: emphasize(theme.palette.secondary.main, 0.2),
},
},
@@ -130,7 +132,7 @@ export const styles = (theme) => {
border: `1px solid ${
theme.palette.type === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'
}`,
- '$clickable&:hover, $clickable&:focus, $deletable&:focus': {
+ '&$focusVisible, $clickable&:hover': {
backgroundColor: fade(theme.palette.text.primary, theme.palette.action.hoverOpacity),
},
'& $avatar': {
@@ -158,7 +160,7 @@ export const styles = (theme) => {
outlinedPrimary: {
color: theme.palette.primary.main,
border: `1px solid ${theme.palette.primary.main}`,
- '$clickable&:hover, $clickable&:focus, $deletable&:focus': {
+ '&$focusVisible, $clickable&:hover': {
backgroundColor: fade(theme.palette.primary.main, theme.palette.action.hoverOpacity),
},
},
@@ -166,7 +168,7 @@ export const styles = (theme) => {
outlinedSecondary: {
color: theme.palette.secondary.main,
border: `1px solid ${theme.palette.secondary.main}`,
- '$clickable&:hover, $clickable&:focus, $deletable&:focus': {
+ '&$focusVisible, $clickable&:hover': {
backgroundColor: fade(theme.palette.secondary.main, theme.palette.action.hoverOpacity),
},
},
@@ -260,6 +262,8 @@ export const styles = (theme) => {
color: theme.palette.secondary.main,
},
},
+ /* Pseudo-class applied to the root element if keyboard focused. */
+ focusVisible: {},
};
};
@@ -280,6 +284,7 @@ const Chip = React.forwardRef(function Chip(props, ref) {
component: ComponentProp,
deleteIcon: deleteIconProp,
disabled = false,
+ focusVisibleClassName,
icon: iconProp,
label,
onClick,
@@ -294,6 +299,39 @@ const Chip = React.forwardRef(function Chip(props, ref) {
const chipRef = React.useRef(null);
const handleRef = useForkRef(chipRef, ref);
+ const {
+ isFocusVisibleRef,
+ onFocus: handleFocusVisible,
+ onBlur: handleBlurVisible
+ } = useIsFocusVisible();
+ const [focusVisible, setFocusVisible] = React.useState(false);
+ if (disabled && focusVisible) {
+ setFocusVisible(false);
+ }
+ React.useEffect(() => {
+ isFocusVisibleRef.current = focusVisible;
+ }, [focusVisible, isFocusVisibleRef]);
+
+ const handleBlur = useEventCallback((event) => {
+ handleBlurVisible(event);
+ if (isFocusVisibleRef.current === false) {
+ setFocusVisible(false);
+ }
+ },
+ false,
+ );
+
+ const handleFocus = useEventCallback((event) => {
+ if (!chipRef.current) {
+ chipRef.current = event.currentTarget;
+ }
+
+ handleFocusVisible(event);
+ if (isFocusVisibleRef.current === true) {
+ setFocusVisible(true);
+ }
+ });
+
const handleDeleteIconClick = (event) => {
// Stop the event from bubbling up to the `Chip`
event.stopPropagation();
@@ -415,6 +453,8 @@ const Chip = React.forwardRef(function Chip(props, ref) {
[classes[`clickableColor${capitalize(color)}`]]: clickable && color !== 'default',
[classes.deletable]: onDelete,
[classes[`deletableColor${capitalize(color)}`]]: onDelete && color !== 'default',
+ [classes.focusVisible]: focusVisible,
+ [focusVisibleClassName]: focusVisible,
[classes.outlinedPrimary]: variant === 'outlined' && color === 'primary',
[classes.outlinedSecondary]: variant === 'outlined' && color === 'secondary',
},
@@ -423,7 +463,9 @@ const Chip = React.forwardRef(function Chip(props, ref) {
)}
aria-disabled={disabled ? true : undefined}
tabIndex={clickable || onDelete ? 0 : undefined}
+ onBlur={handleBlur}
onClick={onClick}
+ onFocus={handleFocus}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
ref={handleRef} |
@alexmotoc This looks like a solid start. I think that we can remove For the future, I wonder if we couldn't look into exposing our focus visible hook publicly as done in useFocusRing. If we go into this direction I think that we should aim to simplify its usage. After #22102, it starts to be quite a challenge. |
@oliviertassinari I was considering using Are there any other major changes I need to make other than removing |
@alexmotoc Oh, right, I wasn't looking at the right place (caniuse.com was down for me this afternoon) 😅. Ok, so I think that after this issue, it would be interesting to look into how we can expose the hook to the public API :) |
@alexmotoc Do you want to move forward with the diff (and without the |
@oliviertassinari Yes, I am on it 👍 |
Our clickable Wouldn't it be more accurate to say that our Chip is missing the selected state? |
From what I understand, the problem is unrelated to the "selected" state. It's about reproducing the behavior of |
I have only access to what is included in the original issue:
|
I don't think so. That PR removes the |
I think that we could extend the scope of the issue to all the components built on top of the
to:
From what I understand, right now, we have |
I had a look over the Material Demo and it seems that the chips do not have a "keyboard focused" state when they are clicked (this is the current behaviour described in this issue). E.g. If I click a chip, the I think the terminology used in the issue description may be a bit confusing. The selected state should probably be a different feature. |
That would be expected. |
OK so we are good to move #22430 forward and ignore the case of BreadcrumbCollapsed because once we click this button, it disappears? |
Could somebody summarize what the issue is now? The initial description still talks about selected state. |
@eps1lon My understanding:
Proposed solution: never set the keyboard style when clicking on a chip, as done with all the other buttons of Material-UI. Chip is the only exception. |
Hello together, First of all, if you think everything is working as expected then I'd not want to waste your time, please just close the issue. Secondly, assume a use case where a) you use the Chips to list some tags where each Chip is used like a "filter", b) you use the Chips as it is, no custom/additional styling. I think it is a valid use case.
It depends from which perspective you look at it. From user's perspective (using the same use-case) a "tag" is selected (clicked/pressed/tapped whatever you mean), afterwards (visually) there is a change ("focused" to be specifically). I am not stating that this must be so or not trying to replace the concept of toggle buttons with Chips but from the user's perspective clicking on an UI Element where visual apperance changes and clicking on it again results in an "expectation" that original appearance is restored. @oliviertassinari Thank you for your recommendations. As a workaround I removed the hover from the css which in my case solved the problem, I mean fulfilled the expectations. |
That's fine. This will not address the select/de-select state described in the original issue though. Using :focus-visible as a selected indicator is not supported because we don't have any selected state for the Chip at the moment. With the proposed change clicking will no longer have any visual effect. |
Use-Case: Assume that there are 2 Chips, clickable, which are always visible and used for filtering purposes. Clicking on it adds a check icon to the chip and filters the list, clicking again removes the icon and list is unfiltered.
Current Behavior 😯
Chip background color is not restored to original value after deselecting which confuses the user.
Expected Behavior 🤔
Chip background color should be restored to original value after deselecting.
Steps to Reproduce 🕹
Steps:
a) Clicking on the page anywhere cause to change the background color again since focus changes or b) using a custom blur function, which is triggered after a timeout period.
Context 🔦
Your Environment 🌎
Mac OS
The text was updated successfully, but these errors were encountered: