@rocketsearch/nuxt-uappi 0.1.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.
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <div ref="root" :style="{ '--min-position': minSlider + '%', '--max-position': maxSlider + '%' }">
3
+ <div class="multi-range">
4
+ <input
5
+ v-model.number="minSlider"
6
+ class="min"
7
+ type="range"
8
+ min="0"
9
+ max="100"
10
+ aria-label="Preço mínimo"
11
+ @mouseup="apply"
12
+ @touchend="apply"
13
+ @keyup="apply"
14
+ >
15
+ <input
16
+ v-model.number="maxSlider"
17
+ class="max"
18
+ type="range"
19
+ min="0"
20
+ max="100"
21
+ aria-label="Preço máximo"
22
+ @mouseup="apply"
23
+ @touchend="apply"
24
+ @keyup="apply"
25
+ >
26
+ </div>
27
+ <div class="prices">
28
+ <span class="min" v-text="formatCurrency(minPrice)" />
29
+ <span class="max" v-text="formatCurrency(maxPrice)" />
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script>
35
+ import { formatCurrency } from '../../uappi.js';
36
+
37
+ // Port do facet-type-range da Linx: sliders 0–100 mapeados na faixa min/max do facet.
38
+ // Reset (limpar filtros) = o pai troca a :key deste componente.
39
+ const GAP = 7;
40
+
41
+ export default {
42
+ name: 'RocketSearchRange',
43
+ props: {
44
+ facet: { type: Object, required: true }
45
+ },
46
+ data () {
47
+ return { minSlider: 0, maxSlider: 100 };
48
+ },
49
+ computed: {
50
+ minPrice () {
51
+ return (this.facet.max - this.facet.min) * this.minSlider / 100 + this.facet.min;
52
+ },
53
+ maxPrice () {
54
+ return (this.facet.max - this.facet.min) * this.maxSlider / 100 + this.facet.min;
55
+ }
56
+ },
57
+ watch: {
58
+ minSlider () {
59
+ if (this.minSlider > this.maxSlider - GAP) this.minSlider = this.maxSlider - GAP;
60
+ },
61
+ maxSlider () {
62
+ if (this.maxSlider < this.minSlider + GAP) this.maxSlider = this.minSlider + GAP;
63
+ }
64
+ },
65
+ methods: {
66
+ formatCurrency,
67
+ apply () {
68
+ this.$emit('apply', {
69
+ min: Number(this.minPrice.toFixed(2)),
70
+ max: Number(this.maxPrice.toFixed(2))
71
+ });
72
+ }
73
+ }
74
+ };
75
+ </script>
@@ -0,0 +1,210 @@
1
+ /* Port de linxecommerce/2.0.0/src/scss/search.scss (form + autocomplete) — CSS nested nativo. */
2
+
3
+ .rocket-search {
4
+ &[data-button-type-mode="text"] .rocket-search__form__button {
5
+ top: 0;
6
+ right: 0;
7
+ height: 100%;
8
+ padding: 0 16px;
9
+ color: #fff;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ transform: none;
14
+ border-radius: var(--radius);
15
+ background-color: var(--primary-color);
16
+ }
17
+
18
+ /* width: o tema pode pôr display:flex no container (ex.: .header-search da Uappi). */
19
+ & .rocket-search__content {
20
+ position: relative;
21
+ width: 100%;
22
+ }
23
+
24
+ & .rocket-search__form {
25
+ position: relative;
26
+ }
27
+
28
+ & .rocket-search__form__input {
29
+ width: 100%;
30
+ padding: 0 15px;
31
+ border-radius: var(--radius);
32
+
33
+ &:focus {
34
+ outline: 0;
35
+ }
36
+ }
37
+
38
+ & .rocket-search__form__button {
39
+ position: absolute;
40
+ top: 50%;
41
+ right: 15px;
42
+ height: 100%;
43
+ padding: 0;
44
+ border: none;
45
+ transform: translateY(-50%);
46
+ background-color: transparent;
47
+ cursor: pointer;
48
+ }
49
+
50
+ & .rocket-search__terms-suggestions__title {
51
+ display: block;
52
+ margin-bottom: 15px;
53
+ color: var(--primary-color);
54
+ font-size: 14px;
55
+ font-weight: 800;
56
+ background: #fff;
57
+ }
58
+
59
+ &.rocket-search--autocomplete {
60
+ & .rocket-search__content {
61
+ position: relative;
62
+ z-index: 1;
63
+ }
64
+
65
+ & .rocket-search__form {
66
+ z-index: 13;
67
+ }
68
+
69
+ & .rocket-search__autocomplete-panel {
70
+ /* Quantos produtos cabem sem rolar — a loja pode sobrescrever no próprio CSS. */
71
+ --rs-autocomplete-visible-products: 4;
72
+ /* Linha de produto = thumb 64px + gap 14px. */
73
+ --rs-autocomplete-row-height: 78px;
74
+
75
+ position: absolute;
76
+ top: calc(100% + 10px);
77
+ left: 0;
78
+ width: 100%;
79
+ opacity: 0;
80
+ z-index: 13;
81
+ padding: 22px 18px;
82
+ visibility: hidden;
83
+ transition: ease all 0.3s;
84
+ border: 1px solid #f0f0f0;
85
+ border-radius: var(--radius);
86
+ background-color: #fff;
87
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1607843137);
88
+ overflow-y: auto;
89
+ overscroll-behavior: contain;
90
+ /* Teto do painel só de termos (linhas de texto, não de 78px). */
91
+ max-height: 70vh;
92
+
93
+ &.opened {
94
+ opacity: 1;
95
+ visibility: visible;
96
+ }
97
+
98
+ /* -14px tira o gap do último item; +44px é o padding vertical do painel. */
99
+ &:has(.rocket-search__autocomplete-products) {
100
+ max-height: calc(var(--rs-autocomplete-visible-products) * var(--rs-autocomplete-row-height) - 14px + 44px);
101
+ }
102
+
103
+ /* No mobile o teclado virtual come a tela: a viewport manda. */
104
+ @media (max-width: 1024px) {
105
+ max-height: 60vh;
106
+
107
+ &:has(.rocket-search__autocomplete-products) {
108
+ max-height: 60vh;
109
+ }
110
+ }
111
+ }
112
+
113
+ & .rocket-search__autocomplete-overlay {
114
+ position: fixed;
115
+ inset: 0;
116
+ z-index: 11;
117
+ background-color: rgba(0, 0, 0, 0.45);
118
+ }
119
+
120
+ & .rocket-search__autocomplete-section + .rocket-search__autocomplete-section {
121
+ margin-top: 20px;
122
+ }
123
+
124
+ & .rocket-search__autocomplete-terms,
125
+ & .rocket-search__autocomplete-products {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 14px;
129
+ margin: 0;
130
+ padding: 0;
131
+ list-style: none;
132
+ }
133
+
134
+ & .rocket-search__autocomplete-terms i {
135
+ display: none;
136
+ }
137
+
138
+ & .rocket-search__autocomplete-terms a {
139
+ color: #000;
140
+ font-size: 14px;
141
+ }
142
+
143
+ & .rocket-search__autocomplete-products {
144
+ & li {
145
+ margin: 0;
146
+ }
147
+
148
+ & a {
149
+ display: grid;
150
+ grid-template-columns: 64px minmax(0, 1fr);
151
+ gap: 12px;
152
+ align-items: center;
153
+ }
154
+ }
155
+
156
+ & .rocket-search__autocomplete-thumb {
157
+ width: 64px;
158
+ height: 64px;
159
+ overflow: hidden;
160
+ border-radius: 8px;
161
+ background-color: #f6f6f6;
162
+
163
+ & img,
164
+ & .rocket-search__autocomplete-thumb-placeholder {
165
+ width: 100%;
166
+ height: 100%;
167
+ display: block;
168
+ }
169
+
170
+ & img {
171
+ object-fit: cover;
172
+ }
173
+ }
174
+
175
+ & .rocket-search__autocomplete-thumb-placeholder {
176
+ background: linear-gradient(135deg, #efefef, #f8f8f8);
177
+ }
178
+
179
+ & .rocket-search__autocomplete-info {
180
+ min-width: 0;
181
+ display: flex;
182
+ flex-direction: column;
183
+ gap: 4px;
184
+ }
185
+
186
+ & .rocket-search__autocomplete-brand {
187
+ color: #8a8a8a;
188
+ font-size: 12px;
189
+ font-weight: 700;
190
+ text-transform: uppercase;
191
+ }
192
+
193
+ & .rocket-search__autocomplete-name {
194
+ color: #232626;
195
+ font-size: 14px;
196
+ line-height: 1.4;
197
+ }
198
+
199
+ & .rocket-search__autocomplete-price {
200
+ color: var(--primary-color);
201
+ font-size: 14px;
202
+ font-weight: 700;
203
+ }
204
+
205
+ & .rocket-search__autocomplete-empty {
206
+ color: #6d6d6d;
207
+ font-size: 14px;
208
+ }
209
+ }
210
+ }
@@ -0,0 +1,138 @@
1
+ /* Port do bloco de banner de linxecommerce/2.0.0/src/scss/showcase.scss — CSS nested nativo.
2
+ A caixa é reservada por aspect-ratio inline (styles.aspectRatio) ANTES da imagem
3
+ carregar — é o que evita o CLS. Não fixar height/aspect-ratio aqui. */
4
+
5
+ @keyframes rocket-search-skeleton {
6
+ 0% {
7
+ background-position: -1000px 0;
8
+ }
9
+
10
+ 100% {
11
+ background-position: 1000px 0;
12
+ }
13
+ }
14
+
15
+ .rocket-search__banner {
16
+ position: relative;
17
+ display: block;
18
+ width: 100%;
19
+ overflow: hidden;
20
+ text-decoration: none;
21
+ color: inherit;
22
+
23
+ & .rocket-search__banner__media {
24
+ display: block;
25
+ width: 100%;
26
+ /* fallback p/ browser sem aspect-ratio: a imagem define a altura */
27
+ background-color: #f2f2f2;
28
+ }
29
+
30
+ /* cover só corta quando a proporção cadastrada difere da imagem (corte intencional);
31
+ sem proporção cadastrada a caixa segue o arquivo e cover = contain. */
32
+ & .rocket-search__banner__image {
33
+ display: block;
34
+ width: 100%;
35
+ height: 100%;
36
+ object-fit: cover;
37
+ }
38
+
39
+ & .rocket-search__banner__caption {
40
+ position: absolute;
41
+ left: 0;
42
+ right: 0;
43
+ bottom: 0;
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 6px;
47
+ padding: 24px;
48
+ color: #fff;
49
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.65), rgba(0, 0, 0, 0));
50
+ pointer-events: none;
51
+ }
52
+
53
+ & .rocket-search__banner__title {
54
+ font-size: 22px;
55
+ font-weight: 700;
56
+ line-height: 1.2;
57
+ }
58
+
59
+ & .rocket-search__banner__subtitle {
60
+ font-size: 15px;
61
+ line-height: 1.35;
62
+ }
63
+
64
+ & .rocket-search__banner__cta {
65
+ align-self: flex-start;
66
+ margin-top: 8px;
67
+ padding: 9px 20px;
68
+ font-size: 14px;
69
+ font-weight: 600;
70
+ color: #111;
71
+ background-color: #fff;
72
+ border-radius: 4px;
73
+ }
74
+
75
+ @media (max-width: 768px) {
76
+ & .rocket-search__banner__caption {
77
+ padding: 16px;
78
+ }
79
+
80
+ & .rocket-search__banner__title {
81
+ font-size: 17px;
82
+ }
83
+
84
+ & .rocket-search__banner__subtitle {
85
+ font-size: 13px;
86
+ }
87
+ }
88
+ }
89
+
90
+ /* Banner vinculado a uma vitrine — respira antes dos produtos. */
91
+ .rocket-search__showcase__banner {
92
+ margin-bottom: 24px;
93
+ }
94
+
95
+ .rocket-search__banner-skeleton {
96
+ display: block;
97
+ width: 100%;
98
+ border-radius: var(--radius, 10px);
99
+ background: linear-gradient(to right, #e6e6e6 5%, #ccc 25%, #e6e6e6 35%);
100
+ background-color: #b4b4b4;
101
+ background-size: 1000px 100%;
102
+ animation: rocket-search-skeleton 3s linear infinite;
103
+
104
+ @media (prefers-reduced-motion: reduce) {
105
+ animation: none;
106
+ }
107
+ }
108
+
109
+ /* Carrossel de banners (prop carousel): scroll-snap, uma imagem por vez. */
110
+ .rocket-search__banner-swiper {
111
+ position: relative;
112
+ width: 100%;
113
+
114
+ /* Banner é full-bleed: setas afastadas da borda para não colar no canto. */
115
+ & [class*="rocket-search__swiper-button"] {
116
+ --pos: 16px;
117
+ }
118
+
119
+ & .rocket-search__banner-swiper__track {
120
+ display: flex;
121
+ margin: 0;
122
+ padding: 0;
123
+ list-style: none;
124
+ overflow-x: auto;
125
+ scroll-snap-type: x mandatory;
126
+ scrollbar-width: none;
127
+
128
+ &::-webkit-scrollbar {
129
+ display: none;
130
+ }
131
+ }
132
+
133
+ & .rocket-search__banner-swiper__slide {
134
+ flex: 0 0 100%;
135
+ min-width: 0;
136
+ scroll-snap-align: start;
137
+ }
138
+ }
@@ -0,0 +1,197 @@
1
+ /* Port de linxecommerce/2.0.0/src/scss/_common.scss — CSS nested nativo. */
2
+
3
+ .rocket-search {
4
+ --primary-color: lab(41.1016% 75.2675 -11.6122);
5
+ --secondary-color: lab(38.0808% 58.3226 -89.2658);
6
+ --product-columns: 4;
7
+ --product-columns-gap: 30px 11px;
8
+ --radius: 10px;
9
+
10
+ @media screen and (max-width: 1023px) {
11
+ --product-columns: 2;
12
+ }
13
+
14
+ & ul li,
15
+ & ol li {
16
+ margin-top: 0;
17
+ margin-bottom: 0;
18
+ }
19
+
20
+ /* Card padrão (sem slot #product do tema): busca e vitrines. */
21
+ & .rocket-search__card {
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 8px;
25
+ color: #232626;
26
+ text-decoration: none;
27
+
28
+ & img {
29
+ width: 100%;
30
+ aspect-ratio: 1;
31
+ object-fit: contain;
32
+ }
33
+
34
+ & .rocket-search__card-name {
35
+ font-size: 14px;
36
+ line-height: 1.4;
37
+ }
38
+
39
+ & .rocket-search__card-price {
40
+ color: var(--primary-color);
41
+ font-weight: 700;
42
+ }
43
+ }
44
+
45
+ /* Esqueleto de carregamento: usado pela busca e pelas vitrines. */
46
+ & .rocket-search__product-placeholder {
47
+ margin-top: 25px;
48
+ display: grid;
49
+ grid-template-columns: repeat(var(--product-columns), minmax(0, 1fr));
50
+ gap: var(--product-columns-gap);
51
+
52
+ @media screen and (max-width: 1023px) {
53
+ grid-template-columns: repeat(2, minmax(0, 1fr));
54
+ gap: 20px 7.5px;
55
+ }
56
+
57
+ & .rocket-search__product-placeholder__product-line {
58
+ aspect-ratio: 290 / 488;
59
+ margin: 0;
60
+ display: grid;
61
+ height: auto;
62
+ gap: 17px;
63
+ border-radius: var(--radius);
64
+ justify-content: space-between;
65
+ grid-auto-flow: column;
66
+ grid-template-columns: 100%;
67
+ grid-template-rows: auto 20px 13px 24px 16px 35px;
68
+
69
+ @media screen and (max-width: 1023px) {
70
+ gap: 10px;
71
+ grid-template-rows: auto 20px 13px 13px 13px 13px;
72
+ }
73
+ }
74
+ }
75
+
76
+ & .rocket-search__product-placeholder__item {
77
+ border-radius: var(--radius);
78
+ background: linear-gradient(to right, #e6e6e6 5%, #ccc 25%, #e6e6e6 35%);
79
+ background-color: #b4b4b4;
80
+ background-size: 1000px 100%;
81
+
82
+ &.item-2 {
83
+ max-width: 90%;
84
+ }
85
+
86
+ &.item-3 {
87
+ max-width: 55px;
88
+ }
89
+
90
+ &.item-4 {
91
+ max-width: 130px;
92
+ }
93
+ }
94
+ }
95
+
96
+ /* DEBUG: ?rocket-search-debug=true contorna e rotula cada slot de vitrine/banner. */
97
+ body.rocket-search-debug {
98
+ & [data-rocket-showcase-position] {
99
+ position: relative;
100
+ border: 2px solid var(--color);
101
+ padding: 60px 15px;
102
+
103
+ &::before,
104
+ &::after {
105
+ content: var(--content);
106
+ position: absolute;
107
+ left: 50%;
108
+ width: fit-content;
109
+ margin: 0 auto;
110
+ padding: 0 5px;
111
+ color: var(--color);
112
+ display: block;
113
+ font-size: 18px;
114
+ font-weight: 700;
115
+ text-align: center;
116
+ background-color: #f6f6f6;
117
+ z-index: 1;
118
+ }
119
+
120
+ &::before {
121
+ top: 0;
122
+ transform: translate(-50%, -50%);
123
+ }
124
+
125
+ &::after {
126
+ bottom: 0;
127
+ transform: translate(-50%, 50%);
128
+ }
129
+
130
+ &[data-rocket-showcase-position="hero"] {
131
+ --color: lab(52.0183% 66.11 -78.2316);
132
+ --content: "Hero";
133
+ }
134
+
135
+ &[data-rocket-showcase-position="top"] {
136
+ --color: lab(70.5521% -66.5147 45.8073);
137
+ --content: "Topo";
138
+ }
139
+
140
+ &[data-rocket-showcase-position="middle"] {
141
+ --color: lab(64.272% 57.1788 90.3583);
142
+ --content: "Meio";
143
+ }
144
+
145
+ &[data-rocket-showcase-position="bottom"] {
146
+ --color: lab(55.4814% 75.0732 48.8528);
147
+ --content: "Rodape";
148
+ }
149
+
150
+ &[data-rocket-showcase-position="prefooter"] {
151
+ --color: lab(48.295% 38.3129 -81.9673);
152
+ --content: "Antes do Footer";
153
+ }
154
+ }
155
+ }
156
+
157
+ /* Setas de carrossel (vitrine e banner) — mesmo visual da Linx. */
158
+ [class*="rocket-search__swiper-button"] {
159
+ --pos: 0;
160
+ position: absolute;
161
+ top: 50%;
162
+ width: 38px;
163
+ height: 38px;
164
+ z-index: 1;
165
+ padding: 0;
166
+ cursor: pointer;
167
+ transform: translateY(-50%);
168
+ border: 1px solid #dfdfdf;
169
+ border-radius: 100%;
170
+ background-color: #fff;
171
+
172
+ &::after {
173
+ content: "";
174
+ display: block;
175
+ width: 100%;
176
+ height: 100%;
177
+ background-size: 9px;
178
+ background-repeat: no-repeat;
179
+ background-position: center center;
180
+ }
181
+ }
182
+
183
+ .rocket-search__swiper-button-prev {
184
+ left: var(--pos);
185
+
186
+ &::after {
187
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="11.034" height="17.87" viewBox="0 0 11.034 17.87"><g transform="translate(11.034 17.87) rotate(180)"><path d="M0,2.1,6.82,8.935,0,15.77l2.1,2.1,8.934-8.936L2.1,0Z"/></g></svg>');
188
+ }
189
+ }
190
+
191
+ .rocket-search__swiper-button-next {
192
+ right: var(--pos);
193
+
194
+ &::after {
195
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="11.034" height="17.87" viewBox="0 0 11.034 17.87"><path d="M0,2.1,6.82,8.935,0,15.77l2.1,2.1,8.934-8.936L2.1,0Z"/></svg>');
196
+ }
197
+ }