wineater-bot 4.0.0 → 4.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.
- package/docs/CUSTOMIZATION.md +133 -0
- package/package.json +30 -5
- package/src/App.svelte +73 -56
- package/src/components/Footer.svelte +26 -37
- package/src/components/Header.svelte +19 -32
- package/src/components/Loader.svelte +8 -8
- package/src/components/Search.svelte +13 -24
- package/src/components/WinePopup.svelte +35 -35
- package/src/components/WineTile.svelte +56 -61
- package/src/components/ui/Action.svelte +83 -0
- package/src/services/i18n/i18n.ts +9 -1
- package/src/services/store/app-store.svelte.ts +11 -0
- package/src/services/ui-config/get-config.ts +22 -0
- package/src/services/ui-config/projects-config.ts +62 -0
- package/src/services/ui-config/types.ts +13 -0
- package/src/services/ui-config/utils.ts +12 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<div
|
|
2
|
-
<div
|
|
1
|
+
<div class="wr-Loader">
|
|
2
|
+
<div class="wr-Loader-indicator">
|
|
3
3
|
<span></span>
|
|
4
4
|
<span></span>
|
|
5
5
|
<span></span>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<style>
|
|
10
|
-
|
|
10
|
+
.wr-Loader {
|
|
11
11
|
position: absolute;
|
|
12
12
|
top: 50%;
|
|
13
13
|
left: 50%;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
text-align: center;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
.
|
|
19
|
+
.wr-Loader-indicator {
|
|
20
20
|
display: flex;
|
|
21
21
|
justify-content: center;
|
|
22
22
|
align-items: center;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
background-color: white;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.
|
|
28
|
+
.wr-Loader-indicator span {
|
|
29
29
|
display: block;
|
|
30
30
|
width: 8px;
|
|
31
31
|
height: 8px;
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
.
|
|
51
|
+
.wr-Loader-indicator span:nth-child(1) {
|
|
52
52
|
animation-delay: 0s;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.
|
|
55
|
+
.wr-Loader-indicator span:nth-child(2) {
|
|
56
56
|
animation-delay: 0.15s;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
.
|
|
59
|
+
.wr-Loader-indicator span:nth-child(3) {
|
|
60
60
|
animation-delay: 0.3s;
|
|
61
61
|
}
|
|
62
62
|
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import { appStore } from '../services/store/app-store.svelte';
|
|
4
4
|
import { getTranslation } from '../services/i18n/i18n';
|
|
5
|
+
import Action from './ui/Action.svelte';
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
searchQuery: string;
|
|
@@ -106,23 +107,23 @@
|
|
|
106
107
|
});
|
|
107
108
|
</script>
|
|
108
109
|
|
|
109
|
-
<form
|
|
110
|
-
<span class="
|
|
110
|
+
<form class="wr-Search" onsubmit={onSubmit}>
|
|
111
|
+
<span class="wr-Search-icon"></span>
|
|
111
112
|
<input
|
|
112
|
-
|
|
113
|
+
class="wr-Search-input"
|
|
113
114
|
bind:this={searchEl}
|
|
114
115
|
type="text"
|
|
115
116
|
bind:value={searchQuery}
|
|
116
117
|
onfocus={onSearchFocus}
|
|
117
118
|
onblur={onSearchBlur}
|
|
118
119
|
/>
|
|
119
|
-
<
|
|
120
|
+
<Action className="wr-Search-button" type="submit" variant="primary">
|
|
120
121
|
{getTranslation({ key: 'searchButton', lang })}
|
|
121
|
-
</
|
|
122
|
+
</Action>
|
|
122
123
|
</form>
|
|
123
124
|
|
|
124
125
|
<style>
|
|
125
|
-
|
|
126
|
+
.wr-Search {
|
|
126
127
|
position: relative;
|
|
127
128
|
margin: 12px auto;
|
|
128
129
|
padding: 0 6px;
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
border-radius: 10px;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
.
|
|
136
|
+
.wr-Search-icon {
|
|
136
137
|
left: 12px;
|
|
137
138
|
width: 24px;
|
|
138
139
|
height: 24px;
|
|
@@ -143,7 +144,7 @@
|
|
|
143
144
|
z-index: 2;
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
.wr-Search-input {
|
|
147
148
|
flex: 1 1 0%;
|
|
148
149
|
min-width: 0;
|
|
149
150
|
padding: 12px;
|
|
@@ -154,7 +155,7 @@
|
|
|
154
155
|
transition: border 0.2s ease;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
.wr-Search-input::placeholder {
|
|
158
159
|
color: var(--wineater-color);
|
|
159
160
|
opacity: 1;
|
|
160
161
|
transition:
|
|
@@ -162,27 +163,15 @@
|
|
|
162
163
|
opacity 0.3s ease-in-out;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
.wr-Search-input:focus {
|
|
166
167
|
border-color: var(--wineater-color);
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
.wr-Search-input:focus::placeholder {
|
|
170
171
|
opacity: 0.5;
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
.wr-Search-button {
|
|
174
175
|
flex: 0 0 auto;
|
|
175
|
-
background-color: var(--wineater-color);
|
|
176
|
-
color: #fff;
|
|
177
|
-
border: none;
|
|
178
|
-
border-radius: 6px;
|
|
179
|
-
padding: 8px 16px;
|
|
180
|
-
font-size: 16px;
|
|
181
|
-
cursor: pointer;
|
|
182
|
-
transition: opacity 0.2s ease;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
#wine-search-button:hover {
|
|
186
|
-
opacity: 0.8;
|
|
187
176
|
}
|
|
188
177
|
</style>
|
|
@@ -21,29 +21,29 @@
|
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
{#if mobile}
|
|
24
|
-
<div class="
|
|
25
|
-
<div class="
|
|
26
|
-
<div class="
|
|
27
|
-
<div class="
|
|
28
|
-
<div class="
|
|
29
|
-
<h2>{title}</h2>
|
|
30
|
-
<button class="
|
|
24
|
+
<div class="wr-WinePopupMobile">
|
|
25
|
+
<div class="wr-WinePopupMobile-overlay" onclick={onClose}></div>
|
|
26
|
+
<div class="wr-WinePopupMobile-wrapper">
|
|
27
|
+
<div class="wr-WinePopup js-WinePopup">
|
|
28
|
+
<div class="wr-WinePopup-header">
|
|
29
|
+
<h2 class="wr-WinePopup-title">{title}</h2>
|
|
30
|
+
<button class="wr-WinePopup-close js-WinePopupClose" onclick={onClose}>×</button>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
{#if shortDescription}
|
|
34
|
-
<p class="
|
|
34
|
+
<p class="wr-WinePopup-desc wr-WinePopup-text">{shortDescription}</p>
|
|
35
35
|
{/if}
|
|
36
36
|
|
|
37
37
|
{#if reason}
|
|
38
|
-
<div class="
|
|
38
|
+
<div class="wr-WinePopup-reason">{reason}</div>
|
|
39
39
|
{:else if pairing}
|
|
40
|
-
<p class="
|
|
40
|
+
<p class="wr-WinePopup-pairing wr-WinePopup-text">{pairing}</p>
|
|
41
41
|
{/if}
|
|
42
42
|
|
|
43
43
|
{#if tags && tags.length}
|
|
44
|
-
<div class="
|
|
44
|
+
<div class="wr-WinePopup-tags">
|
|
45
45
|
{#each tags as tag}
|
|
46
|
-
<span class="
|
|
46
|
+
<span class="wr-WinePopup-tag">{tag}</span>
|
|
47
47
|
{/each}
|
|
48
48
|
</div>
|
|
49
49
|
{/if}
|
|
@@ -51,26 +51,26 @@
|
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
{:else}
|
|
54
|
-
<div class="
|
|
55
|
-
<div class="
|
|
56
|
-
<h2>{title}</h2>
|
|
57
|
-
<button class="
|
|
54
|
+
<div class="wr-WinePopup">
|
|
55
|
+
<div class="wr-WinePopup-header">
|
|
56
|
+
<h2 class="wr-WinePopup-title">{title}</h2>
|
|
57
|
+
<button class="wr-WinePopup-close" onclick={onClose}>×</button>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
60
|
{#if shortDescription}
|
|
61
|
-
<p class="
|
|
61
|
+
<p class="wr-WinePopup-desc wr-WinePopup-text">{shortDescription}</p>
|
|
62
62
|
{/if}
|
|
63
63
|
|
|
64
64
|
{#if reason}
|
|
65
|
-
<div class="
|
|
65
|
+
<div class="wr-WinePopup-reason">{reason}</div>
|
|
66
66
|
{:else if pairing}
|
|
67
|
-
<p class="
|
|
67
|
+
<p class="wr-WinePopup-pairing wr-WinePopup-text">{pairing}</p>
|
|
68
68
|
{/if}
|
|
69
69
|
|
|
70
70
|
{#if tags && tags.length}
|
|
71
|
-
<div class="
|
|
71
|
+
<div class="wr-WinePopup-tags">
|
|
72
72
|
{#each tags as tag}
|
|
73
|
-
<span class="
|
|
73
|
+
<span class="wr-WinePopup-tag">{tag}</span>
|
|
74
74
|
{/each}
|
|
75
75
|
</div>
|
|
76
76
|
{/if}
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
{/if}
|
|
79
79
|
|
|
80
80
|
<style>
|
|
81
|
-
.
|
|
81
|
+
.wr-WinePopup {
|
|
82
82
|
position: absolute;
|
|
83
83
|
width: 100%;
|
|
84
84
|
background-color: white;
|
|
@@ -92,18 +92,18 @@
|
|
|
92
92
|
flex-direction: column;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
.
|
|
95
|
+
.wr-WinePopup-text {
|
|
96
96
|
margin: 5px 0;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
.
|
|
99
|
+
.wr-WinePopup-header {
|
|
100
100
|
display: flex;
|
|
101
101
|
justify-content: space-between;
|
|
102
102
|
align-items: flex-start;
|
|
103
103
|
gap: 12px;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
.
|
|
106
|
+
.wr-WinePopup-title {
|
|
107
107
|
font-size: 16px;
|
|
108
108
|
font-weight: 600;
|
|
109
109
|
margin: 0;
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
line-height: 1.3;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
.
|
|
114
|
+
.wr-WinePopup-close {
|
|
115
115
|
font-size: 20px;
|
|
116
116
|
font-weight: bold;
|
|
117
117
|
cursor: pointer;
|
|
@@ -121,26 +121,26 @@
|
|
|
121
121
|
line-height: 1;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
.
|
|
124
|
+
.wr-WinePopup-close:hover {
|
|
125
125
|
color: #000;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
.
|
|
128
|
+
.wr-WinePopup-desc {
|
|
129
129
|
margin-top: 8px;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
.
|
|
132
|
+
.wr-WinePopup-reason {
|
|
133
133
|
font-size: 14px;
|
|
134
134
|
font-weight: 400;
|
|
135
135
|
color: #333;
|
|
136
136
|
margin-top: 8px;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
.
|
|
139
|
+
.wr-WinePopup-pairing {
|
|
140
140
|
margin-top: 8px;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
.
|
|
143
|
+
.wr-WinePopup-tags {
|
|
144
144
|
display: flex;
|
|
145
145
|
flex-wrap: wrap;
|
|
146
146
|
gap: 8px;
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
justify-content: center;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
.
|
|
151
|
+
.wr-WinePopup-tag {
|
|
152
152
|
display: inline-block;
|
|
153
153
|
padding: 4px 8px;
|
|
154
154
|
border-radius: 12px;
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
text-transform: capitalize;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
.
|
|
162
|
+
.wr-WinePopupMobile {
|
|
163
163
|
position: fixed;
|
|
164
164
|
top: 0;
|
|
165
165
|
left: 0;
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
z-index: 10000;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
.
|
|
171
|
+
.wr-WinePopupMobile-overlay {
|
|
172
172
|
position: absolute;
|
|
173
173
|
top: 0;
|
|
174
174
|
left: 0;
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
background: rgba(0, 0, 0, 0.5);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
.
|
|
180
|
+
.wr-WinePopupMobile-wrapper {
|
|
181
181
|
position: absolute;
|
|
182
182
|
width: 100%;
|
|
183
183
|
height: 100%;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import WinePopup from './WinePopup.svelte';
|
|
5
5
|
import { onMount } from 'svelte';
|
|
6
6
|
import { appStore } from '../services/store/app-store.svelte';
|
|
7
|
+
import Action from './ui/Action.svelte';
|
|
8
|
+
|
|
7
9
|
let lang = $derived(appStore.getAppLanguage());
|
|
8
10
|
|
|
9
11
|
let {
|
|
@@ -80,42 +82,55 @@
|
|
|
80
82
|
|
|
81
83
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
82
84
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
86
|
+
<article
|
|
87
|
+
class="wr-WineTile {isAnimating ? 'ww-slide-up' : ''}"
|
|
85
88
|
onmouseenter={() => onTileEnter(wine)}
|
|
86
89
|
onmouseleave={() => onTileLeave(wine)}
|
|
87
90
|
onclick={(event) => onTileClick(event, wine)}
|
|
88
91
|
>
|
|
89
|
-
<
|
|
92
|
+
<header class="wr-WineTile-header">
|
|
90
93
|
{#if wine.image}
|
|
91
|
-
<img src={wine.image} alt={wine.title} class="
|
|
94
|
+
<img src={wine.image} alt={wine.title} class="wr-WineTile-image" />
|
|
92
95
|
{/if}
|
|
93
96
|
{#if wine.total}
|
|
94
|
-
<div class="
|
|
97
|
+
<div class="wr-WineTile-price">
|
|
95
98
|
{formatPrice(wine.total)}
|
|
96
99
|
</div>
|
|
97
100
|
{/if}
|
|
98
101
|
|
|
99
|
-
<h3 class="
|
|
100
|
-
</
|
|
102
|
+
<h3 class="wr-WineTile-title">{wine.title}</h3>
|
|
103
|
+
</header>
|
|
101
104
|
|
|
102
105
|
{#if joinParams(wine, ['type', 'country', 'region', 'grape'])}
|
|
103
|
-
<div class="
|
|
106
|
+
<div class="wr-WineTile-params">
|
|
104
107
|
{joinParams(wine, ['type', 'country', 'region', 'grape'])}
|
|
105
108
|
</div>
|
|
106
109
|
{/if}
|
|
107
110
|
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
<footer class="wr-WineTile-footer">
|
|
112
|
+
<!-- We need this wrapper for top margin -->
|
|
113
|
+
<div class="wr-WineTile-actions">
|
|
114
|
+
{#if wine.url}
|
|
115
|
+
<Action
|
|
116
|
+
href={wine.url}
|
|
117
|
+
className="wr-WineTile-action wr-WineTile-action--link js-WineTileAction"
|
|
118
|
+
target="_blank"
|
|
119
|
+
variant="outlined"
|
|
120
|
+
>
|
|
121
|
+
{getTranslation({ key: 'wineDetails.buy', lang })}
|
|
122
|
+
</Action>
|
|
123
|
+
{:else}
|
|
124
|
+
<Action
|
|
125
|
+
handleClick={() => onBuyClick(wine, index)}
|
|
126
|
+
variant="outlined"
|
|
127
|
+
className="wr-WineTile-action wr-WineTile-action--button js-WineTileAction"
|
|
128
|
+
>
|
|
129
|
+
{getTranslation({ key: 'moreInfo', lang })}
|
|
130
|
+
</Action>
|
|
131
|
+
{/if}
|
|
132
|
+
</div>
|
|
133
|
+
</footer>
|
|
119
134
|
|
|
120
135
|
{#if isPopupOpen(wine.id) && !isMobile}
|
|
121
136
|
<WinePopup
|
|
@@ -130,9 +145,9 @@
|
|
|
130
145
|
{/if}
|
|
131
146
|
|
|
132
147
|
{#if hasHoverPopupContent(wine)}
|
|
133
|
-
<div class="
|
|
148
|
+
<div class="wr-WineTilePopup {hoveredWineId === wine.id ? 'slide-in visible' : ''}">
|
|
134
149
|
{#if translatedPopup?.reason || translatedPopup?.shortDescription || wine.reason || wine.shortDescription}
|
|
135
|
-
<div class="
|
|
150
|
+
<div class="wr-WineTilePopup-reason">
|
|
136
151
|
{translatedPopup?.reason ||
|
|
137
152
|
translatedPopup?.shortDescription ||
|
|
138
153
|
wine.reason ||
|
|
@@ -140,15 +155,15 @@
|
|
|
140
155
|
</div>
|
|
141
156
|
{/if}
|
|
142
157
|
{#if translatedPopup?.tags || translatedPopup?.Tags || wine.tags}
|
|
143
|
-
<div class="
|
|
158
|
+
<div class="wr-WineTilePopup-tags">
|
|
144
159
|
{#each getTags(translatedPopup, wine) as tag}
|
|
145
|
-
<span class="
|
|
160
|
+
<span class="wr-WineTilePopup-tag">{tag}</span>
|
|
146
161
|
{/each}
|
|
147
162
|
</div>
|
|
148
163
|
{/if}
|
|
149
164
|
</div>
|
|
150
165
|
{/if}
|
|
151
|
-
</
|
|
166
|
+
</article>
|
|
152
167
|
|
|
153
168
|
{#if isPopupOpen(wine.id) && isMobile}
|
|
154
169
|
<WinePopup
|
|
@@ -163,7 +178,7 @@
|
|
|
163
178
|
{/if}
|
|
164
179
|
|
|
165
180
|
<style>
|
|
166
|
-
.
|
|
181
|
+
.wr-WineTile {
|
|
167
182
|
display: flex;
|
|
168
183
|
flex-direction: column;
|
|
169
184
|
align-items: center;
|
|
@@ -180,30 +195,30 @@
|
|
|
180
195
|
will-change: transform, opacity;
|
|
181
196
|
}
|
|
182
197
|
|
|
183
|
-
.
|
|
198
|
+
.wr-WineTile:last-child {
|
|
184
199
|
border-right: none;
|
|
185
200
|
}
|
|
186
201
|
|
|
187
|
-
.
|
|
202
|
+
.wr-WineTile.ww-slide-up {
|
|
188
203
|
transform: translateY(-28px);
|
|
189
204
|
opacity: 0;
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
.
|
|
207
|
+
.wr-WineTile-header {
|
|
193
208
|
display: flex;
|
|
194
209
|
flex-direction: column;
|
|
195
210
|
align-items: center;
|
|
196
211
|
justify-content: flex-start;
|
|
197
212
|
}
|
|
198
213
|
|
|
199
|
-
.
|
|
214
|
+
.wr-WineTile-image {
|
|
200
215
|
height: 200px;
|
|
201
216
|
object-fit: contain;
|
|
202
217
|
margin: 0 auto 16px;
|
|
203
218
|
display: block;
|
|
204
219
|
}
|
|
205
220
|
|
|
206
|
-
.
|
|
221
|
+
.wr-WineTile-title {
|
|
207
222
|
font-size: 15px;
|
|
208
223
|
font-weight: 500;
|
|
209
224
|
margin: 0 0 8px;
|
|
@@ -213,14 +228,14 @@
|
|
|
213
228
|
color: #000;
|
|
214
229
|
}
|
|
215
230
|
|
|
216
|
-
.
|
|
231
|
+
.wr-WineTile-params {
|
|
217
232
|
font-size: 14px;
|
|
218
233
|
color: #666;
|
|
219
234
|
white-space: normal;
|
|
220
235
|
text-align: center;
|
|
221
236
|
}
|
|
222
237
|
|
|
223
|
-
.
|
|
238
|
+
.wr-WineTile-footer {
|
|
224
239
|
margin-top: auto;
|
|
225
240
|
display: flex;
|
|
226
241
|
align-items: center;
|
|
@@ -228,39 +243,19 @@
|
|
|
228
243
|
justify-content: center;
|
|
229
244
|
}
|
|
230
245
|
|
|
231
|
-
.
|
|
246
|
+
.wr-WineTile-price {
|
|
232
247
|
font-size: 16px;
|
|
233
248
|
font-weight: 400;
|
|
234
249
|
margin-bottom: 8px;
|
|
235
250
|
color: #333;
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
.
|
|
253
|
+
.wr-WineTile-actions {
|
|
239
254
|
margin: 16px auto 0;
|
|
240
|
-
background-color: transparent;
|
|
241
|
-
color: var(--wineater-color);
|
|
242
|
-
border: 2px solid var(--wineater-color);
|
|
243
|
-
border-radius: 6px;
|
|
244
|
-
padding: 8px 16px;
|
|
245
|
-
font-size: 14px;
|
|
246
|
-
font-weight: 500;
|
|
247
|
-
cursor: pointer;
|
|
248
|
-
display: block;
|
|
249
|
-
text-decoration: none;
|
|
250
|
-
transition:
|
|
251
|
-
background-color 0.2s,
|
|
252
|
-
color 0.2s,
|
|
253
|
-
box-shadow 0.2s;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.buy-btn:hover {
|
|
257
|
-
box-shadow: 0 4px 8px rgba(123, 43, 235, 0.25);
|
|
258
|
-
background-color: var(--wineater-color);
|
|
259
|
-
color: #fff;
|
|
260
255
|
}
|
|
261
256
|
|
|
262
257
|
/* Hover popup inside tile */
|
|
263
|
-
.
|
|
258
|
+
.wr-WineTilePopup {
|
|
264
259
|
display: none;
|
|
265
260
|
position: absolute;
|
|
266
261
|
top: 0;
|
|
@@ -275,22 +270,22 @@
|
|
|
275
270
|
z-index: 999;
|
|
276
271
|
}
|
|
277
272
|
|
|
278
|
-
.
|
|
273
|
+
.wr-WineTilePopup.visible {
|
|
279
274
|
display: block;
|
|
280
275
|
}
|
|
281
276
|
|
|
282
|
-
.
|
|
277
|
+
.wr-WineTilePopup.slide-in {
|
|
283
278
|
animation: whPopup 0.25s ease;
|
|
284
279
|
}
|
|
285
280
|
|
|
286
|
-
.
|
|
281
|
+
.wr-WineTilePopup-reason {
|
|
287
282
|
font-size: 14px;
|
|
288
283
|
font-weight: 400;
|
|
289
284
|
color: #333;
|
|
290
285
|
margin-bottom: 8px;
|
|
291
286
|
}
|
|
292
287
|
|
|
293
|
-
.
|
|
288
|
+
.wr-WineTilePopup-tags {
|
|
294
289
|
display: flex;
|
|
295
290
|
flex-wrap: wrap;
|
|
296
291
|
gap: 8px;
|
|
@@ -298,7 +293,7 @@
|
|
|
298
293
|
justify-content: center;
|
|
299
294
|
}
|
|
300
295
|
|
|
301
|
-
.
|
|
296
|
+
.wr-WineTilePopup-tag {
|
|
302
297
|
display: inline-block;
|
|
303
298
|
padding: 4px 8px;
|
|
304
299
|
border-radius: 12px;
|
|
@@ -321,7 +316,7 @@
|
|
|
321
316
|
}
|
|
322
317
|
|
|
323
318
|
@media screen and (max-width: 768px) {
|
|
324
|
-
.
|
|
319
|
+
.wr-WineTile {
|
|
325
320
|
flex: 0 0 auto;
|
|
326
321
|
scroll-snap-align: center;
|
|
327
322
|
width: 240px;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { appStore } from '../../services/store/app-store.svelte';
|
|
3
|
+
|
|
4
|
+
type ActionProps = {
|
|
5
|
+
type?: 'button' | 'submit' | 'reset';
|
|
6
|
+
handleClick?: () => void;
|
|
7
|
+
variant?: 'primary' | 'outlined' | 'text';
|
|
8
|
+
className?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
href?: string;
|
|
11
|
+
target?: string;
|
|
12
|
+
};
|
|
13
|
+
let {
|
|
14
|
+
type = 'button',
|
|
15
|
+
handleClick,
|
|
16
|
+
variant = 'primary',
|
|
17
|
+
className,
|
|
18
|
+
id,
|
|
19
|
+
href,
|
|
20
|
+
target = '_self'
|
|
21
|
+
}: ActionProps = $props();
|
|
22
|
+
let appUiConfig = appStore.getUiConfig();
|
|
23
|
+
let calculatedClassName =
|
|
24
|
+
`wr-Action ${variant} ${className || ''} ${appUiConfig.buttonUppercase ? 'wr-Action--uppercase' : ''}`.trim();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if href}
|
|
28
|
+
<a class={calculatedClassName} {href} {id} {target} rel="noopener noreferrer">
|
|
29
|
+
<slot></slot>
|
|
30
|
+
</a>
|
|
31
|
+
{:else}
|
|
32
|
+
<button class={calculatedClassName} {type} onclick={handleClick} {id}>
|
|
33
|
+
<slot></slot>
|
|
34
|
+
</button>
|
|
35
|
+
{/if}
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
.wr-Action {
|
|
39
|
+
background: transparent;
|
|
40
|
+
border: none;
|
|
41
|
+
font-family: inherit;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
display: block;
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
transition:
|
|
46
|
+
background 0.2s,
|
|
47
|
+
color 0.2s,
|
|
48
|
+
box-shadow 0.2s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.wr-Action.primary {
|
|
52
|
+
background: var(--wineater-color);
|
|
53
|
+
color: #fff;
|
|
54
|
+
border-radius: 6px;
|
|
55
|
+
padding: 8px 16px;
|
|
56
|
+
font-size: 14px;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
|
|
59
|
+
&:hover {
|
|
60
|
+
opacity: 0.8;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.wr-Action.outlined {
|
|
65
|
+
background: transparent;
|
|
66
|
+
color: var(--wineater-color);
|
|
67
|
+
border: 2px solid var(--wineater-color);
|
|
68
|
+
border-radius: 6px;
|
|
69
|
+
padding: 8px 16px;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
|
|
73
|
+
&:hover {
|
|
74
|
+
box-shadow: 0 4px 8px rgba(123, 43, 235, 0.25);
|
|
75
|
+
background-color: var(--wineater-color);
|
|
76
|
+
color: #fff;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.wr-Action--uppercase {
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import translationsJson from './translations.json';
|
|
2
|
+
// TODO: use DI to get custom translations to avoid high coupling
|
|
3
|
+
import { appStore } from '../store/app-store.svelte';
|
|
2
4
|
|
|
3
5
|
export type LanguageCode = keyof typeof translationsJson;
|
|
4
6
|
|
|
@@ -38,7 +40,13 @@ export function formatCurrency(amount: number, currencyCode: CurrencyCode, local
|
|
|
38
40
|
// yes: "product.description.short": "..."
|
|
39
41
|
// no: "product": { "description": { "short": "..." } }
|
|
40
42
|
export function getTranslation({ key, lang }): string {
|
|
41
|
-
const translations = translationsJson as Record<LanguageCode, Record<string, string
|
|
43
|
+
const translations = translationsJson as Partial<Record<LanguageCode, Record<string, string>>>;
|
|
44
|
+
const appState = appStore.getUiConfig();
|
|
45
|
+
const projectTranslations = appState?.translations || {};
|
|
46
|
+
if (projectTranslations[lang] && projectTranslations[lang][key]) {
|
|
47
|
+
return projectTranslations[lang][key];
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
if (translations[lang] && translations[lang][key]) {
|
|
43
51
|
return translations[lang][key];
|
|
44
52
|
}
|