mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
feat(component): add autoFocusConfirmButton for confirm-modal (#7801)
close #5813 <div class='graphite__hidden'> <div>🎥 Video uploaded on Graphite:</div> <a href="https://app.graphite.dev/media/video/LakojjjzZNf6ogjOVwKE/aff35b76-9f73-4d15-b2cb-c25b03e2e2c3.mp4"> <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/LakojjjzZNf6ogjOVwKE/aff35b76-9f73-4d15-b2cb-c25b03e2e2c3.mp4"> </a> </div> <video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/aff35b76-9f73-4d15-b2cb-c25b03e2e2c3.mp4">CleanShot 2024-08-09 at 11.25.46.mp4</video>
This commit is contained in:
@@ -7,6 +7,7 @@ import type {
|
||||
} from 'react';
|
||||
import { cloneElement, forwardRef, useCallback } from 'react';
|
||||
|
||||
import { useAutoFocus } from '../../hooks';
|
||||
import { Loading } from '../loading';
|
||||
import { Tooltip, type TooltipProps } from '../tooltip';
|
||||
import * as styles from './button.css';
|
||||
@@ -120,12 +121,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
tooltip,
|
||||
tooltipShortcut,
|
||||
tooltipOptions,
|
||||
autoFocus,
|
||||
onClick,
|
||||
|
||||
...otherProps
|
||||
},
|
||||
ref
|
||||
upstreamRef
|
||||
) => {
|
||||
const ref = useAutoFocus<HTMLButtonElement>(autoFocus);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (loading || disabled) return;
|
||||
@@ -134,11 +138,22 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
[disabled, loading, onClick]
|
||||
);
|
||||
|
||||
const buttonRef = (el: HTMLButtonElement | null) => {
|
||||
ref.current = el;
|
||||
if (upstreamRef) {
|
||||
if (typeof upstreamRef === 'function') {
|
||||
upstreamRef(el);
|
||||
} else {
|
||||
upstreamRef.current = el;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip content={tooltip} shortcut={tooltipShortcut} {...tooltipOptions}>
|
||||
<button
|
||||
{...otherProps}
|
||||
ref={ref}
|
||||
ref={buttonRef}
|
||||
className={clsx(styles.button, className)}
|
||||
data-loading={loading || undefined}
|
||||
data-block={block || undefined}
|
||||
|
||||
@@ -8,14 +8,9 @@ import type {
|
||||
KeyboardEventHandler,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { forwardRef, useCallback, useEffect } from 'react';
|
||||
|
||||
import { useAutoFocus, useAutoSelect } from '../../hooks';
|
||||
import { input, inputWrapper } from './style.css';
|
||||
|
||||
export type InputProps = {
|
||||
@@ -55,30 +50,31 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
}: InputProps,
|
||||
upstreamRef: ForwardedRef<HTMLInputElement>
|
||||
) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (inputRef.current && (autoFocus || autoSelect)) {
|
||||
// to avoid clicking on something focusable(e.g MenuItem),
|
||||
// then the input will not be focused
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 0);
|
||||
if (autoSelect) {
|
||||
inputRef.current?.select();
|
||||
const focusRef = useAutoFocus<HTMLInputElement>(autoFocus);
|
||||
const selectRef = useAutoSelect<HTMLInputElement>(autoSelect);
|
||||
|
||||
const inputRef = (el: HTMLInputElement | null) => {
|
||||
focusRef.current = el;
|
||||
selectRef.current = el;
|
||||
if (upstreamRef) {
|
||||
if (typeof upstreamRef === 'function') {
|
||||
upstreamRef(el);
|
||||
} else {
|
||||
upstreamRef.current = el;
|
||||
}
|
||||
}
|
||||
}, [autoFocus, autoSelect, upstreamRef]);
|
||||
};
|
||||
|
||||
// use native blur event to get event after unmount
|
||||
// don't use useLayoutEffect here, because the cleanup function will be called before unmount
|
||||
useEffect(() => {
|
||||
if (!onBlur) return;
|
||||
inputRef.current?.addEventListener('blur', onBlur as any);
|
||||
selectRef.current?.addEventListener('blur', onBlur as any);
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
inputRef.current?.removeEventListener('blur', onBlur as any);
|
||||
selectRef.current?.removeEventListener('blur', onBlur as any);
|
||||
};
|
||||
}, [onBlur]);
|
||||
}, [onBlur, selectRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -105,16 +101,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
large: size === 'large',
|
||||
'extra-large': size === 'extraLarge',
|
||||
})}
|
||||
ref={ref => {
|
||||
inputRef.current = ref;
|
||||
if (upstreamRef) {
|
||||
if (typeof upstreamRef === 'function') {
|
||||
upstreamRef(ref);
|
||||
} else {
|
||||
upstreamRef.current = ref;
|
||||
}
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
disabled={disabled}
|
||||
style={inputStyle}
|
||||
onChange={useCallback(
|
||||
|
||||
@@ -17,6 +17,11 @@ export interface ConfirmModalProps extends ModalProps {
|
||||
cancelText?: React.ReactNode;
|
||||
cancelButtonOptions?: Omit<ButtonProps, 'children'>;
|
||||
reverseFooter?: boolean;
|
||||
/**
|
||||
* Auto focus on confirm button when modal opened
|
||||
* @default true
|
||||
*/
|
||||
autoFocusConfirm?: boolean;
|
||||
}
|
||||
|
||||
export const ConfirmModal = ({
|
||||
@@ -30,6 +35,7 @@ export const ConfirmModal = ({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
width = 480,
|
||||
autoFocusConfirm = true,
|
||||
...props
|
||||
}: ConfirmModalProps) => {
|
||||
const onConfirmClick = useCallback(() => {
|
||||
@@ -73,6 +79,7 @@ export const ConfirmModal = ({
|
||||
<Button
|
||||
onClick={onConfirmClick}
|
||||
data-testid="confirm-modal-confirm"
|
||||
autoFocus={autoFocusConfirm}
|
||||
{...confirmButtonOptions}
|
||||
>
|
||||
{confirmText}
|
||||
|
||||
Reference in New Issue
Block a user