mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 08:05:43 -06:00
Chore: Rewrite ConfirmButton
(#84402)
* convert to function component, clean up + fix a11y * use position: fixed; * fix
This commit is contained in:
parent
81a63efab2
commit
321148511b
@ -1,14 +1,13 @@
|
||||
import { cx, css } from '@emotion/css';
|
||||
import React, { PureComponent, ReactElement } from 'react';
|
||||
import React, { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { stylesFactory, withTheme2 } from '../../themes';
|
||||
import { Themeable2 } from '../../types';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
|
||||
export interface Props extends Themeable2 {
|
||||
export interface Props {
|
||||
/** Confirm action callback */
|
||||
onConfirm(): void;
|
||||
children: string | ReactElement;
|
||||
@ -24,182 +23,161 @@ export interface Props extends Themeable2 {
|
||||
confirmVariant?: ButtonVariant;
|
||||
/** Hide confirm actions when after of them is clicked */
|
||||
closeOnConfirm?: boolean;
|
||||
/** Move focus to button when mounted */
|
||||
autoFocus?: boolean;
|
||||
|
||||
/** Optional on click handler for the original button */
|
||||
onClick?(): void;
|
||||
/** Callback for the cancel action */
|
||||
onCancel?(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showConfirm: boolean;
|
||||
}
|
||||
export const ConfirmButton = ({
|
||||
children,
|
||||
className,
|
||||
closeOnConfirm,
|
||||
confirmText = 'Save',
|
||||
confirmVariant = 'primary',
|
||||
disabled = false,
|
||||
onCancel,
|
||||
onClick,
|
||||
onConfirm,
|
||||
size = 'md',
|
||||
}: Props) => {
|
||||
const mainButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const confirmButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [shouldRestoreFocus, setShouldRestoreFocus] = useState(false);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
class UnThemedConfirmButton extends PureComponent<Props, State> {
|
||||
mainButtonRef = React.createRef<HTMLButtonElement>();
|
||||
confirmButtonRef = React.createRef<HTMLButtonElement>();
|
||||
state: State = {
|
||||
showConfirm: false,
|
||||
};
|
||||
|
||||
onClickButton = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
showConfirm: true,
|
||||
},
|
||||
() => {
|
||||
if (this.props.autoFocus && this.confirmButtonRef.current) {
|
||||
this.confirmButtonRef.current.focus();
|
||||
}
|
||||
useEffect(() => {
|
||||
if (showConfirm) {
|
||||
confirmButtonRef.current?.focus();
|
||||
setShouldRestoreFocus(true);
|
||||
} else {
|
||||
if (shouldRestoreFocus) {
|
||||
mainButtonRef.current?.focus();
|
||||
setShouldRestoreFocus(false);
|
||||
}
|
||||
);
|
||||
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
};
|
||||
}, [shouldRestoreFocus, showConfirm]);
|
||||
|
||||
onClickCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const onClickButton = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
showConfirm: false,
|
||||
},
|
||||
() => {
|
||||
this.mainButtonRef.current?.focus();
|
||||
}
|
||||
);
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
setShowConfirm(true);
|
||||
onClick?.();
|
||||
};
|
||||
onConfirm = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
||||
const onClickCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.props.onConfirm();
|
||||
if (this.props.closeOnConfirm) {
|
||||
this.setState({
|
||||
showConfirm: false,
|
||||
});
|
||||
setShowConfirm(false);
|
||||
mainButtonRef.current?.focus();
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
const onClickConfirm = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
onConfirm?.();
|
||||
if (closeOnConfirm) {
|
||||
setShowConfirm(false);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
className,
|
||||
size,
|
||||
disabled,
|
||||
confirmText,
|
||||
confirmVariant: confirmButtonVariant,
|
||||
children,
|
||||
} = this.props;
|
||||
const styles = getStyles(theme);
|
||||
const buttonClass = cx(
|
||||
className,
|
||||
this.state.showConfirm ? styles.buttonHide : styles.buttonShow,
|
||||
disabled && styles.buttonDisabled
|
||||
);
|
||||
const confirmButtonClass = cx(
|
||||
styles.confirmButton,
|
||||
this.state.showConfirm ? styles.confirmButtonShow : styles.confirmButtonHide
|
||||
);
|
||||
const buttonClass = cx(className, styles.mainButton, {
|
||||
[styles.mainButtonHide]: showConfirm,
|
||||
});
|
||||
const confirmButtonClass = cx(styles.confirmButton, {
|
||||
[styles.confirmButtonHide]: !showConfirm,
|
||||
});
|
||||
const confirmButtonContainerClass = cx(styles.confirmButtonContainer, {
|
||||
[styles.confirmButtonContainerHide]: !showConfirm,
|
||||
});
|
||||
|
||||
const onClick = disabled ? () => {} : this.onClickButton;
|
||||
|
||||
return (
|
||||
<span className={styles.buttonContainer}>
|
||||
<div className={cx(disabled && styles.disabled)}>
|
||||
<span className={buttonClass}>
|
||||
{typeof children === 'string' ? (
|
||||
<Button size={size} fill="text" onClick={onClick} ref={this.mainButtonRef}>
|
||||
{children}
|
||||
</Button>
|
||||
) : (
|
||||
React.cloneElement(children, { onClick, ref: this.mainButtonRef })
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<span className={buttonClass}>
|
||||
{typeof children === 'string' ? (
|
||||
<Button disabled={disabled} size={size} fill="text" onClick={onClickButton} ref={mainButtonRef}>
|
||||
{children}
|
||||
</Button>
|
||||
) : (
|
||||
React.cloneElement(children, { disabled, onClick: onClickButton, ref: mainButtonRef })
|
||||
)}
|
||||
</span>
|
||||
<div className={confirmButtonContainerClass}>
|
||||
<span className={confirmButtonClass}>
|
||||
<Button size={size} variant={confirmButtonVariant} onClick={this.onConfirm} ref={this.confirmButtonRef}>
|
||||
<Button size={size} variant={confirmVariant} onClick={onClickConfirm} ref={confirmButtonRef}>
|
||||
{confirmText}
|
||||
</Button>
|
||||
<Button size={size} fill="text" onClick={this.onClickCancel}>
|
||||
<Button size={size} fill="text" onClick={onClickCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ConfirmButton.displayName = 'ConfirmButton';
|
||||
|
||||
export const ConfirmButton = withTheme2(UnThemedConfirmButton);
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
buttonContainer: css({
|
||||
display: 'flex',
|
||||
container: css({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
position: 'relative',
|
||||
}),
|
||||
buttonDisabled: css({
|
||||
textDecoration: 'none',
|
||||
color: theme.colors.text.primary,
|
||||
opacity: 0.65,
|
||||
pointerEvents: 'none',
|
||||
}),
|
||||
buttonShow: css({
|
||||
mainButton: css({
|
||||
opacity: 1,
|
||||
transition: 'opacity 0.1s ease',
|
||||
transition: theme.transitions.create(['opacity'], {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
}),
|
||||
zIndex: 2,
|
||||
}),
|
||||
buttonHide: css({
|
||||
mainButtonHide: css({
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.1s ease, visibility 0 0.1s',
|
||||
transition: theme.transitions.create(['opacity', 'visibility'], {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
}),
|
||||
visibility: 'hidden',
|
||||
zIndex: 0,
|
||||
}),
|
||||
confirmButtonContainer: css({
|
||||
overflow: 'visible',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
}),
|
||||
confirmButtonContainerHide: css({
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
confirmButton: css({
|
||||
alignItems: 'flex-start',
|
||||
background: theme.colors.background.primary,
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
}),
|
||||
confirmButtonShow: css({
|
||||
zIndex: 1,
|
||||
opacity: 1,
|
||||
transition: 'opacity 0.08s ease-out, transform 0.1s ease-out',
|
||||
transform: 'translateX(0)',
|
||||
pointerEvents: 'all',
|
||||
transform: 'translateX(0)',
|
||||
transition: theme.transitions.create(['opacity', 'transform'], {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
}),
|
||||
zIndex: 1,
|
||||
}),
|
||||
confirmButtonHide: css({
|
||||
opacity: 0,
|
||||
pointerEvents: 'none',
|
||||
transform: 'translateX(100%)',
|
||||
transition: theme.transitions.create(['opacity', 'transform', 'visibility'], {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
}),
|
||||
visibility: 'hidden',
|
||||
transition: 'opacity 0.12s ease-in, transform 0.14s ease-in, visibility 0s 0.12s',
|
||||
transform: 'translateX(100px)',
|
||||
}),
|
||||
disabled: css({
|
||||
cursor: 'not-allowed',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Declare defaultProps directly on the themed component so they are displayed
|
||||
// in the props table
|
||||
ConfirmButton.defaultProps = {
|
||||
size: 'md',
|
||||
confirmText: 'Save',
|
||||
disabled: false,
|
||||
confirmVariant: 'primary',
|
||||
};
|
||||
ConfirmButton.displayName = 'ConfirmButton';
|
||||
|
@ -245,7 +245,6 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps> {
|
||||
confirmVariant="destructive"
|
||||
onCancel={this.onCancelClick}
|
||||
onConfirm={this.onOrgRemove}
|
||||
autoFocus
|
||||
>
|
||||
Remove from organization
|
||||
</ConfirmButton>
|
||||
|
@ -78,7 +78,6 @@ class BaseUserSessions extends PureComponent<Props, State> {
|
||||
confirmText="Confirm logout"
|
||||
confirmVariant="destructive"
|
||||
onConfirm={this.onSessionRevoke(session.id)}
|
||||
autoFocus
|
||||
>
|
||||
Force logout
|
||||
</ConfirmButton>
|
||||
|
Loading…
Reference in New Issue
Block a user