feat: add hover percentage indicator on dashboard panel rows
Shows percentage of total pageviews on hover with a slide-in animation from right to left across all panels (Content, Locations, Technology, Top Referrers).
This commit is contained in:
@@ -78,6 +78,7 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
|
||||
}
|
||||
|
||||
const data = getData()
|
||||
const totalPageviews = data.reduce((sum, p) => sum + p.pageviews, 0)
|
||||
const hasData = data && data.length > 0
|
||||
const displayedData = hasData ? data.slice(0, LIMIT) : []
|
||||
const emptySlots = Math.max(0, LIMIT - displayedData.length)
|
||||
@@ -152,8 +153,13 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
|
||||
<ArrowUpRightIcon className="w-3 h-3 text-neutral-400 opacity-0 group-hover:opacity-100 transition-opacity hover:text-brand-orange" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<span className="text-xs text-neutral-400 dark:text-neutral-500 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
|
||||
{totalPageviews > 0 ? `${Math.round((page.pageviews / totalPageviews) * 100)}%` : ''}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||
{formatNumber(page.pageviews)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -176,6 +176,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
|
||||
const rawData = activeTab === 'map' ? [] : getData()
|
||||
const data = filterUnknown(rawData)
|
||||
const totalPageviews = data.reduce((sum, item) => sum + item.pageviews, 0)
|
||||
const hasData = activeTab === 'map'
|
||||
? (countries && filterUnknown(countries).length > 0)
|
||||
: (data && data.length > 0)
|
||||
@@ -269,8 +270,13 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
getCityName(item.city ?? '')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<span className="text-xs text-neutral-400 dark:text-neutral-500 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
|
||||
{totalPageviews > 0 ? `${Math.round((item.pageviews / totalPageviews) * 100)}%` : ''}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||
{formatNumber(item.pageviews)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -112,6 +112,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
||||
|
||||
const rawData = getRawData()
|
||||
const data = filterUnknown(rawData)
|
||||
const totalPageviews = data.reduce((sum, item) => sum + item.pageviews, 0)
|
||||
const hasData = data && data.length > 0
|
||||
const displayedData = hasData ? data.slice(0, LIMIT) : []
|
||||
const emptySlots = Math.max(0, LIMIT - displayedData.length)
|
||||
@@ -173,8 +174,13 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
||||
{item.icon && <span className="text-lg">{item.icon}</span>}
|
||||
<span className="truncate">{item.name}</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<span className="text-xs text-neutral-400 dark:text-neutral-500 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
|
||||
{totalPageviews > 0 ? `${Math.round((item.pageviews / totalPageviews) * 100)}%` : ''}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||
{formatNumber(item.pageviews)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
||||
|
||||
const mergedReferrers = mergeReferrersByDisplayName(filteredReferrers)
|
||||
|
||||
const totalPageviews = mergedReferrers.reduce((sum, r) => sum + r.pageviews, 0)
|
||||
const hasData = mergedReferrers.length > 0
|
||||
const displayedReferrers = hasData ? mergedReferrers.slice(0, LIMIT) : []
|
||||
const emptySlots = Math.max(0, LIMIT - displayedReferrers.length)
|
||||
@@ -114,8 +115,13 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
||||
{renderReferrerIcon(ref.referrer)}
|
||||
<span className="truncate" title={ref.referrer}>{getReferrerDisplayName(ref.referrer)}</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<span className="text-xs text-neutral-400 dark:text-neutral-500 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
|
||||
{totalPageviews > 0 ? `${Math.round((ref.pageviews / totalPageviews) * 100)}%` : ''}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||
{formatNumber(ref.pageviews)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user