Last active
April 25, 2023 16:28
-
-
Save tjk/3564f1dd66a3c296f6090c5c971d9d7d to your computer and use it in GitHub Desktop.
vue composable modal pattern (problems)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template lang="pug"> | |
div | |
//- <----- rest of app goes here (RouterView / main shell / w.e) | |
//- don't even need to use teleport with this pattern... | |
Modal(v-for="({ body, bodyProps, modalProps }, idx) in modalStack" v-bind="modalProps" @close="modalClose(idx)") | |
//- XXX support { title, h2Md }, etc. besides body+bodyProps -- | |
Suspense(v-if="!idx && body") | |
Component(:is="body" v-bind="bodyProps") | |
</template> | |
<script setup lang="ts"> | |
import { provideModal } from "./modal" | |
import Modal from "./modal.vue" | |
const { modalStack, modalClose } = provideModal() // stack is readonly -- this is the ProvideModalContext | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template lang="pug"> | |
div {{ modalOneProp }} | |
</template> | |
<script setup lang="ts"> | |
defineProps<{ | |
modalOneProp: string | |
}>() | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template lang="pug"> | |
div | |
button(@click="openModal") Open modal | |
</template> | |
<script setup lang="ts"> | |
import { useModal } from "./modal" | |
import ModalOne from "./modal-one.vue" | |
// this composable maintains a stack of modals to show | |
const { modalPush } = useModal() | |
function openModal() { | |
// this composable api so that we can open modals from other composables | |
modalPush({ | |
// i basically have this working except for 2 really bad issues: | |
// 1) cannot use `inject()` inside @components/modal/one.vue that is "in scope" of stuff provided 'above' this file | |
// 2) hmr is kinda busted | |
body: ModalOne, | |
bodyProps: { | |
modalOneProp: "hello world", | |
}, | |
}) | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
type AllowedComponentProps, | |
type Component, | |
type Ref, | |
type VNodeProps, | |
} from "vue" | |
// https://stackoverflow.com/a/73784241/387413 | |
type ComponentProps<C extends Component> = C extends new (...args: any) => any | |
? Omit< | |
InstanceType<C>["$props"], | |
keyof VNodeProps | keyof AllowedComponentProps | |
> | |
: never | |
type ModalPushOpts<K extends Component> = { | |
id: string | |
modalProps?: ComponentProps<typeof Modal> | |
body?: K | |
bodyProps?: ComponentProps<K> | |
} | |
export type ModalStack = ModalPushOpts<any>[] | |
const MODAL = Symbol("MODAL") | |
export function provideModal(): ModalContext { | |
const ctx = { | |
stack: reactive<ModalStack>([]), | |
} | |
provide(MODAL, ctx) | |
return { | |
modalStack: readonly(ctx.stack), | |
modalClose(idx: number) { | |
ctx.stack.splice(idx, 1) | |
}, | |
} | |
} | |
export function useModal() { | |
const ctx = inject<ModalContext>(MODAL) | |
if (!ctx) { | |
throw new Error("must provideModal()") | |
} | |
return { | |
modalPush<K extends Component>(opts: ModalPushOpts<K>) { | |
if (ctx.stack[0]?.id !== opts.id) { | |
ctx.stack.unshift(markRaw(opts)) | |
} | |
}, | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template lang="pug"> | |
.fixed.inset-0.flex.flex-col.items-center(style="background:rgba(0,0,0,0.4)" @click="$emit('close')") | |
.bg-white(@click.stop) | |
slot | |
</template> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment