| 1 | <template> |
| 2 | <n-card content-class="p-0!" :class="{ hovered }"> |
| 3 | <div class="flex h-full flex-col overflow-hidden"> |
| 4 | <div class="card-header flex items-center justify-between gap-4"> |
| 5 | <div class="title flex grow items-center gap-2"> |
| 6 | <span class="truncate">{{ title }}</span> |
| 7 | <Icon v-if="hovered" :name="ArrowRightIcon" :size="12" /> |
| 8 | </div> |
| 9 | <div class="icon"> |
| 10 | <slot name="icon"></slot> |
| 11 | </div> |
| 12 | </div> |
| 13 | <div class="card-content divide-border grid grow grid-cols-[repeat(auto-fit,minmax(0,1fr))] divide-x"> |
| 14 | <div |
| 15 | v-for="item of values" |
| 16 | :key="JSON.stringify(item)" |
| 17 | class="value-box flex flex-col" |
| 18 | :class="[item.status, values.length !== 1 ? `basis-1/${values.length}` : 'grow']" |
| 19 | > |
| 20 | <div class="value flex grow items-center justify-center"> |
| 21 | {{ item.value }} |
| 22 | </div> |
| 23 | <div v-if="item.label" class="label"> |
| 24 | {{ item.label }} |
| 25 | </div> |
| 26 | </div> |
| 27 | </div> |
| 28 | </div> |
| 29 | </n-card> |
| 30 | </template> |
| 31 | |
| 32 | <script setup lang="ts"> |
| 33 | import { NCard } from "naive-ui" |
| 34 | import { toRefs } from "vue" |
| 35 | import Icon from "@/components/common/Icon.vue" |
| 36 | |
| 37 | export interface ItemProps { |
| 38 | value: number | string |
| 39 | label?: string |
| 40 | status?: "success" | "warning" | "error" |
| 41 | } |
| 42 | |
| 43 | const props = defineProps<{ |
| 44 | title: string |
| 45 | values: ItemProps[] |
| 46 | hovered?: boolean |
| 47 | }>() |
| 48 | const { title, values, hovered } = toRefs(props) |
| 49 | |
| 50 | const ArrowRightIcon = "carbon:arrow-right" |
| 51 | </script> |
| 52 | |
| 53 | <style scoped lang="scss"> |
| 54 | .n-card { |
| 55 | overflow: hidden; |
| 56 | |
| 57 | .card-header { |
| 58 | border-bottom: 1px solid var(--border-color); |
| 59 | overflow: hidden; |
| 60 | padding: 10px 16px; |
| 61 | |
| 62 | .title { |
| 63 | font-size: 16px; |
| 64 | text-overflow: ellipsis; |
| 65 | white-space: nowrap; |
| 66 | overflow: hidden; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | .card-content { |
| 71 | .value-box { |
| 72 | text-align: center; |
| 73 | overflow: hidden; |
| 74 | |
| 75 | .value { |
| 76 | font-family: var(--font-family-display); |
| 77 | padding: 10px 6px; |
| 78 | font-size: 22px; |
| 79 | text-overflow: ellipsis; |
| 80 | white-space: nowrap; |
| 81 | overflow: hidden; |
| 82 | font-weight: bold; |
| 83 | text-overflow: ellipsis; |
| 84 | white-space: nowrap; |
| 85 | line-height: 1; |
| 86 | overflow: hidden; |
| 87 | } |
| 88 | |
| 89 | .label { |
| 90 | font-family: var(--font-family-mono); |
| 91 | border-top: 1px solid var(--border-color); |
| 92 | color: var(--fg-secondary-color); |
| 93 | font-size: 13px; |
| 94 | padding: 6px; |
| 95 | line-height: 1; |
| 96 | background-color: var(--bg-secondary-color); |
| 97 | text-overflow: ellipsis; |
| 98 | white-space: nowrap; |
| 99 | overflow: hidden; |
| 100 | text-transform: uppercase; |
| 101 | } |
| 102 | |
| 103 | &.success { |
| 104 | .value { |
| 105 | color: var(--success-color); |
| 106 | } |
| 107 | .label { |
| 108 | color: var(--success-color); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | &.warning { |
| 113 | .value { |
| 114 | color: var(--warning-color); |
| 115 | } |
| 116 | .label { |
| 117 | color: var(--warning-color); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | &.error { |
| 122 | .value { |
| 123 | color: var(--error-color); |
| 124 | } |
| 125 | .label { |
| 126 | color: var(--error-color); |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | &.hovered { |
| 133 | &:hover { |
| 134 | border-color: var(--primary-color); |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | </style> |