Blocks

Card View

A block for visualizing data in a grid of cards.

A block for visualizing data in a grid of cards.

Usage

Loading preview...
<template>
  <MeCardView
    v-model:card-selection="cardSelection"
    v-model:card-favorite="cardFavorite"
    :data="data"
    selectable
    favorite
  />
</template>

<script setup>
const cardSelection = ref({ card0: true, card2: true })
const cardFavorite = ref({ card1: true, card3: true })
const data = Array.from({ length: 5 }, (_, i) => ({
  id: `card${i}`,
  title: `Card ${i}`,
  description: `Description ${i}`,
  image: { src: 'https://picsum.photos/252/172', alt: 'Image' },
  badges: [
    {
      icon: 'i-lucide-shopping-basket',
      color: 'success',
      description: 'Shopping basket'
    },
    {
      icon: 'i-lucide-file-check',
      color: 'primary'
    }
  ],
  cart: {
    modelValue: i % 2 === 0 ? i : undefined,
    onClick: (cardId, value) => console.log(cardId, value),
  }
}))
</script>

The cards data objects are defined as follows:

type InputCartProps = {
  disabled?: boolean
  incrementDisabled?: boolean
  decrementDisabled?: boolean
  cartDisabled?: boolean
  min?: number
  max?: number
  modelValue?: number
}

type CardViewItemData = {
  id: string
  selectionLabel?: string
  image?: { src: string, alt?: string }
  badges?: {
    icon: string
    color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"
    description?: string
  }[]
  title?: string
  description?: string
  cart?: InputCartProps & { onClick: (cardId: string, value: number | undefined) => unknown }
  cardHeight?: string
}

Selection and favorite

Use the selectable and favorite props to enable card selection and the favorite button. Bound the cardSelection and cardFavorite props to v-model to control both behaviors.

<template>
  <MeCardView
    v-model:card-selection="cardSelection"
    v-model:card-favorite="cardFavorite"
    :data="data"
    selectable
    favorite
  />
</template>

<script setup>
const cardSelection = ref({ card0: true })
const cardFavorite = ref({ card1: true })
const data = [
  {
    id: 'card0',
    title: 'Card 0',
    description: 'Card 0 description',
    image: { src: 'https://picsum.photos/252/172', alt: 'Image' },
    cart: { onClick: (_cardId, _value) => null },
    badges: [{ icon: 'i-lucide-file-check' }]
  },
  {
    id: 'card1',
    title: 'Card 1',
    description: 'Card 1 description',
    image: { src: 'https://picsum.photos/252/172', alt: 'Image' },
    cart: { onClick: (_cardId, _value) => null },
    badges: [{ icon: 'i-lucide-file-check' }]
  }
]
</script>

Use the header slot to add custom content.

Header slot content

<template>
  <MeCardView
    :data="data"
    selectable
    favorite
  >
    <template #header>
      Header slot content
    </template>
  <MeCardView />
</template>

<script setup>
const data = [{
  id: 'card',
  title: 'Card title',
  description: 'Card description',
  image: { src: 'https://picsum.photos/252/172' },
  cart: { modelValue: 1 }
}]
</script>

Cart

Defining the cart field in the card data object will render a InputCart component. The behavior of the cart button is defined by the onClick function.

<template>
  <MeCardView
    :data="data"
    favorite
    selectable
  />
</template>

<script setup>
const data = [{
  id: 'card',
  title: 'Card title',
  description: 'Card description',
  image: { src: 'https://picsum.photos/252/172' },
  cart: { modelValue: 3, onClick: (cardId, value) => console.log(cardId, value) }
}]
</script>

Custom cards

Use the card-header, card-image, card-default, card-extra-content and card-footer slots to customize your card. All slots are scoped with the card data object.

<template>
  <MeCardView
    :data="data"
    selectable
    favorite
  >
    <template #card-header="{ card }">
      Card {{ card.id }} header
    </template>

    <template #card-image="{ card }">
      <div class="h-43 bg-accented flex justify-center items-start">
        Card {{ card.id }} image
      </div>
    </template>

    <template #card-default="{ card }">
      <div class="flex flex-col gap-y-2 mt-4 px-2">
        <span class="text-default font-bold text-base truncate">
          Card {{ card.id }} title
        </span>
        <span class="text-muted font-normal text-sm line-clamp-2">
          Card {{ card.id }} description
        </span>
      </div>
    </template>

    <template #card-extra-content="{ card }">
      Card {{ card.id }} extra content
    </template>

    <template #card-footer="{ card }">
      Card {{ card.id }} footer
    </template>
  </MeCardView>
</template>

<script setup>
const data = [{
  id: 'custom',
  title: 'Custom',
  description: 'Custom',
  badges: [
    {
      icon: 'i-lucide-shopping-basket',
      color: 'success',
      description: 'Shopping basket'
    },
    {
      icon: 'i-lucide-file-check',
      color: 'primary'
    }
  ]
}]
</script>

Empty view

Use the empty prop to customize the view when no data is available. It accepts all props from Empty except for variant, plus a src prop to customize the image. Alternatively, use the empty slot to create your empty view from scratch.

<template>
  <MeCardView
    :data="[]"
    :empty="{
      title: 'No data',
      description: 'No data available',
      actions: [{ label: 'Action A' }, { label: 'Action B' }]
    }"
    selectable
  />
</template>

API

Props

PropDefaultType
dataCardViewItemData | (() => Promise<CardViewItemData[]>)
Data to display
selectableboolean
Enables card selection
cardSelection{}Record<string, boolean>
Current selected cards, bound with v-model
favoriteboolean
Enables card favorite
cardFavorite{}Record<string, boolean>
Current favorite cards, bound with v-model
loadingboolean
Displays loading state
emptyOmit<EmptyProps, 'variant'> & { src?: string }
Empty view configuration. See Empty
virtualizeOmit<ScrollAreaVirtualizeOptions, 'gap' | 'lanes' | 'paddingStart' | 'paddingEnd'>
Virtualization configuration. Use this option appropriately to improve performance with large datasets. See ScrollArea for more info
cardHeight448px
String
Predefined card height

Slots

SlotType
header{}
empty{}
card-headerCardViewItemData
card-imageCardViewItemData
card-defaultCardViewItemData
card-extra-contentCardViewItemData
card-footerCardViewItemData