# Shredder

A list that sits on top of a paper shredder. Drag a row down into the slit and the rollers grab it, pull it through and cut it into strips that curl, flutter and fade as they fall. Drag a row up or down instead to reorder the list. A focused row shreds on Delete or Backspace. The strips are cut from a snapshot of the rendered row, so text, images and inline SVG all go through the shredder as they look.

<demo-shredder>



</demo-shredder>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import Shredder from '~/components/ui/shredder/Shredder.vue';

  const files = ref([
    { id: 1, title: 'Harbour at dusk', size: '4.1 MB' },
    { id: 2, title: 'Studio, take two', size: '2.8 MB' },
    { id: 3, title: 'Fog over the bay', size: '6.3 MB' },
  ]);

  function remove(file: { id: number }) {
    files.value = files.value.filter((row) => row.id !== file.id);
  }
</script>

<template>
  <Shredder :items="files" @shred="remove" @reorder="files = $event">
    <template #default="{ item }">
      <div class="flex items-center gap-3 rounded-xl bg-zinc-800 px-3.5 py-3">
        <span class="flex-1 truncate text-sm font-medium">{{
          item.title
        }}</span>
        <span class="text-xs opacity-45">{{ item.size }}</span>
      </div>
    </template>
  </Shredder>
</template>
```

Remove the item from your list in the `shred` handler, unless `loop`, `loop-after-delete` or `auto-animate` is on. With those on, keep the rows in your list so they can come back.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        T[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Rows to render, top to bottom. Each item needs a unique <code>
        id
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        340
      </code>
    </td>
    
    <td>
      Width of the whole machine in pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        460
      </code>
    </td>
    
    <td>
      Total height. The rows stack up from the slit, which sits above the fall zone at the bottom.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        14
      </code>
    </td>
    
    <td>
      Horizontal inset of the rows from the edges. The slit is 6px wider than the rows on each side.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Space between rows in pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slit-height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        4
      </code>
    </td>
    
    <td>
      Thickness of the slit. It is always a full pill.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fall-height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        140
      </code>
    </td>
    
    <td>
      Height of the zone under the slit where the strips fall and fade out.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        feed-speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        180
      </code>
    </td>
    
    <td>
      Roller speed in pixels per second. The first pull is faster and eases into it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bite
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        18
      </code>
    </td>
    
    <td>
      How far a row has to be pushed into the slit before the rollers grab it, in pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-feed
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Grab the row as soon as it is pushed in far enough, even while it is still held. Off, it feeds only on release over the slit.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        strip-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Target width of each strip in pixels. The row is cut into equal columns.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        curl
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How much the strips curl and wave as they come out. <code>
        0
      </code>
      
       keeps them straight.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-animate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Shreds the rows on its own, bottom to top, then brings them back and starts over. <code>
        shred
      </code>
      
       still fires.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loop
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Once the last row has been shredded, bring them all back.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loop-after-delete
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      A shredded row comes back a moment later at a random spot, so the list never runs dry.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drag-tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Maximum lean in degrees while a row is dragged sideways.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lift
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.02
      </code>
    </td>
    
    <td>
      Scale of a row while it is held.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slit-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#3f3f46'
      </code>
    </td>
    
    <td>
      Colour of the slit.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f5f5f5'
      </code>
    </td>
    
    <td>
      Text colour inherited by the rows.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Rows cannot be dragged or shredded.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root.
    </td>
  </tr>
</tbody>
</table>

## Slots and events

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

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
      
       slot
    </td>
    
    <td>
      <code>
        { item, index }
      </code>
    </td>
    
    <td>
      Renders one row. A snapshot of this markup is what gets cut into strips.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shred
      </code>
      
       event
    </td>
    
    <td>
      <code>
        item
      </code>
    </td>
    
    <td>
      Fires the moment a row has gone through the rollers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reorder
      </code>
      
       event
    </td>
    
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      Fires with the items in their new order after a row is dropped elsewhere in the list.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- Each row is focusable. Delete or Backspace feeds the focused row into the shredder.
- Rows point to a screen-reader hint that explains drag-to-shred, drag-to-reorder and the Delete key.
- The slit and the falling strips are decorative and hidden from assistive tech.
- With `prefers-reduced-motion: reduce`, a shredded row is removed at once with no strips, and the list does not animate.
- Remote images in rows must allow CORS, or they show as a grey block in the strips.
- Ported from [React Bits](https://reactbits.dev/micro/shredder) by David Haz.
