Merge pull request #29755 from backstage/canon-fix-select-label-click

Canon - Fix - Clicking Select label moves focus to trigger + style TextField label to indicate it's interactive.
This commit is contained in:
Johan Persson
2025-04-29 11:35:58 +02:00
committed by GitHub
9 changed files with 70 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive.
+10
View File
@@ -543,6 +543,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-TextFieldLabel[data-disabled] {
cursor: default;
}
.canon-TextFieldDescription {
@@ -836,6 +841,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-SelectLabel[data-disabled] {
cursor: default;
}
.canon-SelectDescription {
+5
View File
@@ -10,6 +10,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-SelectLabel[data-disabled] {
cursor: default;
}
.canon-SelectDescription {
+10
View File
@@ -9767,6 +9767,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-TextFieldLabel[data-disabled] {
cursor: default;
}
.canon-TextFieldDescription {
@@ -10060,6 +10065,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-SelectLabel[data-disabled] {
cursor: default;
}
.canon-SelectDescription {
+5
View File
@@ -10,6 +10,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-TextFieldLabel[data-disabled] {
cursor: default;
}
.canon-TextFieldDescription {
@@ -26,6 +26,10 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-SelectLabel[data-disabled] {
cursor: default;
}
.canon-SelectDescription {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { forwardRef, useId } from 'react';
import { forwardRef, useCallback, useId, useRef, MouseEvent } from 'react';
import { Select as SelectPrimitive } from '@base-ui-components/react/select';
import { Icon } from '../Icon';
import clsx from 'clsx';
@@ -45,10 +45,27 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
const descriptionId = useId();
const errorId = useId();
const triggerRef = useRef<HTMLButtonElement>(null);
const handleLabelClick = useCallback(
(e: MouseEvent<HTMLLabelElement>) => {
if (!props.disabled && triggerRef.current) {
e.preventDefault();
triggerRef.current.focus();
}
},
[props.disabled],
);
return (
<div className={clsx('canon-Select', className)} style={style} ref={ref}>
{label && (
<label className="canon-SelectLabel" htmlFor={selectId}>
<label
className="canon-SelectLabel"
htmlFor={selectId}
onClick={handleLabelClick}
data-disabled={props.disabled ? true : undefined}
>
{label}
{required && (
<span aria-hidden="true" className="canon-SelectRequired">
@@ -59,6 +76,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
)}
<SelectPrimitive.Root {...rest}>
<SelectPrimitive.Trigger
ref={triggerRef}
id={selectId}
className="canon-SelectTrigger"
data-size={responsiveSize}
@@ -26,6 +26,10 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
cursor: pointer;
}
.canon-TextFieldLabel[data-disabled] {
cursor: default;
}
.canon-TextFieldDescription {
@@ -32,6 +32,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
error,
required,
style,
disabled,
...rest
} = props;
@@ -50,7 +51,11 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
ref={ref}
>
{label && (
<label className="canon-TextFieldLabel" htmlFor={inputId}>
<label
className="canon-TextFieldLabel"
htmlFor={inputId}
data-disabled={disabled}
>
{label}
{required && (
<span aria-hidden="true" className="canon-TextFieldRequired">
@@ -70,6 +75,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
})}
data-invalid={error}
required={required}
disabled={disabled}
{...rest}
/>
{description && (