@react-spectrum/button 3.0.0-nightly.2594 → 3.0.0-nightly.2602

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Button.tsx CHANGED
@@ -22,14 +22,14 @@ import {FocusableRef} from '@react-types/shared';
22
22
  import {FocusRing} from '@react-aria/focus';
23
23
  // @ts-ignore
24
24
  import intlMessages from '../intl/*.json';
25
- import {mergeProps} from '@react-aria/utils';
25
+ import {mergeProps, useId} from '@react-aria/utils';
26
26
  import {ProgressCircle} from '@react-spectrum/progress';
27
27
  import React, {ElementType, ReactElement, useEffect, useState} from 'react';
28
28
  import {SpectrumButtonProps} from '@react-types/button';
29
29
  import styles from '@adobe/spectrum-css-temp/components/button/vars.css';
30
30
  import {Text} from '@react-spectrum/text';
31
31
  import {useButton} from '@react-aria/button';
32
- import {useHover} from '@react-aria/interactions';
32
+ import {useFocus, useHover} from '@react-aria/interactions';
33
33
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
34
34
  import {useProviderProps} from '@react-spectrum/provider';
35
35
 
@@ -67,11 +67,21 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
67
67
  let domRef = useFocusableRef(ref);
68
68
  let {buttonProps, isPressed} = useButton(props, domRef);
69
69
  let {hoverProps, isHovered} = useHover({isDisabled});
70
+ let [isFocused, onFocusChange] = useState(false);
71
+ let {focusProps} = useFocus({onFocusChange, isDisabled});
70
72
  let stringFormatter = useLocalizedStringFormatter(intlMessages);
71
73
  let {styleProps} = useStyleProps(otherProps);
72
74
  let hasLabel = useHasChild(`.${styles['spectrum-Button-label']}`, domRef);
73
75
  let hasIcon = useHasChild(`.${styles['spectrum-Icon']}`, domRef);
76
+ // an aria label will block children and their labels from being read, this is undesirable for pending state
77
+ let hasAriaLabel = !!buttonProps['aria-label'] || !!buttonProps['aria-labelledby'];
74
78
  let [isProgressVisible, setIsProgressVisible] = useState(false);
79
+ let backupButtonId = useId();
80
+ let buttonId = buttonProps.id || backupButtonId;
81
+ let spinnerId = useId();
82
+ let textId = useId();
83
+ let iconId = useId();
84
+ let auxLabelId = useId();
75
85
 
76
86
  useEffect(() => {
77
87
  let timeout: ReturnType<typeof setTimeout>;
@@ -102,13 +112,16 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
102
112
  <FocusRing focusRingClass={classNames(styles, 'focus-ring')} autoFocus={autoFocus}>
103
113
  <Element
104
114
  {...styleProps}
105
- {...mergeProps(buttonProps, hoverProps)}
115
+ {...mergeProps(buttonProps, hoverProps, focusProps)}
116
+ id={buttonId}
106
117
  ref={domRef}
107
118
  data-variant={variant}
108
119
  data-style={style}
109
120
  data-static-color={staticColor || undefined}
110
- aria-disabled={isPending || undefined}
111
- aria-live={isPending ? 'polite' : undefined}
121
+ aria-disabled={isPending ? 'true' : undefined}
122
+ aria-label={isPending ? undefined : buttonProps['aria-label']}
123
+ aria-labelledby={isPending ? undefined : buttonProps['aria-labelledby']}
124
+ aria-live={isPending && isFocused ? 'polite' : undefined}
112
125
  className={
113
126
  classNames(
114
127
  styles,
@@ -126,22 +139,38 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
126
139
  <SlotProvider
127
140
  slots={{
128
141
  icon: {
142
+ id: iconId,
129
143
  size: 'S',
130
144
  UNSAFE_className: classNames(styles, 'spectrum-Icon')
131
145
  },
132
146
  text: {
147
+ id: textId,
133
148
  UNSAFE_className: classNames(styles, 'spectrum-Button-label')
134
149
  }
135
150
  }}>
136
- {isProgressVisible && <ProgressCircle
137
- aria-label={stringFormatter.format('loading')}
138
- isIndeterminate
139
- size="S"
140
- UNSAFE_className={classNames(styles, 'spectrum-Button-circleLoader')}
141
- staticColor={staticColor} />}
142
151
  {typeof children === 'string'
143
152
  ? <Text>{children}</Text>
144
153
  : children}
154
+ {isPending && <ProgressCircle
155
+ aria-hidden="true"
156
+ isIndeterminate
157
+ size="S"
158
+ UNSAFE_className={classNames(styles, 'spectrum-Button-circleLoader')}
159
+ UNSAFE_style={{visibility: isProgressVisible ? 'visible' : 'hidden'}}
160
+ staticColor={staticColor} />
161
+ }
162
+ {/* Adding the element here with the same labels as the button itself causes aria-live to pick up the change in Safari.
163
+ Safari with VO unfortunately doesn't announce changes to *all* of its labels specifically for button
164
+ https://a11ysupport.io/tests/tech__html__button-name-change#assertion-aria-aria-label_attribute-convey_name_change-html-button_element-vo_macos-safari
165
+ The aria-live does cause extra announcements in other browsers. */}
166
+ {isPending && hasAriaLabel &&
167
+ <div aria-hidden="true" id={auxLabelId} aria-label={buttonProps['aria-label']} aria-labelledby={buttonProps['aria-labelledby']?.replace(buttonId, auxLabelId)} />
168
+ }
169
+ {isPending && <div
170
+ id={spinnerId}
171
+ aria-label={stringFormatter.format('pending')}
172
+ aria-labelledby={`${hasAriaLabel ? '' : iconId} ${hasAriaLabel ? auxLabelId : textId} ${spinnerId}`} />
173
+ }
145
174
  </SlotProvider>
146
175
  </Element>
147
176
  </FocusRing>