mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
6b30832666
The old version of prettier we were using didn't support the Flow syntax to access properties in a type using `SomeType['prop']`. This updates `prettier` and `rollup-plugin-prettier` to the latest versions. I added the prettier config `arrowParens: "avoid"` to reduce the diff size as the default has changed in Prettier 2.0. The largest amount of changes comes from function expressions now having a space. This doesn't have an option to preserve the old behavior, so we have to update this.
187 lines
4.3 KiB
JavaScript
187 lines
4.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactContext} from 'shared/ReactTypes';
|
|
import * as React from 'react';
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useReducer,
|
|
useRef,
|
|
} from 'react';
|
|
import Button from './Button';
|
|
import {useModalDismissSignal} from './hooks';
|
|
|
|
import styles from './ModalDialog.css';
|
|
|
|
type ID = any;
|
|
|
|
type DIALOG_ACTION_HIDE = {
|
|
type: 'HIDE',
|
|
id: ID,
|
|
};
|
|
type DIALOG_ACTION_SHOW = {
|
|
type: 'SHOW',
|
|
canBeDismissed?: boolean,
|
|
content: React$Node,
|
|
id: ID,
|
|
title?: React$Node | null,
|
|
};
|
|
|
|
type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
|
|
|
|
type Dispatch = (action: Action) => void;
|
|
|
|
type Dialog = {
|
|
canBeDismissed: boolean,
|
|
content: React$Node | null,
|
|
id: ID,
|
|
title: React$Node | null,
|
|
};
|
|
|
|
type State = {
|
|
dialogs: Array<Dialog>,
|
|
};
|
|
|
|
type ModalDialogContextType = {
|
|
...State,
|
|
dispatch: Dispatch,
|
|
};
|
|
|
|
const ModalDialogContext: ReactContext<ModalDialogContextType> =
|
|
createContext<ModalDialogContextType>(((null: any): ModalDialogContextType));
|
|
ModalDialogContext.displayName = 'ModalDialogContext';
|
|
|
|
function dialogReducer(state: State, action: Action) {
|
|
switch (action.type) {
|
|
case 'HIDE':
|
|
return {
|
|
dialogs: state.dialogs.filter(dialog => dialog.id !== action.id),
|
|
};
|
|
case 'SHOW':
|
|
return {
|
|
dialogs: [
|
|
...state.dialogs,
|
|
{
|
|
canBeDismissed: action.canBeDismissed !== false,
|
|
content: action.content,
|
|
id: action.id,
|
|
title: action.title || null,
|
|
},
|
|
],
|
|
};
|
|
default:
|
|
throw new Error(`Invalid action "${action.type}"`);
|
|
}
|
|
}
|
|
|
|
type Props = {
|
|
children: React$Node,
|
|
};
|
|
|
|
function ModalDialogContextController({children}: Props): React.Node {
|
|
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
|
|
dialogs: [],
|
|
});
|
|
|
|
const value = useMemo<ModalDialogContextType>(
|
|
() => ({
|
|
dialogs: state.dialogs,
|
|
dispatch,
|
|
}),
|
|
[state, dispatch],
|
|
);
|
|
|
|
return (
|
|
<ModalDialogContext.Provider value={value}>
|
|
{children}
|
|
</ModalDialogContext.Provider>
|
|
);
|
|
}
|
|
|
|
function ModalDialog(_: {}): React.Node {
|
|
const {dialogs, dispatch} = useContext(ModalDialogContext);
|
|
|
|
if (dialogs.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className={styles.Background}>
|
|
{dialogs.map(dialog => (
|
|
<ModalDialogImpl
|
|
key={dialog.id}
|
|
canBeDismissed={dialog.canBeDismissed}
|
|
content={dialog.content}
|
|
dispatch={dispatch}
|
|
id={dialog.id}
|
|
title={dialog.title}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ModalDialogImpl({
|
|
canBeDismissed,
|
|
content,
|
|
dispatch,
|
|
id,
|
|
title,
|
|
}: {
|
|
canBeDismissed: boolean,
|
|
content: React$Node | null,
|
|
dispatch: Dispatch,
|
|
id: ID,
|
|
title: React$Node | null,
|
|
}) {
|
|
const dismissModal = useCallback(() => {
|
|
if (canBeDismissed) {
|
|
dispatch({type: 'HIDE', id});
|
|
}
|
|
}, [canBeDismissed, dispatch]);
|
|
const dialogRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
// It's important to trap click events within the dialog,
|
|
// so the dismiss hook will use it for click hit detection.
|
|
// Because multiple tabs may be showing this ModalDialog,
|
|
// the normal `dialog.contains(target)` check would fail on a background tab.
|
|
useModalDismissSignal(dialogRef, dismissModal, false);
|
|
|
|
// Clicks on the dialog should not bubble.
|
|
// This way we can dismiss by listening to clicks on the background.
|
|
const handleDialogClick = (event: any) => {
|
|
event.stopPropagation();
|
|
|
|
// It is important that we don't also prevent default,
|
|
// or clicks within the dialog (e.g. on links) won't work.
|
|
};
|
|
|
|
return (
|
|
<div ref={dialogRef} className={styles.Dialog} onClick={handleDialogClick}>
|
|
{title !== null && <div className={styles.Title}>{title}</div>}
|
|
{content}
|
|
{canBeDismissed && (
|
|
<div className={styles.Buttons}>
|
|
<Button
|
|
autoFocus={true}
|
|
className={styles.Button}
|
|
onClick={dismissModal}>
|
|
Okay
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export {ModalDialog, ModalDialogContext, ModalDialogContextController};
|