
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
USPTO and WIPO intellectual property asset portfolio tracker, patent grant status, jurisdiction coverage, and maintenance fee timeline with docket action workflow.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.jsonnpx shadcn-vue@latest add @uipkge/trademark-patent-portfolioInstalls to:app/components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
Type aliases exported from this item's source. Use these to shape the data you pass in.
IpAssetinterface IpAsset {
id: string
identifier: string
officialNumber: string
title: string
type: AssetType
jurisdictionCode: string
jurisdictionName: string
jurisdictionFlag: string
patentOffice: string
applicationDate: string
grantDate: string
status: FilingStatus
nextDeadline: string
deadlineNumber: string
deadlineNote: string
ipcClass: string
attorney: string
gazetteUrl: string
gazetteLabel: string
}<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { computed, ref } from 'vue'
import {
AlertCircle,
ArrowUpRight,
Award,
BadgeCheck,
Building2,
Calendar,
CheckCircle2,
ChevronDown,
Clock,
Copy,
Download,
ExternalLink,
FilePlus2,
FileSpreadsheet,
FileText,
Filter,
Globe,
MoreHorizontal,
Plus,
Scale,
Search,
ShieldCheck,
UserCheck,
} from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { cn } from '@/lib/utils'
export interface TrademarkPatentPortfolioProps {
class?: HTMLAttributes['class']
}
const props = defineProps<TrademarkPatentPortfolioProps>()
export type AssetType = 'Utility Patent' | 'Trademark' | 'PCT International'
export type FilingStatus = 'Granted / Registered' | 'Published / Under Exam'
export interface IpAsset {
id: string
identifier: string
officialNumber: string
title: string
type: AssetType
jurisdictionCode: string
jurisdictionName: string
jurisdictionFlag: string
patentOffice: string
applicationDate: string
grantDate: string
status: FilingStatus
nextDeadline: string
deadlineNumber: string
deadlineNote: string
ipcClass: string
attorney: string
gazetteUrl: string
gazetteLabel: string
}
const ipAssets: IpAsset[] = [
{
id: 'ip-1',
identifier: 'US Patent #11,849,201',
officialNumber: 'US 11,849,201 B2',
title: 'Zero-Dependency Dual-Framework AST Generation',
type: 'Utility Patent',
jurisdictionCode: 'US',
jurisdictionName: 'United States',
jurisdictionFlag: '🇺🇸',
patentOffice: 'USPTO',
applicationDate: '2023-04-12',
grantDate: '2024-06-18',
status: 'Granted / Registered',
nextDeadline: 'Dec 2026',
deadlineNumber: '2026-12-15',
deadlineNote: 'USPTO 3.5-Year Maintenance Window',
ipcClass: 'CPC G06F 8/41 · Abstract Syntax Trees',
attorney: 'Vance & Morrison LLP',
gazetteUrl: 'https://patents.google.com/patent/US11849201B2/en',
gazetteLabel: 'View USPTO Gazette',
},
{
id: 'ip-2',
identifier: 'US Patent #11,420,184',
officialNumber: 'US 11,420,184 B1',
title: 'Perceptually Uniform OKLCH Dark Mode Algorithm',
type: 'Utility Patent',
jurisdictionCode: 'US',
jurisdictionName: 'United States',
jurisdictionFlag: '🇺🇸',
patentOffice: 'USPTO',
applicationDate: '2022-09-14',
grantDate: '2023-08-22',
status: 'Granted / Registered',
nextDeadline: 'Feb 2027',
deadlineNumber: '2027-02-22',
deadlineNote: 'USPTO 3.5-Year Maintenance Window',
ipcClass: 'CPC G06T 11/00 · Perceptual Colorimetry',
attorney: 'Vance & Morrison LLP',
gazetteUrl: 'https://patents.google.com/patent/US11420184B1/en',
gazetteLabel: 'View USPTO Gazette',
},
{
id: 'ip-3',
identifier: 'US Trademark #98421042',
officialNumber: 'Reg. #7,294,810 (SN 98421042)',
title: 'UIPKGE™ (Class 09, 42)',
type: 'Trademark',
jurisdictionCode: 'US',
jurisdictionName: 'United States',
jurisdictionFlag: '🇺🇸',
patentOffice: 'USPTO',
applicationDate: '2023-02-10',
grantDate: '2023-11-14',
status: 'Granted / Registered',
nextDeadline: 'Nov 2029',
deadlineNumber: '2029-11-14',
deadlineNote: 'Section 8 & 15 Affidavit of Use',
ipcClass: 'IC 009, 042 · Computer Software & SaaS',
attorney: 'Kilpatrick Townsend & Stockton',
gazetteUrl: 'https://tsdr.uspto.gov/',
gazetteLabel: 'View USPTO TSDR',
},
{
id: 'ip-4',
identifier: 'WIPO PCT/US2026/018249',
officialNumber: 'WO 2026/148201 A1',
title: 'Headless Component State Serialization',
type: 'PCT International',
jurisdictionCode: 'WIPO',
jurisdictionName: 'WIPO International',
jurisdictionFlag: '🌍',
patentOffice: 'WIPO IB',
applicationDate: '2026-01-15',
grantDate: 'Pending',
status: 'Published / Under Exam',
nextDeadline: 'Jul 2027',
deadlineNumber: '2027-07-15',
deadlineNote: '30-Mo National Phase Entry Deadline',
ipcClass: 'IPC G06F 9/44 · Component State Transfer',
attorney: 'Vance & Morrison LLP',
gazetteUrl: 'https://patentscope.wipo.int/',
gazetteLabel: 'View WIPO Patentscope',
},
{
id: 'ip-5',
identifier: 'EP Patent #3,948,102',
officialNumber: 'EP 3,948,102 A1',
title: 'Reactive Virtual DOM Diffing Engine for Web Components',
type: 'Utility Patent',
jurisdictionCode: 'EU',
jurisdictionName: 'European Union',
jurisdictionFlag: '🇪🇺',
patentOffice: 'EPO',
applicationDate: '2024-03-02',
grantDate: 'Pending',
status: 'Published / Under Exam',
nextDeadline: 'Mar 2027',
deadlineNumber: '2027-03-31',
deadlineNote: 'EPO 3rd Year Annuity Maintenance Fee',
ipcClass: 'CPC G06F 9/455 · Virtualized DOM Execution',
attorney: 'Bardehle Pagenberg Partnerschaft',
gazetteUrl: 'https://worldwide.espacenet.com/',
gazetteLabel: 'View EPO Espacenet',
},
{
id: 'ip-6',
identifier: 'EU Trademark #018942001',
officialNumber: 'Reg. #018942001',
title: 'UIPKGE DESIGN SYSTEM™ (Class 09, 35, 42)',
type: 'Trademark',
jurisdictionCode: 'EU',
jurisdictionName: 'European Union',
jurisdictionFlag: '🇪🇺',
patentOffice: 'EUIPO',
applicationDate: '2023-08-05',
grantDate: '2024-01-19',
status: 'Granted / Registered',
nextDeadline: 'May 2030',
deadlineNumber: '2030-05-08',
deadlineNote: 'EUIPO 10-Year Trademark Renewal',
ipcClass: 'IC 009, 035, 042 · Design Systems & Tooling',
attorney: 'Bardehle Pagenberg Partnerschaft',
gazetteUrl: 'https://euipo.europa.eu/eSearch/',
gazetteLabel: 'View EUIPO eSearch',
},
]
const searchQuery = ref('')
const selectedType = ref<'all' | AssetType>('all')
const selectedStatus = ref<'all' | FilingStatus>('all')
function resetFilters() {
searchQuery.value = ''
selectedType.value = 'all'
selectedStatus.value = 'all'
}
const copiedId = ref<string | null>(null)
const feedbackMessage = ref<string | null>(null)
const filteredAssets = computed(() => {
const query = searchQuery.value.trim().toLowerCase()
return ipAssets.filter((asset) => {
const matchesType = selectedType.value === 'all' || asset.type === selectedType.value
const matchesStatus = selectedStatus.value === 'all' || asset.status === selectedStatus.value
const matchesSearch =
!query ||
asset.identifier.toLowerCase().includes(query) ||
asset.officialNumber.toLowerCase().includes(query) ||
asset.title.toLowerCase().includes(query) ||
asset.jurisdictionName.toLowerCase().includes(query) ||
asset.patentOffice.toLowerCase().includes(query) ||
asset.ipcClass.toLowerCase().includes(query)
return matchesType && matchesStatus && matchesSearch
})
})
function copyText(id: string, text: string) {
navigator.clipboard?.writeText(text)
copiedId.value = id
setTimeout(() => {
copiedId.value = null
}, 2000)
}
function handleExport() {
feedbackMessage.value = 'Exporting IP Portfolio summary (CSV / PDF)...'
setTimeout(() => {
feedbackMessage.value = null
}, 3000)
}
function handleFileNewAsset() {
feedbackMessage.value = 'Opening USPTO / WIPO EFS-Web filing wizard...'
setTimeout(() => {
feedbackMessage.value = null
}, 3000)
}
function handleAction(action: string, asset: IpAsset) {
feedbackMessage.value = `${action}: ${asset.identifier}`
setTimeout(() => {
feedbackMessage.value = null
}, 3000)
}
</script>
<template>
<div data-slot="trademark-patent-portfolio" :class="cn('w-full space-y-6', props.class)">
<!-- Header Section -->
<Card class="border-border shadow-xs">
<CardHeader class="flex flex-col gap-4 pb-6 lg:flex-row lg:items-center lg:justify-between">
<div class="space-y-1.5">
<div class="flex flex-wrap items-center gap-2">
<Badge variant="outline" class="gap-1.5 font-medium">
<Building2 class="text-muted-foreground size-3.5" aria-hidden="true" />
<span>UIPKGE Technologies Inc.</span>
</Badge>
<Badge wrap variant="secondary" class="gap-1 font-mono text-xs">
<Scale class="text-primary size-3" aria-hidden="true" />
<span>12 Patents · 6 Registered Trademarks</span>
</Badge>
</div>
<CardTitle class="text-xl font-bold tracking-tight sm:text-2xl">
Intellectual Property & Patent Portfolio
</CardTitle>
<CardDescription class="text-xs sm:text-sm">
USPTO, EPO, and WIPO asset docket, grant prosecution tracker, and statutory maintenance timeline.
</CardDescription>
</div>
<!-- Action Buttons -->
<div class="flex flex-wrap items-center gap-2.5">
<Button aria-label="Download attachment" variant="outline" class="gap-2 shadow-xs" @click="handleExport">
<Download class="size-4" aria-hidden="true" />
<span>Export Portfolio Report</span>
</Button>
<Button variant="default" class="gap-2 shadow-xs" @click="handleFileNewAsset">
<Plus class="size-4" aria-hidden="true" />
<span>File New IP Asset</span>
</Button>
</div>
</CardHeader>
</Card>
<!-- Feedback Notification Toast/Banner if active -->
<div
v-if="feedbackMessage"
class="border-primary/30 bg-primary/10 text-primary flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium shadow-xs transition-colors"
>
<div class="flex items-center gap-2">
<CheckCircle2 class="size-4 shrink-0" aria-hidden="true" />
<span>{{ feedbackMessage }}</span>
</div>
<button
type="button"
@click="feedbackMessage = null"
class="text-primary/70 hover:text-primary rounded text-xs underline underline-offset-2"
>
Dismiss
</button>
</div>
<!-- 4 IP Portfolio KPI Cards Grid -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<!-- Card 1: Granted Utility Patents -->
<Card class="border-border shadow-xs">
<CardContent class="p-5">
<div class="flex items-center justify-between">
<span class="text-muted-foreground text-xs font-medium">Granted Patents</span>
<div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-md">
<Award class="size-4" aria-hidden="true" />
</div>
</div>
<div class="mt-3">
<div class="text-success text-success text-2xl font-bold tracking-tight tabular-nums">
8 Patents Granted
</div>
<p class="text-muted-foreground mt-1 text-xs">6 USPTO · 2 EPO · 100% Enforceable</p>
</div>
</CardContent>
</Card>
<!-- Card 2: Pending Applications -->
<Card class="border-border shadow-xs">
<CardContent class="p-5">
<div class="flex items-center justify-between">
<span class="text-muted-foreground text-xs font-medium">Pending Examination</span>
<div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md">
<Clock class="size-4" aria-hidden="true" />
</div>
</div>
<div class="mt-3">
<div class="text-warning text-warning text-2xl font-bold tracking-tight tabular-nums">
4 Under Examination · USPTO
</div>
<p class="text-muted-foreground mt-1 text-xs">2 PCT International · 1 USPTO · 1 EPO</p>
</div>
</CardContent>
</Card>
<!-- Card 3: Registered Trademarks -->
<Card class="border-border shadow-xs">
<CardContent class="p-5">
<div class="flex items-center justify-between">
<span class="text-muted-foreground text-xs font-medium">Registered Trademarks</span>
<div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-md">
<BadgeCheck class="size-4" aria-hidden="true" />
</div>
</div>
<div class="mt-3">
<div class="text-info text-info text-2xl font-bold tracking-tight tabular-nums">6 Trademarks Active</div>
<p class="text-muted-foreground mt-1 text-xs">Classes 09, 35, 42 · Global Protection</p>
</div>
</CardContent>
</Card>
<!-- Card 4: Next Maintenance Fee -->
<Card class="border-border shadow-xs">
<CardContent class="p-5">
<div class="flex items-center justify-between">
<span class="text-muted-foreground text-xs font-medium">Next Maintenance Fee</span>
<div class="bg-destructive/10 text-destructive flex size-8 items-center justify-center rounded-md">
<Calendar class="size-4" aria-hidden="true" />
</div>
</div>
<div class="mt-3">
<div class="text-destructive text-destructive text-2xl font-bold tracking-tight tabular-nums">
Due Dec 15, 2026 · USPTO 3.5-Year Window
</div>
<p class="text-muted-foreground mt-1 text-xs">Est. fee $2,000 · Surcharge window opens</p>
</div>
</CardContent>
</Card>
</div>
<!-- Filters & Table Container Card -->
<Card class="border-border shadow-xs">
<CardHeader class="border-border border-b pb-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<CardTitle class="text-base font-semibold">Registered Intellectual Property Assets</CardTitle>
<CardDescription class="text-xs">
Showing {{ filteredAssets.length }} of {{ ipAssets.length }} assets across USPTO, EUIPO, and WIPO
</CardDescription>
</div>
<!-- Controls: Search and Filters -->
<div class="flex flex-wrap items-center gap-2">
<div class="relative min-w-[200px] flex-1 sm:w-64">
<Search
aria-hidden="true"
class="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2"
/>
<Input
v-model="searchQuery"
type="text"
placeholder="Search patent #, title, class..."
class="h-8 pl-8 text-xs"
/>
</div>
<!-- Type Filter Dropdown -->
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" size="sm" class="h-8 gap-1.5 text-xs">
<Filter class="size-3.5 opacity-70" aria-hidden="true" />
<span>{{ selectedType === 'all' ? 'Type: All' : selectedType }}</span>
<ChevronDown class="size-3 opacity-50" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-48">
<DropdownMenuLabel class="text-xs">Filter by Type</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem @click="selectedType = 'all'">All Types</DropdownMenuItem>
<DropdownMenuItem @click="selectedType = 'Utility Patent'">Utility Patent</DropdownMenuItem>
<DropdownMenuItem @click="selectedType = 'Trademark'">Trademark</DropdownMenuItem>
<DropdownMenuItem @click="selectedType = 'PCT International'">PCT International</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<!-- Status Filter Dropdown -->
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" size="sm" class="h-8 gap-1.5 text-xs">
<span>{{ selectedStatus === 'all' ? 'Status: All' : selectedStatus }}</span>
<ChevronDown class="size-3 opacity-50" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-52">
<DropdownMenuLabel class="text-xs">Filter by Status</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem @click="selectedStatus = 'all'">All Statuses</DropdownMenuItem>
<DropdownMenuItem @click="selectedStatus = 'Granted / Registered'"
>Granted / Registered</DropdownMenuItem
>
<DropdownMenuItem @click="selectedStatus = 'Published / Under Exam'"
>Published / Under Exam</DropdownMenuItem
>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</CardHeader>
<CardContent class="p-0">
<div class="overflow-x-auto">
<Table>
<TableHeader class="bg-muted/40">
<TableRow>
<TableHead class="min-w-[280px]">Asset Identifier & Title</TableHead>
<TableHead class="w-[140px]">Type</TableHead>
<TableHead class="w-[160px]">Jurisdiction</TableHead>
<TableHead class="w-[160px]">App / Grant Date</TableHead>
<TableHead class="w-[170px]">Filing Status</TableHead>
<TableHead class="w-[180px]">Next Renewal / Deadline</TableHead>
<TableHead class="w-[70px] text-right"><span class="sr-only">Actions</span></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="asset in filteredAssets" :key="asset.id" class="hover:bg-muted/40 transition-colors">
<!-- Asset Identifier & Title -->
<TableCell class="py-3.5 align-top">
<div class="space-y-1">
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-foreground font-mono text-xs font-bold tracking-tight">
{{ asset.identifier }}
</span>
<button
type="button"
@click="copyText(asset.id, asset.officialNumber)"
class="text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex items-center gap-1 rounded p-0.5 transition-colors focus-visible:ring-2 focus-visible:outline-hidden"
:title="`Copy ${asset.officialNumber}`"
>
<Check v-if="copiedId === asset.id" class="text-success size-3" aria-hidden="true" />
<Copy v-else class="size-3" aria-hidden="true" />
</button>
</div>
<div class="text-foreground text-xs font-medium">
{{ asset.title }}
</div>
<div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
<span>{{ asset.ipcClass }}</span>
<span>•</span>
<span>Counsel: {{ asset.attorney }}</span>
</div>
</div>
</TableCell>
<!-- Type Badge -->
<TableCell class="align-top">
<Badge
variant="outline"
:class="
cn('font-mono text-xs font-medium', {
'border-chart-1/30 bg-chart-1/10 text-chart-1': asset.type === 'Utility Patent',
'border-info/30 bg-info/10 text-info': asset.type === 'Trademark',
'border-warning/30 bg-warning/10 text-warning': asset.type === 'PCT International',
})
"
>
{{ asset.type }}
</Badge>
</TableCell>
<!-- Jurisdiction Flag & Office -->
<TableCell class="align-top">
<div class="flex items-center gap-2">
<span class="text-base leading-none" :aria-label="asset.jurisdictionName">
{{ asset.jurisdictionFlag }}
</span>
<div>
<div class="text-foreground text-xs font-medium">
{{ asset.jurisdictionName }}
</div>
<div class="text-muted-foreground font-mono text-xs">
{{ asset.patentOffice }}
</div>
</div>
</div>
</TableCell>
<!-- Application / Grant Date in tabular-nums -->
<TableCell class="align-top">
<div class="space-y-0.5 font-mono text-xs tabular-nums">
<div class="text-muted-foreground flex items-center gap-1.5">
<span class="text-muted-foreground/70">Filing:</span>
<span class="text-foreground font-medium">{{ asset.applicationDate }}</span>
</div>
<div class="text-muted-foreground flex items-center gap-1.5">
<span class="text-muted-foreground/70">Grant:</span>
<span
:class="
asset.grantDate === 'Pending' ? 'text-warning font-medium' : 'text-foreground font-medium'
"
>
{{ asset.grantDate }}
</span>
</div>
</div>
</TableCell>
<!-- Filing Status -->
<TableCell class="align-top">
<div
v-if="asset.status === 'Granted / Registered'"
class="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium"
>
<span class="relative flex size-1.5">
<span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span>
<span class="bg-success relative inline-flex size-1.5 rounded-full"></span>
</span>
<span>Granted / Registered</span>
</div>
<div
v-else
class="border-warning/30 bg-warning/10 text-warning inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium"
>
<Clock class="text-warning size-3" aria-hidden="true" />
<span>Published / Under Exam</span>
</div>
</TableCell>
<!-- Next Renewal / Maintenance Deadline -->
<TableCell class="align-top">
<div class="space-y-0.5">
<div class="text-foreground flex items-center gap-1.5 font-mono text-xs font-semibold tabular-nums">
<Calendar class="text-muted-foreground size-3.5" aria-hidden="true" />
<span>{{ asset.nextDeadline }}</span>
</div>
<p class="text-muted-foreground text-xs leading-tight">
{{ asset.deadlineNote }}
</p>
</div>
</TableCell>
<!-- Actions Dropdown -->
<TableCell class="py-3.5 text-right align-top">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="ghost"
size="icon-sm"
class="text-muted-foreground hover:text-foreground focus-visible:ring-ring"
>
<MoreHorizontal class="size-4" aria-hidden="true" />
<span class="sr-only">Open actions for {{ asset.identifier }}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-56">
<DropdownMenuLabel class="text-xs">Asset Operations</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem @click="handleAction(asset.gazetteLabel, asset)">
<ExternalLink class="mr-2 size-4" aria-hidden="true" />
<span>{{ asset.gazetteLabel }}</span>
</DropdownMenuItem>
<DropdownMenuItem @click="handleAction('Download Grant Certificate', asset)">
<Download class="mr-2 size-4" aria-hidden="true" />
<span>Download Grant Certificate</span>
</DropdownMenuItem>
<DropdownMenuItem @click="handleAction('Assign Counsel', asset)">
<UserCheck class="mr-2 size-4" aria-hidden="true" />
<span>Assign Counsel</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem @click="copyText(asset.id, asset.officialNumber)">
<Copy class="mr-2 size-4" aria-hidden="true" />
<span>Copy Docket Number</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<!-- Empty Filter State -->
<TableRow v-if="filteredAssets.length === 0">
<TableCell colspan="7" class="text-muted-foreground py-10 text-center text-xs">
<div class="flex flex-col items-center justify-center gap-2">
<FileText class="text-muted-foreground/60 size-8" aria-hidden="true" />
<p class="font-medium">No intellectual property assets match your search filters.</p>
<Button variant="outline" size="sm" class="mt-1 text-xs" @click="resetFilters">
Reset Filters
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
</template>
Raw manifest:https://uipkge.dev/r/vue/trademark-patent-portfolio.json