import * as Ariakit from '@ariakit/react'; import { getClassNames } from '@websolutespa/bom-core'; import { forwardRef, ReactNode, useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import { getVariantKey } from '../../variants'; import { Role } from '../role/role'; import { UIStyledComponentProps } from '../types'; import { getCssResponsive } from '../utils'; import { ModalDialogContext, ModalForceMountContext } from './modal-dialog.context'; import { ModalContext } from './modal.context'; import { IModalVariants, modalVariants } from './modal.variants'; const StyledModalDialog = styled(Ariakit.Dialog) ` ${props => { const vs = getVariantKey('dialog', props.variant, props.variants); // console.log(props.variant, props.variants, vs); return vs; }} ${props => getCssResponsive(props)} `; export type ModalDialogPlacement = 'center' | 'top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; export type ModalDialogProps = Ariakit.DialogProps & UIStyledComponentProps<{ placement?: ModalDialogPlacement; className?: string; variant?: keyof typeof modalVariants | (string & {}); variants?: IModalVariants; children?: ReactNode; asChild?: boolean; forceMount?: boolean; onOpenAutoFocus?: (event: Event) => void; onCloseAutoFocus?: (event: Event) => void; onEscapeKeyDown?: (event: KeyboardEvent) => void; onPointerDownOutside?: (event: Event) => void; onInteractOutside?: (event: Event) => void; }>; export const ModalDialog = forwardRef( function ModalDialog({ asChild, forceMount, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onPointerDownOutside, onInteractOutside, className, variant, variants, placement = 'center', ...props }, ref) { const { store, modal } = useContext(ModalDialogContext)!; const forceMountContext = useContext(ModalForceMountContext); forceMount = forceMount ?? forceMountContext; const mounted = Ariakit.useStoreState(store, (state) => forceMount || state.mounted); const contentElement = Ariakit.useStoreState(store, 'contentElement'); const open = Ariakit.useStoreState(store, 'open'); const [state, setState] = useState('closed'); // Sets the state on the next animation frame so that the transition can be // triggered. useEffect(() => { const raf = requestAnimationFrame(() => { setState(open ? 'open' : 'closed'); }); return () => cancelAnimationFrame(raf); }, [open]); const classNames = getClassNames('modal__dialog', className, placement); const context = useContext(ModalContext); if (!mounted) { return null; } return ( :
} autoFocusOnShow={() => { const type = 'focusScope.autoFocusOnMount'; const event = new CustomEvent(type, { cancelable: true }); const cb = onOpenAutoFocus || (() => { }); contentElement?.addEventListener(type, cb, { once: true }); contentElement?.dispatchEvent(event); return !event.defaultPrevented; }} autoFocusOnHide={() => { const type = 'focusScope.autoFocusOnUnmount'; const event = new CustomEvent(type, { cancelable: true }); const cb = onCloseAutoFocus || (() => { }); contentElement?.addEventListener(type, cb, { once: true }); contentElement?.dispatchEvent(event); return !event.defaultPrevented; }} hideOnEscape={(event: KeyboardEvent | React.KeyboardEvent) => { if (!onEscapeKeyDown) return true; const nativeEvent = event instanceof KeyboardEvent ? event : event.nativeEvent; onEscapeKeyDown(nativeEvent); return !event.defaultPrevented; }} hideOnInteractOutside={(originalEvent: Event | React.SyntheticEvent) => { // Ariakit doesn't hide dialogs on pointer down (although this can be // implemented in userland). So we treat onPointerDownOutside and // onInteractOutside the same. const type = 'dismissableLayer.pointerDownOutside'; const detail = { originalEvent }; const event = new CustomEvent(type, { detail, cancelable: true }); const cb = (evt: Event) => { onPointerDownOutside?.(evt); onInteractOutside?.(evt); }; contentElement?.addEventListener(type, cb, { once: true }); contentElement?.dispatchEvent(event); return !event.defaultPrevented; }} /> ); } );