diff --git a/playground/components/ModalProgrammaticExample.vue b/playground/components/ModalProgrammaticExample.vue new file mode 100644 index 00000000..1b1749ac --- /dev/null +++ b/playground/components/ModalProgrammaticExample.vue @@ -0,0 +1,18 @@ + + + diff --git a/playground/pages/modal.vue b/playground/pages/modal.vue index 8955b673..a6fb83f9 100644 --- a/playground/pages/modal.vue +++ b/playground/pages/modal.vue @@ -1,5 +1,17 @@ diff --git a/src/module.ts b/src/module.ts index 77d54c5c..43e5fc16 100644 --- a/src/module.ts +++ b/src/module.ts @@ -51,6 +51,9 @@ export default defineNuxtModule({ addPlugin({ src: resolve('./runtime/plugins/colors') }) + addPlugin({ + src: resolve('./runtime/plugins/modal') + }) addComponentsDir({ path: resolve('./runtime/components'), diff --git a/src/runtime/components/App.vue b/src/runtime/components/App.vue index 6d9a79df..2010e0bd 100644 --- a/src/runtime/components/App.vue +++ b/src/runtime/components/App.vue @@ -13,7 +13,7 @@ import { toRef } from 'vue' import { ConfigProvider, TooltipProvider, useForwardProps } from 'radix-vue' import { reactivePick } from '@vueuse/core' import { useId } from '#imports' -import { UToaster } from '#components' +import { UToaster, UModalProvider } from '#components' const props = withDefaults(defineProps(), { useId: () => useId() @@ -31,5 +31,7 @@ const toasterProps = toRef(() => props.toaster) + + diff --git a/src/runtime/components/ModalProvider.vue b/src/runtime/components/ModalProvider.vue new file mode 100644 index 00000000..9d7adb2b --- /dev/null +++ b/src/runtime/components/ModalProvider.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/runtime/composables/useModal.ts b/src/runtime/composables/useModal.ts new file mode 100644 index 00000000..e6a74bdd --- /dev/null +++ b/src/runtime/composables/useModal.ts @@ -0,0 +1,71 @@ +import { ref, inject } from 'vue' +import type { ShallowRef, Component, InjectionKey } from 'vue' +import { createSharedComposable } from '@vueuse/core' +import type { ModalProps } from '#ui/types' +import type { ComponentProps } from '#ui/types/component' + +export interface ModalState { + component: Component | string + props: ModalProps +} + +export const modalInjectionKey: InjectionKey> = Symbol('nuxt-ui.modal') + +function _useModal() { + const modalState = inject(modalInjectionKey) + + const isOpen = ref(false) + + function open(component: T, props?: ModalProps & ComponentProps) { + if (!modalState) { + throw new Error('useModal() is called without provider') + } + + modalState.value = { + component, + props: props ?? {} + } + + isOpen.value = true + } + + async function close() { + if (!modalState) return + + isOpen.value = false + } + + function reset() { + if (!modalState) return + + modalState.value = { + component: 'div', + props: {} + } + } + + /** + * Allows updating the modal props + */ + function patch>(props: Partial>) { + if (!modalState) return + + modalState.value = { + ...modalState.value, + props: { + ...modalState.value.props, + ...props + } + } + } + + return { + open, + close, + reset, + patch, + isOpen + } +} + +export const useModal = createSharedComposable(_useModal) diff --git a/src/runtime/plugins/modal.ts b/src/runtime/plugins/modal.ts new file mode 100644 index 00000000..be80dbe6 --- /dev/null +++ b/src/runtime/plugins/modal.ts @@ -0,0 +1,11 @@ +import { shallowRef } from 'vue' +import { defineNuxtPlugin } from '#imports' +import { modalInjectionKey, type ModalState } from '../composables/useModal' + +export default defineNuxtPlugin((nuxtApp) => { + const modalState = shallowRef({ + component: 'div', + props: {} + }) + nuxtApp.vueApp.provide(modalInjectionKey, modalState) +}) diff --git a/src/runtime/types/component.d.ts b/src/runtime/types/component.d.ts new file mode 100644 index 00000000..6babe7bd --- /dev/null +++ b/src/runtime/types/component.d.ts @@ -0,0 +1,14 @@ +export type ComponentProps = +T extends new () => { $props: infer P } ? NonNullable

: + T extends (props: infer P, ...args: any) => any ? P : + Record + +export type ComponentSlots = +T extends new () => { $slots: infer S } ? NonNullable : + T extends (props: any, ctx: { slots: infer S, attrs: any, emit: any }, ...args: any) => any ? NonNullable : + Record + +export type ComponentEmit = +T extends new () => { $emit: infer E } ? NonNullable : + T extends (props: any, ctx: { slots: any, attrs: any, emit: infer E }, ...args: any) => any ? NonNullable : + Record