Skip to content

Commit

Permalink
Remove radix-vue dependency (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet authored Jan 7, 2025
1 parent b5b98d3 commit 55b1b33
Show file tree
Hide file tree
Showing 17 changed files with 695 additions and 703 deletions.
6 changes: 3 additions & 3 deletions demo-app/tests/Browser/ModalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function it_can_open_the_modal_and_close_it_with_the_close_button()
->assertSeeIn('.im-modal-content', 'Edit User')
->clickModalCloseButton()
->waitUntilMissingModal()
->assertMissing('#headlessui-portal-root');
->assertMissing('div[data-inertiaui-modal-id]');
});
}

Expand All @@ -38,7 +38,7 @@ public function it_can_close_the_modal_by_clicking_outside_of_it()
->waitForModal()
->clickAt(100, 100)
->waitUntilMissingModal()
->assertMissing('#headlessui-portal-root');
->assertMissing('div[data-inertiaui-modal-id]');
});
}

Expand All @@ -55,7 +55,7 @@ public function it_can_close_the_modal_with_a_custom_button()
->waitForModal()
->press('Cancel')
->waitUntilMissingModal()
->assertMissing('#headlessui-portal-root');
->assertMissing('div[data-inertiaui-modal-id]');
});
}
}
2 changes: 1 addition & 1 deletion docs/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ You're probably the fastest if you just inspect the modal in your browser and se

| Class | Description |
| --- | --- |
| `im-dialog` | The [Headless UI dialog](https://headlessui.com/v1/vue/dialog) component |
| `im-dialog` | The root container for the modal |
| `im-close-button` | The close button |
| `im-backdrop` | The backdrop behind the modal |
| `im-modal-container` | The screen-filling container for the modal |
Expand Down
680 changes: 396 additions & 284 deletions react/package-lock.json

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,21 @@
"eslint": "eslint \"src/**/*.{js,jsx}\" --fix",
"prettier": "prettier -w src/"
},
"dependencies": {
"devDependencies": {
"@headlessui/react": "^2.1.0",
"@heroicons/react": "^2.1.4",
"clsx": "^2.1.1"
},
"devDependencies": {
"@inertiajs/react": "^1.3.0||^2.0.0",
"@vitejs/plugin-react": "^4.3.1",
"axios": "^1.6.0",
"clsx": "^2.1.1",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.1.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-tailwindcss": "^3.15.1",
"eslint-plugin-unused-imports": "^3.1.0",
"eslint": "^8.57.0",
"focus-trap": "^7.6.2",
"prettier-plugin-tailwindcss": "^0.5.12",
"prettier": "^3.2.4",
"react-dom": "^18.2.0",
Expand Down
19 changes: 13 additions & 6 deletions react/src/Modal.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Dialog, Transition, TransitionChild } from '@headlessui/react'
import { forwardRef, useRef, useImperativeHandle } from 'react'
import { Transition, TransitionChild } from '@headlessui/react'
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react'
import HeadlessModal from './HeadlessModal'
import ModalContent from './ModalContent'
import SlideoverContent from './SlideoverContent'
import { modalDOMHandler } from './helpers'

const Modal = forwardRef(({ name, children, ...props }, ref) => {
const renderChildren = (contentProps) => {
Expand All @@ -15,6 +16,14 @@ const Modal = forwardRef(({ name, children, ...props }, ref) => {

const headlessModalRef = useRef(null)

useEffect(() => {
if (headlessModalRef?.current?.index === 0) {
modalDOMHandler.prepare()

return () => modalDOMHandler.cleanup()
}
}, [headlessModalRef])

useImperativeHandle(ref, () => headlessModalRef.current, [headlessModalRef])

return (
Expand Down Expand Up @@ -43,10 +52,8 @@ const Modal = forwardRef(({ name, children, ...props }, ref) => {
appear={true}
show={isOpen ?? false}
>
<Dialog
as="div"
<div
className="im-dialog relative z-20"
onClose={() => (config.closeExplicitly ? null : close())}
data-inertiaui-modal-id={id}
data-inertiaui-modal-index={index}
>
Expand Down Expand Up @@ -120,7 +127,7 @@ const Modal = forwardRef(({ name, children, ...props }, ref) => {
})}
</ModalContent>
)}
</Dialog>
</div>
</Transition>
)}
</HeadlessModal>
Expand Down
39 changes: 24 additions & 15 deletions react/src/ModalContent.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { TransitionChild, DialogPanel } from '@headlessui/react'
import { TransitionChild } from '@headlessui/react'
import CloseButton from './CloseButton'
import clsx from 'clsx'
import { useFocusTrap } from './useFocusTrap'

const ModalContent = ({ modalContext, config, children }) => {
const wrapper = useFocusTrap(config?.closeExplicitly, () => modalContext.close())

return (
<div className="im-modal-container fixed inset-0 z-40 overflow-y-auto p-4">
<div
Expand All @@ -13,32 +16,38 @@ const ModalContent = ({ modalContext, config, children }) => {
})}
>
<TransitionChild
as="div"
ref={wrapper}
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
afterLeave={modalContext.afterLeave}
className={clsx('im-modal-wrapper w-full transition duration-300 ease-in-out', modalContext.onTopOfStack ? '' : 'blur-sm', {
'sm:max-w-sm': config.maxWidth === 'sm',
'sm:max-w-md': config.maxWidth === 'md',
'sm:max-w-md md:max-w-lg': config.maxWidth === 'lg',
'sm:max-w-md md:max-w-xl': config.maxWidth === 'xl',
'sm:max-w-md md:max-w-xl lg:max-w-2xl': config.maxWidth === '2xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl': config.maxWidth === '3xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-4xl': config.maxWidth === '4xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl': config.maxWidth === '5xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-6xl': config.maxWidth === '6xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-7xl': config.maxWidth === '7xl',
})}
className={clsx(
'im-modal-wrapper pointer-events-auto w-full transition duration-300 ease-in-out',
modalContext.onTopOfStack ? '' : 'blur-sm',
{
'sm:max-w-sm': config.maxWidth === 'sm',
'sm:max-w-md': config.maxWidth === 'md',
'sm:max-w-md md:max-w-lg': config.maxWidth === 'lg',
'sm:max-w-md md:max-w-xl': config.maxWidth === 'xl',
'sm:max-w-md md:max-w-xl lg:max-w-2xl': config.maxWidth === '2xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl': config.maxWidth === '3xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-4xl': config.maxWidth === '4xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl': config.maxWidth === '5xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-6xl': config.maxWidth === '6xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-7xl': config.maxWidth === '7xl',
},
)}
>
<DialogPanel className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`}>
<div className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`}>
{config.closeButton && (
<div className="absolute right-0 top-0 pr-3 pt-3">
<CloseButton onClick={modalContext.close} />
</div>
)}
{typeof children === 'function' ? children({ modalContext, config }) : children}
</DialogPanel>
</div>
</TransitionChild>
</div>
</div>
Expand Down
39 changes: 24 additions & 15 deletions react/src/SlideoverContent.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { TransitionChild, DialogPanel } from '@headlessui/react'
import { TransitionChild } from '@headlessui/react'
import CloseButton from './CloseButton'
import clsx from 'clsx'
import { useFocusTrap } from './useFocusTrap'

const SlideoverContent = ({ modalContext, config, children }) => {
const wrapper = useFocusTrap(config?.closeExplicitly, () => modalContext.close())

return (
<div className="im-slideover-container fixed inset-0 z-40 overflow-y-auto overflow-x-hidden">
<div
Expand All @@ -12,32 +15,38 @@ const SlideoverContent = ({ modalContext, config, children }) => {
})}
>
<TransitionChild
as="div"
ref={wrapper}
enterFrom={`opacity-0 ${config.position === 'left' ? '-translate-x-full' : 'translate-x-full'}`}
enterTo="opacity-100 translate-x-0"
leaveFrom="opacity-100 translate-x-0"
leaveTo={`opacity-0 ${config.position === 'left' ? '-translate-x-full' : 'translate-x-full'}`}
afterLeave={modalContext.afterLeave}
className={clsx('im-slideover-wrapper w-full transition duration-300 ease-in-out', modalContext.onTopOfStack ? '' : 'blur-sm', {
'sm:max-w-sm': config.maxWidth === 'sm',
'sm:max-w-md': config.maxWidth === 'md',
'sm:max-w-md md:max-w-lg': config.maxWidth === 'lg',
'sm:max-w-md md:max-w-xl': config.maxWidth === 'xl',
'sm:max-w-md md:max-w-xl lg:max-w-2xl': config.maxWidth === '2xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl': config.maxWidth === '3xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-4xl': config.maxWidth === '4xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl': config.maxWidth === '5xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-6xl': config.maxWidth === '6xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-7xl': config.maxWidth === '7xl',
})}
className={clsx(
'im-slideover-wrapper pointer-events-auto w-full transition duration-300 ease-in-out',
modalContext.onTopOfStack ? '' : 'blur-sm',
{
'sm:max-w-sm': config.maxWidth === 'sm',
'sm:max-w-md': config.maxWidth === 'md',
'sm:max-w-md md:max-w-lg': config.maxWidth === 'lg',
'sm:max-w-md md:max-w-xl': config.maxWidth === 'xl',
'sm:max-w-md md:max-w-xl lg:max-w-2xl': config.maxWidth === '2xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl': config.maxWidth === '3xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-4xl': config.maxWidth === '4xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl': config.maxWidth === '5xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-6xl': config.maxWidth === '6xl',
'sm:max-w-md md:max-w-xl lg:max-w-3xl xl:max-w-5xl 2xl:max-w-7xl': config.maxWidth === '7xl',
},
)}
>
<DialogPanel className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`}>
<div className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`}>
{config.closeButton && (
<div className="absolute right-0 top-0 pr-3 pt-3">
<CloseButton onClick={modalContext.close} />
</div>
)}
{typeof children === 'function' ? children({ modalContext, config }) : children}
</DialogPanel>
</div>
</TransitionChild>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions react/src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { except, only, rejectNullValues, waitFor, kebabCase } from './../../vue/src/helpers.js'
export { except, only, rejectNullValues, waitFor, kebabCase }
import { modalDOMHandler, except, only, rejectNullValues, waitFor, kebabCase } from './../../vue/src/helpers.js'
export { modalDOMHandler, except, only, rejectNullValues, waitFor, kebabCase }
25 changes: 25 additions & 0 deletions react/src/useFocusTrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useRef } from 'react'
import { createFocusTrap } from 'focus-trap'

export function useFocusTrap(closeExplicitly, onDeactivateCallback) {
const wrapperRef = useRef(null)

useEffect(() => {
if (!wrapperRef.current) {
return
}

const trap = createFocusTrap(wrapperRef.current, {
clickOutsideDeactivates: !closeExplicitly,
escapeDeactivates: !closeExplicitly,
onDeactivate: () => onDeactivateCallback?.(),
fallbackFocus: () => wrapperRef.current,
})

trap.activate()

return () => trap.deactivate()
}, [])

return wrapperRef
}
Loading

0 comments on commit 55b1b33

Please sign in to comment.