@repobit/dex-system-design 0.11.0 → 0.12.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 (105) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +2 -2
  3. package/src/assets/icons/Identity_protection.png +0 -0
  4. package/src/assets/icons/arrow_down.png +0 -0
  5. package/src/assets/icons/arrow_up.png +0 -0
  6. package/src/assets/icons/device_protection.png +0 -0
  7. package/src/assets/icons/financial_insurance.png +0 -0
  8. package/src/assets/icons/privacy_protection.png +0 -0
  9. package/src/assets/icons/user_guide.png +0 -0
  10. package/src/components/Button/Button.js +19 -16
  11. package/src/components/Button/button.css.js +18 -16
  12. package/src/components/Button/icons.js +8 -8
  13. package/src/components/FAQ/faq.css.js +48 -49
  14. package/src/components/FAQ/faq.js +0 -86
  15. package/src/components/Input/Input.js +68 -6
  16. package/src/components/Input/custom-form.stories.js +88 -0
  17. package/src/components/Input/input-clipboard.css.js +168 -0
  18. package/src/components/Input/input-clipboard.js +137 -0
  19. package/src/components/Input/input.css.js +122 -42
  20. package/src/components/accordion/accordion-bg.css.js +117 -0
  21. package/src/components/accordion/accordion-bg.js +80 -0
  22. package/src/components/accordion/accordion-no-bg.css.js +114 -0
  23. package/src/components/accordion/accordion-no-bg.js +80 -0
  24. package/src/components/accordion/accordion.css.js +88 -0
  25. package/src/components/accordion/accordion.js +81 -0
  26. package/src/components/anchor/anchor-nav.css.js +15 -15
  27. package/src/components/anchor/anchor-nav.js +0 -1
  28. package/src/components/anchor/anchor.stories.js +10 -13
  29. package/src/components/badge/badge.css.js +6 -6
  30. package/src/components/badge/badge.js +1 -2
  31. package/src/components/badge/badge.stories.js +6 -6
  32. package/src/components/carousel/carousel.css.js +60 -60
  33. package/src/components/carousel/carousel.js +26 -30
  34. package/src/components/carousel/carousel.stories.js +55 -55
  35. package/src/components/checkbox/checkbox.css.js +14 -14
  36. package/src/components/divider/divider-horizontal.js +19 -14
  37. package/src/components/divider/divider-vertical.js +23 -14
  38. package/src/components/divider/divider.css.js +19 -0
  39. package/src/components/dropdown/dropdown.css.js +138 -0
  40. package/src/components/dropdown/dropdown.js +111 -0
  41. package/src/components/footer/footer-links-group.css.js +42 -0
  42. package/src/components/footer/footer-links-group.js +25 -0
  43. package/src/components/footer/footer-lp.css.js +625 -0
  44. package/src/components/footer/footer-lp.js +368 -0
  45. package/src/components/footer/footer-lp.stories.js +69 -0
  46. package/src/components/footer/footer-nav-menu.css.js +24 -0
  47. package/src/components/footer/footer-nav-menu.js +36 -0
  48. package/src/components/footer/footer.css.js +625 -0
  49. package/src/components/footer/footer.js +465 -0
  50. package/src/components/footer/footer.stories.js +60 -0
  51. package/src/components/footer/localeMap.js +1 -0
  52. package/src/components/grid/grid.css.js +38 -0
  53. package/src/components/grid/grid.js +55 -0
  54. package/src/components/header/header.css.js +81 -52
  55. package/src/components/header/header.js +19 -19
  56. package/src/components/highlight/highlight.css.js +32 -22
  57. package/src/components/highlight/highlight.js +15 -4
  58. package/src/components/highlight/highlight.stories.js +4 -4
  59. package/src/components/light-carousel/light-carousel-simple.css.js +183 -0
  60. package/src/components/light-carousel/light-carousel-simple.js +73 -0
  61. package/src/components/light-carousel/light-carousel.css.js +50 -31
  62. package/src/components/light-carousel/light-carousel.js +14 -57
  63. package/src/components/light-carousel/light-carousel.stories.js +51 -10
  64. package/src/components/link/link.css.js +41 -0
  65. package/src/components/link/link.js +54 -0
  66. package/src/components/modal/modal.css.js +75 -0
  67. package/src/components/modal/modal.js +41 -0
  68. package/src/components/modal/modal.stories.js +40 -0
  69. package/src/components/paragraph/paragraph.css.js +1 -3
  70. package/src/components/pricing-cards/new-pricing-card.js +30 -0
  71. package/src/components/pricing-cards/new-pricing.css.js +58 -0
  72. package/src/components/pricing-cards/pricing-card-actions.css.js +16 -0
  73. package/src/components/pricing-cards/pricing-card-actions.js +20 -0
  74. package/src/components/pricing-cards/pricing-card-container.css.js +41 -0
  75. package/src/components/pricing-cards/pricing-card-container.js +31 -0
  76. package/src/components/pricing-cards/pricing-card-header.css.js +70 -0
  77. package/src/components/pricing-cards/pricing-card-header.js +46 -0
  78. package/src/components/pricing-cards/pricing-card-pricing.css.js +63 -0
  79. package/src/components/pricing-cards/pricing-card-pricing.js +101 -0
  80. package/src/components/pricing-cards/pricing-card-show-more.css.js +22 -0
  81. package/src/components/pricing-cards/pricing-card-show-more.js +33 -0
  82. package/src/components/pricing-cards/pricing-card.css.js +91 -89
  83. package/src/components/pricing-cards/pricing-card.js +13 -16
  84. package/src/components/pricing-cards/pricing-feature-item.css.js +18 -0
  85. package/src/components/pricing-cards/pricing-feature-item.js +14 -0
  86. package/src/components/radio/radio.css.js +18 -18
  87. package/src/components/radio/radio.js +1 -0
  88. package/src/components/tabs/tabs.css.js +21 -11
  89. package/src/components/tabs/tabs.js +24 -18
  90. package/src/components/termsOfUse/terms.css.js +6 -6
  91. package/src/components/termsOfUse/terms.js +0 -1
  92. package/src/stories/demo.stories.js +271 -0
  93. package/src/tokens/colors.js +10 -10
  94. package/src/tokens/fonts.stories.js +5 -5
  95. package/src/tokens/layout.css +4 -3
  96. package/src/tokens/new-tokens.css +698 -0
  97. package/src/tokens/spacing.stories.js +1 -1
  98. package/src/tokens/tokens.css +1063 -0
  99. package/src/tokens/tokens.stories.js +3 -3
  100. package/src/tokens/typography.css.js +0 -4
  101. package/src/tokens/typography.stories.js +2 -2
  102. package/src/components/Input/index.js +0 -0
  103. package/src/components/highlight/highlight-s.css.js +0 -88
  104. package/src/components/highlight/highlight-s.js +0 -35
  105. package/src/components/highlight/highlight-s.stories.js +0 -22
@@ -0,0 +1,465 @@
1
+ import { LitElement, html } from 'lit';
2
+ import '../../components/divider/divider-horizontal.js';
3
+ import '../../components/divider/divider-vertical.js';
4
+ import '../../components/footer/footer-links-group.js';
5
+ import '../../components/footer/footer-nav-menu.js';
6
+ import '../../components/grid/grid.js';
7
+ import { tokens } from "../../tokens/tokens.js";
8
+ import footerCSS from './footer.css.js';
9
+ import { localeMap } from './localeMap.js';
10
+
11
+ export class BdFooter extends LitElement {
12
+ static properties = {
13
+ _countriesOpen : { state: true },
14
+ selectedCountry : { state: true },
15
+ currentLocale : { type: String },
16
+ maxColumnsPerRow : { type: Number },
17
+ _isMobile : { state: true },
18
+ _countriesContainer: { state: true }
19
+ };
20
+
21
+ constructor() {
22
+ super();
23
+ this._countriesOpen = false;
24
+ this.maxColumnsPerRow = 2;
25
+ this.currentLocale = 'en';
26
+ this.selectedCountry = 'Choose your country';
27
+ this._isMobile = window.innerWidth <= 768;
28
+ this._countriesContainer = null;
29
+ console.log('Constructor - isMobile:', this._isMobile, 'Window width:', window.innerWidth);
30
+ }
31
+
32
+ connectedCallback() {
33
+ super.connectedCallback();
34
+ this._initLocaleFromUrl();
35
+ window.addEventListener('resize', this._handleResize.bind(this));
36
+ console.log('Connected - isMobile:', this._isMobile);
37
+ }
38
+
39
+ disconnectedCallback() {
40
+ super.disconnectedCallback();
41
+ window.removeEventListener('resize', this._handleResize.bind(this));
42
+ }
43
+
44
+ firstUpdated() {
45
+ super.firstUpdated();
46
+ this._updateNavLinks();
47
+ // Obține referința la containerul cu țări
48
+ // this._countriesContainer = this.shadowRoot.querySelector('.footer-countries-container');
49
+ }
50
+
51
+ updated(changedProps) {
52
+ if (changedProps.has('currentLocale')) {
53
+ this._updateNavLinks();
54
+ }
55
+
56
+ // Dacă s-a deschis lista de țări, fac scroll smooth către ea
57
+ if (changedProps.has('_countriesOpen') && this._countriesOpen) {
58
+ // Obține referința la containerul cu țări doar când este deschis
59
+ this._countriesContainer = this.shadowRoot.querySelector('.footer-countries-container');
60
+ this._scrollToCountries();
61
+ }
62
+ }
63
+
64
+ _scrollToCountries() {
65
+ // Obține referința la container doar atunci când este necesar
66
+ const container = this.shadowRoot.querySelector('.footer-countries-container');
67
+ if (container) {
68
+ // Așteaptă ca render-ul să se finalizeze
69
+ setTimeout(() => {
70
+ container.scrollIntoView({
71
+ behavior: 'smooth',
72
+ block : 'start'
73
+ });
74
+ }, 100);
75
+ }
76
+ }
77
+
78
+ _scrollToTop() {
79
+ // Scroll smooth către începutul footer-ului
80
+ this.scrollIntoView({
81
+ behavior: 'smooth',
82
+ block : 'start'
83
+ });
84
+ }
85
+
86
+ _handleResize() {
87
+ this._isMobile = window.innerWidth <= 768;
88
+ const newIsMobile = window.innerWidth <= 768;
89
+ console.log('Resize - Old isMobile:', this._isMobile, 'New isMobile:', newIsMobile, 'Window width:', window.innerWidth);
90
+
91
+ if (this._isMobile !== newIsMobile) {
92
+ this._isMobile = newIsMobile;
93
+ console.log('isMobile changed to:', this._isMobile);
94
+ }
95
+ }
96
+
97
+ _initLocaleFromUrl() {
98
+ const urlLocale = window.location.pathname.split('/')[1]?.toLowerCase();
99
+
100
+ if (!urlLocale) {
101
+ this._updateNavLinks();
102
+ return;
103
+ }
104
+
105
+ // Creează o versiune normalized a localeMap cu cheile lowercase
106
+ const normalizedLocaleMap = {};
107
+ Object.entries(localeMap).forEach(([key, value]) => {
108
+ normalizedLocaleMap[key.toLowerCase()] = value;
109
+ });
110
+
111
+ // Încearcă mai întâi potrivire exactă
112
+ let country = normalizedLocaleMap[urlLocale];
113
+
114
+ // Dacă nu găsești potrivire exactă, încearcă să găsești cea mai bună potrivire
115
+ if (!country && urlLocale) {
116
+ const localeParts = urlLocale.split('-');
117
+ const language = localeParts[0];
118
+ const region = localeParts[1];
119
+
120
+ // Caută o potrivire care să înceapă cu același limbaj
121
+ const possibleMatches = Object.entries(normalizedLocaleMap)
122
+ .filter(([key]) => key.startsWith(language));
123
+
124
+ if (possibleMatches.length > 0) {
125
+ // Preferă potrivirea cu aceeași regiune dacă există
126
+ const exactRegionMatch = possibleMatches.find(([key]) => {
127
+ const keyParts = key.split('-');
128
+ const keyRegion = keyParts[1];
129
+ const regionMatch = region && keyRegion === region;
130
+ const languageOnlyMatch = !region && keyParts.length === 1;
131
+
132
+ return regionMatch || languageOnlyMatch;
133
+ });
134
+
135
+ if (exactRegionMatch) {
136
+ country = exactRegionMatch[1];
137
+ // Recuperează cheia originală din localeMap
138
+ const originalKey = Object.keys(localeMap).find(k =>
139
+ k.toLowerCase() === exactRegionMatch[0]
140
+ );
141
+ this.currentLocale = originalKey || exactRegionMatch[0];
142
+ } else {
143
+ // Altfel, ia prima potrivire
144
+ country = possibleMatches[0][1];
145
+ const originalKey = Object.keys(localeMap).find(k =>
146
+ k.toLowerCase() === possibleMatches[0][0]
147
+ );
148
+ this.currentLocale = originalKey || possibleMatches[0][0];
149
+ }
150
+ }
151
+ }
152
+
153
+ // Setează valorile pentru țară și locale
154
+ if (country) {
155
+ this.selectedCountry = country;
156
+ }
157
+
158
+ if (this.currentLocale !== urlLocale) {
159
+ const originalKey = Object.keys(localeMap).find(k =>
160
+ k.toLowerCase() === urlLocale
161
+ );
162
+ this.currentLocale = originalKey || urlLocale;
163
+ }
164
+
165
+ // Actualizează linkurile de navigație
166
+ this._updateNavLinks();
167
+ }
168
+
169
+ _toggleCountries() {
170
+ this._countriesOpen = !this._countriesOpen;
171
+
172
+ // Dacă se închide dropdown-ul, fac scroll înapoi sus
173
+ if (!this._countriesOpen) {
174
+ this._scrollToTop();
175
+ }
176
+ }
177
+
178
+ _closeCountries() {
179
+ this._countriesOpen = false;
180
+
181
+ // Doar pentru desktop, facem scroll înapoi sus
182
+ if (!this._isMobile) {
183
+ this._scrollToTop();
184
+ }
185
+ }
186
+
187
+ _handleCountryClick(e, countryLabel, locale) {
188
+ e.preventDefault();
189
+ this._selectCountry(countryLabel);
190
+
191
+ // Redirect către pagina cu noul locale
192
+ const newPath = `/${locale.toLowerCase()}/`;
193
+ window.location.href = newPath;
194
+ }
195
+
196
+ _selectCountry(countryLabel) {
197
+ this.selectedCountry = countryLabel;
198
+ this._countriesOpen = false;
199
+
200
+ // Găsește locale-ul corespunzător pentru țara selectată
201
+ const localeEntry = Object.entries(localeMap).find(
202
+ ([, country]) => country === countryLabel
203
+ );
204
+
205
+ if (localeEntry) {
206
+ this.currentLocale = localeEntry[0];
207
+ }
208
+
209
+ this._updateNavLinks();
210
+ this._scrollToTop();
211
+ }
212
+
213
+ getCountriesList() {
214
+ return Object.entries(localeMap).map(([locale, country]) => ({
215
+ locale,
216
+ label: country
217
+ }));
218
+ }
219
+
220
+ renderQuickLinksSection(slotName) {
221
+ const assignedElements = this.shadowRoot?.querySelector(`slot[name="${slotName}"]`)?.assignedElements({ flatten: true }) || [];
222
+ if (assignedElements.length === 0) return null;
223
+
224
+ const maxCols = this.maxColumnsPerRow || 3;
225
+ const rows = Math.ceil(assignedElements.length / maxCols);
226
+
227
+ const groupedRows = Array.from({ length: rows }, (_, rowIndex) => {
228
+ const start = rowIndex * maxCols;
229
+ return assignedElements.slice(start, start + maxCols);
230
+ });
231
+
232
+ return html`
233
+ <div class="quick-links-section" style="display: flex; flex-direction: column; gap: 1rem;">
234
+ ${groupedRows.map(row => html`
235
+ <div style="display: grid; grid-template-columns: repeat(${row.length}, 1fr); gap: 1rem;">
236
+ ${row.map(el => html`${el}`)}
237
+ </div>
238
+ `)}
239
+ </div>
240
+ `;
241
+ }
242
+
243
+ get locale() {
244
+ const val = (this.currentLocale || 'en').toLowerCase();
245
+ return val;
246
+ }
247
+
248
+ _updateSlotStyles() {
249
+ const isMobile = window.innerWidth <= 768;
250
+ const slot = this.shadowRoot.querySelector('slot[name="nav"]');
251
+ const assignedElements = slot.assignedElements();
252
+
253
+ assignedElements.forEach(element => {
254
+ if (isMobile) {
255
+ element.style.color = '#151719';
256
+ element.style.setProperty('color', '#151719', 'important');
257
+ } else {
258
+ element.style.color = '';
259
+ element.style.removeProperty('color');
260
+ }
261
+ });
262
+ }
263
+
264
+ _updateNavLinks() {
265
+ // Actualizează linkurile care sunt acum în shadow DOM
266
+ const links = this.shadowRoot.querySelectorAll('.footer-nav-main a');
267
+ const linkPaths = ['consumer', 'small-business', 'business', 'partners'];
268
+ const currentLocale = this.locale;
269
+
270
+ links.forEach((link, index) => {
271
+ if (index < linkPaths.length) {
272
+ link.href = `/${currentLocale}/${linkPaths[index]}/`;
273
+ }
274
+ });
275
+ }
276
+
277
+ render() {
278
+ const year = new Date().getFullYear();
279
+ const showAnpc = this.locale.startsWith('ro');
280
+ const showImpressum = this.locale === 'de-de' || this.locale === 'de';
281
+ const countriesList = this.getCountriesList();
282
+ console.log('Render - isMobile:', this._isMobile, 'Window width:', window.innerWidth);
283
+
284
+ const masterbrandUrl = this._isMobile
285
+ ? '/assets/Bitdefender-Masterbrand_mobile.png'
286
+ : '/assets/Bitdefender-Masterbrand.png';
287
+
288
+ const anpcUrl = this._isMobile
289
+ ? '/assets/anpc_mobile.png'
290
+ : '/assets/anpc.png';
291
+
292
+ const logoUrl = '/assets/BD_logo.png';
293
+ const facebookIcon = '/assets/facebook_vector.png';
294
+ const xIcon = '/assets/x_vector.png';
295
+ const linkedinIcon = '/assets/linkedin_vector.png';
296
+ const youtubeIcon = '/assets/youtube_vector.png';
297
+ const instagramIcon = '/assets/instagram_vector.png';
298
+ const tiktokIcon = '/assets/tiktok_vector.png';
299
+ const leadingIcon = '/assets/leading.png';
300
+ const closeIcon = '/assets/close_icon.png'; // Adaugă un icon pentru butonul de închidere
301
+
302
+ const chunkSize = Math.ceil(countriesList.length / 3);
303
+ const countryChunks = [
304
+ countriesList.slice(0, chunkSize),
305
+ countriesList.slice(chunkSize, chunkSize * 2),
306
+ countriesList.slice(chunkSize * 2)
307
+
308
+ ];
309
+
310
+ return html`
311
+ <div class="footer-top-bleed">
312
+ <div class="container">
313
+ <div class="footer-top">
314
+ <div class="footer-logo">
315
+ <slot name="logo">
316
+ <span><img src="${logoUrl}" alt="Bitdefender Logo"></span>
317
+ </slot>
318
+ </div>
319
+ <div class="footer-top-right">
320
+ <slot name="top-right">
321
+ <div slot="top-right">
322
+ <span slot="logo">
323
+ <img src="${masterbrandUrl}" alt="Bitdefender Masterbrand">
324
+ </span>
325
+ </div>
326
+ </slot>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+
332
+ <footer class="container">
333
+ <bd-footer-nav bold>
334
+ <div class="footer-nav-main">
335
+ <a href="/${this.locale}/consumer/">For Consumer</a>
336
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
337
+ <a href="/${this.locale}/small-business/">For Small Business</a>
338
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
339
+ <a href="/${this.locale}/business/">For Enterprise</a>
340
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
341
+ <a href="/${this.locale}/partners/">For Partners</a>
342
+ </div>
343
+ </bd-footer-nav>
344
+
345
+ <bd-divider-horizontal width="100%" color="white" opacity="0.25"></bd-divider-horizontal>
346
+
347
+ <div class="footer-links">
348
+ <div class="footer-links-left">
349
+ <bd-footer-links-group slot="quick-links" title="Quick links">
350
+ <a href="https://central.bitdefender.com/?adobe_mc=TS%3D1755696435%7CMCMID%3D63411747395182077784244429321314993670%7CMCORGID%3D0E920C0F53DA9E9B0A490D45%2540AdobeOrg">Bitdefender Central</a>
351
+ <a href="https://gravityzone.bitdefender.com/?adobe_mc=TS%3D1755696435%7CMCMID%3D63411747395182077784244429321314993670%7CMCORGID%3D0E920C0F53DA9E9B0A490D45%2540AdobeOrg">GravityZone Cloud Control Center</a>
352
+ <a href="https://www.bitdefender.com/en-us/cyberpedia/">Bitdefender Cyberpedia</a>
353
+ <a href="https://pan.bitdefender.com/partners/save/?adobe_mc=TS%3D1755696435%7CMCMID%3D63411747395182077784244429321314993670%7CMCORGID%3D0E920C0F53DA9E9B0A490D45%2540AdobeOrg">Partner Advantage Network Portal</a>
354
+ <a href="https://brand.bitdefender.com/point/en/bitdefenderhub/component/default/104804?adobe_mc=TS%3D1755696435%7CMCMID%3D63411747395182077784244429321314993670%7CMCORGID%3D0E920C0F53DA9E9B0A490D45%2540AdobeOrg">Brand Portal</a>
355
+ </bd-footer-links-group>
356
+
357
+ <bd-footer-links-group slot="quick-links">
358
+ <a href="https://www.bitdefender.com/consumer/support/">Support for Home Products</a>
359
+ <a href="https://www.bitdefender.com/business/support/">Support for Business Products</a>
360
+ <a href="/${this.locale}/company/">Investors</a>
361
+ <a href="/${this.locale}/company/job-opportunities/">Careers</a>
362
+ <a href="/${this.locale}/business/infozone/">InfoZone</a>
363
+ </bd-footer-links-group>
364
+ </div>
365
+
366
+ <div class="footer-links-right social-icons">
367
+ <a href="https://www.facebook.com/bitdefender"><img src="${facebookIcon}" alt="Facebook"></a>
368
+ <a href="https://twitter.com/bitdefender"><img src="${xIcon}" alt="X"></a>
369
+ <a href="https://www.linkedin.com/company/bitdefender"><img src="${linkedinIcon}" alt="LinkedIn"></a>
370
+ <a href="https://www.youtube.com/c/Bitdefender"><img src="${youtubeIcon}" alt="YouTube"></a>
371
+ <a href="https://www.instagram.com/bitdefender/"><img src="${instagramIcon}" alt="Instagram"></a>
372
+ <a href="https://www.tiktok.com/@bitdefender"><img src="${tiktokIcon}" alt="TikTok"></a>
373
+ </div>
374
+ </div>
375
+
376
+ <bd-divider-horizontal width="100%" color="white" opacity="0.25"></bd-divider-horizontal>
377
+
378
+ <div class="footer-middle-line">
379
+ <bd-footer-nav>
380
+ <div class="footer-nav-main">
381
+ <a href="legal/">Legal Information</a>
382
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
383
+ <a href="site/view/legal-privacy-policy-for-bitdefender-websites/">Privacy Policy</a>
384
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
385
+ <a href="sitemap/">Site Map</a>
386
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
387
+ <a href="company/">Company</a>
388
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
389
+ <a href="https://www.bitdefender.com/consumer/support/">Contact Us</a>
390
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
391
+ <a href="#">Privacy Settings</a>
392
+ ${showImpressum
393
+ ? html`
394
+ <bd-divider-vertical color="white" height="18px"></bd-divider-vertical>
395
+ <a href="#" class="impressum-link">Impressum</a>
396
+ `
397
+ : ''}
398
+ </div>
399
+ </bd-footer-nav>
400
+ <div class="footer-address">
401
+ <slot name="address">
402
+ <div slot="address">
403
+ 111 W. Houston Street, Suite 2105, Frost Tower Building<br />
404
+ San Antonio, Texas 78205
405
+ </div>
406
+ </slot>
407
+ </div>
408
+ </div>
409
+
410
+ <bd-divider-horizontal width="100%" color="white" opacity="0.25"></bd-divider-horizontal>
411
+
412
+ <div class="footer-extra">
413
+ <div class="footer-copy">Copyright © 1997 - ${year} Bitdefender</div>
414
+ ${showAnpc
415
+ ? html`
416
+ <div class="footer-anpc">
417
+ <slot name="anpc">
418
+ <div slot="anpc">
419
+ <img src="${anpcUrl}" alt="Certificat" />
420
+ </div>
421
+ </slot>
422
+ </div>
423
+ `
424
+ : null}
425
+ <div class="footer-countries-toggle">
426
+ <button class="country-toggle" @click="${this._toggleCountries}">
427
+ <img src="${leadingIcon}" alt="Leading icon">
428
+ ${this.selectedCountry}
429
+ <span class="arrow">
430
+ ${this._countriesOpen
431
+ ? html`<img src="/assets/arrow_up.png" alt="Arrow up">`
432
+ : html`<img src="/assets/arrow_down.png" alt="Arrow down">`}
433
+ </span>
434
+ </button>
435
+ </div>
436
+ </div>
437
+
438
+ ${this._countriesOpen
439
+ ? html`
440
+ <div class="footer-countries-container">
441
+ <button class="close-countries-button" @click="${this._closeCountries}">
442
+ <img src="${closeIcon}" alt="Close countries list">
443
+ </button>
444
+ ${countryChunks.map(chunk => html`
445
+ <bd-footer-links-group slot="countries" title="${chunk === countryChunks[0] ? 'Choose your country' : ''}">
446
+ ${chunk.map(c => html`
447
+ <a href="/${c.locale.toLowerCase()}/"
448
+ data-locale="${c.locale}"
449
+ class="${this.selectedCountry === c.label ? 'selected' : ''}"
450
+ @click="${(e) => this._handleCountryClick(e, c.label, c.locale)}">
451
+ ${c.label}
452
+ </a>
453
+ `)}
454
+ </bd-footer-links-group>
455
+ `)}
456
+ </div>
457
+ `
458
+ : null}
459
+ </footer>
460
+ `;
461
+ }
462
+ static styles = [tokens, footerCSS];
463
+ }
464
+
465
+ customElements.define('bd-footer', BdFooter);
@@ -0,0 +1,60 @@
1
+ // BdFooter.stories.js
2
+ import { html } from 'lit';
3
+ import './footer.js';
4
+
5
+ export default {
6
+ title : 'Components/Footer',
7
+ component: 'bd-footer',
8
+ argTypes : {
9
+ currentLocale : { control: 'text' },
10
+ selectedCountry : { control: 'text' },
11
+ maxColumnsPerRow: { control: { type: 'number', min: 1, max: 4 } },
12
+ quickLinks1 : { control: 'object' },
13
+ quickLinks2 : { control: 'object' },
14
+ address : { control: 'text' }
15
+ }
16
+ };
17
+
18
+ const Template = ({ currentLocale, selectedCountry, maxColumnsPerRow, quickLinks1, quickLinks2, address }) => {
19
+ return html`
20
+ <bd-footer
21
+ .currentLocale=${currentLocale}
22
+ .selectedCountry=${selectedCountry}
23
+ .maxColumnsPerRow=${maxColumnsPerRow}
24
+ >
25
+ <span slot="logo"><img src="/assets/BD_logo.png" alt="Bitdefender Logo"></span>
26
+
27
+ <bd-footer-links-group slot="quick-links" title="Quick Links 1">
28
+ ${quickLinks1.map(
29
+ link => html`<a href="${link.href}" target="_blank">${link.label}</a>`
30
+ )}
31
+ </bd-footer-links-group>
32
+
33
+ <bd-footer-links-group slot="quick-links" title="Quick Links 2">
34
+ ${quickLinks2.map(
35
+ link => html`<a href="${link.href}" target="_blank">${link.label}</a>`
36
+ )}
37
+ </bd-footer-links-group>
38
+
39
+ <div slot="address">${address}</div>
40
+ </bd-footer>
41
+ `;
42
+ };
43
+
44
+ export const Default = Template.bind({});
45
+ Default.args = {
46
+ currentLocale : 'en',
47
+ selectedCountry : 'Choose your country',
48
+ maxColumnsPerRow: 2,
49
+ quickLinks1 : [
50
+ { label: 'Bitdefender Central', href: 'https://central.bitdefender.com/' },
51
+ { label: 'GravityZone Cloud Control Center', href: 'https://gravityzone.bitdefender.com/' },
52
+ { label: 'Bitdefender Cyberpedia', href: 'https://www.bitdefender.com/en-us/cyberpedia/' }
53
+ ],
54
+ quickLinks2: [
55
+ { label: 'Support for Home Products', href: 'https://www.bitdefender.com/consumer/support/' },
56
+ { label: 'Support for Business Products', href: 'https://www.bitdefender.com/business/support/' },
57
+ { label: 'Careers', href: '/en/company/job-opportunities/' }
58
+ ],
59
+ address: '111 W. Houston Street, Suite 2105, Frost Tower Building<br/>San Antonio, Texas 78205'
60
+ };
@@ -0,0 +1 @@
1
+ export const localeMap = { "en-AU": "Australia - English", "nl-BE": "België - Nederlands", "fr-BE": "Belgique - Français", "en-BZ": "Belize - English", "pt-BR": "Brasil - Português", "en-BG": "Bulgaria - English", "en-CA": "Canada - English", "es-CL": "Chile - Español", "es-CO": "Colombia - Español", "en-CZ": "Czechia - English", "en-DK": "Denmark - English", "de-DE": "Deutschland - Deutsch", "es-ES": "España - Español", "fr-FR": "France - Français", "zh-HK": "Hong Kong - China", "hu-HU": "Hungary - Magyar", "en-IN": "India - English", "en-ID": "Indonesia - English", "en-IL": "Israel - English", "it-IT": "Italia - Italiano", "en-JM": "Jamaica - English", "en-LV": "Latvia - English", "en-MY": "Malaysia - English", "en-MT": "Malta - English", "es-MX": "México - Español", "nl-NL": "Nederland - Nederlands", "en-NZ": "New Zealand - English", "en-NO": "Norway - English", "de-AT": "Österreich - Deutsch", "es-PE": "Perú - Español", "en-PH": "Philippines - English", "en-PL": "Poland - English", "pt-PT": "Portugal - Português", "ro-RO": "România - Română", "en-SA": "Saudi Arabia - English", "de-CH": "Schweiz - Deutsch", "en-SG": "Singapore - English", "en-ZA": "South Africa - English", "en-KR": "South Korea - English", "sv-SE": "Sverige - Svenska", "zh-TW": "Taiwan - 台灣", "en-TH": "Thailand - English", "en-AE": "United Arab Emirates - English", "en-GB": "United Kingdom - English", "en-US": "United States - English", "ja-JP": "日本 - 日本語" };
@@ -0,0 +1,38 @@
1
+ import { css } from "lit";
2
+
3
+ export default css`
4
+ :host {
5
+ display: block;
6
+ }
7
+
8
+ .grid {
9
+ display: grid;
10
+ gap: var(--space-md);
11
+ align-items: stretch;
12
+ justify-items: start;
13
+ }
14
+
15
+ /* Default */
16
+ .grid {
17
+ grid-template-columns: repeat(var(--grid-cols), 1fr);
18
+ }
19
+
20
+ /* Responsive overrides */
21
+ @media (min-width: 576px) {
22
+ .grid {
23
+ grid-template-columns: repeat(var(--grid-cols-sm), 1fr);
24
+ }
25
+ }
26
+
27
+ @media (min-width: 768px) {
28
+ .grid {
29
+ grid-template-columns: repeat(var(--grid-cols-md), 1fr);
30
+ }
31
+ }
32
+
33
+ @media (min-width: 992px) {
34
+ .grid {
35
+ grid-template-columns: repeat(var(--grid-cols-lg), 1fr);
36
+ }
37
+ }
38
+ `;
@@ -0,0 +1,55 @@
1
+ import { LitElement, html } from "lit";
2
+ import { tokens } from "../../tokens/tokens.js";
3
+ import gridCSS from "../grid/grid.css.js";
4
+
5
+ class BdGrid extends LitElement {
6
+ static properties = {
7
+ cols : { type: Number }, // default cols
8
+ colsSm : { type: Number }, // optional cols on small screens
9
+ colsMd : { type: Number }, // optional cols on medium
10
+ colsLg : { type: Number }, // optional cols on large
11
+ gap : { type: String }, // e.g., 'sm', 'md'
12
+ align : { type: String }, // start, center, end, stretch
13
+ justify: { type: String }, // start, center, end, space-between
14
+ padding: { type: String } // e.g., 'none', 'sm', 'md', 'lg'
15
+
16
+ };
17
+
18
+ constructor() {
19
+ super();
20
+ this.cols = 1;
21
+ this.colsSm = 1;
22
+ this.colsMd = 2;
23
+ this.colsLg = 3;
24
+ this.gap = "md";
25
+ this.align = "stretch";
26
+ this.justify = "start";
27
+ this.padding = "none";
28
+
29
+ }
30
+
31
+
32
+ render() {
33
+ return html`
34
+ <div
35
+ class="grid"
36
+ style="
37
+ --grid-cols: ${this.cols};
38
+ --grid-cols-sm: ${this.colsSm};
39
+ --grid-cols-md: ${this.colsMd};
40
+ --grid-cols-lg: ${this.colsLg};
41
+ gap: var(--space-${this.gap});
42
+ align-items: ${this.align};
43
+ justify-items: ${this.justify};
44
+ padding: var(--space-${this.padding});
45
+
46
+ "
47
+ >
48
+ <slot></slot>
49
+ </div>
50
+ `;
51
+ }
52
+ }
53
+ BdGrid.styles = [tokens, gridCSS];
54
+
55
+ customElements.define("bd-grid", BdGrid);