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 #card-extra-content="{ card }">
      <div class="flex flex-col gap-y-2 text-sm">
        <div class="flex items-center gap-x-1">
          <span class="text-muted">By:</span>
          <span class="text-toned font-medium truncate">
            {{ itemDetailsById[card.id]?.supplier }}
          </span>
        </div>

        <div class="flex flex-nowrap gap-x-2 overflow-hidden">
          <UBadge
            v-for="tag in itemDetailsById[card.id]?.tags ?? []"
            :key="tag"
            color="neutral"
            variant="soft"
            size="sm"
            :label="tag"
            class="truncate"
          />
        </div>
      </div>
    </template>
  </MeCardView>
</template>

<script setup>
const cardSelection = ref({
  'card-1': true,
  'card-2': true
})

const cardFavorite = ref({
  'card-2': true,
  'card-3': true
})

const itemDetailsById = {
  'card-1': {
    supplier: 'MAXEL MATERIALS ELECTRICS',
    tags: ['Abbott currency EUR', '+7']
  },
  'card-2': {
    supplier: 'MAXEL MATERIALS ELECTRICS',
    tags: ['Abbott currency EUR', '+7']
  },
  'card-3': {
    supplier: 'MAXEL MATERIALS ELECTRICS',
    tags: ['Abbott currency EUR', '+7']
  },
  'card-4': {
    supplier: 'MAXEL MATERIALS ELECTRICS',
    tags: ['Abbott currency EUR', '+7']
  }
}

const data = [
  {
    id: 'card-1',
    title: 'USD 32,00 - USD 50,00 / UNIT',
    description: '000000 - Item name with a long description example.',
    image: { src: 'https://picsum.photos/seed/card-1/252/172', alt: 'Product image' },
    badges: [
      { icon: 'i-lucide-file-check', color: 'success', description: 'Approved supplier' },
      { icon: 'i-lucide-shopping-basket', color: 'primary', description: 'Currency EUR' }
    ],
    cart: { modelValue: 0, min: 0, onClick: (_cardId, _value) => null }
  },
  {
    id: 'card-2',
    title: 'USD 32,00 - USD 50,00 / UNIT',
    description: '000000 - Item name with a long description example.',
    image: { src: 'https://picsum.photos/seed/card-2/252/172', alt: 'Product image' },
    badges: [
      { icon: 'i-lucide-file-check', color: 'success', description: 'Approved supplier' },
      { icon: 'i-lucide-shopping-basket', color: 'primary', description: 'Currency EUR' }
    ],
    cart: { modelValue: 2, min: 0, onClick: (_cardId, _value) => null }
  },
  {
    id: 'card-3',
    title: 'USD 39,00 - USD 59,00 / UNIT',
    description: '000001 - Item name with a long description example.',
    image: { src: 'https://picsum.photos/seed/card-3/252/172', alt: 'Product image' },
    badges: [
      { icon: 'i-lucide-file-check', color: 'success', description: 'Approved supplier' },
      { icon: 'i-lucide-shopping-basket', color: 'primary', description: 'Currency EUR' }
    ],
    cart: { modelValue: 1, min: 0, onClick: (_cardId, _value) => null }
  },
  {
    id: 'card-4',
    title: 'USD 39,00 - USD 59,00 / UNIT',
    description: '000001 - Item name with a long description example.',
    image: { src: 'https://picsum.photos/seed/card-4/252/172', alt: 'Product image' },
    badges: [
      { icon: 'i-lucide-file-check', color: 'success', description: 'Approved supplier' },
      { icon: 'i-lucide-shopping-basket', color: 'primary', description: 'Currency EUR' }
    ],
    cart: { modelValue: 0, min: 0, onClick: (_cardId, _value) => null }
  }
]
</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