Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 1x 1x 1x 1x 1x 5x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 19x 19x 19x 19x 19x 19x 19x 24x 24x 24x 24x 1x 1x 1x 1x 1x 3x 3x 3x 1x 1x 1x 1x | <template>
<BaseCard class="h-full">
<!-- Header -->
<template #header>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span v-if="icon" class="text-lg" :aria-hidden="true">{{ icon }}</span>
<h2 class="text-lg font-semibold text-secondary-900">
{{ t(titleKey) }}
<span
v-if="count !== undefined"
class="text-sm font-normal text-secondary-500 ml-1"
>
({{ count }})
</span>
</h2>
</div>
<div class="flex items-center gap-2">
<slot name="actions" />
<NuxtLink
v-if="viewAllLink"
:to="viewAllLink"
class="text-sm text-primary-600 hover:text-primary-700 hover:underline focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded"
>
{{ t('widgets.viewAll') }}
</NuxtLink>
</div>
</div>
</template>
<!-- Content -->
<div v-if="loading" class="flex items-center justify-center py-8">
<BaseSpinner size="md" />
</div>
<div v-else-if="isEmpty" class="py-8 text-center">
<slot name="empty">
<p class="text-secondary-500">
{{ emptyMessage || t('widgets.noContent') }}
</p>
</slot>
</div>
<div v-else>
<slot />
</div>
</BaseCard>
</template>
<script setup lang="ts">
/**
* BaseWidget - Displays content in a consistent card container with translatable header and optional view-all link.
*
* @slot default - Main content area
* @slot empty - Custom empty state content
* @slot actions - Additional header actions (buttons, links)
*/
interface Props {
/** i18n key for the widget title (required) */
titleKey: string
/** Optional icon to display before title */
icon?: string
/** Optional link for "View All" action */
viewAllLink?: string
/** Optional count to display next to title */
count?: number
/** Loading state */
loading?: boolean
/** Whether the widget content is empty */
isEmpty?: boolean
/** Custom empty state message (i18n key or text) */
emptyMessage?: string
}
const props = withDefaults(defineProps<Props>(), {
icon: undefined,
viewAllLink: undefined,
count: undefined,
loading: false,
isEmpty: false,
emptyMessage: undefined
})
const { t } = useI18n()
</script>
|