Skip to content

Commit

Permalink
Support for custom app mounting (see #54)
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet committed Nov 19, 2024
1 parent f45940f commit 4e9d4f2
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 5 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ jobs:
matrix:
inertia: [v1, v2]
stack: [react, vue]
app_mounting: [auto, custom]
exclude:
- inertia: v2
app_mounting: custom

env:
APP_URL: "http://127.0.0.1:8000"
Expand Down Expand Up @@ -99,6 +103,13 @@ jobs:
cd vue
npm run test
- name: Switch to custom app mounting
if: ${{ matrix.app_mounting == 'custom' }}
run: |
cd demo-app/resources/js
mv app-custom-mount.js app.js
mv app-custom-mount.jsx app.jsx
- name: Prepare Laravel app
run: |
cd demo-app
Expand Down
10 changes: 10 additions & 0 deletions demo-app/resources/js/ModalLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ModalRoot } from '@inertiaui/modal-react'

export default function Layout({ children }) {
return (
<>
{children}
<ModalRoot />
</>
)
}
40 changes: 40 additions & 0 deletions demo-app/resources/js/app-custom-mount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import './bootstrap'
import '../css/app.css'

import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import { ZiggyVue } from '../../vendor/tightenco/ziggy'
import { putConfig, initFromPageProps, ModalRoot } from '@inertiaui/modal-vue'

const appName = import.meta.env.VITE_APP_NAME || 'Laravel'

createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
initFromPageProps(props)

return createApp({ render: () => h(ModalRoot, () => h(App, props)) })
.use(plugin)
.use(ZiggyVue)
.mount(el)
},
progress: {
color: '#4B5563',
},
})

if(window.location.pathname === '/props-from-config') {
putConfig({
type: 'slideover',
slideover: {
closeButton: false,
closeExplicitly: true,
maxWidth: '2xl',
paddingClasses: 'p-8',
panelClasses: 'min-h-screen bg-red-100',
position: 'left',
}
})
}
44 changes: 44 additions & 0 deletions demo-app/resources/js/app-custom-mount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import './bootstrap';
import '../css/app.css';

import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { putConfig, ModalStackProvider, initFromPageProps, setPageLayout } from '@inertiaui/modal-react'
import ModalLayout from './ModalLayout';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx'))
.then(setPageLayout(ModalLayout)),
setup({ el, App, props }) {
const root = createRoot(el);

initFromPageProps(props);

root.render(
<ModalStackProvider>
<App {...props} />
</ModalStackProvider>
);
},
progress: {
color: '#4B5563',
},
});

if (window.location.pathname === '/props-from-config') {
putConfig({
type: 'slideover',
slideover: {
closeButton: false,
closeExplicitly: true,
maxWidth: '2xl',
paddingClasses: 'p-8',
panelClasses: 'min-h-screen bg-red-100',
position: 'left',
}
})
}
6 changes: 6 additions & 0 deletions docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export default defineConfig({
{text: 'Reload Props', link: '/reload-props'},
{text: 'Local Modals', link: '/local-modals'},
{text: 'Styling', link: '/styling'},
]
},
{
text: 'Advanced',
items: [
{text: 'Custom App Mounting', link: '/custom-app-mounting'},
{text: 'Headless Mode', link: '/headless-mode'},
]
}
Expand Down
88 changes: 88 additions & 0 deletions docs/custom-app-mounting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Custom App Mounting

If you need more refined control over the mounting process, contrary to the regular [Inertia.js installation](/installation.html#inertia-js-configuration), you may ignore the `renderApp` helper method and perform the mounting manually.

## React

In React, you need to make changes to the `app.jsx` file and use a Layout component that renders the `ModalRoot` component after your main content.

### Create a Layout Component

First, create a Layout component that renders the `ModalRoot` component after your main content. In this example, we are naming the component `ModalLayout.jsx`:

```jsx [React]
import { ModalRoot } from '@inertiaui/modal-react'

export default function Layout({ children }) {
return (
<>
{children}
<ModalRoot />
</>
)
}
```

### Update `app.jsx`

First, import your new Layout component and set it as the layout for your pages:

```jsx [React]
createInertiaApp({
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx'))
.then(function (page) { // [!code ++]
page.default.layout = (page => <ModalLayout children={page} />) // [!code ++]
return page; // [!code ++]
}), // [!code ++]
});
```
If you find this cumbersome, you can also use the `setPageLayout` helper function to set the layout for all pages:
```jsx [React]
import { setPageLayout } from '@inertiaui/modal-react' // [!code ++]

createInertiaApp({
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx'))
.then(setPageLayout(ModalLayout)), // [!code ++]
});
```
Next, you need to call the `initFromPageProps` method with the `props` object. Lastly, you need to wrap the `App` component within the `ModalStackProvider` component:
```jsx [React]
import { ModalStackProvider, initFromPageProps } from '@inertiaui/modal-react' // [!code ++]

createInertiaApp({
setup({ el, App, props }) {
const root = createRoot(el);
initFromPageProps(props) // [!code ++]

root.render(
<ModalStackProvider> // [!code ++]
<App {...props} />
</ModalStackProvider> // [!code ++]
);
}
});
```
## Vue
In Vue, it is a little bit simpler because you only need to make changes to the main `app.js` file. In this file, you need to call the `initFromPageProps` method with the `props` object. Then, you need to wrap the `App` component within the `ModalRoot` component:
```js [Vue]
import { ModalRoot, initFromPageProps } from '@inertiaui/modal-vue' // [!code ++]

createInertiaApp({
setup({ el, App, props, plugin }) {
initFromPageProps(props) // [!code ++]

return
createApp({ render: () => h(App, props) }) // [!code --]
createApp({ render: () => h(ModalRoot, () => h(App, props)) }) // [!code ++]
.use(plugin)
.mount(el)
}
})
```
2 changes: 2 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ createInertiaApp({

:::

If you need more refined control over the mounting process, you should check out the [Custom App Mounting](/custom-app-mounting.md) documentation.

## Tailwind Configuration

Inertia Modal uses Tailwind CSS for styling. You need to include the package path in the *content* array of your `tailwind.config.js` file:
Expand Down
6 changes: 5 additions & 1 deletion react/src/ModalRoot.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,14 +410,18 @@ export const useModalStack = () => {

export const modalPropNames = ['closeButton', 'closeExplicitly', 'maxWidth', 'paddingClasses', 'panelClasses', 'position', 'slideover']

export const renderApp = (App, pageProps) => {
export const initFromPageProps = (pageProps) => {
if (pageProps.initialPage) {
pageVersion = pageProps.initialPage.version
}

if (pageProps.resolveComponent) {
resolveComponent = pageProps.resolveComponent
}
}

export const renderApp = (App, pageProps) => {
initFromPageProps(pageProps)

const renderInertiaApp = ({ Component, props, key }) => {
const renderComponent = () => {
Expand Down
25 changes: 23 additions & 2 deletions react/src/inertiauiModal.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { createElement } from 'react'
import { getConfig, putConfig, resetConfig } from './config.js'
import { useModalIndex } from './ModalRenderer.jsx'
import { useModalStack, ModalRoot, ModalStackProvider, renderApp } from './ModalRoot.jsx'
import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps } from './ModalRoot.jsx'
import HeadlessModal from './HeadlessModal.jsx'
import Modal from './Modal.jsx'
import ModalLink from './ModalLink.jsx'
import useModal from './useModal.js'

export { getConfig, putConfig, resetConfig, useModalStack, useModalIndex, HeadlessModal, Modal, ModalLink, ModalRoot, ModalStackProvider, renderApp, useModal }
const setPageLayout = (layout) => (module) => {
module.default.layout = (page) => createElement(layout, { children: page })
return module
}

export {
getConfig,
putConfig,
resetConfig,
useModalStack,
useModalIndex,
HeadlessModal,
Modal,
ModalLink,
ModalRoot,
ModalStackProvider,
renderApp,
useModal,
initFromPageProps,
setPageLayout,
}
4 changes: 2 additions & 2 deletions vue/src/inertiauiModal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getConfig, putConfig, resetConfig } from './config.js'
import { useModalStack, renderApp } from './modalStack.js'
import { useModalStack, initFromPageProps, renderApp } from './modalStack.js'
import HeadlessModal from './HeadlessModal.vue'
import Modal from './Modal.vue'
import ModalLink from './ModalLink.vue'
Expand Down Expand Up @@ -32,4 +32,4 @@ function visitModal(url, options = {}) {
})
}

export { HeadlessModal, Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig, visitModal, renderApp, useModal }
export { HeadlessModal, Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig, visitModal, renderApp, useModal, initFromPageProps }
6 changes: 6 additions & 0 deletions vue/src/modalStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const setComponentResolver = (resolver) => {
resolveComponent = resolver
}

export const initFromPageProps = (pageProps) => {
if (pageProps.resolveComponent) {
resolveComponent = pageProps.resolveComponent
}
}

class Modal {
constructor(component, response, config, onClose, afterLeave) {
this.id = Modal.generateId()
Expand Down

0 comments on commit 4e9d4f2

Please sign in to comment.