PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.7.0
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.7.0
1.7.1 1.7.0 1.6.0 1.5.9 1.5.8 1.5.7 1.5.6 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6
hostinger-reach / frontend / vue / components / UsageCard.vue
hostinger-reach / frontend / vue / components Last commit date
Modals 1 month ago skeletons 4 months ago ActionButton.vue 5 months ago ActionButtonsSection.vue 5 months ago Banner.vue 2 months ago FAQ.vue 5 months ago FormItem.vue 1 month ago FormsSection.vue 10 months ago Hero.vue 2 weeks ago Integrations.vue 4 months ago Pagination.vue 10 months ago PluginEntriesTable.vue 3 months ago PluginEntry.vue 4 months ago PluginExpansion.vue 7 months ago SyncStatusLabel.vue 6 months ago Tabs.vue 9 months ago Toggle.vue 10 months ago UsageCard.vue 10 months ago UsageCardsRow.vue 11 months ago UsageCardsSection.vue 11 months ago WooCommerce.vue 5 months ago WooCommerceEntriesTable.vue 4 months ago
UsageCard.vue
247 lines
1 <script setup lang="ts">
2 import { HSkeletonLoader } from '@hostinger/hcomponents';
3 import { computed } from 'vue';
4
5 import { translate } from '@/utils/translate';
6
7 interface MetricData {
8 label: string;
9 value: string | number;
10 hasIcon?: boolean;
11 tooltip?: string;
12 }
13
14 interface Props {
15 title: string;
16 layout: 'horizontal' | 'vertical';
17 metrics: MetricData[];
18 isLoading?: boolean;
19 }
20
21 const props = withDefaults(defineProps<Props>(), {
22 isLoading: false
23 });
24
25 const isHorizontal = computed(() => props.layout === 'horizontal');
26 const isVertical = computed(() => props.layout === 'vertical');
27
28 const contentClasses = computed(() => ({
29 'usage-card__content': true,
30 'usage-card__content--horizontal': isHorizontal.value,
31 'usage-card__content--vertical': isVertical.value
32 }));
33
34 const getMetricClasses = (isVerticalMetric: boolean) => ({
35 'usage-card__metric': true,
36 'usage-card__metric--vertical': isVerticalMetric
37 });
38
39 const SKELETON_CONFIG = {
40 title: { height: '24px', width: '120px' },
41 label: { height: '16px', width: isVertical.value ? '80px' : '60px' },
42 value: { height: '32px', width: isVertical.value ? '50px' : '40px' }
43 } as const;
44 </script>
45
46 <template>
47 <article
48 class="usage-card"
49 :class="{ 'usage-card--vertical': isVertical }"
50 role="article"
51 :aria-label="`${title} ${translate('hostinger_reach_ui_usage_statistics')}`"
52 >
53 <HCard class="usage-card__container" padding="20px 32px" border-radius="16px">
54 <div class="usage-card__inner">
55 <header class="usage-card__header">
56 <HSkeletonLoader
57 v-if="isLoading"
58 :height="SKELETON_CONFIG.title.height"
59 :width="SKELETON_CONFIG.title.width"
60 rounded
61 />
62 <HText v-else id="usage-card-title" as="h3" variant="heading-3" class="usage-card__title">
63 {{ title }}
64 </HText>
65 </header>
66
67 <div :class="contentClasses">
68 <template v-for="(metric, index) in metrics" :key="index">
69 <div :class="getMetricClasses(isVertical)">
70 <div class="usage-card__metric-content">
71 <div class="usage-card__metric-label-container">
72 <HSkeletonLoader
73 v-if="isLoading"
74 :height="SKELETON_CONFIG.label.height"
75 :width="SKELETON_CONFIG.label.width"
76 rounded
77 />
78 <HText v-else as="div" variant="body-3-secondary" class="usage-card__metric-label">
79 {{ metric.label }}
80 </HText>
81 <span v-if="metric.hasIcon && !isLoading" v-tooltip="metric.tooltip" class="usage-card__metric-icon">
82 <HIcon name="ic-info-circle-filled-16" color="neutral--300" aria-hidden="true" />
83 </span>
84 </div>
85
86 <HSkeletonLoader
87 v-if="isLoading"
88 :height="SKELETON_CONFIG.value.height"
89 :width="SKELETON_CONFIG.value.width"
90 rounded
91 />
92 <HText v-else as="div" variant="heading-1" class="usage-card__metric-value">
93 {{ metric.value }}
94 </HText>
95 </div>
96 </div>
97
98 <div v-if="isHorizontal && index < metrics.length - 1" class="usage-card__divider" aria-hidden="true"></div>
99 </template>
100 </div>
101 </div>
102 </HCard>
103 </article>
104 </template>
105
106 <style scoped lang="scss">
107 $mobile-breakpoint: 767px;
108
109 .usage-card {
110 flex: 1;
111 display: flex;
112 flex-direction: column;
113
114 &--vertical {
115 max-width: none;
116 }
117
118 &__container {
119 flex: 1;
120 display: flex;
121 flex-direction: column;
122 border: 1px solid var(--neutral--200);
123 }
124
125 &__inner {
126 display: flex;
127 flex-direction: column;
128 align-self: stretch;
129 }
130 }
131
132 .usage-card__header {
133 display: flex;
134 align-items: center;
135 align-self: stretch;
136 gap: 4px;
137 margin-bottom: 8px;
138 }
139
140 .usage-card__title {
141 font-weight: 700;
142 font-size: 16px;
143 line-height: 1.5;
144 color: var(--neutral--700);
145 }
146
147 .usage-card__content {
148 display: flex;
149 align-self: stretch;
150
151 &--horizontal {
152 flex-direction: row;
153 align-items: center;
154 gap: 40px;
155 }
156
157 &--vertical {
158 flex-direction: column;
159 justify-content: space-between;
160 height: 200px;
161 }
162 }
163
164 .usage-card__metric {
165 display: flex;
166 align-items: center;
167
168 &:not(&--vertical) {
169 flex-direction: row;
170 gap: 40px;
171 }
172
173 &--vertical {
174 flex-direction: column;
175 align-self: stretch;
176 gap: 16px;
177 padding: 8px 0 4px;
178 }
179
180 &-content {
181 display: flex;
182 flex-direction: column;
183 gap: 4px;
184
185 .usage-card__metric--vertical & {
186 align-self: stretch;
187 }
188 }
189
190 &-label-container {
191 display: flex;
192 align-items: center;
193 gap: 8px;
194 margin-bottom: 4px;
195 }
196
197 &-icon {
198 display: flex;
199 align-items: center;
200 }
201
202 &-label {
203 font-weight: 400;
204 font-size: 12px;
205 line-height: 1.67;
206 color: var(--neutral--300);
207 }
208
209 &-value {
210 font-weight: 700;
211 font-size: 24px;
212 line-height: 1.33;
213 color: var(--neutral--600);
214 }
215 }
216
217 .usage-card__divider {
218 width: 1px;
219 height: 20px;
220 background-color: var(--neutral--200);
221 flex-shrink: 0;
222 }
223
224 @media (max-width: $mobile-breakpoint) {
225 .usage-card__content--horizontal {
226 flex-direction: column;
227 align-items: flex-start;
228 gap: 16px;
229 }
230
231 .usage-card__metric:not(.usage-card__metric--vertical) {
232 flex-direction: column;
233 align-items: flex-start;
234 gap: 8px;
235 width: 100%;
236
237 .usage-card__metric-content {
238 width: 100%;
239 }
240 }
241
242 .usage-card__divider {
243 display: none;
244 }
245 }
246 </style>
247