# Tear Ticket

A ticket with a perforated stub. Grab the stub and pull it away from the ticket: it pivots from the end of the tear line, the paper bridges between the holes stretch into fibres and snap one by one, and once the last bridge goes the stub hangs from your pointer. Let go and it drops with gravity and fades. The ticket then turns grey and glides over to sit centred. Release before the tear finishes and the stub springs back. The whole ticket leans toward the cursor in 3D, and the artwork moves against the tilt.

<demo-tear-ticket>



</demo-tear-ticket>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/tear-ticket.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import TearTicket from '~/components/ui/tear-ticket/TearTicket.vue';

  const torn = ref(false);
</script>

<template>
  <TearTicket
    v-model:torn="torn"
    image="https://images.unsplash.com/photo-1604852961945-155837ffebe2?w=1200"
    image-alt="Visitors in a walkway of coloured glass"
    :scrim="false"
    :tear-angle="30"
    :resistance="0.45"
    @tear="console.log('used')"
  >
    <div class="flex h-full items-end justify-between px-6 pb-8">
      <span>Spectrum</span>
      <span class="opacity-45">14 Nov</span>
    </div>
    <template #stub>
      <div class="p-6">Admit one</div>
    </template>
  </TearTicket>
  <button @click="torn = false">Restore stub</button>
</template>
```

Inside the slots you can use the CSS variables `--tt-body-w`, `--tt-body-h`, `--tt-span` and `--tt-inset` to place text under the artwork.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        image
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Artwork behind the body. It shifts against the tilt and turns grey once used.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        image-alt
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Alt text for the artwork.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scrim
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      A fade from the paper colour over the artwork, so text stays readable on busy images.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        image-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      Corner radius of the artwork panel in px. Use the ticket radius minus 8 to stay concentric.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        orientation
      </code>
    </td>
    
    <td>
      <code>
        'horizontal' | 'vertical'
      </code>
    </td>
    
    <td>
      <code>
        'horizontal'
      </code>
    </td>
    
    <td>
      Horizontal puts the stub on the right. Vertical puts the artwork on top and the stub below.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        torn
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Controlled state (<code>
        v-model:torn
      </code>
      
      ). Set it back to <code>
        false
      </code>
      
       to restore the stub.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        default-torn
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Start already used (uncontrolled).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        460
      </code>
    </td>
    
    <td>
      Ticket width in px. It scales down to fit a narrower parent.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        250
      </code>
    </td>
    
    <td>
      Ticket height in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stub-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        150
      </code>
    </td>
    
    <td>
      Stub size along the ticket in px: its width when horizontal, its height when vertical.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        16
      </code>
    </td>
    
    <td>
      Outer corner radius in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        holes
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        12
      </code>
    </td>
    
    <td>
      Perforation holes along the tear line.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hole-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Hole diameter in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        notch
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Radius of the notches at both ends of the tear line.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        roughness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Jitter of the torn line between holes in px. <code>
        0
      </code>
      
       keeps the edge straight.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tear-angle
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        30
      </code>
    </td>
    
    <td>
      Degrees of pull at which the last bridge gives way and the stub comes free.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stretch
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        30
      </code>
    </td>
    
    <td>
      How far a paper bridge stretches before it snaps, in px. Bridges far from the hinge go first.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        resistance
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.45
      </code>
    </td>
    
    <td>
      How much the intact fibres hold the stub back, 0 to 1. The pull eases as bridges give way.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rotate
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        4
      </code>
    </td>
    
    <td>
      Resting tilt of the whole ticket in the page plane, in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      The ticket leans toward the cursor in 3D.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt-max
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        9
      </code>
    </td>
    
    <td>
      Largest tilt angle in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt-reach
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        260
      </code>
    </td>
    
    <td>
      How far beyond the ticket the pointer still steers the tilt, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      How far the artwork travels against the tilt, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        perspective
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1000
      </code>
    </td>
    
    <td>
      Viewing distance in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        background
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#27272a'
      </code>
    </td>
    
    <td>
      Paper colour, also used by the fibres.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f5f5f5'
      </code>
    </td>
    
    <td>
      Text colour.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      A hairline along the cut edge of each piece, following every hole and notch.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Hairline colour. Empty derives a faint tint of the text colour.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Hairline thickness in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stub-background
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Stub paper colour. Empty follows <code>
        background
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        recenter
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Once the stub is gone, the remaining ticket glides over to sit centred in its box.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Dimmed and inert.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Tear off the stub'
      </code>
    </td>
    
    <td>
      Accessible name of the stub.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root element.
    </td>
  </tr>
</tbody>
</table>

## Slots and events

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      Props
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      default slot
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Content of the ticket body.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        #stub
      </code>
      
       slot
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Content of the tear-off stub.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tear
      </code>
      
       event
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Fires once the stub has come free and gone.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        update:torn
      </code>
      
       event
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Emits <code>
        true
      </code>
      
       with <code>
        tear
      </code>
      
      , for <code>
        v-model:torn
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The stub is a focusable `role="button"` named by `aria-label`. Enter or Space tears it at once.
- A visually hidden `role="status"` region announces "Used" after the tear.
- With reduced motion, the tilt, parallax and fibres are off, and a short drag on the stub tears it at once.
- Ported from [React Bits](https://reactbits.dev/micro/tear-ticket) by David Haz.
