@edc-motor/ui 0.3.0 → 0.4.0
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/scss/_tokens.scss +1 -1
- package/scss/components/_blocks.scss +52 -7
- package/scss/components/_index.scss +1 -0
- package/scss/components/_rich-content.scss +53 -0
- package/src/blocks/BlockCta.vue +23 -4
- package/src/blocks/BlockFaq.vue +1 -0
- package/src/blocks/BlockHeader.vue +1 -1
- package/src/blocks/BlockIndex.vue +9 -3
- package/src/blocks/BlockQuote.vue +2 -0
- package/src/blocks/BlockShell.vue +2 -1
- package/src/blocks/BlockText.vue +26 -4
- package/src/blocks/BlockTextCard.vue +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edc-motor/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "EdC Motor — componentes públicos Vue 3 + tokens SCSS para webs de juegos de mesa (paquete fuente: lo compila el consumidor con Vite)",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
package/scss/_tokens.scss
CHANGED
|
@@ -38,7 +38,7 @@ $k-fw-bold: 700;
|
|
|
38
38
|
$k-lh-tight: 1.15;
|
|
39
39
|
$k-lh-ui: 1.35;
|
|
40
40
|
$k-lh-body: 1.55;
|
|
41
|
-
$fs-11: 11px; $fs-12: 12px; $fs-13: 13px; $fs-14: 14px; $fs-16: 16px; $fs-18: 18px; $fs-20: 20px; $fs-24: 24px; $fs-28: 28px;
|
|
41
|
+
$fs-11: 11px; $fs-12: 12px; $fs-13: 13px; $fs-14: 14px; $fs-16: 16px; $fs-18: 18px; $fs-20: 20px; $fs-24: 24px; $fs-28: 28px; $fs-32: 32px;
|
|
42
42
|
|
|
43
43
|
// Espaciado (px, como kontuan)
|
|
44
44
|
$space-0: 0;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
background: var(--block-bg, transparent);
|
|
8
8
|
|
|
9
9
|
&__inner {
|
|
10
|
-
max-width:
|
|
10
|
+
max-width: 1200px; // 'wide' (por defecto)
|
|
11
11
|
margin: 0 auto;
|
|
12
12
|
padding: 0 $space-4;
|
|
13
13
|
display: grid;
|
|
@@ -18,9 +18,14 @@
|
|
|
18
18
|
&--w-narrow .block__inner { max-width: 680px; }
|
|
19
19
|
&--w-full .block__inner { max-width: none; padding-inline: $space-6; }
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
&
|
|
21
|
+
// Tipografía de bloque: texto $fs-16, subtítulo $fs-20, título $fs-28
|
|
22
|
+
// (la cabecera sube a 32/24). Los títulos nunca se justifican.
|
|
23
|
+
&__title { margin: 0; font-size: $fs-28; }
|
|
24
|
+
&__subtitle { margin: 0; color: $text-2; font-size: $fs-20; }
|
|
25
|
+
&__text { color: $text-1; font-size: $fs-16; }
|
|
26
|
+
|
|
27
|
+
&--align-justify &__title,
|
|
28
|
+
&--align-justify &__subtitle { text-align: left; }
|
|
24
29
|
|
|
25
30
|
&__label {
|
|
26
31
|
display: inline-block;
|
|
@@ -50,7 +55,8 @@
|
|
|
50
55
|
.edc-button { justify-self: start; text-decoration: none; }
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
&--header .block__title { font-size: $fs-
|
|
58
|
+
&--header .block__title { font-size: $fs-32; }
|
|
59
|
+
&--header .block__subtitle { font-size: $fs-24; }
|
|
54
60
|
|
|
55
61
|
&__media-layout { display: grid; gap: $space-4; }
|
|
56
62
|
&__image {
|
|
@@ -62,13 +68,49 @@
|
|
|
62
68
|
&--image-left .block__media-layout,
|
|
63
69
|
&--image-right .block__media-layout {
|
|
64
70
|
@container content (min-width: #{$bp-sm}) {
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
// Reparto izquierda:derecha del campo image_columns (--img-cols).
|
|
72
|
+
grid-template-columns: var(--img-cols, minmax(0, 2fr) minmax(0, 3fr));
|
|
73
|
+
align-items: stretch;
|
|
67
74
|
}
|
|
68
75
|
}
|
|
76
|
+
&--image-right .block__media-layout .block__image-frame,
|
|
69
77
|
&--image-right .block__media-layout .block__image { order: 2; }
|
|
70
78
|
&--image-bottom .block__media-layout .block__image { order: 2; }
|
|
71
79
|
|
|
80
|
+
// Marco de la imagen en columnas: el ALTO lo marca el texto de al lado
|
|
81
|
+
// (la imagen absoluta no aporta altura) y el modo de escalado hace el resto.
|
|
82
|
+
&__image-frame {
|
|
83
|
+
position: relative;
|
|
84
|
+
min-height: 200px; // que no colapse con textos muy cortos
|
|
85
|
+
|
|
86
|
+
.block__image {
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset: 0;
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
max-width: none;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&--fit-contain &__image-frame .block__image { object-fit: contain; }
|
|
96
|
+
&--fit-cover &__image-frame .block__image { object-fit: cover; }
|
|
97
|
+
&--fit-fill &__image-frame .block__image { object-fit: fill; }
|
|
98
|
+
|
|
99
|
+
// En una columna (estrecho) la imagen vuelve al flujo normal.
|
|
100
|
+
@container content (max-width: #{$bp-sm}) {
|
|
101
|
+
&__image-frame {
|
|
102
|
+
position: static;
|
|
103
|
+
min-height: 0;
|
|
104
|
+
|
|
105
|
+
.block__image {
|
|
106
|
+
position: static;
|
|
107
|
+
width: auto;
|
|
108
|
+
height: auto;
|
|
109
|
+
max-width: 100%;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
// Imagen flotada: el texto la rodea y sigue por debajo (cleartext, como
|
|
73
115
|
// CDL). En estrecho la imagen va arriba a lo ancho, sin flotar.
|
|
74
116
|
&--image-clear-left .block__media-layout,
|
|
@@ -145,6 +187,9 @@
|
|
|
145
187
|
|
|
146
188
|
a { color: $accent-500; text-decoration: none; }
|
|
147
189
|
a:hover { text-decoration: underline; }
|
|
190
|
+
|
|
191
|
+
// Entradas de bloques hijos: sangradas bajo su padre.
|
|
192
|
+
.block__index-child { margin-left: $space-5; }
|
|
148
193
|
}
|
|
149
194
|
|
|
150
195
|
// FAQ (doc 03): acordeón nativo details/summary del bloque `faq`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@use "../tokens" as *;
|
|
2
|
+
|
|
3
|
+
// Contenido enriquecido renderizado (v-html de los campos richtext): aire
|
|
4
|
+
// entre elementos y escala tipográfica del wysiwyg. Los juegos pueden
|
|
5
|
+
// ampliarlo desde su propio SCSS.
|
|
6
|
+
.rich-content {
|
|
7
|
+
font-size: $fs-16;
|
|
8
|
+
line-height: $k-lh-body;
|
|
9
|
+
|
|
10
|
+
// Aire entre TODOS los elementos del wysiwyg (párrafos, listas, títulos…).
|
|
11
|
+
p,
|
|
12
|
+
ul,
|
|
13
|
+
ol,
|
|
14
|
+
blockquote {
|
|
15
|
+
margin-block: $space-3;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
h2,
|
|
19
|
+
h3,
|
|
20
|
+
h4,
|
|
21
|
+
h5,
|
|
22
|
+
h6 {
|
|
23
|
+
margin-block: $space-5 $space-3;
|
|
24
|
+
line-height: $k-lh-tight;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Sin huecos extra en los bordes del campo.
|
|
28
|
+
> :first-child { margin-block-start: 0; }
|
|
29
|
+
> :last-child { margin-block-end: 0; }
|
|
30
|
+
|
|
31
|
+
// Escala del wysiwyg: h2 28 · h3 24 · h4 20 · h5 18 · h6 16.
|
|
32
|
+
h2 { font-size: $fs-28; }
|
|
33
|
+
h3 { font-size: $fs-24; }
|
|
34
|
+
h4 { font-size: $fs-20; }
|
|
35
|
+
h5 { font-size: $fs-18; }
|
|
36
|
+
h6 { font-size: $fs-16; }
|
|
37
|
+
|
|
38
|
+
ul,
|
|
39
|
+
ol {
|
|
40
|
+
padding-left: 1.4em;
|
|
41
|
+
|
|
42
|
+
li + li { margin-block-start: $space-1; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Iconos del juego insertados en línea por el editor (<img class="rt-icon">).
|
|
46
|
+
img.rt-icon {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
width: 1.25em;
|
|
49
|
+
height: 1.25em;
|
|
50
|
+
vertical-align: -0.25em;
|
|
51
|
+
margin: 0 1px;
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/blocks/BlockCta.vue
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
2
3
|
import BlockShell from './BlockShell.vue'
|
|
3
4
|
|
|
4
|
-
defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
|
|
5
|
+
const props = defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
|
|
6
|
+
|
|
7
|
+
const position = computed(() => String(props.settings.image_position || 'top'))
|
|
8
|
+
const inColumns = computed(
|
|
9
|
+
() => !!props.settings.image && (position.value === 'left' || position.value === 'right'),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const layoutStyle = computed(() => {
|
|
13
|
+
if (!inColumns.value) return {}
|
|
14
|
+
const [a, b] = String(props.settings.image_columns || '2:3').split(':')
|
|
15
|
+
return { '--img-cols': `minmax(0, ${a || 1}fr) minmax(0, ${b || 1}fr)` }
|
|
16
|
+
})
|
|
5
17
|
</script>
|
|
6
18
|
|
|
7
19
|
<!-- eslint-disable vue/no-v-html -- HTML saneado en servidor (DC-09) -->
|
|
@@ -9,12 +21,19 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
|
|
|
9
21
|
<BlockShell
|
|
10
22
|
:settings="settings"
|
|
11
23
|
class="block--cta"
|
|
12
|
-
:class="
|
|
24
|
+
:class="[
|
|
25
|
+
settings.image ? `block--image-${position}` : '',
|
|
26
|
+
inColumns ? `block--fit-${settings.image_fit || 'contain'}` : '',
|
|
27
|
+
]"
|
|
13
28
|
>
|
|
14
29
|
<div class="block__card">
|
|
15
30
|
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
16
|
-
<
|
|
17
|
-
|
|
31
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
32
|
+
<div class="block__media-layout" :style="layoutStyle">
|
|
33
|
+
<span v-if="settings.image && inColumns" class="block__image-frame">
|
|
34
|
+
<img class="block__image" :src="String(settings.image)" alt="" />
|
|
35
|
+
</span>
|
|
36
|
+
<img v-else-if="settings.image" class="block__image" :src="String(settings.image)" alt="" />
|
|
18
37
|
<div class="block__cta-body">
|
|
19
38
|
<div v-if="settings.body" class="block__text rich-content" v-html="settings.body" />
|
|
20
39
|
<a
|
package/src/blocks/BlockFaq.vue
CHANGED
|
@@ -17,6 +17,7 @@ defineProps<{
|
|
|
17
17
|
<template>
|
|
18
18
|
<BlockShell :settings="settings" class="block--faq">
|
|
19
19
|
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
20
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
20
21
|
<details v-for="(item, i) in settings.items ?? []" :key="i" class="block__faq-item">
|
|
21
22
|
<summary class="block__faq-question">{{ item.question }}</summary>
|
|
22
23
|
<div v-if="item.answer" class="block__faq-answer rich-content" v-html="item.answer" />
|
|
@@ -6,7 +6,7 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
|
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<BlockShell :settings="settings" class="block--header">
|
|
9
|
-
<h1 class="block__title">{{ settings.title }}</h1>
|
|
9
|
+
<h1 v-if="settings.title" class="block__title">{{ settings.title }}</h1>
|
|
10
10
|
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
11
11
|
<img v-if="settings.image" class="block__banner" :src="String(settings.image)" alt="" />
|
|
12
12
|
</BlockShell>
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import BlockShell from './BlockShell.vue'
|
|
3
3
|
|
|
4
|
-
// Índice automático: enlaces de ancla a los bloques posteriores de la página
|
|
4
|
+
// Índice automático: enlaces de ancla a los bloques posteriores de la página,
|
|
5
|
+
// con sangría por nivel (bloques hijos → depth 1).
|
|
5
6
|
defineProps<{
|
|
6
7
|
settings: Record<string, unknown>
|
|
7
|
-
data: { items?: { id: number; label: string }[] }
|
|
8
|
+
data: { items?: { id: number; label: string; depth?: number }[] }
|
|
8
9
|
}>()
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
<template>
|
|
12
13
|
<BlockShell :settings="settings" class="block--index">
|
|
13
14
|
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
15
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
14
16
|
<component :is="settings.numbered ? 'ol' : 'ul'" class="block__index">
|
|
15
|
-
<li
|
|
17
|
+
<li
|
|
18
|
+
v-for="item in data.items ?? []"
|
|
19
|
+
:key="item.id"
|
|
20
|
+
:class="{ 'block__index-child': (item.depth ?? 0) > 0 }"
|
|
21
|
+
>
|
|
16
22
|
<a :href="`#block-${item.id}`">{{ item.label }}</a>
|
|
17
23
|
</li>
|
|
18
24
|
</component>
|
|
@@ -7,6 +7,8 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
|
|
|
7
7
|
<!-- eslint-disable vue/no-v-html -- HTML saneado en servidor (DC-09) -->
|
|
8
8
|
<template>
|
|
9
9
|
<BlockShell :settings="settings" class="block--quote">
|
|
10
|
+
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
11
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
10
12
|
<blockquote class="block__quote">
|
|
11
13
|
<img v-if="settings.image" class="block__avatar" :src="String(settings.image)" alt="" />
|
|
12
14
|
<div class="rich-content" v-html="settings.quote" />
|
|
@@ -9,6 +9,7 @@ import { computed, type CSSProperties } from 'vue'
|
|
|
9
9
|
const props = defineProps<{ settings: Record<string, unknown> }>()
|
|
10
10
|
|
|
11
11
|
const width = computed(() => `block--w-${(props.settings.width as string) || 'wide'}`)
|
|
12
|
+
const align = computed(() => `block--align-${(props.settings.align as string) || 'left'}`)
|
|
12
13
|
|
|
13
14
|
const style = computed<CSSProperties>(() => {
|
|
14
15
|
const background = props.settings.background as string | undefined
|
|
@@ -22,7 +23,7 @@ const style = computed<CSSProperties>(() => {
|
|
|
22
23
|
</script>
|
|
23
24
|
|
|
24
25
|
<template>
|
|
25
|
-
<section class="block" :class="width" :style="style">
|
|
26
|
+
<section class="block" :class="[width, align]" :style="style">
|
|
26
27
|
<div class="block__inner">
|
|
27
28
|
<slot />
|
|
28
29
|
</div>
|
package/src/blocks/BlockText.vue
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
2
3
|
import BlockShell from './BlockShell.vue'
|
|
3
4
|
|
|
4
|
-
defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
|
|
5
|
+
const props = defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
|
|
6
|
+
|
|
7
|
+
const position = computed(() => String(props.settings.image_position || 'top'))
|
|
8
|
+
const inColumns = computed(
|
|
9
|
+
() => !!props.settings.image && (position.value === 'left' || position.value === 'right'),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Reparto de columnas izquierda:derecha ("2:3" → 2fr 3fr) para el layout
|
|
13
|
+
// imagen+texto; el modo de escalado (contain/cover/fill) va por clase.
|
|
14
|
+
const layoutStyle = computed(() => {
|
|
15
|
+
if (!inColumns.value) return {}
|
|
16
|
+
const [a, b] = String(props.settings.image_columns || '2:3').split(':')
|
|
17
|
+
return { '--img-cols': `minmax(0, ${a || 1}fr) minmax(0, ${b || 1}fr)` }
|
|
18
|
+
})
|
|
5
19
|
</script>
|
|
6
20
|
|
|
7
21
|
<!-- eslint-disable vue/no-v-html -- HTML saneado en servidor (DC-09) -->
|
|
@@ -9,11 +23,19 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
|
|
|
9
23
|
<BlockShell
|
|
10
24
|
:settings="settings"
|
|
11
25
|
class="block--text"
|
|
12
|
-
:class="
|
|
26
|
+
:class="[
|
|
27
|
+
settings.image ? `block--image-${position}` : '',
|
|
28
|
+
inColumns ? `block--fit-${settings.image_fit || 'contain'}` : '',
|
|
29
|
+
]"
|
|
13
30
|
>
|
|
14
31
|
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
15
|
-
<
|
|
16
|
-
|
|
32
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
33
|
+
<div class="block__media-layout" :style="layoutStyle">
|
|
34
|
+
<!-- En columnas, el marco fija el alto de la imagen al del texto -->
|
|
35
|
+
<span v-if="settings.image && inColumns" class="block__image-frame">
|
|
36
|
+
<img class="block__image" :src="String(settings.image)" alt="" />
|
|
37
|
+
</span>
|
|
38
|
+
<img v-else-if="settings.image" class="block__image" :src="String(settings.image)" alt="" />
|
|
17
39
|
<div class="block__text rich-content" v-html="settings.body" />
|
|
18
40
|
</div>
|
|
19
41
|
</BlockShell>
|
|
@@ -14,6 +14,7 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
|
|
|
14
14
|
<div class="block__card">
|
|
15
15
|
<span v-if="settings.label" class="block__label">{{ settings.label }}</span>
|
|
16
16
|
<h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
|
|
17
|
+
<p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
|
|
17
18
|
<div class="block__media-layout">
|
|
18
19
|
<img v-if="settings.image" class="block__image" :src="String(settings.image)" alt="" />
|
|
19
20
|
<div class="block__text rich-content" v-html="settings.body" />
|