UIPackage
Menu

Framework

Change language

Boilerplate repo

Exit Intent — Offer

blockmarketing

Dialog triggered when the pointer leaves toward the browser chrome, offering an extended trial once per session, with a dismissal that is remembered.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/exit-intent-offer.json
Named registry:npx shadcn-vue@latest add @uipkge/exit-intent-offerInstalls to:app/components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
storageKeystring'uipkge:exit-intent-seen'optional

Files installed (1)

  • app/components/blocks/ExitIntentOffer.vue2.6 kB
    <script setup lang="ts">
    import { onBeforeUnmount, onMounted, ref } from 'vue'
    import { ArrowRight, Clock } from 'lucide-vue-next'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import {
      Dialog,
      DialogContent,
      DialogDescription,
      DialogFooter,
      DialogHeader,
      DialogTitle,
    } from '@/components/ui/dialog'
    import { Separator } from '@/components/ui/separator'
    
    const props = withDefaults(defineProps<{ storageKey?: string }>(), {
      storageKey: 'uipkge:exit-intent-seen',
    })
    
    const open = ref(false)
    
    // Only the top edge counts. A pointer leaving left, right, or bottom is usually
    // someone reaching for a scrollbar or another window, not leaving the page.
    function onPointerOut(event: MouseEvent) {
      if (event.relatedTarget || event.clientY > 4) return
      show()
    }
    
    function show() {
      try {
        if (window.sessionStorage.getItem(props.storageKey) === '1') return
        window.sessionStorage.setItem(props.storageKey, '1')
      } catch {
        // Blocked storage: it can show once more this page view, never in a loop.
      }
      open.value = true
      document.removeEventListener('mouseout', onPointerOut)
    }
    
    onMounted(() => document.addEventListener('mouseout', onPointerOut))
    onBeforeUnmount(() => document.removeEventListener('mouseout', onPointerOut))
    </script>
    
    <template>
      <div data-slot="exit-intent-offer">
        <Dialog v-model:open="open">
          <DialogContent class="sm:max-w-md">
            <DialogHeader>
              <Badge variant="secondary" class="w-fit gap-1.5">
                <Clock class="size-3" aria-hidden="true" />
                Before you go
              </Badge>
              <DialogTitle class="mt-3 text-xl leading-snug text-balance"> Take 30 days instead of 14 </DialogTitle>
              <DialogDescription class="leading-relaxed">
                A full close cycle fits in 30 days and 14 does not, which is the actual reason most trials stall. No card,
                no call, cancel from the dashboard.
              </DialogDescription>
            </DialogHeader>
    
            <Separator />
    
            <ul class="text-muted-foreground space-y-1.5 text-sm">
              <li>· Everything on the Business plan</li>
              <li>· Connect your real warehouse, read-only</li>
              <li>· Keep the definitions you author either way</li>
            </ul>
    
            <DialogFooter class="sm:justify-start">
              <Button>
                Start the 30-day trial
                <ArrowRight class="ml-2 size-4" aria-hidden="true" />
              </Button>
              <Button variant="ghost" @click="open = false">No thanks</Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </div>
    </template>
    

Raw manifest:https://uipkge.dev/r/vue/exit-intent-offer.json