✨ Add skill projects modal with Headless UI (Story 2.5)
- Add GET /skills/{slug}/projects endpoint with level progression
- Install @headlessui/vue for accessible modal
- Create SkillProjectsModal with Dialog component:
- Focus trap and keyboard navigation (automatic)
- Fade + scale transitions with backdrop blur
- prefers-reduced-motion support
- Create ProjectListItem with thumbnail and level display
- Integrate modal in competences.vue page
- Add translations for related projects UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
68
frontend/app/components/feature/ProjectListItem.vue
Normal file
68
frontend/app/components/feature/ProjectListItem.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="localePath(`/projets/${project.slug}`)"
|
||||
class="block bg-sky-dark/50 rounded-lg p-4 hover:bg-sky-dark transition-colors group"
|
||||
@click="emit('click')"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Image thumbnail -->
|
||||
<div v-if="project.image" class="shrink-0 w-20 h-14 rounded overflow-hidden bg-sky-text/5">
|
||||
<NuxtImg
|
||||
:src="project.image"
|
||||
:alt="project.title"
|
||||
format="webp"
|
||||
width="80"
|
||||
height="56"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="font-ui font-medium text-sky-text group-hover:text-sky-accent transition-colors truncate">
|
||||
{{ project.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-sky-text/60 line-clamp-2 mt-1">
|
||||
{{ project.short_description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Level progress -->
|
||||
<div class="shrink-0 text-right">
|
||||
<div class="text-xs text-sky-text/40 font-ui">{{ $t('skills.level') }}</div>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<span class="text-sky-text text-sm">{{ project.level_before }}</span>
|
||||
<span class="text-sky-accent">→</span>
|
||||
<span class="text-sky-accent font-semibold text-sm">{{ project.level_after }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-sky-accent/80 font-medium">
|
||||
({{ levelProgress }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Level description if available -->
|
||||
<p v-if="project.level_description" class="mt-3 text-xs text-sky-text/50 italic border-t border-sky-text/10 pt-3">
|
||||
{{ project.level_description }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SkillProject } from '~/composables/useFetchSkillProjects'
|
||||
|
||||
const props = defineProps<{
|
||||
project: SkillProject
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const localePath = useLocalePath()
|
||||
|
||||
const levelProgress = computed(() => {
|
||||
const diff = props.project.level_after - props.project.level_before
|
||||
return diff > 0 ? `+${diff}` : diff.toString()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user