Skip to content

Commit 4a1bc01

Browse files
authored
feat: add hero bloc (#855)
- Require opendatateam/udata#3609
1 parent 75f8922 commit 4a1bc01

File tree

8 files changed

+156
-16
lines changed

8 files changed

+156
-16
lines changed

components/Pages/AddBlocDropdown.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const { t } = useTranslation()
6565
const blocsTypes = useBlocsTypes()
6666
6767
const newBlocsTypes: Array<{ name: string, blocsTypes: Array<keyof typeof blocsTypes> }> = [
68+
{ name: t('Mise en page'), blocsTypes: ['HeroBloc'] },
6869
{ name: t('Contenus à la une'), blocsTypes: ['DatasetsListBloc', 'ReusesListBloc', 'DataservicesListBloc', 'LinksListBloc'] },
6970
]
7071
</script>

components/Pages/HeroBloc.vue

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<div
3+
class="py-16"
4+
:class="heroBgColor(bloc.color)"
5+
>
6+
<div class="container space-y-5">
7+
<EditableText
8+
v-if="edit"
9+
v-model="bloc.title"
10+
class="text-white font-extrabold text-5xl mb-0"
11+
/>
12+
<h1
13+
v-else
14+
class="text-white font-extrabold text-5xl mb-0"
15+
>
16+
{{ bloc.title }}
17+
</h1>
18+
19+
<EditableText
20+
v-if="edit && bloc.description"
21+
:model-value="bloc.description ?? ''"
22+
class="italic font-spectral text-2xl text-white mb-0"
23+
@update:model-value="bloc.description = $event"
24+
/>
25+
<p
26+
v-else-if="bloc.description"
27+
class="italic font-spectral text-2xl text-white mb-0"
28+
>
29+
{{ bloc.description }}
30+
</p>
31+
<button
32+
v-else-if="edit"
33+
class="text-white/70 hover:text-white text-sm"
34+
@click="bloc.description = $t('Description')"
35+
>
36+
+ {{ $t('Ajouter une description') }}
37+
</button>
38+
39+
<div
40+
v-if="edit"
41+
class="flex items-center gap-2 pt-4"
42+
>
43+
<span class="text-white text-sm">{{ $t('Couleur') }} :</span>
44+
<button
45+
v-for="color in heroColors"
46+
:key="color.value"
47+
type="button"
48+
class="size-8 rounded-full border-2 transition-transform"
49+
:class="[
50+
heroBgColor(color.value),
51+
bloc.color === color.value ? 'border-white scale-110' : 'border-transparent opacity-70 hover:opacity-100',
52+
]"
53+
:title="color.label"
54+
@click="bloc.color = color.value"
55+
/>
56+
</div>
57+
</div>
58+
</div>
59+
</template>
60+
61+
<script setup lang="ts">
62+
import EditableText from './EditableText.vue'
63+
import type { HeroBloc } from '~/types/pages'
64+
65+
defineProps<{
66+
edit: boolean
67+
}>()
68+
69+
const bloc = defineModel<HeroBloc>({ required: true })
70+
71+
const { t } = useTranslation()
72+
73+
const heroColors = [
74+
{ label: t('Bleu'), value: 'primary' as const },
75+
{ label: t('Vert'), value: 'green' as const },
76+
{ label: t('Violet'), value: 'purple' as const },
77+
]
78+
79+
function heroBgColor(color: 'primary' | 'green' | 'purple') {
80+
return {
81+
primary: 'bg-new-blue-illustration',
82+
green: 'bg-new-green-illustration',
83+
purple: 'bg-new-brown-illustration',
84+
}[color]
85+
}
86+
</script>

components/Pages/PageShow.vue

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<div
44
v-for="(bloc, index) in workingPage.blocs"
55
:key="bloc.id"
6-
class="py-24 odd:bg-gray-some even:bg-white relative"
6+
class="relative"
7+
:class="bloc.class !== 'HeroBloc' && 'py-24 odd:bg-gray-some even:bg-white'"
78
>
89
<!-- Add button above the bloc (absolute positioned) -->
910
<div
@@ -48,7 +49,18 @@
4849
/>
4950
</div>
5051

51-
<div class="container space-y-6">
52+
<!-- HeroBloc has its own full-width layout -->
53+
<HeroBloc
54+
v-if="bloc.class === 'HeroBloc'"
55+
v-model="(workingPage.blocs[index] as HeroBlocType)"
56+
:edit
57+
/>
58+
59+
<!-- Other blocs use container layout with title/subtitle -->
60+
<div
61+
v-else
62+
class="container space-y-6"
63+
>
5264
<div class="space-y-2.5">
5365
<!-- Editable title -->
5466
<EditableText
@@ -173,12 +185,14 @@ import DatasetsListBloc from './DatasetsListBloc.vue'
173185
import DataservicesListBloc from './DataservicesListBloc.vue'
174186
import ReusesListBloc from './ReusesListBloc.vue'
175187
import LinksListBloc from './LinksListBloc.vue'
188+
import HeroBloc from './HeroBloc.vue'
176189
import type {
177190
Page,
178191
DatasetsListBloc as DatasetsListBlocType,
179192
DataservicesListBloc as DataservicesListBlocType,
180193
ReusesListBloc as ReusesListBlocType,
181194
LinksListBloc as LinksListBlocType,
195+
HeroBloc as HeroBlocType,
182196
} from '~/types/pages'
183197
184198
const props = withDefaults(defineProps<{

tests/edito/pages-editor.spec.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,46 @@ test('can edit edito page with all bloc types', async ({ page }) => {
2121
// Debug screenshot: after deleting all blocs
2222
await page.screenshot({ path: 'tests/edito/screenshots/01b-empty-page.png', fullPage: true })
2323

24-
// === Step 1: Add a DatasetsListBloc with multiple datasets ===
24+
// === Step 1: Add a HeroBloc ===
2525
await page.getByRole('button', { name: 'Ajouter un bloc' }).first().click()
26+
await page.getByText('Hero').click()
27+
await page.waitForTimeout(300)
28+
29+
// Edit the hero title (it has a default "Titre")
30+
const heroTitle = page.locator('.bg-new-blue-illustration [contenteditable="true"]').first()
31+
await heroTitle.click()
32+
await heroTitle.fill('Bienvenue sur data.gouv.fr')
33+
await page.mouse.click(1, 1)
34+
35+
// Add a description
36+
await page.getByText('Ajouter une description').click()
37+
await page.waitForTimeout(200)
38+
const heroDescription = page.locator('.bg-new-blue-illustration [contenteditable="true"]').nth(1)
39+
await heroDescription.click()
40+
await heroDescription.fill('La plateforme ouverte des données publiques françaises')
41+
await page.mouse.click(1, 1)
42+
43+
// Change the color to green
44+
await page.locator('.bg-new-green-illustration.size-8').click()
45+
await page.waitForTimeout(200)
46+
47+
// Debug screenshot: after adding hero bloc
48+
await page.screenshot({ path: 'tests/edito/screenshots/01c-hero-bloc.png', fullPage: true })
49+
50+
// === Step 2: Add a DatasetsListBloc with multiple datasets ===
51+
await page.getByRole('button', { name: 'Ajouter un bloc' }).last().click()
2652
await page.getByText('Données à la une').click()
2753
await page.waitForTimeout(300)
2854

29-
// Edit the title
30-
const datasetsBlocTitle = page.locator('[contenteditable="true"]').first()
55+
// Edit the title (use container to avoid hero bloc)
56+
const datasetsBlocTitle = page.locator('.container [contenteditable="true"]').first()
3157
await datasetsBlocTitle.click()
3258
await datasetsBlocTitle.fill('Nos jeux de données phares')
3359
await page.mouse.click(1, 1)
3460

3561
// Add subtitle
3662
await page.getByText('Ajouter un sous-titre').first().click()
37-
const datasetsSubtitle = page.locator('[contenteditable="true"]').nth(1)
63+
const datasetsSubtitle = page.locator('.container [contenteditable="true"]').nth(1)
3864
await datasetsSubtitle.click()
3965
await datasetsSubtitle.fill('Découvrez les données les plus consultées')
4066
await page.mouse.click(1, 1)
@@ -65,7 +91,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
6591
// Debug screenshot: after adding datasets
6692
await page.screenshot({ path: 'tests/edito/screenshots/02-datasets-bloc.png', fullPage: true })
6793

68-
// === Step 2: Add a ReusesListBloc with multiple reuses ===
94+
// === Step 3: Add a ReusesListBloc with multiple reuses ===
6995
await page.getByRole('button', { name: 'Ajouter un bloc' }).last().click()
7096
await page.getByText('Réutilisations à la une').click()
7197
await page.waitForTimeout(300)
@@ -102,7 +128,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
102128
// Debug screenshot: after adding reuses
103129
await page.screenshot({ path: 'tests/edito/screenshots/03-reuses-bloc.png', fullPage: true })
104130

105-
// === Step 3: Add a DataservicesListBloc with multiple APIs ===
131+
// === Step 4: Add a DataservicesListBloc with multiple APIs ===
106132
await page.getByRole('button', { name: 'Ajouter un bloc' }).last().click()
107133
await page.getByText('APIs à la une').click()
108134
await page.waitForTimeout(300)
@@ -139,7 +165,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
139165
// Debug screenshot: after adding dataservices
140166
await page.screenshot({ path: 'tests/edito/screenshots/04-dataservices-bloc.png', fullPage: true })
141167

142-
// === Step 4: Add a LinksListBloc WITH paragraph ===
168+
// === Step 5: Add a LinksListBloc WITH paragraph ===
143169
await page.getByRole('button', { name: 'Ajouter un bloc' }).last().click()
144170
await page.getByText('Liens à la une').click()
145171
await page.waitForTimeout(300)
@@ -188,7 +214,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
188214
// Debug screenshot: after adding links bloc with paragraph
189215
await page.screenshot({ path: 'tests/edito/screenshots/05-links-bloc-with-paragraph.png', fullPage: true })
190216

191-
// === Step 5: Add a LinksListBloc WITHOUT paragraph ===
217+
// === Step 6: Add a LinksListBloc WITHOUT paragraph ===
192218
await page.getByRole('button', { name: 'Ajouter un bloc' }).last().click()
193219
await page.getByText('Liens à la une').click()
194220
await page.waitForTimeout(300)
@@ -228,7 +254,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
228254
// Debug screenshot: after adding links bloc without paragraph
229255
await page.screenshot({ path: 'tests/edito/screenshots/06-links-bloc-without-paragraph.png', fullPage: true })
230256

231-
// === Step 6: Test bloc manipulation ===
257+
// === Step 7: Test bloc manipulation ===
232258
// Move the first bloc down
233259
await page.locator('[title="Descendre"]').first().click()
234260
await page.waitForTimeout(300)
@@ -278,7 +304,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
278304
// Debug screenshot: after re-adding reuses bloc
279305
await page.screenshot({ path: 'tests/edito/screenshots/09-reuses-bloc-readded.png', fullPage: true })
280306

281-
// === Step 7: Save the page ===
307+
// === Step 8: Save the page ===
282308
await page.getByRole('button', { name: 'Sauvegarder' }).click()
283309
await page.waitForTimeout(1000)
284310

@@ -288,7 +314,7 @@ test('can edit edito page with all bloc types', async ({ page }) => {
288314
// Wait for the toast to confirm save
289315
await expect(page.getByText('Page créée').or(page.getByText('Page sauvegardée'))).toBeVisible()
290316

291-
// === Step 8: Final screenshot for visual regression ===
317+
// === Step 9: Final screenshot for visual regression ===
292318
await page.waitForLoadState('networkidle')
293319
await page.waitForTimeout(500)
294320

-35.1 KB
Loading
-61.9 KB
Loading

types/pages.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,11 @@ export type LinksListBloc = {
3737
links: Array<LinkInBloc>
3838
} & BlocWithTitle & { id: string }
3939

40-
export type PageBloc = DatasetsListBloc | DataservicesListBloc | ReusesListBloc | LinksListBloc
40+
export type HeroBloc = {
41+
class: 'HeroBloc'
42+
title: string
43+
description: string | null
44+
color: 'primary' | 'green' | 'purple'
45+
} & { id: string }
46+
47+
export type PageBloc = DatasetsListBloc | DataservicesListBloc | ReusesListBloc | LinksListBloc | HeroBloc

utils/pages.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { RiDatabase2Line, RiLineChartLine, RiLink, RiRobot2Line } from '@remixicon/vue'
2-
import type { DataservicesListBloc, DatasetsListBloc, LinksListBloc, ReusesListBloc } from '~/types/pages'
1+
import { RiDatabase2Line, RiLineChartLine, RiLink, RiRobot2Line, RiWindow2Line } from '@remixicon/vue'
2+
import type { DataservicesListBloc, DatasetsListBloc, HeroBloc, LinksListBloc, ReusesListBloc } from '~/types/pages'
33

44
export function useBlocsTypes() {
55
const { t } = useTranslation()
@@ -29,5 +29,11 @@ export function useBlocsTypes() {
2929
description: t('Mettre en avant jusqu\'à 4 liens'),
3030
default: (): Omit<LinksListBloc, 'id'> => ({ class: 'LinksListBloc', title: 'Mes liens', subtitle: '', paragraph: '', main_link_title: '', main_link_url: '', links: [] }),
3131
},
32+
HeroBloc: {
33+
icon: RiWindow2Line,
34+
name: t('Hero'),
35+
description: t('Bandeau d\'en-tête avec titre et description'),
36+
default: (): Omit<HeroBloc, 'id'> => ({ class: 'HeroBloc', title: 'Titre', description: null, color: 'primary' }),
37+
},
3238
}
3339
}

0 commit comments

Comments
 (0)