# Block Palette

A compact block library for canvas and notebook editors. Each row shows a colored icon tile, a title, a short hint and a tiny preview of the block. Drag a row and it lifts as a tilted card with a blue outline while a blue line marks where it will land. Drop it on the list to reorder, or drop it anywhere else and handle `drag-end` to place it on your canvas. Press `/` to search.

<demo-block-palette>



</demo-block-palette>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import BlockPalette from '~/components/ui/block-palette/BlockPalette.vue';
  import type {
    BlockPaletteDragEnd,
    BlockPaletteItem,
  } from '~/components/ui/block-palette/types';

  const blocks = ref<BlockPaletteItem[]>([
    { id: 'group', kind: 'group', title: 'Group', subtitle: 'frame blocks' },
    {
      id: 'chart',
      kind: 'chart',
      title: 'Chart',
      subtitle: 'bar · line · area',
    },
    { id: 'slice', kind: 'slice', title: 'Slice', subtitle: 'query a cube' },
    {
      id: 'parameter',
      kind: 'parameter',
      title: 'Parameter',
      subtitle: 'date, shift, cell',
    },
    {
      id: 'markdown',
      kind: 'markdown',
      title: 'Markdown',
      subtitle: 'notes & headings',
    },
  ]);

  function addToCanvas(block: BlockPaletteItem) {
    console.log('add', block.id);
  }

  function onDragEnd({ block, inside, x, y }: BlockPaletteDragEnd) {
    if (!inside) console.log('drop', block.id, 'at', x, y);
  }
</script>

<template>
  <BlockPalette v-model="blocks" @pick="addToCanvas" @drag-end="onDragEnd" />
</template>
```

`v-model` holds the block order. A drop on the list writes the new order back. A drop outside the list keeps the order and reports the pointer position in `drag-end`.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        model-value
      </code>
    </td>
    
    <td>
      <code>
        BlockPaletteItem[]
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        v-model
      </code>
      
      . The blocks, in order.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Add Block'
      </code>
    </td>
    
    <td>
      Panel heading.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        description
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'drag onto the canvas'
      </code>
    </td>
    
    <td>
      Muted line under the heading.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        search-placeholder
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Search blocks'
      </code>
    </td>
    
    <td>
      Placeholder for the search field.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shortcut
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '/'
      </code>
    </td>
    
    <td>
      Key that focuses the search. <code>
        ''
      </code>
      
       turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        empty-text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'No blocks match.'
      </code>
    </td>
    
    <td>
      Shown when the search matches nothing.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        add-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Add block'
      </code>
    </td>
    
    <td>
      Accessible label for the <code>
        +
      </code>
      
       button.
    </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>

## Block options

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Unique id.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        kind
      </code>
    </td>
    
    <td>
      <code>
        'group' | 'chart' | 'slice' | 'parameter' | 'markdown'
      </code>
    </td>
    
    <td>
      Picks the icon color and the preview.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Row title. The search matches it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        subtitle
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Muted hint. The search matches it too.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Replaces the icon for the kind, e.g. <code>
        lucide:table
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        pick
      </code>
    </td>
    
    <td>
      <code>
        block
      </code>
    </td>
    
    <td>
      A row was clicked, or Enter was pressed on it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drag-start
      </code>
    </td>
    
    <td>
      <code>
        block, PointerEvent
      </code>
    </td>
    
    <td>
      The pointer moved far enough to lift a row.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drag-end
      </code>
    </td>
    
    <td>
      <code>
        { block, inside, x, y }
      </code>
    </td>
    
    <td>
      The row was dropped. <code>
        inside
      </code>
      
       is <code>
        false
      </code>
      
       when it was dropped off the list.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        add
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      The <code>
        +
      </code>
      
       button was pressed.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- Rows are focusable. Up and Down move focus between rows.
- Space picks up the focused row, the arrow keys move it and Space drops it. Escape puts it back. Every step is announced through `aria-live`.
- Escape also cancels a pointer drag.
- `/` focuses the search unless you are typing in a field.
- With reduced motion the lifted card does not tilt or spring.
