@edc-motor/ui 0.2.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.
Files changed (73) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +45 -0
  3. package/package.json +47 -0
  4. package/scss/_base.scss +33 -0
  5. package/scss/_forms.scss +33 -0
  6. package/scss/_shared-components.scss +1 -0
  7. package/scss/_theme.scss +74 -0
  8. package/scss/_tokens.scss +114 -0
  9. package/scss/components/_base-button.scss +113 -0
  10. package/scss/components/_blocks.scss +211 -0
  11. package/scss/components/_breadcrumbs.scss +31 -0
  12. package/scss/components/_checkbox.scss +42 -0
  13. package/scss/components/_chip.scss +23 -0
  14. package/scss/components/_confirm.scss +3 -0
  15. package/scss/components/_edit-modal.scss +11 -0
  16. package/scss/components/_font-upload.scss +91 -0
  17. package/scss/components/_form-field.scss +85 -0
  18. package/scss/components/_icon-button.scss +23 -0
  19. package/scss/components/_image-upload.scss +85 -0
  20. package/scss/components/_index.scss +24 -0
  21. package/scss/components/_locale-selector.scss +89 -0
  22. package/scss/components/_modal.scss +68 -0
  23. package/scss/components/_motor-badge.scss +21 -0
  24. package/scss/components/_numeric-input.scss +53 -0
  25. package/scss/components/_page-background.scss +22 -0
  26. package/scss/components/_palette-color-picker.scss +69 -0
  27. package/scss/components/_rich-text.scss +144 -0
  28. package/scss/components/_search-select.scss +138 -0
  29. package/scss/components/_tabs.scss +120 -0
  30. package/scss/components/_theme-selector.scss +34 -0
  31. package/scss/components/_toast-container.scss +16 -0
  32. package/scss/components/_toast.scss +25 -0
  33. package/scss/components/_translatable-input.scss +73 -0
  34. package/src/blocks/BlockCta.vue +32 -0
  35. package/src/blocks/BlockFaq.vue +25 -0
  36. package/src/blocks/BlockHeader.vue +13 -0
  37. package/src/blocks/BlockIndex.vue +20 -0
  38. package/src/blocks/BlockQuote.vue +16 -0
  39. package/src/blocks/BlockShell.vue +30 -0
  40. package/src/blocks/BlockText.vue +20 -0
  41. package/src/blocks/BlockTextCard.vue +23 -0
  42. package/src/blocks/PageBackground.vue +15 -0
  43. package/src/blocks/index.ts +25 -0
  44. package/src/components/AppBreadcrumbs.vue +44 -0
  45. package/src/components/BaseButton.vue +24 -0
  46. package/src/components/BaseCheckbox.vue +37 -0
  47. package/src/components/BaseInput.vue +53 -0
  48. package/src/components/BaseModal.vue +98 -0
  49. package/src/components/BaseSelect.vue +53 -0
  50. package/src/components/BaseTabs.vue +30 -0
  51. package/src/components/BaseTextarea.vue +45 -0
  52. package/src/components/BaseToast.vue +31 -0
  53. package/src/components/ConfirmDialog.vue +35 -0
  54. package/src/components/EditModal.vue +57 -0
  55. package/src/components/FontUpload.vue +123 -0
  56. package/src/components/IconButton.vue +24 -0
  57. package/src/components/ImageUpload.vue +142 -0
  58. package/src/components/LocaleSelector.vue +59 -0
  59. package/src/components/MotorBadge.vue +17 -0
  60. package/src/components/NumericInput.vue +115 -0
  61. package/src/components/PaletteColorPicker.vue +94 -0
  62. package/src/components/RichTextInput.vue +273 -0
  63. package/src/components/SearchSelect.vue +172 -0
  64. package/src/components/ThemeSelector.vue +28 -0
  65. package/src/components/ToastContainer.vue +23 -0
  66. package/src/components/TranslatableImage.vue +120 -0
  67. package/src/components/TranslatableInput.vue +135 -0
  68. package/src/composables/useConfirm.ts +49 -0
  69. package/src/composables/useHead.ts +54 -0
  70. package/src/composables/useTheme.ts +50 -0
  71. package/src/composables/useToast.ts +26 -0
  72. package/src/index.ts +39 -0
  73. package/src/lib/createApi.ts +42 -0
@@ -0,0 +1,69 @@
1
+ @use "tokens" as *;
2
+
3
+ // Selector de color (portado de kontuan).
4
+ .palette-color-picker {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: $space-1;
8
+
9
+ &__label {
10
+ font-size: $fs-13;
11
+ font-weight: $k-fw-medium;
12
+ color: $text-2;
13
+ }
14
+
15
+ &__grid {
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ gap: $space-2;
19
+ }
20
+
21
+ &__swatch {
22
+ --swatch-color: #64748b;
23
+ position: relative;
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ width: 32px;
28
+ height: 32px;
29
+ padding: 0;
30
+ border: 2px solid transparent;
31
+ border-radius: $radius-sm;
32
+ background: var(--swatch-color);
33
+ color: #fff;
34
+ cursor: pointer;
35
+ transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
36
+
37
+ &:hover { transform: scale(1.06); }
38
+
39
+ &--selected { box-shadow: 0 0 0 2px $surface, 0 0 0 4px var(--swatch-color); }
40
+
41
+ &--custom { overflow: hidden; }
42
+
43
+ &--custom-idle {
44
+ background: linear-gradient(135deg, #facc15 0%, #3b82f6 50%, #ef4444 100%);
45
+ }
46
+
47
+ &--custom-idle#{&}--selected {
48
+ box-shadow: 0 0 0 2px $surface, 0 0 0 4px $text-1;
49
+ }
50
+ }
51
+
52
+ &__swatch-check {
53
+ position: absolute;
54
+ filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.35));
55
+ }
56
+
57
+ &__swatch-pipette { color: #000; pointer-events: none; }
58
+
59
+ &__swatch-input {
60
+ position: absolute;
61
+ inset: 0;
62
+ width: 100%;
63
+ height: 100%;
64
+ opacity: 0;
65
+ border: none;
66
+ padding: 0;
67
+ cursor: pointer;
68
+ }
69
+ }
@@ -0,0 +1,144 @@
1
+ @use "tokens" as *;
2
+
3
+ // Editor WYSIWYG (TipTap). Enmarcado como los demás campos de formulario.
4
+ .rich-text {
5
+ border: 1px solid $input-border;
6
+ border-radius: $input-radius;
7
+ background: $input-bg;
8
+ overflow: hidden;
9
+ transition: border-color 0.15s, box-shadow 0.15s;
10
+
11
+ &:focus-within {
12
+ border-color: $input-border-focus;
13
+ box-shadow: $input-focus-ring;
14
+ }
15
+
16
+ &--disabled { opacity: $disabled-opacity; pointer-events: none; }
17
+
18
+ &__toolbar {
19
+ display: flex;
20
+ align-items: center;
21
+ flex-wrap: wrap;
22
+ gap: 2px;
23
+ padding: $space-1;
24
+ border-bottom: 1px solid $border;
25
+ background: $surface-2;
26
+ }
27
+
28
+ &__tool {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ width: 30px;
33
+ height: 30px;
34
+ border: none;
35
+ border-radius: $radius-sm;
36
+ background: transparent;
37
+ color: $text-2;
38
+ cursor: pointer;
39
+ transition: background-color 0.15s, color 0.15s;
40
+
41
+ &:hover:not(:disabled) { background: $ui-hover-bg; color: $text-1; }
42
+ &--active { background: $accent-100; color: $accent-600; }
43
+ &:disabled { opacity: 0.5; cursor: not-allowed; }
44
+ }
45
+
46
+ &__sep {
47
+ width: 1px;
48
+ height: 20px;
49
+ background: $border;
50
+ margin: 0 $space-1;
51
+ }
52
+
53
+ // El toggle de HTML se ancla a la derecha de la barra.
54
+ &__tool--source { margin-left: auto; }
55
+
56
+ // Modo HTML: edición directa del código.
57
+ &__source {
58
+ display: block;
59
+ width: 100%;
60
+ min-height: 160px;
61
+ padding: $input-padding-y $input-padding-x;
62
+ border: none;
63
+ background: $surface;
64
+ color: $text-1;
65
+ font-family: $k-font-mono;
66
+ font-size: $fs-13;
67
+ line-height: 1.5;
68
+ resize: vertical;
69
+ outline: none;
70
+ }
71
+
72
+ // Selector de iconos del juego.
73
+ &__picker { position: relative; display: inline-flex; }
74
+
75
+ &__icons {
76
+ position: absolute;
77
+ top: calc(100% + 4px);
78
+ left: 0;
79
+ z-index: 100;
80
+ display: grid;
81
+ grid-template-columns: repeat(5, 1fr);
82
+ gap: 2px;
83
+ max-width: 220px;
84
+ max-height: 200px;
85
+ overflow-y: auto;
86
+ padding: $space-1;
87
+ background: $surface;
88
+ border: 1px solid $border;
89
+ border-radius: $radius-md;
90
+ box-shadow: $shadow-md;
91
+ }
92
+
93
+ &__icon {
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ width: 36px;
98
+ height: 36px;
99
+ padding: 4px;
100
+ border: none;
101
+ border-radius: $radius-sm;
102
+ background: transparent;
103
+ cursor: pointer;
104
+ transition: background-color 0.15s;
105
+
106
+ &:hover { background: $ui-hover-bg; }
107
+ img { width: 100%; height: 100%; object-fit: contain; }
108
+ }
109
+
110
+ &__content {
111
+ .ProseMirror {
112
+ min-height: 120px;
113
+ padding: $input-padding-y $input-padding-x;
114
+ color: $input-text;
115
+ font-size: $fs-14;
116
+ line-height: $k-lh-body;
117
+ outline: none;
118
+
119
+ > * + * { margin-top: 0.6em; }
120
+ h2 { font-size: $fs-18; font-weight: $k-fw-semibold; }
121
+ ul, ol { padding-left: 1.4em; }
122
+ ul { list-style: disc; }
123
+ ol { list-style: decimal; }
124
+ a { color: $accent-500; text-decoration: underline; }
125
+
126
+ // Iconos insertados: en línea con el texto.
127
+ img.rt-icon {
128
+ display: inline-block;
129
+ width: 1.25em;
130
+ height: 1.25em;
131
+ vertical-align: -0.25em;
132
+ margin: 0 1px;
133
+ }
134
+
135
+ p.is-editor-empty:first-child::before {
136
+ content: attr(data-placeholder);
137
+ color: $input-placeholder;
138
+ float: left;
139
+ height: 0;
140
+ pointer-events: none;
141
+ }
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,138 @@
1
+ @use "tokens" as *;
2
+
3
+ // Select con buscador (combobox): cerrado parece un input; desplegado, un
4
+ // panel flotante con búsqueda y opciones (lista larga => scroll interno).
5
+ .search-select {
6
+ position: relative;
7
+ width: 100%;
8
+
9
+ &__trigger {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: $space-2;
13
+ width: 100%;
14
+ background: $input-bg;
15
+ border: 1px solid $input-border;
16
+ border-radius: $input-radius;
17
+ color: $input-text;
18
+ cursor: pointer;
19
+ padding: $space-2 $space-3;
20
+ text-align: left;
21
+ font: inherit;
22
+
23
+ &:hover { border-color: $input-border-hover; }
24
+ }
25
+
26
+ &.is-open &__trigger {
27
+ border-color: $input-border-focus;
28
+ }
29
+
30
+ &__value {
31
+ flex: 1;
32
+ min-width: 0;
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ white-space: nowrap;
36
+
37
+ &.is-placeholder { color: $input-placeholder; }
38
+ }
39
+
40
+ &__chevron {
41
+ flex: 0 0 auto;
42
+ color: $text-3;
43
+ transition: transform 0.15s ease;
44
+ }
45
+
46
+ &.is-open &__chevron { transform: rotate(180deg); }
47
+
48
+ &__dropdown {
49
+ position: absolute;
50
+ top: calc(100% + #{$space-1});
51
+ left: 0;
52
+ right: 0;
53
+ z-index: 50;
54
+ background: $surface;
55
+ border: 1px solid $border-strong;
56
+ border-radius: $radius-md;
57
+ box-shadow: $shadow-md;
58
+ overflow: hidden;
59
+ display: grid;
60
+ }
61
+
62
+ &__input {
63
+ width: 100%;
64
+ background: $input-bg;
65
+ border: 0;
66
+ border-bottom: 1px solid $border;
67
+ color: $input-text;
68
+ padding: $space-2 $space-3;
69
+ font: inherit;
70
+ outline: none;
71
+
72
+ &::placeholder { color: $input-placeholder; }
73
+ }
74
+
75
+ &__options {
76
+ list-style: none;
77
+ margin: 0;
78
+ padding: $space-1;
79
+ display: grid;
80
+ gap: 1px;
81
+ max-height: 240px;
82
+ overflow-y: auto;
83
+ }
84
+
85
+ &__option {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: $space-2;
89
+ width: 100%;
90
+ background: none;
91
+ border: 0;
92
+ border-radius: $radius-sm;
93
+ color: $text-1;
94
+ cursor: pointer;
95
+ padding: $space-2 $space-2;
96
+ text-align: left;
97
+ font-size: $fs-13;
98
+
99
+ &:hover,
100
+ &.is-highlighted { background: $surface-2; }
101
+
102
+ &.is-active {
103
+ background: color-mix(in srgb, $accent-500 14%, transparent);
104
+ color: $accent-500;
105
+ font-weight: $k-fw-semibold;
106
+ }
107
+ }
108
+
109
+ &__label {
110
+ flex: 1;
111
+ min-width: 0;
112
+ overflow: hidden;
113
+ text-overflow: ellipsis;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ &__empty {
118
+ color: $text-3;
119
+ font-size: $fs-13;
120
+ padding: $space-2 $space-2;
121
+ }
122
+
123
+ &__more {
124
+ display: flex;
125
+ justify-content: center;
126
+
127
+ button {
128
+ background: none;
129
+ border: 0;
130
+ color: $accent-500;
131
+ cursor: pointer;
132
+ font-size: $fs-13;
133
+ padding: $space-2;
134
+
135
+ &:hover { text-decoration: underline; }
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,120 @@
1
+ @use "tokens" as *;
2
+
3
+ .tabs {
4
+ display: flex;
5
+ gap: 0;
6
+ border-bottom: 1px solid $border;
7
+ margin-bottom: $space-5;
8
+
9
+ @container content (min-width: #{$bp-sm}) {
10
+ gap: $space-1;
11
+ }
12
+
13
+ &__tab {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ gap: $space-1;
17
+ padding: $space-1 $space-2;
18
+ border: none;
19
+ background: none;
20
+ font-family: $k-font-sans;
21
+ font-size: $fs-12;
22
+ font-weight: $k-fw-medium;
23
+ color: $text-3;
24
+ cursor: pointer;
25
+ border-bottom: 2px solid transparent;
26
+ margin-bottom: -1px;
27
+ transition: color 0.15s, border-color 0.15s;
28
+ flex: 1;
29
+ min-width: 0;
30
+ justify-content: center;
31
+
32
+ // Estrecho: las tabs con icono se apilan (icono + etiqueta pequeña)
33
+ &--has-icon {
34
+ flex-direction: column;
35
+ gap: 2px;
36
+ padding: $space-1;
37
+ }
38
+
39
+ @container content (min-width: #{$bp-sm}) {
40
+ flex: none;
41
+ min-width: auto;
42
+ padding: $space-2;
43
+
44
+ &--has-icon {
45
+ flex-direction: row;
46
+ gap: $space-1;
47
+ padding: $space-2;
48
+ }
49
+ }
50
+
51
+ @container content (min-width: #{$bp-md}) {
52
+ font-size: $fs-14;
53
+ gap: $space-2;
54
+ padding: $space-2 $space-3;
55
+
56
+ &--has-icon {
57
+ gap: $space-2;
58
+ padding: $space-2 $space-3;
59
+ }
60
+ }
61
+
62
+ &:hover {
63
+ color: $text-1;
64
+ }
65
+
66
+ &--active {
67
+ color: $accent-600;
68
+ border-bottom-color: $accent-500;
69
+ }
70
+ }
71
+
72
+ &__icon {
73
+ flex-shrink: 0;
74
+ }
75
+
76
+ &__label {
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+
81
+ .tabs__tab--has-icon & {
82
+ font-size: 10px;
83
+ line-height: 1;
84
+
85
+ @container content (min-width: #{$bp-sm}) {
86
+ font-size: $fs-12;
87
+ line-height: inherit;
88
+ }
89
+
90
+ @container content (min-width: #{$bp-md}) {
91
+ font-size: $fs-14;
92
+ }
93
+ }
94
+ }
95
+
96
+ &__count {
97
+ font-size: $fs-12;
98
+ font-weight: $k-fw-semibold;
99
+ background: $surface-2;
100
+ color: $text-2;
101
+ padding: 0 $space-2;
102
+ border-radius: $radius-pill;
103
+ line-height: 1.6;
104
+ flex-shrink: 0;
105
+
106
+ // En estrecho con iconos, oculta el contador
107
+ .tabs__tab--has-icon & {
108
+ display: none;
109
+
110
+ @container content (min-width: #{$bp-sm}) {
111
+ display: inline;
112
+ }
113
+ }
114
+
115
+ .tabs__tab--active & {
116
+ background: $accent-100;
117
+ color: $accent-600;
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,34 @@
1
+ @use "tokens" as *;
2
+
3
+ .theme-selector {
4
+ display: flex;
5
+ background: $surface-2;
6
+ border-radius: $radius-sm;
7
+ padding: 2px;
8
+ gap: 2px;
9
+ }
10
+
11
+ .theme-btn {
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ width: 28px;
16
+ height: 24px;
17
+ border: none;
18
+ border-radius: $radius-sm;
19
+ background: transparent;
20
+ cursor: pointer;
21
+ font-size: $fs-13;
22
+ color: $text-3;
23
+ transition: all 0.15s ease;
24
+
25
+ &:hover {
26
+ color: $text-1;
27
+ }
28
+
29
+ &.active {
30
+ background: $surface;
31
+ color: $text-1;
32
+ box-shadow: $shadow-sm;
33
+ }
34
+ }
@@ -0,0 +1,16 @@
1
+ @use "tokens" as *;
2
+
3
+ .toast-container {
4
+ position: fixed;
5
+ top: $space-4;
6
+ right: $space-4;
7
+ z-index: 2000;
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: $space-2;
11
+ max-width: 400px;
12
+ }
13
+
14
+ .toast-enter-active, .toast-leave-active { transition: all 0.3s ease; }
15
+ .toast-enter-from, .toast-leave-to { opacity: 0; transform: translateX(20px); }
16
+ .toast-move { transition: transform 0.3s ease; }
@@ -0,0 +1,25 @@
1
+ @use "tokens" as *;
2
+
3
+ .toast {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: $space-3;
7
+ padding: $space-3 $space-4;
8
+ border-radius: $radius-md;
9
+ border: 1px solid transparent;
10
+ box-shadow: $shadow-md;
11
+ font-size: $fs-14;
12
+
13
+ &--success { background: var(--toast-success-bg); color: $success; border-color: $success; }
14
+ &--warning { background: var(--toast-warning-bg); color: $warning; border-color: $warning; }
15
+ &--danger { background: var(--toast-danger-bg); color: $danger; border-color: $danger; }
16
+ &--info { background: var(--toast-info-bg); color: $info; border-color: $info; }
17
+
18
+ &__message { flex: 1; }
19
+ &__close {
20
+ display: flex; align-items: center; justify-content: center;
21
+ width: 24px; height: 24px; border: none; border-radius: $radius-sm;
22
+ background: transparent; color: currentColor; cursor: pointer; opacity: 0.7; flex-shrink: 0;
23
+ &:hover { opacity: 1; }
24
+ }
25
+ }
@@ -0,0 +1,73 @@
1
+ @use "tokens" as *;
2
+
3
+ // Cabecera y selector de locale del TranslatableInput (portado de kontuan).
4
+ // El input/textarea en sí usa las clases `.form-field__*`.
5
+ .translatable-header {
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: space-between;
9
+
10
+ .form-field__label { margin-bottom: 0; }
11
+ }
12
+
13
+ .translatable-selector { position: relative; }
14
+
15
+ .translatable-trigger {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: $space-1;
19
+ background: $surface-2;
20
+ border: 1px solid $border;
21
+ border-radius: $radius-sm;
22
+ padding: 1px $space-2;
23
+ cursor: pointer;
24
+ transition: all 0.15s;
25
+
26
+ &:hover { background: $surface-3; border-color: $border-strong; }
27
+
28
+ &__code {
29
+ font-size: $fs-11;
30
+ font-weight: $k-fw-bold;
31
+ color: $text-1;
32
+ letter-spacing: $k-track-wide;
33
+ }
34
+ &__count { font-size: $fs-11; color: $text-3; font-variant-numeric: tabular-nums; }
35
+ &__chevron { color: $text-3; flex-shrink: 0; }
36
+ }
37
+
38
+ .translatable-dropdown {
39
+ position: absolute;
40
+ top: calc(100% + 4px);
41
+ right: 0;
42
+ background: $surface;
43
+ border: 1px solid $border;
44
+ border-radius: $radius-md;
45
+ box-shadow: $shadow-md;
46
+ min-width: 160px;
47
+ padding: 4px;
48
+ z-index: 100;
49
+ }
50
+
51
+ .translatable-option {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: $space-2;
55
+ width: 100%;
56
+ padding: $space-1 $space-3;
57
+ border: none;
58
+ border-radius: $radius-sm;
59
+ background: transparent;
60
+ font-size: $fs-13;
61
+ color: $text-2;
62
+ cursor: pointer;
63
+ text-align: left;
64
+ transition: all 0.1s;
65
+
66
+ &:hover { background: $ui-hover-bg; color: $text-1; }
67
+ &--active { color: $accent-600; font-weight: $k-fw-semibold; }
68
+ &--empty:not(&--active) { color: $text-3; }
69
+
70
+ &__code { font-weight: $k-fw-bold; font-size: $fs-12; min-width: 24px; }
71
+ &__label { flex: 1; }
72
+ &__filled { width: 6px; height: 6px; border-radius: 50%; background: $success; flex-shrink: 0; }
73
+ }
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import BlockShell from './BlockShell.vue'
3
+
4
+ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
5
+ </script>
6
+
7
+ <!-- eslint-disable vue/no-v-html -- HTML saneado en servidor (DC-09) -->
8
+ <template>
9
+ <BlockShell
10
+ :settings="settings"
11
+ class="block--cta"
12
+ :class="settings.image ? `block--image-${settings.image_position || 'top'}` : ''"
13
+ >
14
+ <div class="block__card">
15
+ <h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
16
+ <div class="block__media-layout">
17
+ <img v-if="settings.image" class="block__image" :src="String(settings.image)" alt="" />
18
+ <div class="block__cta-body">
19
+ <div v-if="settings.body" class="block__text rich-content" v-html="settings.body" />
20
+ <a
21
+ v-if="settings.button_text && settings.button_url"
22
+ class="block-button"
23
+ :class="`block-button--${settings.button_variant || 'primary'}`"
24
+ :href="String(settings.button_url)"
25
+ >
26
+ {{ settings.button_text }}
27
+ </a>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </BlockShell>
32
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import BlockShell from './BlockShell.vue'
3
+
4
+ // Preguntas frecuentes (doc 03): acordeón nativo (details/summary). Cada
5
+ // fila viene ya localizada del repeater {question, answer}.
6
+ defineProps<{
7
+ settings: {
8
+ title?: string
9
+ items?: { question?: string; answer?: string }[]
10
+ [key: string]: unknown
11
+ }
12
+ data?: Record<string, unknown>
13
+ }>()
14
+ </script>
15
+
16
+ <!-- eslint-disable vue/no-v-html -- HTML saneado en servidor (DC-09) -->
17
+ <template>
18
+ <BlockShell :settings="settings" class="block--faq">
19
+ <h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
20
+ <details v-for="(item, i) in settings.items ?? []" :key="i" class="block__faq-item">
21
+ <summary class="block__faq-question">{{ item.question }}</summary>
22
+ <div v-if="item.answer" class="block__faq-answer rich-content" v-html="item.answer" />
23
+ </details>
24
+ </BlockShell>
25
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import BlockShell from './BlockShell.vue'
3
+
4
+ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown> }>()
5
+ </script>
6
+
7
+ <template>
8
+ <BlockShell :settings="settings" class="block--header">
9
+ <h1 class="block__title">{{ settings.title }}</h1>
10
+ <p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
11
+ <img v-if="settings.image" class="block__banner" :src="String(settings.image)" alt="" />
12
+ </BlockShell>
13
+ </template>