Files
wondif_vue/app/components/RessourcesCard.vue
2026-06-14 16:02:36 +02:00

263 lines
6.5 KiB
Vue

<template>
<article class="ressources-card bg-surface-container-lowest border border-outline-variant/10 rounded-xl overflow-hidden shadow-sm transition-shadow hover:shadow-xl">
<div class="ressources-card__media bg-surface-container-low">
<img
v-if="imgSrc"
:src="imgSrc"
:alt="imgAlt || title"
class="h-full w-full object-cover"
>
<div
v-else
class="flex h-full w-full items-center justify-center bg-surface-container-low text-outline"
aria-hidden="true"
>
<span class="material-symbols-outlined text-5xl">image</span>
</div>
<span
v-if="badge"
class="absolute left-3 top-3 rounded-md bg-surface px-2.5 py-1 text-xs font-extrabold uppercase text-error shadow-sm"
>
{{ badge }}
</span>
</div>
<div class="ressources-card__content grid gap-6 p-6">
<div class="ressources-card__copy grid min-w-0 gap-3">
<h3 class="ressources-card__title min-w-0 text-xl font-extrabold leading-tight">
{{ title }}
</h3>
<p
v-if="description"
class="ressources-card__description min-w-0 text-sm"
>
{{ description }}
</p>
</div>
<div class="ressources-card__actions grid gap-3">
<template
v-for="action in resourceActions"
:key="action.id || action.label"
>
<a
v-if="action.url"
:href="action.url"
:download="getDownloadAttribute(action)"
class="ressources-card__action group flex items-center justify-between gap-4 rounded-lg border border-outline-variant/30 bg-surface-container-low px-4 py-3 text-on-surface transition-colors hover:bg-primary-container"
target="_blank"
rel="noopener noreferrer"
>
<span class="min-w-0">
<span class="block truncate text-sm font-bold text-primary group-hover:text-on-primary-container">
{{ action.label }}
</span>
<span
v-if="action.size"
class="block text-xs text-on-surface-variant group-hover:text-on-primary-container/80"
>
{{ action.size }}
</span>
</span>
<span class="ressources-card__action-icon grid h-9 w-9 shrink-0 place-items-center rounded-full bg-primary-container text-primary transition-colors group-hover:bg-primary group-hover:text-on-primary">
<span class="material-symbols-outlined text-xl">{{ getActionIcon(action) }}</span>
</span>
</a>
<div
v-else
class="ressources-card__action flex items-center justify-between gap-4 rounded-lg border border-outline-variant/30 bg-surface-container-low px-4 py-3 text-on-surface opacity-70"
>
<span class="min-w-0">
<span class="block text-sm font-bold text-primary">{{ action.label }}</span>
<span
v-if="action.size"
class="block text-xs text-on-surface-variant"
>
{{ action.size }}
</span>
</span>
<span class="ressources-card__action-icon grid h-9 w-9 shrink-0 place-items-center rounded-full bg-surface-container text-outline">
<span class="material-symbols-outlined text-xl">{{ getActionIcon(action) }}</span>
</span>
</div>
</template>
</div>
</div>
</article>
</template>
<script setup>
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: '',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap',
},
],
})
const props = defineProps({
title: {
type: String,
required: true,
},
description: {
type: String,
default: '',
},
imgSrc: {
type: String,
default: '',
},
imgAlt: {
type: String,
default: '',
},
badge: {
type: String,
default: 'PDF',
},
fileLabel: {
type: String,
default: 'Outil pédagogique (PDF)',
},
fileSize: {
type: String,
default: '',
},
fileUrl: {
type: String,
default: '',
},
fileName: {
type: String,
default: '',
},
linkType: {
type: String,
default: 'file',
validator: (value) => ['file', 'web'].includes(value),
},
actions: {
type: Array,
default: () => [],
},
})
const resourceActions = computed(() => {
if (props.actions.length) {
return props.actions.map((action) => ({
id: action.id || action.label,
type: action.type || 'file',
label: action.label || props.fileLabel,
size: action.size || '',
url: action.url || '',
fileName: action.fileName || '',
}))
}
return [
{
id: 'file',
type: props.linkType,
label: props.fileLabel,
size: props.fileSize,
url: props.fileUrl,
fileName: props.fileName,
},
]
})
function getDownloadAttribute(action) {
if (action.type === 'web') return null
return action.fileName || true
}
function getActionIcon(action) {
return action.type === 'web' ? 'open_in_new' : 'download'
}
</script>
<style lang="scss" scoped>
.ressources-card {
width: 100%;
max-width: 392px;
container-type: inline-size;
font-family: 'Inter', sans-serif;
}
.ressources-card__media {
position: relative;
aspect-ratio: 392 / 200;
}
.ressources-card__title,
.ressources-card__description,
.ressources-card__action {
max-width: 100%;
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
}
.ressources-card__copy {
max-width: 100%;
min-width: 0;
}
@container (max-width: 320px) {
.ressources-card__content {
gap: 16px;
padding: 16px;
}
.ressources-card__copy,
.ressources-card__actions {
gap: 10px;
}
.ressources-card__title {
font-size: 1rem;
}
.ressources-card__description {
font-size: 0.8125rem;
text-align: justify;
}
.ressources-card__action {
gap: 10px;
padding: 10px 12px;
}
.ressources-card__action-icon {
width: 32px;
height: 32px;
}
}
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24;
}
</style>