From 6f29c620ab758e27be63f8af53674828b59fb6ed Mon Sep 17 00:00:00 2001 From: Neil Richter Date: Wed, 7 Feb 2024 16:53:17 +0100 Subject: [PATCH] feat(Modal): open programmatically (#1319) --- docs/app.vue | 1 + .../examples/ModalExampleComponent.vue | 17 +++++++ .../examples/ModalExampleComposable.vue | 17 +++++++ docs/content/2.components/modal.md | 20 ++++++++ src/module.ts | 4 ++ src/runtime/components/overlays/Modals.vue | 12 +++++ src/runtime/composables/useModal.ts | 51 +++++++++++++++++++ src/runtime/plugins/modals.ts | 13 +++++ src/runtime/types/component.d.ts | 14 +++++ src/runtime/types/index.d.ts | 1 + src/runtime/types/modal.d.ts | 18 +++++++ 11 files changed, 168 insertions(+) create mode 100644 docs/components/content/examples/ModalExampleComponent.vue create mode 100644 docs/components/content/examples/ModalExampleComposable.vue create mode 100644 src/runtime/components/overlays/Modals.vue create mode 100644 src/runtime/composables/useModal.ts create mode 100644 src/runtime/plugins/modals.ts create mode 100644 src/runtime/types/component.d.ts create mode 100644 src/runtime/types/modal.d.ts diff --git a/docs/app.vue b/docs/app.vue index 9ca6329558..fec4adfa9d 100644 --- a/docs/app.vue +++ b/docs/app.vue @@ -20,6 +20,7 @@ + diff --git a/docs/components/content/examples/ModalExampleComponent.vue b/docs/components/content/examples/ModalExampleComponent.vue new file mode 100644 index 0000000000..6b3dc331f8 --- /dev/null +++ b/docs/components/content/examples/ModalExampleComponent.vue @@ -0,0 +1,17 @@ + + + diff --git a/docs/components/content/examples/ModalExampleComposable.vue b/docs/components/content/examples/ModalExampleComposable.vue new file mode 100644 index 0000000000..255c7fc6d0 --- /dev/null +++ b/docs/components/content/examples/ModalExampleComposable.vue @@ -0,0 +1,17 @@ + + + diff --git a/docs/content/2.components/modal.md b/docs/content/2.components/modal.md index 82e6c3e5c7..030446c875 100644 --- a/docs/content/2.components/modal.md +++ b/docs/content/2.components/modal.md @@ -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] + +``` + +Then, you can use the `useModal` composable to control your modals within your app. + +:component-example{component="modal-example-composable"} + ## Props :component-props diff --git a/src/module.ts b/src/module.ts index caf6bb7ae2..7da940909d 100644 --- a/src/module.ts +++ b/src/module.ts @@ -90,6 +90,10 @@ export default defineNuxtModule({ src: resolve(runtimeDir, 'plugins', 'colors') }) + addPlugin({ + src: resolve(runtimeDir, 'plugins', 'modals') + }) + // Components addComponentsDir({ diff --git a/src/runtime/components/overlays/Modals.vue b/src/runtime/components/overlays/Modals.vue new file mode 100644 index 0000000000..2a54970cdd --- /dev/null +++ b/src/runtime/components/overlays/Modals.vue @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/src/runtime/composables/useModal.ts b/src/runtime/composables/useModal.ts new file mode 100644 index 0000000000..50b6acdf70 --- /dev/null +++ b/src/runtime/composables/useModal.ts @@ -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> = Symbol('nuxt-ui.modal') + +function _useModal () { + const modalState = inject(modalInjectionKey) + + const isOpen = ref(false) + + function open (component: T, props?: Modal & ComponentProps) { + 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 (props: Partial>) { + modalState.value = { + ...modalState.value, + props: { + ...modalState.value.props, + ...props + } + } + } + + return { + isOpen, + open, + close, + patch + } +} + +export const useModal = createSharedComposable(_useModal) \ No newline at end of file diff --git a/src/runtime/plugins/modals.ts b/src/runtime/plugins/modals.ts new file mode 100644 index 0000000000..1f2b5d241b --- /dev/null +++ b/src/runtime/plugins/modals.ts @@ -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({ + component: 'div', + props: {} + }) + + nuxtApp.vueApp.provide(modalInjectionKey, modalState) +}) \ No newline at end of file diff --git a/src/runtime/types/component.d.ts b/src/runtime/types/component.d.ts new file mode 100644 index 0000000000..b20ba1433b --- /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 : + {} + +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 : + {} + +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 : + {} \ No newline at end of file diff --git a/src/runtime/types/index.d.ts b/src/runtime/types/index.d.ts index 3c6fe96d31..21188a9ee1 100644 --- a/src/runtime/types/index.d.ts +++ b/src/runtime/types/index.d.ts @@ -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' diff --git a/src/runtime/types/modal.d.ts b/src/runtime/types/modal.d.ts new file mode 100644 index 0000000000..9c56123206 --- /dev/null +++ b/src/runtime/types/modal.d.ts @@ -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 +} \ No newline at end of file