[READ-ONLY] a fast, modern browser for the npm registry
at main 155 lines 5.2 kB view raw
1<script setup lang="ts"> 2import type { FacetValue } from '#shared/types' 3 4const props = defineProps<{ 5 /** Facet label */ 6 label: string 7 /** Description/tooltip for the facet */ 8 description?: string 9 /** Values for each column */ 10 values: (FacetValue | null | undefined)[] 11 /** Whether this facet is loading (e.g., install size) */ 12 facetLoading?: boolean 13 /** Whether each column is loading (array matching values) */ 14 columnLoading?: boolean[] 15 /** Whether to show the proportional bar (defaults to true for numeric values) */ 16 bar?: boolean 17}>() 18 19// Check if all values are numeric (for bar visualization) 20const isNumeric = computed(() => { 21 return props.values.every(v => v === null || v === undefined || typeof v.raw === 'number') 22}) 23 24// Show bar if explicitly enabled, or if not specified and values are numeric 25const showBar = computed(() => { 26 return props.bar ?? isNumeric.value 27}) 28 29// Get max value for bar width calculation 30const maxValue = computed(() => { 31 if (!isNumeric.value) return 0 32 return Math.max(...props.values.map(v => (typeof v?.raw === 'number' ? v.raw : 0))) 33}) 34 35// Calculate bar width percentage for a value 36function getBarWidth(value: FacetValue | null | undefined): number { 37 if (!isNumeric.value || !maxValue.value || !value || typeof value.raw !== 'number') return 0 38 return (value.raw / maxValue.value) * 100 39} 40 41function getStatusClass(status?: FacetValue['status'], hasBar = false): string { 42 // When there's a bar, only apply text color, not background 43 if (hasBar) { 44 switch (status) { 45 case 'muted': 46 return 'text-fg-subtle' 47 default: 48 return 'text-fg' 49 } 50 } 51 52 // Original behavior when no bar 53 switch (status) { 54 case 'good': 55 return 'bg-emerald-400/20 px-3 py-0.5 rounded-xl' 56 case 'info': 57 return 'bg-blue-400/20 px-3 py-0.5 rounded-xl' 58 case 'warning': 59 return 'bg-amber-400/20 px-3 py-0.5 rounded-xl' 60 case 'bad': 61 return 'bg-red-400/20 px-3 py-0.5 rounded-xl' 62 case 'muted': 63 return 'text-fg-subtle' 64 default: 65 return 'text-fg' 66 } 67} 68 69function getStatusBarClass(status?: FacetValue['status']): string { 70 switch (status) { 71 case 'good': 72 return 'bg-emerald-500/20' 73 case 'info': 74 return 'bg-blue-500/20' 75 case 'warning': 76 return 'bg-amber-500/20' 77 case 'bad': 78 return 'bg-red-500/20' 79 default: 80 return 'bg-fg/5' 81 } 82} 83 84// Check if a specific cell is loading 85function isCellLoading(index: number): boolean { 86 return props.facetLoading || (props.columnLoading?.[index] ?? false) 87} 88</script> 89 90<template> 91 <div class="contents"> 92 <!-- Label cell --> 93 <div class="comparison-label flex items-center gap-1.5 px-4 py-3 border-b border-border"> 94 <span class="text-xs text-fg-muted uppercase tracking-wider">{{ label }}</span> 95 <TooltipApp v-if="description" :text="description" position="top"> 96 <span class="i-lucide:info w-3 h-3 text-fg-subtle cursor-help" aria-hidden="true" /> 97 </TooltipApp> 98 </div> 99 100 <!-- Value cells --> 101 <div 102 v-for="(value, index) in values" 103 :key="index" 104 class="comparison-cell relative flex items-center justify-center px-4 py-3 border-b border-border" 105 > 106 <!-- Background bar for numeric values --> 107 <div 108 v-if="showBar && value && getBarWidth(value) > 0" 109 class="absolute inset-y-1 inset-is-1 rounded-sm transition-all duration-300" 110 :class="getStatusBarClass(value.status)" 111 :style="{ width: `calc(${getBarWidth(value)}% - 8px)` }" 112 aria-hidden="true" 113 /> 114 115 <!-- Loading state --> 116 <template v-if="isCellLoading(index)"> 117 <span class="i-svg-spinners:ring-resize w-4 h-4 text-fg-subtle" aria-hidden="true" /> 118 </template> 119 120 <!-- No data --> 121 <template v-else-if="!value"> 122 <span class="text-fg-subtle text-sm">-</span> 123 </template> 124 125 <!-- Value display --> 126 <template v-else> 127 <TooltipApp v-if="value.tooltip" :text="value.tooltip" position="top"> 128 <span 129 class="relative font-mono text-sm text-center tabular-nums cursor-help" 130 :class="getStatusClass(value.status, showBar && getBarWidth(value) > 0)" 131 :data-status="value.status" 132 > 133 <!-- Date values use DateTime component for i18n and user settings --> 134 <DateTime v-if="value.type === 'date'" :datetime="value.display" date-style="medium" /> 135 <template v-else> 136 <span dir="auto">{{ value.display }}</span> 137 </template> 138 </span> 139 </TooltipApp> 140 <span 141 v-else 142 class="relative font-mono text-sm text-center tabular-nums" 143 :class="getStatusClass(value.status, showBar && getBarWidth(value) > 0)" 144 :data-status="value.status" 145 > 146 <!-- Date values use DateTime component for i18n and user settings --> 147 <DateTime v-if="value.type === 'date'" :datetime="value.display" date-style="medium" /> 148 <template v-else> 149 <span dir="auto">{{ value.display }}</span> 150 </template> 151 </span> 152 </template> 153 </div> 154 </div> 155</template>