
Research
6 Malicious Packagist Themes Ship Trojanized jQuery and FUNNULL Redirect Payloads
Six malicious Packagist packages posing as OphimCMS themes contain trojanized jQuery that exfiltrates URLs, injects ads, and loads FUNNULL-linked redirects.
pnpm add pushmodal
We take for granted that you already have
@radix-ui/react-dialoginstalled. If not ➡️pnpm add @radix-ui/react-dialog
When creating a dialog/sheet/drawer you need to wrap your component with the <(Dialog|Sheet|Drawer)Content> component. But skip the Root since we do that for you.
// file: src/modals/modal-example.tsx
import { DialogContent } from '@/ui/dialog' // shadcn dialog
// or any of the below
// import { SheetContent } from '@/ui/sheet' // shadcn sheet
// import { DrawerContent } from '@/ui/drawer' // shadcn drawer
export default function ModalExample({ foo }: { foo: string }) {
return (
<DialogContent>
Your modal
</DialogContent>
)
}
// file: src/modals/index.tsx (alias '@/modals')
import ModalExample from './modal-example'
import SheetExample from './sheet-example'
import DrawerExample from './drawer-examle'
import { createPushModal } from 'pushmodal'
import { Drawer } from '@/ui/drawer' // shadcn drawer
export const {
pushModal,
popModal,
popAllModals,
replaceWithModal,
useOnPushModal,
onPushModal,
ModalProvider
} = createPushModal({
modals: {
// Short hand
ModalExample,
SheetExample,
// Longer definition where you can choose what wrapper you want
// Only needed if you don't want `Dialog.Root` from '@radix-ui/react-dialog'
// shadcn drawer needs a custom Wrapper
DrawerExample: {
Wrapper: Drawer,
Component: DrawerExample
}
},
})
How we usually structure things
src
├── ...
├── modals
│ ├── modal-example.tsx
│ ├── sheet-example.tsx
│ ├── drawer-examle.tsx
│ ├── ... more modals here ...
│ └── index.tsx
├── ...
└── ...
<ModalProvider /> to your root file.import { ModalProvider } from '@/modals'
export default function App({ children }: { children: React.ReactNode }) {
return (
<>
{/* Notice! You should not wrap your children */}
<ModalProvider />
{children}
</>
)
}
pushModalpushModal can have 1-2 arguments
name - name of your modalprops (might be optional) - props for your modal, types are infered from your component!import { pushModal } from '@/modals'
export default function RandomComponent() {
return (
<div>
<button onClick={() => pushModal('ModalExample', { foo: 'string' })}>
Open modal
</button>
<button onClick={() => pushModal('SheetExample')}>
Open Sheet
</button>
<button onClick={() => pushModal('DrawerExample')}>
Open Drawer
</button>
</div>
)
}
You can close a modal in three different ways:
popModal() - will pop the last added modalpopModal('Modal1') - will pop the last added modal with name Modal1popAllModals() - will close all your modalsReplace the last pushed modal. Same interface as pushModal.
replaceWithModal('SheetExample', { /* Props if any */ })
You can listen to events with useOnPushModal (inside react component) or onPushModal (or globally).
The event recieve the state of the modal (open/closed), the modals name and props. You can listen to all modal changes with * or provide a name of the modal you want to listen on.
Inside a component
import { useCallback } from 'react'
import { useOnPushModal } from '@/modals'
// file: a-react-component.tsx
export default function ReactComponent() {
// listen to any modal open/close
useOnPushModal('*',
useCallback((open, props, name) => {
console.log('is open?', open);
console.log('props from component', props);
console.log('name', name);
}, [])
)
// listen to `ModalExample` open/close
useOnPushModal('ModalExample',
useCallback((open, props) => {
console.log('is `ModalExample` open?', open);
console.log('props for ModalExample', props);
}, [])
)
}
In some cases you want to show a drawer on mobile and a dialog on desktop. This is possible and we have created a helper function to get you going faster. createResponsiveWrapper 💪
// path: src/modals/dynamic.tsx
import { createResponsiveWrapper } from 'pushmodal'
import { Dialog, DialogContent } from '@/ui/dialog'; // shadcn dialog
import { Drawer, DrawerContent } from '@/ui/drawer'; // shadcn drawer
export default createResponsiveWrapper({
desktop: {
Wrapper: Dialog,
Content: DialogContent,
},
mobile: {
Wrapper: Drawer,
Content: DrawerContent,
},
breakpoint: 640,
});
// path: src/modals/your-modal.tsx
import * as Dynamic from './dynamic'
export default function YourModal() {
return (
<Dynamic.Content>
Drawer in mobile and dialog on desktop 🤘
</Dynamic.Content>
)
}
// path: src/modals/index.ts
import * as Dynamic from './dynamic'
import YourModal from './your-modal'
import { createPushModal } from 'pushmodal'
export const {
pushModal,
popModal,
popAllModals,
replaceWithModal,
useOnPushModal,
onPushModal,
ModalProvider
} = createPushModal({
modals: {
YourModal: {
Wrapper: Dynamic.Wrapper,
Component: YourModal
}
},
})
Globally
import { onPushModal } from '@/modals'
const unsub = onPushModal('*', (open, props, name) => {
// do stuff
})
Issues or limitations will be listed here.
FAQs
Handle shadcn dialog, sheet and drawer with ease
The npm package pushmodal receives a total of 3,144 weekly downloads. As such, pushmodal popularity was classified as popular.
We found that pushmodal demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Six malicious Packagist packages posing as OphimCMS themes contain trojanized jQuery that exfiltrates URLs, injects ads, and loads FUNNULL-linked redirects.

Security News
The GCVE initiative operated by CIRCL has officially opened its publishing ecosystem, letting organizations issue and share vulnerability identifiers without routing through a central authority.

Security News
The project is retiring its odd/even release model in favor of a simpler annual cadence where every major version becomes LTS.