refactor: rewrite the Dialog root family without reka-ui
Root/Trigger/Overlay/Close/Title/Description now share a small provide/inject context instead of reka-ui's Dialog primitives. DialogContent/DialogScrollContent follow in the next commit since they need the focus-trap and dismissable-layer composables.
This commit is contained in:
parent
07b44f6de4
commit
3d8bdaa903
8 changed files with 85 additions and 65 deletions
|
|
@ -1,19 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
|
||||
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
|
||||
import { provide, useId } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
const props = defineProps<{ open?: boolean, defaultOpen?: boolean }>()
|
||||
const emit = defineEmits<{ 'update:open': [value: boolean] }>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
const open = useVModel(props, 'open', emit, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultOpen ?? false,
|
||||
})
|
||||
|
||||
provide(DialogContextKey, {
|
||||
open,
|
||||
titleId: `dialog-title-${useId()}`,
|
||||
descriptionId: `dialog-description-${useId()}`,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot
|
||||
v-slot="slotProps"
|
||||
data-slot="dialog"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</DialogRoot>
|
||||
<slot />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogCloseProps } from "reka-ui"
|
||||
import { DialogClose } from "reka-ui"
|
||||
import { inject } from 'vue'
|
||||
import { Primitive } from '@/lib/primitive'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
const props = defineProps<{ asChild?: boolean }>()
|
||||
|
||||
const context = inject(DialogContextKey)!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose
|
||||
<Primitive
|
||||
as="button"
|
||||
:as-child="props.asChild"
|
||||
type="button"
|
||||
data-slot="dialog-close"
|
||||
v-bind="props"
|
||||
@click="context.open.value = false"
|
||||
>
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogDescriptionProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogDescription, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { inject } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
const context = inject(DialogContextKey)!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
<p
|
||||
:id="context.descriptionId"
|
||||
data-slot="dialog-description"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</p>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { DialogClose } from "reka-ui"
|
||||
import DialogClose from "./DialogClose.vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogOverlay } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { inject } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
const context = inject(DialogContextKey)!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50', props.class)"
|
||||
<Transition
|
||||
enter-active-class="animate-in fade-in-0 duration-100"
|
||||
leave-active-class="animate-out fade-out-0 duration-100"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
<div
|
||||
v-if="context.open.value"
|
||||
data-slot="dialog-overlay"
|
||||
:class="cn('bg-black/10 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50', props.class)"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogTitleProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogTitle, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { inject } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
const context = inject(DialogContextKey)!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
<h2
|
||||
:id="context.titleId"
|
||||
data-slot="dialog-title"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-base leading-none font-medium font-serif', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</h2>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import type { DialogTriggerProps } from "reka-ui"
|
||||
import { DialogTrigger } from "reka-ui"
|
||||
import { inject } from 'vue'
|
||||
import { Primitive } from '@/lib/primitive'
|
||||
import { DialogContextKey } from './context'
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
const props = defineProps<{ asChild?: boolean }>()
|
||||
|
||||
const context = inject(DialogContextKey)!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTrigger
|
||||
<Primitive
|
||||
as="button"
|
||||
:as-child="props.asChild"
|
||||
type="button"
|
||||
data-slot="dialog-trigger"
|
||||
v-bind="props"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="context.open.value"
|
||||
@click="context.open.value = true"
|
||||
>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
|
|
|||
9
packages/library/src/components/ui/dialog/context.ts
Normal file
9
packages/library/src/components/ui/dialog/context.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { InjectionKey, Ref } from 'vue'
|
||||
|
||||
export interface DialogContext {
|
||||
open: Ref<boolean>
|
||||
titleId: string
|
||||
descriptionId: string
|
||||
}
|
||||
|
||||
export const DialogContextKey: InjectionKey<DialogContext> = Symbol('DialogContext')
|
||||
Loading…
Reference in a new issue