|
| 1 | +<template> |
| 2 | + <div |
| 3 | + :style="{ backgroundColor: bgColor }" |
| 4 | + class="flex flex-col gap-y-4 rounded-md p-4" |
| 5 | + > |
| 6 | + <div |
| 7 | + v-if="showImage" |
| 8 | + :style="{ |
| 9 | + backgroundColor: skeletonColor, |
| 10 | + width: imageWidth, |
| 11 | + height: imageHeight, |
| 12 | + order: orderMap.image, |
| 13 | + }" |
| 14 | + class="relative rounded-md" |
| 15 | + > |
| 16 | + <!-- Mountain triangle design for image placeholder --> |
| 17 | + <svg |
| 18 | + v-if="showImage" |
| 19 | + class="absolute inset-0 h-full w-full text-gray-300" |
| 20 | + fill="currentColor" |
| 21 | + viewBox="0 0 24 24" |
| 22 | + xmlns="http://www.w3.org/2000/svg" |
| 23 | + > |
| 24 | + <path fill-rule="evenodd" d="M2 20h20L12 4 2 20z" clip-rule="evenodd" /> |
| 25 | + </svg> |
| 26 | + </div> |
| 27 | + <div v-if="showText" class="space-y-2" :style="{ order: orderMap.text }"> |
| 28 | + <div |
| 29 | + :style="{ |
| 30 | + backgroundColor: skeletonColor, |
| 31 | + width: textWidthFirst, |
| 32 | + height: `${textHeight}`, |
| 33 | + }" |
| 34 | + class="rounded-md" |
| 35 | + ></div> |
| 36 | + <div |
| 37 | + v-for="n in textLines" |
| 38 | + :key="n" |
| 39 | + :style="{ |
| 40 | + backgroundColor: skeletonColor, |
| 41 | + width: textWidth, |
| 42 | + height: `${textHeight}`, |
| 43 | + }" |
| 44 | + class="rounded-md" |
| 45 | + ></div> |
| 46 | + </div> |
| 47 | + <div |
| 48 | + v-if="showVideo" |
| 49 | + :style="{ |
| 50 | + backgroundColor: skeletonColor, |
| 51 | + width: videoWidth, |
| 52 | + height: videoHeight, |
| 53 | + order: orderMap.video, |
| 54 | + }" |
| 55 | + class="relative flex items-center justify-center rounded-md" |
| 56 | + > |
| 57 | + <!-- Play button SVG for video placeholder --> |
| 58 | + <svg |
| 59 | + v-if="showVideo" |
| 60 | + fill="#000000" |
| 61 | + height="48px" |
| 62 | + width="48px" |
| 63 | + viewBox="0 0 60 60" |
| 64 | + xmlns="http://www.w3.org/2000/svg" |
| 65 | + > |
| 66 | + <path |
| 67 | + d="M45.563,29.174l-22-15c-0.307-0.208-0.703-0.231-1.031-0.058C22.205,14.289,22,14.629,22,15v30c0,0.371,0.205,0.711,0.533,0.884C22.679,45.962,22.84,46,23,46c0.197,0,0.394-0.059,0.563-0.174l22-15C45.836,30.64,46,30.331,46,30S45.836,29.36,45.563,29.174z M24,43.107V16.893L43.225,30L24,43.107z" |
| 68 | + /> |
| 69 | + <path |
| 70 | + d="M30,0C13.458,0,0,13.458,0,30s13.458,30,30,30s30-13.458,30-30S46.542,0,30,0z M30,58C14.561,58,2,45.439,2,30S14.561,2,30,2s28,12.561,28,28S45.439,58,30,58z" |
| 71 | + /> |
| 72 | + </svg> |
| 73 | + </div> |
| 74 | + </div> |
| 75 | +</template> |
| 76 | + |
| 77 | +<script setup lang="ts"> |
| 78 | +import { computed } from "vue" |
| 79 | +
|
| 80 | +const props = withDefaults( |
| 81 | + defineProps<{ |
| 82 | + bgColor: string |
| 83 | + skeletonColor: string |
| 84 | + showImage: boolean |
| 85 | + showText: boolean |
| 86 | + showVideo: boolean |
| 87 | + imageWidth: string |
| 88 | + imageHeight: string |
| 89 | + textWidthFirst: string |
| 90 | + textWidth: string |
| 91 | + textHeight: string |
| 92 | + textTotalHeight: string |
| 93 | + videoWidth: string |
| 94 | + videoHeight: string |
| 95 | + orders: string |
| 96 | + }>(), |
| 97 | + { |
| 98 | + bgColor: "#f3f4f6", // Default background color |
| 99 | + skeletonColor: "#e1e1e1", // Default skeleton color |
| 100 | + showImage: false, // Toggle for showing image skeleton |
| 101 | + showText: false, // Toggle for showing text skeleton |
| 102 | + showVideo: false, // Toggle for showing video skeleton |
| 103 | + imageWidth: "100%", // Default image width |
| 104 | + imageHeight: "150px", // Default image height |
| 105 | + textWidthFirst: "25%", // Default text width of first line |
| 106 | + textWidth: "75%", // Default text width |
| 107 | + textHeight: "20px", // Default text height |
| 108 | + textTotalHeight: "40px", // Default total text height |
| 109 | + videoWidth: "100%", // Default video width |
| 110 | + videoHeight: "200px", // Default video height |
| 111 | + orders: "text image video", // Default order of elements |
| 112 | + }, |
| 113 | +) |
| 114 | +
|
| 115 | +const textLines = computed(() => { |
| 116 | + const totalHeight = parseFloat(props.textTotalHeight) |
| 117 | + const lineHeight = parseFloat(props.textHeight) |
| 118 | + return Math.floor((totalHeight - lineHeight) / lineHeight) |
| 119 | +}) |
| 120 | +
|
| 121 | +const orderMap = computed(() => { |
| 122 | + const orderArray = props.orders.split(" ") |
| 123 | + // TODO: fix this any type |
| 124 | + const map: any = { |
| 125 | + text: 0, |
| 126 | + image: 1, |
| 127 | + video: 2, |
| 128 | + } |
| 129 | + orderArray.forEach((element, index) => { |
| 130 | + map[element] = index |
| 131 | + }) |
| 132 | + return map |
| 133 | +}) |
| 134 | +</script> |
| 135 | + |
| 136 | +<style scoped> |
| 137 | +div { |
| 138 | + @apply animate-pulse; |
| 139 | +} |
| 140 | +</style> |
0 commit comments