@react-crates/modal
Version:
리액트 모달을 쉽게 등록하고 사용가능한 라이브러리입니다.
507 lines (486 loc) • 20.3 kB
TypeScript
/// <reference types="react" />
import * as react from 'react';
import { FC, ReactNode, CSSProperties, FocusEventHandler, ReactElement, ButtonHTMLAttributes, ElementType, ComponentPropsWithoutRef, Fragment } from 'react';
type ModalLifecycleState = "open" | "active" | "close";
interface ModalTransition {
transitionProperty: string;
transitionDuration: string;
transitionTimingFunction: string;
transitionDelay: string;
}
type ModalTransitionProps = {
[key in keyof ModalTransition]?: ModalTransition[key];
};
type DefaultModalPosition = "default" | "bottom" | "top" | "left" | "right" | "center" | "leftTop" | "leftBottom" | "rightTop" | "rightBottom";
interface PositionStyle {
left?: string;
right?: string;
top?: string;
bottom?: string;
transform?: string;
opacity?: number;
background?: string;
className?: string;
}
type ModalPositionStyle = {
[key in ModalLifecycleState]: PositionStyle;
};
type ModalPositionTable<T extends string = string> = {
[key in DefaultModalPosition | T]: ModalPositionStyle;
};
type ModalPositionMap<T extends string = string> = Map<T | DefaultModalPosition, ModalPositionStyle>;
type CombinedPosition<T extends string> = T | `${T}-${T}` | `${T}-${T}-${T}`;
type ModalPosition<T extends string = DefaultModalPosition> = ((breakPoint: number) => CombinedPosition<DefaultModalPosition | T>) | CombinedPosition<DefaultModalPosition | T>;
type ModalTransitionOptions = Omit<ModalTransitionProps, "transitionDuration">;
type ModalConfirmType = string | boolean;
type ModalActionState = "initial" | "pending" | "success" | "error" | "final";
interface ComponentPropsOptions {
title?: ReactNode;
subTitle?: ReactNode;
content?: ReactNode;
subContent?: ReactNode;
confirmContent?: ReactNode;
cancelContent?: ReactNode;
customActionContent?: ReactNode;
}
interface ModalComponentProps<T = any> extends ComponentPropsOptions {
action: (confirm?: string | boolean) => void;
actionState: ModalActionState;
currentPosition: string;
lifecycleState: ModalLifecycleState;
payload?: T;
}
type ModalComponent<T = any> = FC<ModalComponentProps<T>>;
interface ModalState {
component: ModalComponent;
isOpened: boolean;
isActive: boolean;
actionState: ModalActionState;
modalClassName?: string;
modalStyle: CSSProperties;
backCoverStyle: CSSProperties;
componentProps: ModalComponentProps;
isEscKeyActive: boolean;
label: string;
role: string;
disableFocusHandling: boolean;
}
interface ModalProps {
id: number;
modalKey: string | null;
name: string;
component: ModalComponent;
options: ModalOptions<any>;
}
declare class Modal$1 {
private manager;
private lifecycleState;
private actionState;
private actionCallback;
private afterCloseCallback;
private listeners;
private breakPoint;
private isInitial;
private stateResponsive;
private initialComponent;
private currentComponent;
private componentProps;
private escKeyActive;
private role;
private label;
private isOpened;
private isPostOpened;
private _id;
private _modalKey;
private _name;
private _options;
private _isCurrent;
private _isAwaitingConfirm;
private _isCloseDelay;
private _closeDelayDuration;
private _confirm;
private _onOpenAutoFocus;
private _state;
private _currentPosition;
private _disableFocusHandling;
componentRef: HTMLDivElement | null;
constructor({ id, modalKey, name, component, options }: ModalProps, manager: ModalManagerInterface);
private bind;
private setOption;
private initComponent;
get id(): number;
get options(): ModalOptions<any, string>;
get modalKey(): string | null;
get name(): string;
get component(): ModalComponent;
get confirm(): ModalConfirmType | undefined;
get isCurrent(): boolean;
get isAwaitingConfirm(): boolean;
get isCloseDelay(): boolean;
get closeDelayDuration(): number;
get callback(): ModalCallback;
get onOpenAutoFocus(): FocusEventHandler<HTMLDivElement> | undefined;
get state(): ModalState;
get currentPosition(): string;
private setComponentProps;
/**
* TO-DO
* modal에서는 그냥 바꾸는 로직만 있고 modalManager에서 처리할 것.
*/
private changeComponent;
private changeStateResponsiveComponent;
private changeState;
edit({ component, ...contents }: Omit<ModalEditOptions, 'zIndex'>): void;
getActionState(): ModalActionState;
getLifecycleState(): ModalLifecycleState;
init(): Promise<void>;
postOpen(callback?: () => void): void;
active(): void;
close(): Promise<boolean>;
blockCloseDelay(): this;
setCloseDelay(duration?: number): this;
updateIsCurrent(isCurrent: boolean): this;
getState(): ModalState;
subscribe(listener: (state: ModalState) => void): this;
unsubscribe(listener: (state: ModalState) => void): this;
notify(): this;
getMiddlewareProps(): ModalMiddlewareProps;
action(confirm?: ModalConfirmType, callback?: ModalCallback): Promise<boolean>;
initial(): this;
pending(message?: string | Omit<StateControllerOptions, "afterCloseCallback" | "isAwaitingConfirm">): this;
success(message?: string | StateControllerOptions | ((confirm?: ModalConfirmType) => void)): this;
error(message?: string | StateControllerOptions | ((confirm?: ModalConfirmType) => void)): this;
end(message?: string | Omit<StateControllerOptions, "component"> | ((confirm?: ModalConfirmType) => void)): this;
getModalStyle(): {
className?: string;
style: CSSProperties;
};
getBackCoverStyle(): CSSProperties;
setBreakPoint(breakPoint: number): void;
}
interface StateControllerOptions {
afterCloseCallback?: (confirm?: ModalConfirmType) => void;
isAwaitingConfirm?: boolean;
component?: string | ModalComponent;
options?: ComponentPropsOptions;
}
interface StateController {
initial: () => void;
pending: (message?: string | Omit<StateControllerOptions, "afterCloseCallback" | "isAwaitingConfirm">) => void;
success: (message?: string | StateControllerOptions | ((confirm?: ModalConfirmType) => unknown)) => void;
error: (message?: string | StateControllerOptions | ((confirm?: ModalConfirmType) => unknown)) => void;
end: (message?: string | StateControllerOptions | ((confirm?: ModalConfirmType) => unknown)) => void;
getLifecycleState: () => ModalLifecycleState;
getActionState: () => ModalActionState;
}
interface ModalMiddlewareProps {
modalState: Modal$1;
}
type ModalMiddleware = (props: ModalMiddlewareProps) => Promise<boolean>;
type ModalCallback = (confirm: ModalConfirmType | undefined, stateController: StateController) => any | Promise<any>;
type ModalTransactionState = "idle" | "standby" | "active";
interface ModalManagerState {
modalStack: Modal$1[];
breakPoint: number;
isOpen: boolean;
transactionState: ModalTransactionState;
currentModalId: number;
zIndex: number;
}
type ModalListener = (state: ModalManagerState) => void;
type ReservedModalName = "clear" | "unknown" | "open" | "close" | "edit" | "remove" | "action";
type ModalRemovedName = ReservedModalName | string | string[];
interface ModalManagerOptionsProps<T extends ModalPositionTable = ModalPositionTable> {
position?: T;
transition?: ModalTransitionOptions;
duration?: number;
backCoverColor?: string;
backCoverOpacity?: number;
stateResponsiveComponent?: boolean;
zIndex?: number;
}
type CloseModalProps = ModalRemovedName | number | [number, ModalRemovedName];
/**
* modalKey는 중복 방지
*/
interface ModalDispatchOptions<T = any, P extends string = string> extends ComponentPropsOptions {
modalKey?: string;
action?: ModalCallback;
middleware?: ModalMiddleware;
backCoverConfirm?: ModalConfirmType | null;
backCoverColor?: string;
backCoverOpacity?: number;
escKeyActive?: boolean;
payload?: T;
closeDelay?: number;
duration?: number;
transitionOptions?: ModalTransitionOptions;
position?: ModalPosition<P>;
stateResponsiveComponent?: boolean;
role?: string;
label?: string;
onOpenAutoFocus?: FocusEventHandler<HTMLDivElement>;
zIndex?: number;
disableFocusHandling?: boolean;
required?: boolean;
}
type ModalClose = (callback?: (confirm?: ModalConfirmType) => void, confirm?: ModalConfirmType) => Promise<boolean>;
interface ModalOptions<T = any, P extends string = string> extends ModalDispatchOptions<T, P> {
closeModal: ModalClose;
middleware: ModalMiddleware;
}
interface ModalEditOptions<T extends string = string> extends ComponentPropsOptions {
component?: ModalComponent;
action?: ModalCallback;
backCoverConfirm?: ModalConfirmType | null;
backCoverColor?: string;
backCoverOpacity?: number;
escKeyActive?: boolean;
closeDelay?: number;
duration?: number;
transitionOptions?: ModalTransitionOptions;
position?: ModalPosition<T>;
stateResponsiveComponent?: boolean;
payload?: any;
onOpenAutoFocus?: FocusEventHandler<HTMLDivElement>;
zIndex?: number;
}
interface ModalComponentSeed<T extends any = any, P extends string = string> {
name: string;
component: ModalComponent;
defaultOptions?: ModalDispatchOptions<T, P>;
}
interface ModalSeed<T extends ModalDispatchOptions = ModalOptions> {
id: number;
modalKey: string | null;
name: string;
component: ModalComponent<any>;
options: T;
}
type ModalMeta<T, P extends string = DefaultModalPosition> = {
component: ModalComponent<T>;
defaultOptions?: ModalDispatchOptions<T, P>;
};
type ModalComponentSeedTable<T extends string = string, P extends string = string> = {
[name in T]: ModalMeta<any, P>;
};
interface ModalManagerInterface {
getTransactionState: () => ModalTransactionState;
getCurrentModalPosition: (positionState: ModalLifecycleState, position?: string) => [PositionStyle, string];
getModalTransition: (duration?: number, options?: ModalTransitionOptions) => ModalTransition;
getModalComponentSeed: (name: string) => ModalComponentSeed | undefined;
executeAsync: <F = any, P = any>(asyncCallback: (props: P) => Promise<F>, asyncCallbackProps: P) => Promise<F>;
executeWithTransaction: <T = any>(asyncCallback: (props: T) => Promise<boolean>, asyncCallbackProps: T) => Promise<boolean>;
}
type Controller<T extends ModalComponentSeedTable, P extends ModalPositionTable> = {
[K in keyof T]: (options?: (T[K]["defaultOptions"] extends {
payload: infer R;
} ? Omit<ModalDispatchOptions<R, Exclude<Extract<keyof P, string>, "backCover" | "default">>, "required"> : Omit<ModalDispatchOptions<any, Exclude<Extract<keyof P, string>, "backCover" | "default">>, "required">) | ModalCallback | string) => number;
};
type ModalController<T extends ModalComponentSeedTable, P extends ModalPositionTable> = {
open: <K = any>(name: string | ModalComponent | ReactElement, options?: Omit<ModalDispatchOptions<K, Exclude<Extract<keyof P, string>, "backCover" | "default">>, "required"> | ModalCallback | string) => number;
remove: (closeTarget?: CloseModalProps) => number;
action: (targetModalId: number, confirm?: boolean | string) => Promise<boolean>;
} & Controller<T, P>;
declare class ModalManager<T extends ModalPositionTable = ModalPositionTable> implements ModalManagerInterface {
private currentId;
private transactionCount;
private transactionState;
private modalStack;
private listeners;
private modalComponentSeedMap;
private modalPositionMap;
private modalTransition;
private modalDuration;
private stateResponsiveComponent;
private breakPoint;
private originZindex;
private zIndex;
private modalManagerState;
constructor(baseModalComponentSeed?: ModalComponentSeed[], options?: ModalManagerOptionsProps<T>);
private bind;
initModalOptions(optionsProps: ModalManagerOptionsProps<T>): void;
private setModalManagerState;
private setModalComponentSeedMap;
setZIndex(zIndex?: number): void;
resetZIndex(): void;
setModalComponent(componentSeed: ModalComponentSeed | ModalComponentSeed[]): this;
removeModalComponent(name: string | string[]): this;
getModalComponentSeed(name: string): ModalComponentSeed<any, string> | undefined;
private createModalCloser;
setModalTransition(transitionProps?: ModalTransitionProps): this;
setModalDuration(duration?: number): this;
setModalPosition(modalPositionTable: ModalPositionTable): this;
setModalOptions<P extends ModalPositionTable = ModalPositionTable>(optionsProps: ModalManagerOptionsProps<T & P>): void;
getModalTransition(duration?: number, options?: ModalTransitionOptions): ModalTransition;
getModalPosition(key?: string): ModalPositionStyle;
getModalPositionMap(): ModalPositionMap;
getCurrentModalPosition(positionState: ModalLifecycleState, position?: string): [PositionStyle, string];
setTransactionState(transactionState: ModalTransactionState): number;
startTransaction(): number;
endTransaction(): number;
getTransactionState(): ModalTransactionState;
executeAsync<F = any, P = any>(asyncCallback: (props: P) => Promise<F>, asyncCallbackProps: P): Promise<F>;
executeWithTransaction<T = any>(callback: (props: T) => Promise<boolean>, callbackProps: T): Promise<boolean>;
subscribe(listener: ModalListener): this;
unsubscribe(listener: ModalListener): void;
notify(): void;
getState(): ModalManagerState;
getModalStack(): Modal$1[];
private createModal;
filterModalByName(name: string | string[]): this;
pushModal(modalSeed: ModalSeed<ModalDispatchOptions> | ModalSeed<ModalDispatchOptions>[]): void;
popModal(removedName?: ModalRemovedName): this;
clearModalStack(): this;
getCurrentModalId(): number;
/**
* action이 실행되지 않으면 false
* 성공적으로 실행되면 true;
* @param targetModalId
* @param confirm
* @returns
*/
action(targetModalId?: number, confirm?: ModalConfirmType): Promise<boolean>;
/**
* @param name
* @param options
* @returns 현재 등록된 모달의 id를 반환합니다. 만약 등록되지 않은 모달이라면 0을 반환합니다.
*/
open<P = any>(name: string | ModalComponent | ReactElement, action?: Omit<ModalDispatchOptions<P, Extract<keyof T, string>>, "required"> | ModalCallback | string): number;
/**
* @param closeTarget
* @returns 마지막으로 등록된 모달의 id를 반환합니다. 만약 등록된 모달이 없다면 0을 반환합니다.
*/
remove(closeTarget?: CloseModalProps): number;
edit(id: number, options: ModalEditOptions<Extract<keyof T, string>>): boolean;
setBreakPoint(breakPoint: number): void;
}
interface ModalProviderProps {
disableInteraction?: boolean;
children?: ReactNode;
}
interface ModalActionProps extends ButtonHTMLAttributes<HTMLButtonElement> {
confirmType?: ModalConfirmType;
}
interface ModalCustomActionProps extends ButtonHTMLAttributes<HTMLButtonElement> {
confirmType: ModalConfirmType;
}
interface DynamicModalTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
}
interface DynamicModalElementProps {
children: ReactElement;
}
interface DynamicModalProps<T extends string> extends ModalDispatchOptions<any, T> {
children: ReactNode;
}
type AsProp<T extends ElementType> = {
as?: T;
children?: ReactNode;
};
type PolymorphicComponentPropsWithoutRef<T extends ElementType> = AsProp<T> & ComponentPropsWithoutRef<T>;
type ModalContentElement = "div" | "p" | typeof Fragment;
type ModalTitleElement = "div" | "h1" | "h2" | "h3" | "h4" | typeof Fragment;
declare const Modal: {
Title: {
<T extends ModalTitleElement = "h2">({ as, children, ...restProps }: PolymorphicComponentPropsWithoutRef<T>): JSX.Element | null;
displayName: string;
Sub: {
<T_1 extends ModalTitleElement = "h3">({ as, children, ...restProps }: PolymorphicComponentPropsWithoutRef<T_1>): JSX.Element | null;
displayNmae: string;
};
};
Content: {
<T_2 extends ModalContentElement = "div">({ as, children, ...restProps }: PolymorphicComponentPropsWithoutRef<T_2>): JSX.Element | null;
displayName: string;
Sub: {
<T_3 extends ModalContentElement = "div">({ as, children, ...restProps }: PolymorphicComponentPropsWithoutRef<T_3>): JSX.Element | null;
displayName: string;
};
};
Action: {
({ onClick, children, confirmType, ...restProps }: ModalActionProps): JSX.Element | null;
displayName: string;
Confirm: react.ForwardRefExoticComponent<react.ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
Cancel: react.ForwardRefExoticComponent<react.ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
Custom: react.ForwardRefExoticComponent<ModalCustomActionProps & react.RefAttributes<HTMLButtonElement>>;
};
};
declare const modalCollection: {
confirm: {
component: ModalComponent;
defaultOptions: {
backCoverConfirm: boolean;
escKeyActive: boolean;
role: string;
label: string;
};
};
alert: {
component: ModalComponent;
defaultOptions: {
backCoverConfirm: boolean;
escKeyActive: boolean;
role: string;
label: string;
};
};
prompt: {
component: ModalComponent;
defaultOptions: {
backCoverConfirm: undefined;
escKeyActive: boolean;
role: string;
label: string;
};
};
};
interface TemplateProps {
children: ReactNode;
}
interface ModalTemplateProps extends TemplateProps {
className?: string;
}
declare const ModalTemplate: {
({ className, children }: ModalTemplateProps): JSX.Element;
Header: {
({ children }: TemplateProps): JSX.Element;
displayName: string;
};
Main: {
({ children }: TemplateProps): JSX.Element;
displayName: string;
};
Footer: {
({ children }: TemplateProps): JSX.Element;
displayName: string;
};
};
type ExtractPositionType<T extends ModalManagerOptionsProps> = T extends {
position?: infer R;
} ? R : ModalPositionTable<DefaultModalPosition>;
declare function generateModal<T extends ModalComponentSeedTable<string, Extract<keyof ExtractPositionType<P>, string>>, P extends ModalManagerOptionsProps = ModalManagerOptionsProps<ModalPositionTable<DefaultModalPosition>>>(modalComponentSeedTable?: T, options?: P): {
modalManager: ModalManager<ModalPositionTable>;
modalCtrl: ModalController<T, ExtractPositionType<P>>;
ModalProvider: (props: ModalProviderProps) => JSX.Element;
DynamicModal: {
({ children, ...options }: DynamicModalProps<Extract<keyof ExtractPositionType<P>, string>>): JSX.Element;
displayName: string;
Trigger: {
({ onClick, ...restProps }: DynamicModalTriggerProps): JSX.Element;
displayName: string;
};
Element: {
({ children }: DynamicModalElementProps): null;
displayName: string;
};
Action: {
({ onClick, children, confirmType, ...restProps }: ModalActionProps): JSX.Element | null;
displayName: string;
Confirm: react.ForwardRefExoticComponent<react.ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
Cancel: react.ForwardRefExoticComponent<react.ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
Custom: react.ForwardRefExoticComponent<ModalCustomActionProps & react.RefAttributes<HTMLButtonElement>>;
};
};
useIsOpenModal: () => boolean;
};
export { Modal, ModalCallback, ModalComponentSeed, ModalConfirmType, ModalComponent as ModalFC, ModalOptions, ModalTemplate, generateModal, modalCollection };