@react-spectrum/button 3.12.3 → 3.13.0

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
@@ -20,18 +20,39 @@ import {
20
20
  } from '@react-spectrum/utils';
21
21
  import {FocusableRef} from '@react-types/shared';
22
22
  import {FocusRing} from '@react-aria/focus';
23
+ // @ts-ignore
24
+ import intlMessages from '../intl/*.json';
23
25
  import {mergeProps} from '@react-aria/utils';
24
- import React, {ElementType, ReactElement} from 'react';
26
+ import {ProgressCircle} from '@react-spectrum/progress';
27
+ import React, {ElementType, ReactElement, useEffect, useState} from 'react';
25
28
  import {SpectrumButtonProps} from '@react-types/button';
26
29
  import styles from '@adobe/spectrum-css-temp/components/button/vars.css';
27
30
  import {Text} from '@react-spectrum/text';
28
31
  import {useButton} from '@react-aria/button';
29
32
  import {useHover} from '@react-aria/interactions';
33
+ import {useLocalizedStringFormatter} from '@react-aria/i18n';
30
34
  import {useProviderProps} from '@react-spectrum/provider';
31
35
 
36
+ function disablePendingProps(props) {
37
+ // Don't allow interaction while UNSTABLE_isPending is true
38
+ if (props.UNSTABLE_isPending) {
39
+ props.onPress = undefined;
40
+ props.onPressStart = undefined;
41
+ props.onPressEnd = undefined;
42
+ props.onPressChange = undefined;
43
+ props.onPressUp = undefined;
44
+ props.onKeyDown = undefined;
45
+ props.onKeyUp = undefined;
46
+ props.onClick = undefined;
47
+ props.href = undefined;
48
+ }
49
+ return props;
50
+ }
51
+
32
52
  function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>, ref: FocusableRef<HTMLElement>) {
33
53
  props = useProviderProps(props);
34
54
  props = useSlotProps(props, 'button');
55
+ props = disablePendingProps(props);
35
56
  let {
36
57
  elementType: ElementType = 'button',
37
58
  children,
@@ -39,15 +60,36 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
39
60
  style = variant === 'accent' || variant === 'cta' ? 'fill' : 'outline',
40
61
  staticColor,
41
62
  isDisabled,
63
+ UNSTABLE_isPending,
42
64
  autoFocus,
43
65
  ...otherProps
44
66
  } = props;
45
67
  let domRef = useFocusableRef(ref);
46
68
  let {buttonProps, isPressed} = useButton(props, domRef);
47
69
  let {hoverProps, isHovered} = useHover({isDisabled});
70
+ let stringFormatter = useLocalizedStringFormatter(intlMessages);
48
71
  let {styleProps} = useStyleProps(otherProps);
49
72
  let hasLabel = useHasChild(`.${styles['spectrum-Button-label']}`, domRef);
50
73
  let hasIcon = useHasChild(`.${styles['spectrum-Icon']}`, domRef);
74
+ let [isProgressVisible, setIsProgressVisible] = useState(false);
75
+
76
+ useEffect(() => {
77
+ let timeout: ReturnType<typeof setTimeout>;
78
+
79
+ if (UNSTABLE_isPending) {
80
+ // Start timer when UNSTABLE_isPending is set to true.
81
+ timeout = setTimeout(() => {
82
+ setIsProgressVisible(true);
83
+ }, 1000);
84
+ } else {
85
+ // Exit loading state when UNSTABLE_isPending is set to false. */
86
+ setIsProgressVisible(false);
87
+ }
88
+ return () => {
89
+ // Clean up on unmount or when user removes UNSTABLE_isPending prop before entering loading state.
90
+ clearTimeout(timeout);
91
+ };
92
+ }, [UNSTABLE_isPending]);
51
93
 
52
94
  if (variant === 'cta') {
53
95
  variant = 'accent';
@@ -65,15 +107,18 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
65
107
  data-variant={variant}
66
108
  data-style={style}
67
109
  data-static-color={staticColor || undefined}
110
+ aria-disabled={UNSTABLE_isPending || undefined}
111
+ aria-live={UNSTABLE_isPending ? 'polite' : undefined}
68
112
  className={
69
113
  classNames(
70
114
  styles,
71
115
  'spectrum-Button',
72
116
  {
73
117
  'spectrum-Button--iconOnly': hasIcon && !hasLabel,
74
- 'is-disabled': isDisabled,
118
+ 'is-disabled': isDisabled || isProgressVisible,
75
119
  'is-active': isPressed,
76
- 'is-hovered': isHovered
120
+ 'is-hovered': isHovered,
121
+ 'spectrum-Button--pending': isProgressVisible
77
122
  },
78
123
  styleProps.className
79
124
  )
@@ -88,6 +133,12 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
88
133
  UNSAFE_className: classNames(styles, 'spectrum-Button-label')
89
134
  }
90
135
  }}>
136
+ {isProgressVisible && <ProgressCircle
137
+ aria-label={stringFormatter.format('loading')}
138
+ isIndeterminate
139
+ size="S"
140
+ UNSAFE_className={classNames(styles, 'spectrum-Button-circleLoader')}
141
+ variant={staticColor ? 'overBackground' : undefined} />}
91
142
  {typeof children === 'string'
92
143
  ? <Text>{children}</Text>
93
144
  : children}
@@ -24,6 +24,7 @@ interface FieldButtonProps extends ButtonProps, DOMProps, StyleProps {
24
24
  isQuiet?: boolean,
25
25
  isActive?: boolean,
26
26
  validationState?: 'valid' | 'invalid',
27
+ isInvalid?: boolean,
27
28
  focusRingClass?: string
28
29
  }
29
30
 
@@ -34,6 +35,7 @@ function FieldButton(props: FieldButtonProps, ref: FocusableRef) {
34
35
  isQuiet,
35
36
  isDisabled,
36
37
  validationState,
38
+ isInvalid,
37
39
  children,
38
40
  autoFocus,
39
41
  isActive,
@@ -58,7 +60,7 @@ function FieldButton(props: FieldButtonProps, ref: FocusableRef) {
58
60
  'spectrum-FieldButton--quiet': isQuiet,
59
61
  'is-active': isActive || isPressed,
60
62
  'is-disabled': isDisabled,
61
- 'spectrum-FieldButton--invalid': validationState === 'invalid',
63
+ 'spectrum-FieldButton--invalid': isInvalid || validationState === 'invalid',
62
64
  'is-hovered': isHovered
63
65
  },
64
66
  styleProps.className