|
| 1 | +<script setup lang="ts"> |
| 2 | +// @ts-expect-error |
| 3 | +import appConfig from '#build/app.config'; |
| 4 | +
|
| 5 | +import UiButton from '#ui/components/elements/Button.vue'; |
| 6 | +import { useUI } from '#ui/composables/useUI'; |
| 7 | +import type { AlertDialogProps, Strategy, UiOverlayEmits } from '#ui/types'; |
| 8 | +import { alertDialog } from '#ui/ui.config'; |
| 9 | +import { mergeConfig } from '#ui/utils'; |
| 10 | +import { uiToTransitionProps } from '#ui/utils/transitions'; |
| 11 | +import { usePreferredReducedMotion, useVModel } from '@vueuse/core'; |
| 12 | +import { AlertDialog } from 'radix-vue/namespaced'; |
| 13 | +import { computed, defineOptions, ref, toRef, withDefaults } from 'vue'; |
| 14 | +
|
| 15 | +const config = mergeConfig<typeof alertDialog>( |
| 16 | + appConfig.ui?.alertDialog?.strategy, |
| 17 | + appConfig.ui?.alertDialog, |
| 18 | + alertDialog, |
| 19 | +); |
| 20 | +type UiConfig = Partial<typeof config> & { strategy?: Strategy }; |
| 21 | +
|
| 22 | +defineOptions({ inheritAttrs: false }); |
| 23 | +
|
| 24 | +const props = withDefaults(defineProps<AlertDialogProps<UiConfig>>(), { |
| 25 | + open: undefined, |
| 26 | + ui: () => ({}) as UiConfig, |
| 27 | +}); |
| 28 | +
|
| 29 | +const emits = defineEmits<{ (e: 'update:open', value: boolean): void } & UiOverlayEmits>(); |
| 30 | +
|
| 31 | +const $open = useVModel(props, 'open', emits, { |
| 32 | + defaultValue: props.defaultOpen, |
| 33 | + passive: (props.open === undefined) as any, |
| 34 | +}); |
| 35 | +
|
| 36 | +const { ui } = useUI('alertDialog', toRef(props, 'ui'), config); |
| 37 | +
|
| 38 | +// With config defaults |
| 39 | +const variant = computed(() => props.variant ?? ui.value.default.variant); |
| 40 | +const iconName = computed(() => props.icon ?? ui.value.variant[variant.value].icon); |
| 41 | +
|
| 42 | +// Disable transitions when prefered reduced motion |
| 43 | +const reduceMotion = usePreferredReducedMotion(); |
| 44 | +const contentTransition = computed(() => |
| 45 | + reduceMotion.value === 'no-preference' ? uiToTransitionProps(ui.value.transition) : {}, |
| 46 | +); |
| 47 | +const overlayTransition = computed(() => |
| 48 | + reduceMotion.value === 'no-preference' ? uiToTransitionProps(ui.value.overlay.transition) : {}, |
| 49 | +); |
| 50 | +
|
| 51 | +// Trigger functionality |
| 52 | +const loading = ref(false); |
| 53 | +
|
| 54 | +async function handleConfirm() { |
| 55 | + if (!props.confirmBtn?.action) return; |
| 56 | +
|
| 57 | + loading.value = true; |
| 58 | +
|
| 59 | + try { |
| 60 | + await props.confirmBtn.action(); |
| 61 | + $open.value = false; |
| 62 | + } catch (error) { |
| 63 | + console.error('Unhandled error on alert dialog:', error); |
| 64 | + } |
| 65 | +
|
| 66 | + loading.value = false; |
| 67 | +} |
| 68 | +</script> |
| 69 | + |
| 70 | +<template> |
| 71 | + <AlertDialog.Root v-model:open="$open"> |
| 72 | + <AlertDialog.Trigger v-if="$slots.trigger" as-child> |
| 73 | + <slot name="trigger" :open="$open" /> |
| 74 | + </AlertDialog.Trigger> |
| 75 | + |
| 76 | + <AlertDialog.Portal> |
| 77 | + <Transition v-bind="overlayTransition"> |
| 78 | + <AlertDialog.Overlay :class="ui.overlay.base" /> |
| 79 | + </Transition> |
| 80 | + |
| 81 | + <Transition |
| 82 | + v-bind="contentTransition" |
| 83 | + @before-enter="emits('before-enter')" |
| 84 | + @after-enter="emits('after-enter')" |
| 85 | + @before-leave="emits('before-leave')" |
| 86 | + @after-leave="emits('after-leave')" |
| 87 | + > |
| 88 | + <AlertDialog.Content :class="[ui.container, ui.layout, ui.size, ui.padding]"> |
| 89 | + <div :class="[ui.icon.container, ui.icon.rounded, ui.variant[variant].color]"> |
| 90 | + <UiIcon :name="iconName" :class="ui.icon.size" /> |
| 91 | + </div> |
| 92 | + |
| 93 | + <div class="flex-1"> |
| 94 | + <AlertDialog.Title :class="ui.title">{{ props.title }}</AlertDialog.Title> |
| 95 | + |
| 96 | + <AlertDialog.Description :class="ui.description"> |
| 97 | + {{ props.description }} |
| 98 | + </AlertDialog.Description> |
| 99 | + |
| 100 | + <slot name="addon" /> |
| 101 | + |
| 102 | + <div :class="ui.actions.container"> |
| 103 | + <UiButton |
| 104 | + v-if="props.confirmBtn" |
| 105 | + block |
| 106 | + :loading="loading" |
| 107 | + :class="ui.actions.btnSize" |
| 108 | + :label="props.confirmBtn.label" |
| 109 | + :variant="props.confirmBtn.variant" |
| 110 | + @click="handleConfirm" |
| 111 | + /> |
| 112 | + |
| 113 | + <AlertDialog.Cancel v-if="props.cancelBtn" as-child> |
| 114 | + <UiButton |
| 115 | + block |
| 116 | + :disabled="loading" |
| 117 | + :class="ui.actions.btnSize" |
| 118 | + :label="props.cancelBtn.label" |
| 119 | + :variant="props.cancelBtn.variant" |
| 120 | + @click="$open = false" |
| 121 | + /> |
| 122 | + </AlertDialog.Cancel> |
| 123 | + </div> |
| 124 | + </div> |
| 125 | + </AlertDialog.Content> |
| 126 | + </Transition> |
| 127 | + </AlertDialog.Portal> |
| 128 | + </AlertDialog.Root> |
| 129 | +</template> |
0 commit comments