@crtobiasdelsud/portal-ui 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/ArticleDetailView/ArticleDetailView.jsx +39 -0
- package/src/components/ArticleDetailView/ArticleDetailView.module.scss +18 -0
- package/src/components/ArticleDetailView/Full.jsx +46 -0
- package/src/components/ArticleDetailView/Full.module.scss +48 -0
- package/src/components/ArticleDetailView/Standard.jsx +116 -0
- package/src/components/ArticleDetailView/Standard.module.scss +179 -0
- package/src/components/ArticleHero/ArticleHero.jsx +13 -2
- package/src/components/ArticleHero/ArticleHero.module.scss +16 -0
- package/src/components/ArticleHeroFull/ArticleHeroFull.jsx +4 -1
- package/src/components/ArticleHeroFull/ArticleHeroFull.module.scss +26 -3
- package/src/components/AuthorBlock/AuthorBlock.jsx +1 -1
- package/src/components/EditorOutput/EditorOutput.jsx +10 -5
- package/src/components/EditorOutput/EditorOutput.module.scss +47 -0
- package/src/components/EditorOutputFull/EditorOutputFull.jsx +10 -5
- package/src/components/EditorOutputFull/EditorOutputFull.module.scss +47 -0
- package/src/components/Footers/FooterSimple/FooterSimple.jsx +14 -8
- package/src/components/Footers/FooterSimple/FooterSimple.module.scss +10 -11
- package/src/index.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crtobiasdelsud/portal-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Componentes compartidos entre el portal (Next) y el CMS (Vite) — widgets, views, providers para adapters y article pool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import styles from './ArticleDetailView.module.scss'
|
|
4
|
+
import Standard from './Standard.jsx'
|
|
5
|
+
import Full from './Full.jsx'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ArticleDetailView — réplica portable de las screens `ArticleDetail` /
|
|
9
|
+
* `ArticleDetailFull` del portal (editor-template-front).
|
|
10
|
+
*
|
|
11
|
+
* Pensada para previews fuera del portal (ej. el CMS): es una *vista pura*,
|
|
12
|
+
* recibe el artículo ya resuelto y no hace data-fetching ni depende del
|
|
13
|
+
* registry de widgets. Reproduce el layout y los estilos de las screens, y
|
|
14
|
+
* elige la variante igual que el portal:
|
|
15
|
+
*
|
|
16
|
+
* article.tipoContenido === 'notaEspecial' → Full (ArticleDetailFull)
|
|
17
|
+
* resto → Standard (ArticleDetail)
|
|
18
|
+
*
|
|
19
|
+
* El root recrea el `<main>` del portal con `container-type: inline-size`
|
|
20
|
+
* para que las container-queries de los componentes portal-ui respondan al
|
|
21
|
+
* ancho real del contenedor — imprescindible al renderizar en un iframe.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} props
|
|
24
|
+
* @param {object} props.article - Artículo resuelto. Shape esperado:
|
|
25
|
+
* { id, titulo, volanta, copete, imagen: { url, epigrafe }, imagenEpigrafe,
|
|
26
|
+
* focalPoint, categoria: { nombre, slug }, autor, publicarComoOrg,
|
|
27
|
+
* fechaPublicacion, tipoContenido, cuerpo (payload EditorJS) }
|
|
28
|
+
*/
|
|
29
|
+
export default function ArticleDetailView({ article }) {
|
|
30
|
+
if (!article) return null
|
|
31
|
+
|
|
32
|
+
const isFull = article.tipoContenido === 'notaEspecial'
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<main className={styles.root}>
|
|
36
|
+
{isFull ? <Full article={article} /> : <Standard article={article} />}
|
|
37
|
+
</main>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Root del preview — recrea el `<main>` del portal: es el container de las
|
|
2
|
+
// container-queries (`container-type: inline-size`) de los componentes
|
|
3
|
+
// portal-ui, para que el responsive responda al ancho real del contenedor
|
|
4
|
+
// (clave cuando se renderiza dentro de un iframe).
|
|
5
|
+
//
|
|
6
|
+
// NOTA: a diferencia del `<main>` real, NO se usa `min-height: 100dvh` —
|
|
7
|
+
// dentro de un iframe auto-dimensionado generaría un loop de altura.
|
|
8
|
+
.root {
|
|
9
|
+
overflow-x: clip;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
container-type: inline-size;
|
|
13
|
+
background: #fff;
|
|
14
|
+
|
|
15
|
+
// Fija la altura del hero "full" a un valor estable: dentro de un iframe
|
|
16
|
+
// auto-dimensionado, el `90vh/75vh` por defecto generaría un loop de altura.
|
|
17
|
+
--ahf-min-h: 620px;
|
|
18
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import ArticleHeroFull from '../ArticleHeroFull/ArticleHeroFull.jsx'
|
|
4
|
+
import AuthorBlock from '../AuthorBlock/AuthorBlock.jsx'
|
|
5
|
+
import ShareBlock from '../ShareBlock/ShareBlock.jsx'
|
|
6
|
+
import EditorOutputFull from '../EditorOutputFull/EditorOutputFull.jsx'
|
|
7
|
+
import styles from './Full.module.scss'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Réplica de la screen `ArticleDetailFull` (camino non-AMP) del portal —
|
|
11
|
+
* la "nota especial": hero a sangre + cuerpo a una columna.
|
|
12
|
+
*
|
|
13
|
+
* Se omiten las piezas de infraestructura de la app (ArticleTracker y la
|
|
14
|
+
* zona post-body de widgets desde el registry).
|
|
15
|
+
*/
|
|
16
|
+
export default function Full({ article }) {
|
|
17
|
+
const imagenEpigrafe = article.imagen?.epigrafe ?? article.imagenEpigrafe ?? null
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<ArticleHeroFull
|
|
22
|
+
titulo={article.titulo}
|
|
23
|
+
copete={article.copete}
|
|
24
|
+
imagen={article.imagen?.url ?? null}
|
|
25
|
+
imagenEpigrafe={imagenEpigrafe}
|
|
26
|
+
focalPoint={article.focalPoint}
|
|
27
|
+
categoria={article.categoria ?? null}
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<div className={styles.wrap}>
|
|
31
|
+
<div className={styles.author}>
|
|
32
|
+
<AuthorBlock
|
|
33
|
+
autor={article.autor}
|
|
34
|
+
publicarComoOrg={article.publicarComoOrg}
|
|
35
|
+
fechaPublicacion={article.fechaPublicacion}
|
|
36
|
+
/>
|
|
37
|
+
<ShareBlock />
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className={styles.body}>
|
|
41
|
+
<EditorOutputFull data={article.cuerpo} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Réplica de ArticleDetailFull.module.scss del portal (editor-template-front).
|
|
2
|
+
@use "../../styles/index" as *;
|
|
3
|
+
|
|
4
|
+
.wrap {
|
|
5
|
+
width: 100%;
|
|
6
|
+
margin: 0 auto;
|
|
7
|
+
padding: 1.5rem 1rem;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
|
|
10
|
+
@include respond(tablet) {
|
|
11
|
+
max-width: 66.66667%;
|
|
12
|
+
min-width: 66.66667%;
|
|
13
|
+
margin-right: auto;
|
|
14
|
+
margin-left: auto;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@include respond(laptop) {
|
|
19
|
+
max-width: 1250px;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.author {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: stretch;
|
|
26
|
+
justify-content: space-between;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
margin: 1rem 0 1.5rem;
|
|
29
|
+
width: 100%;
|
|
30
|
+
|
|
31
|
+
@include respond(tablet) {
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
align-items: center;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.heroImage {
|
|
38
|
+
width: 100%;
|
|
39
|
+
margin-bottom: 2rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.body {
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.postBody {
|
|
47
|
+
margin-top: 2rem;
|
|
48
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import PageWrapper from '../UI/PageWrapper/PageWrapper.jsx'
|
|
4
|
+
import AspectImage from '../UI/AspectImage/AspectImage.jsx'
|
|
5
|
+
import EditorOutput from '../EditorOutput/EditorOutput.jsx'
|
|
6
|
+
import Breadcrumb from '../Breadcrumb/Breadcrumb.jsx'
|
|
7
|
+
import ArticleHero from '../ArticleHero/ArticleHero.jsx'
|
|
8
|
+
import AuthorBlock from '../AuthorBlock/AuthorBlock.jsx'
|
|
9
|
+
import ShareBlock from '../ShareBlock/ShareBlock.jsx'
|
|
10
|
+
import SpeechButton from '../SpeechButton/SpeechButton.jsx'
|
|
11
|
+
import ArticleSidebar from '../ArticleSidebar/ArticleSidebar.jsx'
|
|
12
|
+
import styles from './Standard.module.scss'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Réplica de la screen `ArticleDetail` (camino non-AMP) del portal.
|
|
16
|
+
*
|
|
17
|
+
* Reproduce el mismo árbol JSX y los mismos estilos. Se omiten las piezas
|
|
18
|
+
* que dependen de infraestructura de la app (ArticleTracker, zonas de
|
|
19
|
+
* widgets pre/post/in-body desde el registry, LoQueSeLee con data-fetching):
|
|
20
|
+
* el preview muestra el artículo en sí, no la config de layout del sitio.
|
|
21
|
+
*/
|
|
22
|
+
export default function Standard({ article }) {
|
|
23
|
+
// Epígrafe de la imagen principal — normalizado en `imagen.epigrafe` y/o
|
|
24
|
+
// `imagenEpigrafe` a nivel raíz; puede venir null.
|
|
25
|
+
const imagenEpigrafe = article.imagen?.epigrafe ?? article.imagenEpigrafe ?? null
|
|
26
|
+
|
|
27
|
+
const breadcrumbItems = [
|
|
28
|
+
{ label: 'Inicio', href: '/' },
|
|
29
|
+
{ label: article.categoria?.nombre ?? '', href: `/${article.categoria?.slug ?? ''}` },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<PageWrapper>
|
|
34
|
+
<div className={styles.pageWrap}>
|
|
35
|
+
<div className={styles.articleWrap}>
|
|
36
|
+
|
|
37
|
+
{/* Row 2 — breadcrumb, hero, autor, speechbutton */}
|
|
38
|
+
<div className={styles.articleHeader}>
|
|
39
|
+
<Breadcrumb items={breadcrumbItems} />
|
|
40
|
+
<ArticleHero
|
|
41
|
+
titulo={article.titulo}
|
|
42
|
+
volanta={article.volanta}
|
|
43
|
+
copete={article.copete}
|
|
44
|
+
imagen={article.imagen?.url ?? null}
|
|
45
|
+
imagenEpigrafe={imagenEpigrafe}
|
|
46
|
+
focalPoint={article.focalPoint}
|
|
47
|
+
hideImageOnDesktop
|
|
48
|
+
extras={
|
|
49
|
+
<div className={styles.heroExtras}>
|
|
50
|
+
<AuthorBlock
|
|
51
|
+
autor={article.autor}
|
|
52
|
+
publicarComoOrg={article.publicarComoOrg}
|
|
53
|
+
fechaPublicacion={article.fechaPublicacion}
|
|
54
|
+
/>
|
|
55
|
+
<ShareBlock />
|
|
56
|
+
</div>
|
|
57
|
+
}
|
|
58
|
+
/>
|
|
59
|
+
{/* mobile-only: autor + share debajo del hero */}
|
|
60
|
+
<div className={styles.authorContainer}>
|
|
61
|
+
<AuthorBlock
|
|
62
|
+
autor={article.autor}
|
|
63
|
+
publicarComoOrg={article.publicarComoOrg}
|
|
64
|
+
fechaPublicacion={article.fechaPublicacion}
|
|
65
|
+
/>
|
|
66
|
+
<ShareBlock />
|
|
67
|
+
</div>
|
|
68
|
+
{/* mobile-only: speechbutton debajo del hero */}
|
|
69
|
+
<div className={styles.speechMobile}>
|
|
70
|
+
<SpeechButton
|
|
71
|
+
titulo={article.titulo}
|
|
72
|
+
copete={article.copete}
|
|
73
|
+
cuerpo={article.cuerpo}
|
|
74
|
+
imagen={article.imagen?.url ?? null}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Row 3 — body (2fr) + sidebar sticky (1fr) */}
|
|
80
|
+
<div className={styles.contentRow}>
|
|
81
|
+
<div className={styles.body}>
|
|
82
|
+
{article.imagen?.url && (
|
|
83
|
+
<figure className={styles.bodyImage}>
|
|
84
|
+
<AspectImage
|
|
85
|
+
src={article.imagen.url}
|
|
86
|
+
alt={article.titulo ?? ''}
|
|
87
|
+
aspect="16:9"
|
|
88
|
+
focalPoint={article.focalPoint}
|
|
89
|
+
/>
|
|
90
|
+
{imagenEpigrafe && (
|
|
91
|
+
<figcaption className={styles.bodyImageCaption}>{imagenEpigrafe}</figcaption>
|
|
92
|
+
)}
|
|
93
|
+
</figure>
|
|
94
|
+
)}
|
|
95
|
+
<div className={styles.speechDesktop}>
|
|
96
|
+
<SpeechButton
|
|
97
|
+
titulo={article.titulo}
|
|
98
|
+
copete={article.copete}
|
|
99
|
+
cuerpo={article.cuerpo}
|
|
100
|
+
imagen={article.imagen?.url ?? null}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
{article.cuerpo?.blocks?.length > 0 && (
|
|
104
|
+
<EditorOutput data={article.cuerpo} />
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
<div className={styles.sidebarPlacement}>
|
|
108
|
+
<ArticleSidebar hasWidgets={false} />
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</PageWrapper>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Réplica de ArticleDetail.module.scss del portal (editor-template-front).
|
|
2
|
+
@use "../../styles/index" as *;
|
|
3
|
+
|
|
4
|
+
// ── Tokens ────────────────────────────────────────────────────────────────────
|
|
5
|
+
$page-max: 1376px;
|
|
6
|
+
$sidebar-w: 322px;
|
|
7
|
+
$col-gap: 30px;
|
|
8
|
+
$page-pad: 16px;
|
|
9
|
+
|
|
10
|
+
// ── Contenedor de ancho máximo ────────────────────────────────────────────────
|
|
11
|
+
.pageWrap {
|
|
12
|
+
width: 100%;
|
|
13
|
+
max-width: $page-max;
|
|
14
|
+
margin: 0 auto;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
|
|
17
|
+
@include respond(tablet) {
|
|
18
|
+
max-width: 720px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@include respond(laptop) {
|
|
22
|
+
max-width: 960px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@include respond(desktop) {
|
|
26
|
+
max-width: $page-max;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Grid de 4 columnas iguales (igual que Home) ───────────────────────────────
|
|
31
|
+
.articleWrap {
|
|
32
|
+
@include respond(laptop) {
|
|
33
|
+
display: grid;
|
|
34
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
35
|
+
column-gap: $col-gap;
|
|
36
|
+
row-gap: 0;
|
|
37
|
+
align-items: start;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@include respond(desktop) {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
43
|
+
column-gap: $col-gap;
|
|
44
|
+
row-gap: 0;
|
|
45
|
+
align-items: start;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Breadcrumb + Hero + Author + SpeechButton — cols 1-3, row 2
|
|
50
|
+
.articleHeader {
|
|
51
|
+
min-width: 0;
|
|
52
|
+
padding-top: 1.5rem;
|
|
53
|
+
|
|
54
|
+
@include respond(laptop) {
|
|
55
|
+
grid-column: 1 / 3;
|
|
56
|
+
grid-row: 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@include respond(desktop) {
|
|
60
|
+
grid-column: 1 / 4;
|
|
61
|
+
grid-row: 2;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Row 3 — grid item que agrupa body + sidebar.
|
|
66
|
+
.contentRow {
|
|
67
|
+
min-width: 0;
|
|
68
|
+
|
|
69
|
+
@include respond(laptop) {
|
|
70
|
+
grid-column: 1 / 4;
|
|
71
|
+
grid-row: 3;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: stretch;
|
|
74
|
+
gap: $col-gap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@include respond(desktop) {
|
|
78
|
+
grid-column: 1 / 4;
|
|
79
|
+
grid-row: 3;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: stretch;
|
|
82
|
+
gap: $col-gap;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Body — hijo flex del contentRow (2/3 del ancho)
|
|
87
|
+
.body {
|
|
88
|
+
min-width: 0;
|
|
89
|
+
flex: 2 1 0;
|
|
90
|
+
align-self: flex-start;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Sidebar — ocupa el alto del body para que los sticky internos funcionen
|
|
94
|
+
.sidebarPlacement {
|
|
95
|
+
display: none;
|
|
96
|
+
container-type: inline-size;
|
|
97
|
+
|
|
98
|
+
@include respond(laptop) {
|
|
99
|
+
display: block;
|
|
100
|
+
flex: 1 0 0;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@include respond(desktop) {
|
|
105
|
+
display: block;
|
|
106
|
+
flex: 1 0 0;
|
|
107
|
+
min-width: 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Widgets dentro del body — separación vertical entre widgets consecutivos
|
|
112
|
+
.inBodyWidget {
|
|
113
|
+
margin-top: 24px;
|
|
114
|
+
margin-bottom: 24px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Imagen del artículo en el body (tablet+) ─────────────────────────────────
|
|
118
|
+
.bodyImage {
|
|
119
|
+
display: none;
|
|
120
|
+
position: relative;
|
|
121
|
+
margin: 0;
|
|
122
|
+
|
|
123
|
+
@include respond(tablet) {
|
|
124
|
+
display: block;
|
|
125
|
+
margin: 0 0 1.5rem;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Epígrafe — franja negra sobre el borde inferior de la imagen ─────────────
|
|
130
|
+
.bodyImageCaption {
|
|
131
|
+
position: absolute;
|
|
132
|
+
left: 0;
|
|
133
|
+
right: 0;
|
|
134
|
+
bottom: 0;
|
|
135
|
+
z-index: 1;
|
|
136
|
+
margin: 0;
|
|
137
|
+
padding: 10px 14px;
|
|
138
|
+
font-family: "Inter", "Helvetica", Arial, sans-serif;
|
|
139
|
+
font-size: 0.85rem;
|
|
140
|
+
line-height: 1.4;
|
|
141
|
+
color: #fff;
|
|
142
|
+
background: #000;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── SpeechButton mobile (en articleHeader) / desktop (en body tras imagen) ───
|
|
146
|
+
.speechMobile {
|
|
147
|
+
@include respond(laptop) {
|
|
148
|
+
display: none;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.speechDesktop {
|
|
153
|
+
display: none;
|
|
154
|
+
|
|
155
|
+
@include respond(laptop) {
|
|
156
|
+
display: block;
|
|
157
|
+
margin-bottom: 1rem;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Author + share (mobile) ───────────────────────────────────────────────────
|
|
162
|
+
.authorContainer {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
margin-bottom: 0.5rem;
|
|
166
|
+
|
|
167
|
+
@include respond(laptop) {
|
|
168
|
+
display: none;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Extras dentro del hero — visible solo en desktop (el ArticleHero lo oculta en mobile)
|
|
173
|
+
.heroExtras {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: row;
|
|
176
|
+
justify-content: space-between;
|
|
177
|
+
align-items: center;
|
|
178
|
+
padding-top: 0.75rem;
|
|
179
|
+
}
|
|
@@ -14,7 +14,7 @@ import V0Desktop from './variants/V0Desktop/V0Desktop'
|
|
|
14
14
|
|
|
15
15
|
const VARIANTS = { '0': V0, '1': V1, '2': V2, '3': V3, '4': V4, '5': V5 }
|
|
16
16
|
|
|
17
|
-
export default function ArticleHero({ titulo, volanta, copete, imagen, focalPoint, isAmp = false, extras = null, hideImageOnDesktop = false }) {
|
|
17
|
+
export default function ArticleHero({ titulo, volanta, copete, imagen, imagenEpigrafe, focalPoint, isAmp = false, extras = null, hideImageOnDesktop = false }) {
|
|
18
18
|
const theme = useTheme()
|
|
19
19
|
const variant = String(theme.articleHero ?? 1)
|
|
20
20
|
|
|
@@ -27,10 +27,21 @@ export default function ArticleHero({ titulo, volanta, copete, imagen, focalPoin
|
|
|
27
27
|
|
|
28
28
|
const ExtrasEl = (!isAmp && extras) ? <div className={styles.extras}>{extras}</div> : null
|
|
29
29
|
|
|
30
|
+
// Epígrafe: overlay sutil sobre el borde inferior de la imagen del hero.
|
|
31
|
+
// Solo non-amp (en amp no se aplican los CSS modules).
|
|
32
|
+
const EpigrafeEl = (!isAmp && imagen && imagenEpigrafe)
|
|
33
|
+
? <p className={styles.epigrafe}>{imagenEpigrafe}</p>
|
|
34
|
+
: null
|
|
35
|
+
|
|
30
36
|
const ImgEl = imagen
|
|
31
37
|
? isAmp
|
|
32
38
|
? <img src={imagen} alt={titulo ?? ''} className="article-hero__img" />
|
|
33
|
-
:
|
|
39
|
+
: (
|
|
40
|
+
<>
|
|
41
|
+
<AspectImage src={imagen} alt={titulo ?? ''} aspect="16:9" fill={true} focalPoint={focalPoint} />
|
|
42
|
+
{EpigrafeEl}
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
34
45
|
: null
|
|
35
46
|
|
|
36
47
|
const imgWrapClass = isAmp
|
|
@@ -87,3 +87,19 @@
|
|
|
87
87
|
max-height: none;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// ── Epígrafe de la imagen — franja negra sobre el borde inferior de la foto ──
|
|
92
|
+
.epigrafe {
|
|
93
|
+
position: absolute;
|
|
94
|
+
left: 0;
|
|
95
|
+
right: 0;
|
|
96
|
+
bottom: 0;
|
|
97
|
+
z-index: 1;
|
|
98
|
+
margin: 0;
|
|
99
|
+
padding: 10px 14px;
|
|
100
|
+
font-family: "Inter", "Helvetica", Arial, sans-serif;
|
|
101
|
+
font-size: 0.8rem;
|
|
102
|
+
line-height: 1.35;
|
|
103
|
+
color: #fff;
|
|
104
|
+
background: #000;
|
|
105
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import styles from './ArticleHeroFull.module.scss'
|
|
4
4
|
import { useSiteConfig } from '../../context/SiteConfigContext.jsx'
|
|
5
5
|
|
|
6
|
-
export default function ArticleHeroFull({ titulo, copete, imagen, focalPoint, categoria }) {
|
|
6
|
+
export default function ArticleHeroFull({ titulo, copete, imagen, imagenEpigrafe, focalPoint, categoria }) {
|
|
7
7
|
const { config } = useSiteConfig()
|
|
8
8
|
const siteName = config?.slots?.header?.settings?.siteName ?? ''
|
|
9
9
|
|
|
@@ -22,6 +22,9 @@ export default function ArticleHeroFull({ titulo, copete, imagen, focalPoint, ca
|
|
|
22
22
|
/>
|
|
23
23
|
)}
|
|
24
24
|
<div className={styles.gradient} />
|
|
25
|
+
{imagen && imagenEpigrafe && (
|
|
26
|
+
<p className={styles.epigrafe}>{imagenEpigrafe}</p>
|
|
27
|
+
)}
|
|
25
28
|
<div className={styles.content}>
|
|
26
29
|
{categoria && (
|
|
27
30
|
<div className={styles.breadcrumb}>
|
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
.hero {
|
|
4
4
|
position: relative;
|
|
5
5
|
width: 100%;
|
|
6
|
-
|
|
6
|
+
// Altura via custom property: el portal usa el default en `vh`; un preview
|
|
7
|
+
// dentro de un iframe puede fijar `--ahf-min-h` a un valor estable y evitar
|
|
8
|
+
// el loop de altura (vh → alto del iframe → scrollHeight → vh…).
|
|
9
|
+
min-height: var(--ahf-min-h, 90vh);
|
|
7
10
|
overflow: hidden;
|
|
8
11
|
display: flex;
|
|
9
12
|
align-items: flex-end;
|
|
10
13
|
|
|
11
14
|
@include respond(tablet) {
|
|
12
|
-
min-height: 75vh;
|
|
15
|
+
min-height: var(--ahf-min-h, 75vh);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
@include respond(desktop) {
|
|
16
|
-
min-height: 75vh;
|
|
19
|
+
min-height: var(--ahf-min-h, 75vh);
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -113,4 +116,24 @@
|
|
|
113
116
|
line-height: 1.3em;
|
|
114
117
|
padding-top: 1.875rem;
|
|
115
118
|
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Epígrafe de la imagen — crédito sutil en el borde inferior del hero ──────
|
|
122
|
+
.epigrafe {
|
|
123
|
+
position: absolute;
|
|
124
|
+
right: 0;
|
|
125
|
+
bottom: 0;
|
|
126
|
+
left: 0;
|
|
127
|
+
z-index: 1;
|
|
128
|
+
margin: 0;
|
|
129
|
+
padding: 0 5% 0.5rem;
|
|
130
|
+
font-family: var(--font-inter), Inter, sans-serif;
|
|
131
|
+
font-size: 0.78rem;
|
|
132
|
+
line-height: 1.35;
|
|
133
|
+
text-align: right;
|
|
134
|
+
color: rgb(255 255 255 / 0.75);
|
|
135
|
+
|
|
136
|
+
@include respond(tablet) {
|
|
137
|
+
padding: 0 10% 0.6rem;
|
|
138
|
+
}
|
|
116
139
|
}
|
|
@@ -15,7 +15,7 @@ function formatDate(fechaPublicacion, useLongDate) {
|
|
|
15
15
|
const fecha = date.toLocaleDateString('es-AR', useLongDate
|
|
16
16
|
? { day: '2-digit', month: 'long', year: 'numeric' }
|
|
17
17
|
: { day: '2-digit', month: '2-digit', year: 'numeric' })
|
|
18
|
-
const hora = date.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })
|
|
18
|
+
const hora = date.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' })
|
|
19
19
|
return `${fecha} - ${hora}`
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -225,22 +225,27 @@ function Block({ block, cls, isAmp }) {
|
|
|
225
225
|
return <div className={cls.raw} dangerouslySetInnerHTML={{ __html: block.data.html }} suppressHydrationWarning />
|
|
226
226
|
|
|
227
227
|
case "pullquote": {
|
|
228
|
-
const { variant, text } = block.data
|
|
228
|
+
const { variant, text, color, align } = block.data
|
|
229
229
|
const hasClose = variant !== "2"
|
|
230
|
+
// color === '' → el CSS cae a var(--primary-color, #af0437).
|
|
231
|
+
// align ausente (bloques viejos) → default 'center'. Sólo afecta al
|
|
232
|
+
// texto; las comillas quedan fijas (apertura izq, cierre der).
|
|
233
|
+
const pqStyle = { "--pq-text-align": align || "center" }
|
|
234
|
+
if (color) pqStyle["--eo-pullquote-color"] = color
|
|
230
235
|
if (isAmp) {
|
|
231
236
|
return (
|
|
232
|
-
<div className={cls.pullquote}>
|
|
237
|
+
<div className={cls.pullquote} style={pqStyle}>
|
|
233
238
|
<span className={cls.pullquoteOpen}>“</span>
|
|
234
|
-
<p
|
|
239
|
+
<p dangerouslySetInnerHTML={{ __html: text }} suppressHydrationWarning />
|
|
235
240
|
{hasClose && <span className={cls.pullquoteClose}>”</span>}
|
|
236
241
|
</div>
|
|
237
242
|
)
|
|
238
243
|
}
|
|
239
244
|
const pullCls = [cls.pullquote, cls[`pullquoteV${variant}`]].filter(Boolean).join(" ")
|
|
240
245
|
return (
|
|
241
|
-
<div className={pullCls}>
|
|
246
|
+
<div className={pullCls} style={pqStyle}>
|
|
242
247
|
<span className={cls.pullquoteOpen}>“</span>
|
|
243
|
-
<p
|
|
248
|
+
<p dangerouslySetInnerHTML={{ __html: text }} suppressHydrationWarning />
|
|
244
249
|
{hasClose && <span className={cls.pullquoteClose}>”</span>}
|
|
245
250
|
</div>
|
|
246
251
|
)
|
|
@@ -209,6 +209,53 @@
|
|
|
209
209
|
line-height: 1.6;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/* ── Pullquote / Cita destacada ──
|
|
213
|
+
El color usa `--eo-pullquote-color` (parámetro elegido en el CMS);
|
|
214
|
+
si no se setea, cae al color del portal: var(--primary-color, #af0437). */
|
|
215
|
+
.pullquote {
|
|
216
|
+
--pq-color: var(--eo-pullquote-color, var(--primary-color, #af0437));
|
|
217
|
+
margin: 2.25rem 0;
|
|
218
|
+
padding: 0.25rem 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.pullquoteOpen,
|
|
222
|
+
.pullquoteClose {
|
|
223
|
+
display: block;
|
|
224
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
225
|
+
font-weight: 700;
|
|
226
|
+
font-size: 3.75rem;
|
|
227
|
+
line-height: 0.1;
|
|
228
|
+
color: var(--pq-color);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Comillas fijas: apertura a la izquierda, cierre a la derecha. */
|
|
232
|
+
.pullquoteOpen { margin-bottom: 1.5rem; text-align: left; }
|
|
233
|
+
.pullquoteClose { margin-top: 1.25rem; text-align: right; }
|
|
234
|
+
|
|
235
|
+
.pullquote p {
|
|
236
|
+
margin: 0;
|
|
237
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
238
|
+
font-size: 1.6rem;
|
|
239
|
+
font-weight: 700;
|
|
240
|
+
line-height: 1.4;
|
|
241
|
+
color: var(--pq-color);
|
|
242
|
+
/* La alineación elegida en el CMS sólo afecta al texto. */
|
|
243
|
+
text-align: var(--pq-text-align, center);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Variante 3 — comillas en color, texto en negro. */
|
|
247
|
+
.pullquoteV3 p { color: #111; }
|
|
248
|
+
|
|
249
|
+
/* Variante 2 — fondo oscuro, texto blanco, sólo comilla de apertura. */
|
|
250
|
+
.pullquoteV2 {
|
|
251
|
+
background: #333;
|
|
252
|
+
border-radius: 6px;
|
|
253
|
+
padding: 1.5rem 1.75rem 2rem;
|
|
254
|
+
|
|
255
|
+
p { color: #fff; }
|
|
256
|
+
.pullquoteClose { display: none; }
|
|
257
|
+
}
|
|
258
|
+
|
|
212
259
|
.table {
|
|
213
260
|
width: 100%;
|
|
214
261
|
border-collapse: collapse;
|
|
@@ -225,22 +225,27 @@ function Block({ block, cls, isAmp }) {
|
|
|
225
225
|
return <div className={cls.raw} dangerouslySetInnerHTML={{ __html: block.data.html }} />
|
|
226
226
|
|
|
227
227
|
case "pullquote": {
|
|
228
|
-
const { variant, text } = block.data
|
|
228
|
+
const { variant, text, color, align } = block.data
|
|
229
229
|
const hasClose = variant !== "2"
|
|
230
|
+
// color === '' → el CSS cae a var(--primary-color, #af0437).
|
|
231
|
+
// align ausente (bloques viejos) → default 'center'. Sólo afecta al
|
|
232
|
+
// texto; las comillas quedan fijas (apertura izq, cierre der).
|
|
233
|
+
const pqStyle = { "--pq-text-align": align || "center" }
|
|
234
|
+
if (color) pqStyle["--eo-pullquote-color"] = color
|
|
230
235
|
if (isAmp) {
|
|
231
236
|
return (
|
|
232
|
-
<div className={cls.pullquote}>
|
|
237
|
+
<div className={cls.pullquote} style={pqStyle}>
|
|
233
238
|
<span className={cls.pullquoteOpen}>“</span>
|
|
234
|
-
<p
|
|
239
|
+
<p dangerouslySetInnerHTML={{ __html: text }} />
|
|
235
240
|
{hasClose && <span className={cls.pullquoteClose}>”</span>}
|
|
236
241
|
</div>
|
|
237
242
|
)
|
|
238
243
|
}
|
|
239
244
|
const pullCls = [cls.pullquote, cls[`pullquoteV${variant}`]].filter(Boolean).join(" ")
|
|
240
245
|
return (
|
|
241
|
-
<div className={pullCls}>
|
|
246
|
+
<div className={pullCls} style={pqStyle}>
|
|
242
247
|
<span className={cls.pullquoteOpen}>“</span>
|
|
243
|
-
<p
|
|
248
|
+
<p dangerouslySetInnerHTML={{ __html: text }} />
|
|
244
249
|
{hasClose && <span className={cls.pullquoteClose}>”</span>}
|
|
245
250
|
</div>
|
|
246
251
|
)
|
|
@@ -216,6 +216,53 @@
|
|
|
216
216
|
line-height: 1.6;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/* ── Pullquote / Cita destacada ──
|
|
220
|
+
El color usa `--eo-pullquote-color` (parámetro elegido en el CMS);
|
|
221
|
+
si no se setea, cae al color del portal: var(--primary-color, #af0437). */
|
|
222
|
+
.pullquote {
|
|
223
|
+
--pq-color: var(--eo-pullquote-color, var(--primary-color, #af0437));
|
|
224
|
+
margin: 2.25rem 0;
|
|
225
|
+
padding: 0.25rem 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.pullquoteOpen,
|
|
229
|
+
.pullquoteClose {
|
|
230
|
+
display: block;
|
|
231
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
232
|
+
font-weight: 700;
|
|
233
|
+
font-size: 3.75rem;
|
|
234
|
+
line-height: 0.1;
|
|
235
|
+
color: var(--pq-color);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Comillas fijas: apertura a la izquierda, cierre a la derecha. */
|
|
239
|
+
.pullquoteOpen { margin-bottom: 1.5rem; text-align: left; }
|
|
240
|
+
.pullquoteClose { margin-top: 1.25rem; text-align: right; }
|
|
241
|
+
|
|
242
|
+
.pullquote p {
|
|
243
|
+
margin: 0;
|
|
244
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
245
|
+
font-size: 1.6rem;
|
|
246
|
+
font-weight: 700;
|
|
247
|
+
line-height: 1.4;
|
|
248
|
+
color: var(--pq-color);
|
|
249
|
+
/* La alineación elegida en el CMS sólo afecta al texto. */
|
|
250
|
+
text-align: var(--pq-text-align, center);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Variante 3 — comillas en color, texto en negro. */
|
|
254
|
+
.pullquoteV3 p { color: #111; }
|
|
255
|
+
|
|
256
|
+
/* Variante 2 — fondo oscuro, texto blanco, sólo comilla de apertura. */
|
|
257
|
+
.pullquoteV2 {
|
|
258
|
+
background: #333;
|
|
259
|
+
border-radius: 6px;
|
|
260
|
+
padding: 1.5rem 1.75rem 2rem;
|
|
261
|
+
|
|
262
|
+
p { color: #fff; }
|
|
263
|
+
.pullquoteClose { display: none; }
|
|
264
|
+
}
|
|
265
|
+
|
|
219
266
|
.table {
|
|
220
267
|
width: 100%;
|
|
221
268
|
border-collapse: collapse;
|
|
@@ -33,13 +33,19 @@ export default function FooterSimple({ isAmp = false }) {
|
|
|
33
33
|
} = s
|
|
34
34
|
const social = { ...headerSocial, ...(s.social ?? {}) }
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// Colores propios del footer (footer.settings) con fallback al theme global
|
|
37
|
+
const footerBg = s.backgroundColor ?? theme.secondary ?? '#0D1333'
|
|
38
|
+
const footerText = s.textColor ?? footerTextColor ?? theme.textColor ?? '#ffffff'
|
|
39
|
+
const footerAccent = s.primaryColor ?? theme.primary ?? footerBg
|
|
40
|
+
const footerFont = s.fontFamily
|
|
41
|
+
|
|
37
42
|
const inlineStyle = {
|
|
38
|
-
color:
|
|
39
|
-
|
|
40
|
-
'--
|
|
41
|
-
'--
|
|
42
|
-
'--
|
|
43
|
+
color: footerText,
|
|
44
|
+
...(footerFont && { fontFamily: footerFont }),
|
|
45
|
+
'--primary-color': footerAccent,
|
|
46
|
+
'--secondary-color': footerBg,
|
|
47
|
+
'--text-color': footerText,
|
|
48
|
+
'--social-hover-filter': footerAccent ? hexToCssFilter(footerAccent) : 'none',
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
const logoEl = (logoUrl || iconUrl) && (
|
|
@@ -140,8 +146,8 @@ export default function FooterSimple({ isAmp = false }) {
|
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
return (
|
|
143
|
-
<div className={styles.fullcontainer}>
|
|
144
|
-
<footer className={styles.container}
|
|
149
|
+
<div className={styles.fullcontainer} style={inlineStyle}>
|
|
150
|
+
<footer className={styles.container}>
|
|
145
151
|
|
|
146
152
|
{/* ── MAIN: izq (logo+slogan+legal) | der (nav + social+links) ── */}
|
|
147
153
|
<div className={styles.mainRow}>
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
display: flex;
|
|
15
15
|
align-items: center;
|
|
16
16
|
padding-left: 15px;
|
|
17
|
-
|
|
18
|
-
color:
|
|
17
|
+
|
|
18
|
+
color: var(--text-color, #fff);
|
|
19
19
|
font-family: var(--font-inter, Inter, sans-serif);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.fullcontainer{
|
|
23
|
-
color:
|
|
23
|
+
color: var(--text-color, #fff);
|
|
24
24
|
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
box-sizing: border-box;
|
|
35
35
|
margin-top: auto;
|
|
36
36
|
min-height: 268px;
|
|
37
|
-
color:
|
|
37
|
+
color: var(--text-color, #fff);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// ── MAIN ROW (2 columns) ──────────────────────────────────────────────────────
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
width: 100%;
|
|
92
92
|
|
|
93
93
|
@media (min-width: $tablet) {
|
|
94
|
-
border-top: 1px solid
|
|
94
|
+
border-top: 1px solid var(--text-color, #fff);
|
|
95
95
|
padding-top: 20px;
|
|
96
96
|
flex-direction: row;
|
|
97
97
|
align-items: center;
|
|
@@ -155,10 +155,9 @@
|
|
|
155
155
|
text-transform: uppercase;
|
|
156
156
|
color: inherit;
|
|
157
157
|
opacity: 1;
|
|
158
|
-
transition:
|
|
159
|
-
|
|
158
|
+
transition: color 0.15s;
|
|
160
159
|
|
|
161
|
-
&:hover {
|
|
160
|
+
&:hover { color: var(--primary-color, #B1043F); }
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
// ── Divider ───────────────────────────────────────────────────────────────────
|
|
@@ -209,7 +208,7 @@
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
a {
|
|
212
|
-
color:
|
|
211
|
+
color: var(--text-color, #fff);
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
214
|
|
|
@@ -246,11 +245,11 @@
|
|
|
246
245
|
color: inherit;
|
|
247
246
|
text-decoration: none;
|
|
248
247
|
opacity: 1;
|
|
249
|
-
transition:
|
|
248
|
+
transition: color 0.15s;
|
|
250
249
|
white-space: nowrap;
|
|
251
250
|
|
|
252
251
|
&:hover {
|
|
253
|
-
|
|
252
|
+
color: var(--primary-color, #B1043F);
|
|
254
253
|
text-decoration: underline;
|
|
255
254
|
}
|
|
256
255
|
}
|
package/src/index.js
CHANGED
|
@@ -57,6 +57,9 @@ export { default as ArticleHero } from './components/ArticleHero/ArticleHero
|
|
|
57
57
|
export { default as ArticleHeroFull } from './components/ArticleHeroFull/ArticleHeroFull.jsx'
|
|
58
58
|
export { default as ArticleSidebar } from './components/ArticleSidebar/ArticleSidebar.jsx'
|
|
59
59
|
|
|
60
|
+
// === Screens / vistas de detalle de artículo (réplica portable para previews) ===
|
|
61
|
+
export { default as ArticleDetailView } from './components/ArticleDetailView/ArticleDetailView.jsx'
|
|
62
|
+
|
|
60
63
|
// === Headers ===
|
|
61
64
|
export { default as HeaderSimpleSwitch } from './components/Headers/HeaderSimple/HeaderSimpleSwitch/HeaderSimpleSwitch.jsx'
|
|
62
65
|
export { default as HeaderSimpleDesktop } from './components/Headers/HeaderSimple/HeaderSimpleDesktop/HeaderSimpleDesktop.jsx'
|