@locmod/modal
TypeScript icon, indicating that this package has built-in type declarations

3.0.1 • Public • Published

@locmod/modal

Installation

npm install --save @locmod/modal

Structure

Modal system consists of:

  • manager (controls the state)
  • and public methods and types:
global type ModalsRegistry Provides modal props mapping for all other methods: { [modalName: string]: {...} }
global type ExtendModalsRegistry Type helper. Use in modal cmponents to add mapping of modal name & props to ModalsRegistry. See Typecheking section for details
type ModalName Registered modal names keyof ModalsRegistry
type ModalComponentProps<YourModalProps> helper type to use for modals, combine your business-logic props with props provided by ModalRenderer / standaloneModal. See ModalComponentProps section for details.
type ModalComponent<YourModalProps> helper type to use for modals, combine your business-logic props with ModalComponentProps
openModal(name: ModalName) Opens a modal by name
closeModal(name: ModalName) Closes all modals by name
registerModals(registry: Record<string, ModalComponent>) Registers modal to render by ModalRenderer. See Registry section for details
getOpenModalNames() Returns an array of open modal names
<ModalsRenderer /> Handles render of all modals which added by registerModals, should be added to App render
standaloneModal(string: ModalName, ModalComponent) HOC. It handles render of wrapped modal. Should be added to render manually. Use for specific modals, e.g. a modal requires some non-global Context that isn't accessible from ModalsRenderer

Difference of standalone modals

  • ModalsRenderer skips (doesn't render) modals which are wrapped by this HOC
  • so you don't need to register it by registerModals
  • you must put this modal in render manually

Use for specific modals, e.g. a modal requires some non-global Context that isn't accessible from ModalsRenderer

Examples

See live examples on codesandbox.io:

Manager

Controls modals system state, provides event listeners and emit events. Controls modals registry.

Only some methods of the manager are public.

How to

ModalComponentProps

When you build your modal, even if you don't need business logic props, you have additional props which are provided by ModalRenderer / standaloneModal - use type ModalComponentProps:

import { type ModalComponentProps } from '@locmod/modal'

/*
*  type ModalComponentProps = {
*    name: string
*    closeModal: (withOnClose?: boolean) => void
*    onClose?: () => void
*  }
*/

const ExampleModal: React.FC<ModalComponentProps> = (props) => {
  const { closeModal } = props
  
  const handleClick = () => {
    // closeModal has a boolean argument "withOnClose"
    // if it's true, it will trigger the "onClose" prop, if passed
    closeModal(true)
  }
  
  return (
    <div>
      <button onClick={handleClick}>Ok</button>
    </div>
  )
}

How to open and close modals

To control modals you have openModal and closeModal helpers:

import { openModal, closeModal } from '@locmod/modal'

openModal('ExampleModal') // just open a modal that is registered by registerModals() or rendered by standaloneModal

// it returns a unique closer (is the same as closeModal in Modal component)
const closeThisExactModal = openModal('ExampleModal', { 
  // will be triggered by closeThisExactModal(true) or by closeModal(true) inside ExampleModal
  onClose: () => console.log('onclose triggered'),
})

// to close every possible common modal
closeModal('commonModal') // use with careful and only outside the modal itself

When you call openModal, it generates a unique id and an instance of a modal. Such behaviour helps to display multiple modals with the same base component (like CommonModal) and handle them separately.

If a modal has some required props, props argument in openModal is required too. All props will be passed to the modal component.

Registry

Registry is a simple record of modal components keyed by modal name. Each modal will be rendered only when it's opened by manager. If you don't want to load a modal immediately, provide a dynamic component.

const modalRegistry = {
  commonModal: CommonModal, // component
  lazyModal: React.lazy(() => import('compositions/modals/LazyModal/LazyModal')),
  loadableModal: loadable(() => import('compositions/modals/LazyModal/LazyModal')),
  nextjsDynamicModal: dynamic(() => import('compositions/modals/LazyModal/LazyModal')),
}

To register global modals (that could be opened from any place of app) in runtime use registerModals helper:

import { registerModals, ModalsRenderer } from '@locmod/modal'
import InfoModal from './InfoModal/InfoModal'
import ErrorModal from './ErrorModal/ErrorModal'

const modalRegistry = {
  InfoModal,
  ErrorModal,
}

// you can register modals permanently
registerModals(modalRegistry)

const App = () => {
  // or temporary in useEffect
  useEffect(() => {
    return registerModals(modalRegistry)
  }, [])

  return (
    <>
      <Head />
      <Content />
      {/* or via registry prop, which do the same in useEffect */}
      <ModalsRenderer registry={modalRegistry} />
    </>
  )
}

You shouldn't add modals wrapped by standaloneModal to registry, use standaloneModal for non-global modals that used in specific place, e.g. your modal requires some specific Context

Typechecking

To use registered modals in typechecking you need to extend global interface ModalsRegistry:

declare global {
  // dumb but works
  interface ModalsRegistry {
    newModal: { priority?: number }
  }
}

To generate properties from the component, use ExtendModalsRegistry helper. It automatically extracts all properties from the component and removes ModalComponentProps from them:

declare global {
  interface ModalsRegistry extends ExtendModalsRegistry<{ newModal: typeof NewModal }> {}
}

Readme

Keywords

Package Sidebar

Install

npm i @locmod/modal

Weekly Downloads

59

Version

3.0.1

License

MIT

Unpacked Size

20.3 kB

Total Files

9

Last publish

Collaborators

  • on47sky
  • clean_bread
  • irondsd
  • grammka