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
<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
| Prop | Type | Default | Description |
|---|---|---|---|
data | OvViewData | - | Required. Data object with key-value pairs matching item names |
options | OvViewOptions | undefined | View configuration with items, columns, and actions |
loading | boolean | false | Show loading overlay with spinner |
t | (text?: string) => string | (text) => text | Translation/localization function for labels and messages |
Events
| Event | Payload | Description |
|---|---|---|
details | itemName: string, value: string | Emitted when detail button clicked for truncated value |
action | actionName: string, value: unknown | Emitted when item action or grid action triggered |
View Options
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:
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
| Property | Type | Description |
|---|---|---|
name | string | Required. Unique item identifier matching data object key |
label | string | Display label above the value (optional) |
maxLength | number | Character limit for display (0 = always show detail button, undefined = no truncation) |
format | OvFormat | OvFormat[] | Conditional formatting rules for item styling |
actions | OvAction[] | Array of actions available for this item |
actionFormat | OvFormat | 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:
{
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:
{
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
colssetting - Unformatted items display as plain text spans
- Formatted items render as Vuetify chips with colors and icons
- All labels and messages respect the
ttranslation function - Actions with
hidden: trueformat don't render - Detail modal uses readonly textarea to prevent accidental edits
- Links use
hreffor standard links,tofor 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