mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-29 11:20:36 +01:00
feat(Modal): open programmatically (#1319)
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
<span v-html="title" />
|
<span v-html="title" />
|
||||||
</template>
|
</template>
|
||||||
</UNotifications>
|
</UNotifications>
|
||||||
|
<UModals />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
17
docs/components/content/examples/ModalExampleComponent.vue
Normal file
17
docs/components/content/examples/ModalExampleComponent.vue
Normal file
@@ -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
docs/components/content/examples/ModalExampleComposable.vue
Normal file
17
docs/components/content/examples/ModalExampleComposable.vue
Normal file
@@ -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>
|
||||||
@@ -59,6 +59,26 @@ Set the `fullscreen` prop to `true` to enable it.
|
|||||||
|
|
||||||
:component-example{component="modal-example-fullscreen"}
|
: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
|
## Props
|
||||||
|
|
||||||
:component-props
|
:component-props
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
src: resolve(runtimeDir, 'plugins', 'colors')
|
src: resolve(runtimeDir, 'plugins', 'colors')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
addPlugin({
|
||||||
|
src: resolve(runtimeDir, 'plugins', 'modals')
|
||||||
|
})
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
||||||
addComponentsDir({
|
addComponentsDir({
|
||||||
|
|||||||
12
src/runtime/components/overlays/Modals.vue
Normal file
12
src/runtime/components/overlays/Modals.vue
Normal file
@@ -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
src/runtime/composables/useModal.ts
Normal file
51
src/runtime/composables/useModal.ts
Normal file
@@ -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
src/runtime/plugins/modals.ts
Normal file
13
src/runtime/plugins/modals.ts
Normal file
@@ -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
src/runtime/types/component.d.ts
vendored
Normal file
14
src/runtime/types/component.d.ts
vendored
Normal file
@@ -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
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -16,6 +16,7 @@ export * from './input'
|
|||||||
export * from './kbd'
|
export * from './kbd'
|
||||||
export * from './link'
|
export * from './link'
|
||||||
export * from './meter'
|
export * from './meter'
|
||||||
|
export * from './modal'
|
||||||
export * from './notification'
|
export * from './notification'
|
||||||
export * from './popper'
|
export * from './popper'
|
||||||
export * from './progress'
|
export * from './progress'
|
||||||
|
|||||||
18
src/runtime/types/modal.d.ts
vendored
Normal file
18
src/runtime/types/modal.d.ts
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user