Skip to content

View ​

Lightweight display component for rendering structured data with conditional formatting, icons, labels, and item-level actions. Perfect for presenting read-only details or quick-access data with visual enhancements.

Overview ​

VOvView provides a flexible data display solution with configurable items, custom formatting rules, and action buttons. Displays single items inline or multiple items in a responsive grid layout with automatic column distribution. Supports conditional formatting, icon rendering, value truncation with detail modal, and item-specific actions with full TypeScript support.

Features:

  • Single-item inline display or multi-item grid layout
  • Responsive column layout with auto-sizing
  • Conditional formatting with validation rules
  • Icon support with custom colors and styling
  • Item labels with optional display
  • Value truncation with detail view modal
  • Item-level actions with custom handlers
  • Grid-level actions (print, export, etc.)
  • Loading state with overlay indicator
  • Full TypeScript support

Usage ​

Examples
vue
<template>
  <v-container fluid>
    <v-row>
      <v-col cols="12">
        <h3>Single item</h3>
        <v-ov-view :data="{ name: data.name }" :options="{ items: [{ name: 'name' }] }" />
      </v-col>
      <v-col cols="12">
        <h3>Action icons</h3>
        <v-ov-view :data="data" :options="options" @action="action" />
      </v-col>
      <v-col cols="12">
        <h3>Contact Information with Icons</h3>
        <v-ov-view :data="contactData" :options="contactOptions" />
      </v-col>
      <v-col cols="12">
        <h3>Various Data Formats</h3>
        <v-ov-view :data="dataFormats" :options="formatOptions" />
      </v-col>
    </v-row>
  </v-container>
</template>
<script setup lang="ts">
definePage({
  meta: {
    visibility: 'with-role',
    access: 'with-role',
    roles: ['developer'],
  },
})

import type { OvViewOptions } from '@/components'

function action(name: string, value: unknown) {
  if (!name || !value) return
  if (name === 'status') data.value.status = value === 'active' ? 'disabled' : 'active'
  console.log('action triggered', name, value)
}

const data = ref({
  name: 'Sample Sandbox',
  description: 'This is a sample description for the sandbox view component demonstration.',
  status: 'active',
})

const contactData = ref({
  email: 'john.doe@example.com',
  phone: '+1 (555) 123-4567',
  mobile: '+1 (555) 987-6543',
  website: 'https://example.com',
  address: '123 Main Street, New York, NY 10001',
  zipcode: '10001',
})

const dataFormats = ref({
  date: '2024-11-18',
  time: '14:30:45',
  url: 'https://github.com/odbvue',
  ipAddress: '192.168.1.1',
  fileSize: '2.5 GB',
  percentage: '87%',
  rating: '4.5/5',
  amount: '$1,234.56',
  verified: 'verified',
  error: 'failed',
})

const options = <OvViewOptions>{
  cols: 2,
  items: [
    {
      name: 'name',
    },
    {
      name: 'description',
      label: 'Description',
      maxLength: 30,
    },
    {
      name: 'status',
      format: [
        {
          rules: { type: 'starts-with', params: 'active' },
          color: 'green',
          rounded: true,
        },
        { color: 'red', rounded: true },
      ],
      actions: [
        {
          name: 'status',
          key: 'status',
          format: { icon: '$mdiSwapHorizontal', class: 'mx-2', text: 'Toggle Status' },
        },
      ],
    },
    {
      name: 'status',
      format: { hidden: true },
      actions: [
        {
          name: 'status',
          key: 'status',
          format: { text: 'Toggle Status' },
        },
      ],
    },
  ],
  actions: [
    {
      name: 'print',
      format: { icon: '$mdiPrinter', text: 'Print' },
    },
  ],
}

const contactOptions = <OvViewOptions>{
  cols: 3,
  items: [
    {
      name: 'email',
      label: 'Email',
      format: {
        icon: '$mdiEmail',
        color: 'blue',
        href: 'mailto:{{value}}',
        target: '_blank',
      },
    },
    {
      name: 'phone',
      label: 'Phone',
      format: {
        icon: '$mdiPhone',
        color: 'green',
      },
      actions: [
        {
          name: 'phone',
          key: 'phone',
          format: { class: 'mx-2', size: 'small', icon: '$mdiPhoneDial', text: 'Call' },
        },
      ],
    },
    {
      name: 'mobile',
      label: 'Mobile',
      format: {
        icon: '$mdiCellphone',
        color: 'purple',
      },
    },
    {
      name: 'website',
      label: 'Website',
      format: {
        icon: '$mdiWeb',
        color: 'indigo',
        href: '{{value}}',
        target: '_blank',
      },
    },
    {
      name: 'address',
      label: 'Address',
      format: {
        icon: '$mdiMapMarker',
        color: 'orange',
      },
      maxLength: 35,
    },
    {
      name: 'zipcode',
      label: 'ZIP Code',
      format: [
        {
          rules: { type: 'equals', params: '10001' },
          icon: '$mdiCheckCircle',
          color: 'success',
        },
        {
          icon: '$mdiAlert',
          color: 'warning',
        },
      ],
    },
  ],
}

const formatOptions = <OvViewOptions>{
  cols: 2,
  items: [
    {
      name: 'date',
      label: 'Date',
      format: {
        icon: '$mdiCalendar',
        color: 'red',
      },
    },
    {
      name: 'time',
      label: 'Time',
      format: {
        icon: '$mdiClockOutline',
        color: 'cyan',
      },
    },
    {
      name: 'url',
      label: 'URL Link',
      format: {
        icon: '$mdiLink',
        color: 'teal',
      },
      maxLength: 30,
    },
    {
      name: 'ipAddress',
      label: 'IP Address',
      format: {
        icon: '$mdiNetworkOutline',
        color: 'grey',
      },
    },
    {
      name: 'fileSize',
      label: 'File Size',
      format: {
        icon: '$mdiFileDocument',
        color: 'amber',
      },
    },
    {
      name: 'percentage',
      label: 'Progress',
      format: [
        {
          rules: { type: 'contains', params: '87' },
          icon: '$mdiProgressCheck',
          color: 'lightGreen',
        },
      ],
    },
    {
      name: 'rating',
      label: 'Rating',
      format: {
        icon: '$mdiStar',
        color: 'yellow',
      },
    },
    {
      name: 'amount',
      label: 'Amount',
      format: {
        icon: '$mdiCurrencyUsd',
        color: 'green',
      },
    },
    {
      name: 'verified',
      label: 'Status',
      format: [
        {
          rules: { type: 'equals', params: 'verified' },
          icon: '$mdiCheck',
          color: 'success',
          rounded: true,
        },
        {
          icon: '$mdiClose',
          color: 'error',
          rounded: true,
        },
      ],
    },
    {
      name: 'error',
      label: 'Result',
      format: [
        {
          rules: { type: 'equals', params: 'failed' },
          icon: '$mdiAlert',
          color: 'red',
          rounded: true,
        },
        {
          icon: '$mdiCheckCircle',
          color: 'green',
          rounded: true,
        },
      ],
    },
  ],
}
</script>

API ​

Props ​

PropTypeDefaultDescription
dataOvViewData-Required. Data object with key-value pairs matching item names
optionsOvViewOptionsundefinedView configuration with items, columns, and actions
loadingbooleanfalseShow loading overlay with spinner
t(text?: string) => string(text) => textTranslation/localization function for labels and messages

Events ​

EventPayloadDescription
detailsitemName: string, value: stringEmitted when detail button clicked for truncated value
actionactionName: string, value: unknownEmitted when item action or grid action triggered

View Options ​

typescript
interface OvViewOptions {
  // Required
  items: OvViewItem[]

  // Optional - Layout
  cols?: number
  maxLength?: number

  // Optional - Actions
  actions?: OvAction[]
  actionFormat?: OvFormat | OvFormat[]
  actionAlign?: 'left' | 'center' | 'right'
}

Item Definition ​

Items define what data to display and how to format it:

typescript
interface OvViewItem {
  // Required
  name: string

  // Optional - Display
  label?: string
  maxLength?: number

  // Optional - Formatting
  format?: OvFormat | OvFormat[]

  // Optional - Actions
  actions?: OvAction[]
  actionFormat?: OvFormat | OvFormat[]
}

Item Properties ​

PropertyTypeDescription
namestringRequired. Unique item identifier matching data object key
labelstringDisplay label above the value (optional)
maxLengthnumberCharacter limit for display (0 = always show detail button, undefined = no truncation)
formatOvFormat | OvFormat[]Conditional formatting rules for item styling
actionsOvAction[]Array of actions available for this item
actionFormatOvFormat | OvFormat[]Default formatting for actions on this item

Display Modes ​

Single Item Display ​

When only one item is provided, it renders inline without container or grid:

typescript
{
  data: { name: 'John Doe' },
  options: {
    items: [{ name: 'name' }]
  }
}

Renders as: <span>John Doe</span>

Multi-Item Grid Display ​

With multiple items, renders in a responsive grid layout:

typescript
{
  options: {
    cols: 3,  // 3 columns on desktop, 1 on mobile
    items: [
      { name: 'name' },
      { name: 'email' },
      { name: 'phone' }
    ]
  }
}
  • Desktop: 3-column layout (1920px+)
  • Mobile: Single column layout (automatically on small screens)

Notes ​

  • Single-item display renders inline without container or grid wrapper
  • Multiple items automatically use responsive grid layout
  • Mobile devices always render single-column layout regardless of cols setting
  • Unformatted items display as plain text spans
  • Formatted items render as Vuetify chips with colors and icons
  • All labels and messages respect the t translation function
  • Actions with hidden: true format don't render
  • Detail modal uses readonly textarea to prevent accidental edits
  • Links use href for standard links, to for Vue Router navigation
  • Loading overlay prevents interaction during async operations
  • Grid actions render in separate row below all items
  • Item value substitution in links happens at render time, not template parse time