Skip to content

Commit

Permalink
feat(Modal): open programmatically (#1319)
Browse files Browse the repository at this point in the history
  • Loading branch information
noook committed Feb 7, 2024
1 parent 98a2d0f commit 6f29c62
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<span v-html="title" />
</template>
</UNotifications>
<UModals />
</div>
</template>

Expand Down
17 changes: 17 additions & 0 deletions docs/components/content/examples/ModalExampleComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts" setup>
defineProps({
count: {
type: Number,
default: 0
}
})
</script>

<template>
<UModal>
<UCard>
<p>This modal was opened programmatically !</p>
<p>Count: {{ count }}</p>
</UCard>
</UModal>
</template>
17 changes: 17 additions & 0 deletions docs/components/content/examples/ModalExampleComposable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { ModalExampleComponent } from '#components'
const modal = useModal()
const count = ref(0)
function openModal () {
count.value += 1
modal.open(ModalExampleComponent, {
count: count.value
})
}
</script>

<template>
<UButton label="Reveal modal" @click="openModal" />
</template>
20 changes: 20 additions & 0 deletions docs/content/2.components/modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ Set the `fullscreen` prop to `true` to enable it.

:component-example{component="modal-example-fullscreen"}

### Control programmatically

First of all, add the `Modals` component to your app, preferably inside `app.vue`.

```vue [app.vue]
<template>
<div>
<UContainer>
<NuxtPage />
</UContainer>
<UModals />
</div>
</template>
```

Then, you can use the `useModal` composable to control your modals within your app.

:component-example{component="modal-example-composable"}

## Props

:component-props
Expand Down
4 changes: 4 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export default defineNuxtModule<ModuleOptions>({
src: resolve(runtimeDir, 'plugins', 'colors')
})

addPlugin({
src: resolve(runtimeDir, 'plugins', 'modals')
})

// Components

addComponentsDir({
Expand Down
12 changes: 12 additions & 0 deletions src/runtime/components/overlays/Modals.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<component :is="modalState.component" v-bind="modalState.props" v-model="isOpen" />
</template>

<script lang="ts" setup>
import { inject } from 'vue'
import { useModal, modalInjectionKey } from '../../composables/useModal'
const modalState = inject(modalInjectionKey)
const { isOpen } = useModal()
</script>
51 changes: 51 additions & 0 deletions src/runtime/composables/useModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ref, inject } from 'vue'
import type { ShallowRef, Component, InjectionKey } from 'vue'
import { createSharedComposable } from '@vueuse/core'
import type { ComponentProps } from '../types/component'
import type { Modal, ModalState } from '../types/modal'

export const modalInjectionKey: InjectionKey<ShallowRef<ModalState>> = Symbol('nuxt-ui.modal')

function _useModal () {
const modalState = inject(modalInjectionKey)

const isOpen = ref(false)

function open<T extends Component> (component: T, props?: Modal & ComponentProps<T>) {
modalState.value = {
component,
props: props ?? {}
}
isOpen.value = true
}

function close () {
isOpen.value = false
modalState.value = {
component: 'div',
props: {}
}
}

/**
* Allows updating the modal props
*/
function patch <T extends Component = {}> (props: Partial<Modal & ComponentProps<T>>) {
modalState.value = {
...modalState.value,
props: {
...modalState.value.props,
...props
}
}
}

return {
isOpen,
open,
close,
patch
}
}

export const useModal = createSharedComposable(_useModal)
13 changes: 13 additions & 0 deletions src/runtime/plugins/modals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineNuxtPlugin } from '#imports'
import { shallowRef } from 'vue'
import { modalInjectionKey } from '../composables/useModal'
import type { ModalState } from '../types/modal'

export default defineNuxtPlugin((nuxtApp) => {
const modalState = shallowRef<ModalState>({
component: 'div',
props: {}
})

nuxtApp.vueApp.provide(modalInjectionKey, modalState)
})
14 changes: 14 additions & 0 deletions src/runtime/types/component.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type ComponentProps<T> =
T extends new () => { $props: infer P } ? NonNullable<P> :
T extends (props: infer P, ...args: any) => any ? P :
{}

export type ComponentSlots<T> =
T extends new () => { $slots: infer S } ? NonNullable<S> :
T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any ? NonNullable<S> :
{}

export type ComponentEmit<T> =
T extends new () => { $emit: infer E } ? NonNullable<E> :
T extends (props: any, ctx: { slots: any; attrs: any; emit: infer E }, ...args: any) => any ? NonNullable<E> :
{}
1 change: 1 addition & 0 deletions src/runtime/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './input'
export * from './kbd'
export * from './link'
export * from './meter'
export * from './modal'
export * from './notification'
export * from './popper'
export * from './progress'
Expand Down
18 changes: 18 additions & 0 deletions src/runtime/types/modal.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Component } from 'vue'

export interface Modal {
appear?: boolean
overlay?: boolean
transition?: boolean
preventClose?: boolean
fullscreen?: boolean
class?: string | Object | string[]
ui?: any
onClose?: () => void
onClosePrevented?: () => void
}

export interface ModalState {
component: Component | string
props: Modal
}

0 comments on commit 6f29c62

Please sign in to comment.