@repobit/dex-system-design 0.23.41 → 0.23.43

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.
@@ -1,193 +1,189 @@
1
1
  import { LitElement, html } from "lit";
2
2
  import { tokens } from "../../tokens/tokens.js";
3
+ import "../accordion/accordion.js";
3
4
  import "../heading/heading.js";
4
5
  import "../highlight/highlight.js";
5
- import "../list/list-item/list-item.js";
6
- import "../list/list.js";
6
+ import "../icons/family-icon.js";
7
+ import "../icons/globe-icon.js";
8
+ import "../icons/individual-icon.js";
9
+ import "../icons/laurel-icon.js";
7
10
  import "../paragraph/paragraph.js";
8
- import tabsCSS from "../tabs/tabs.css.js";
11
+ import tabsCSS from "./tabs.css.js";
9
12
 
10
- class TabsComponent extends LitElement {
13
+ // ─── BdTabPanel Light DOM wrapper ──────────────────────────────────────────
14
+
15
+ class BdTabPanel extends HTMLElement {
16
+ connectedCallback() {
17
+ this.style.display = "none";
18
+ }
19
+ }
20
+ customElements.define("bd-tab-panel", BdTabPanel);
21
+
22
+ // ─── BdTabCol — Light DOM wrapper ────────────────────────────────────────────
23
+
24
+ class BdTabCol extends HTMLElement {}
25
+ customElements.define("bd-tab-col", BdTabCol);
26
+
27
+ // ─── BdTabs ──────────────────────────────────────────────────────────────────
28
+
29
+ class BdTabs extends LitElement {
11
30
  static properties = {
12
- selectedTab: { type: Number }
31
+ title : { type: String },
32
+ subtitle : { type: String },
33
+ selectedTab: { type: Number, state: true },
34
+ _minHeight : { state: true }
13
35
  };
14
36
 
37
+ static styles = [tokens, tabsCSS];
38
+
15
39
  constructor() {
16
40
  super();
41
+ this.title = "";
42
+ this.subtitle = "";
17
43
  this.selectedTab = 0;
44
+ this._minHeight = 0;
45
+ }
46
+
47
+ _getPanels() {
48
+ return Array.from(this.querySelectorAll(":scope > bd-tab-panel"));
49
+ }
50
+
51
+ _getCols(panel) {
52
+ return Array.from(panel.querySelectorAll(":scope > bd-tab-col"));
18
53
  }
19
54
 
20
55
  selectTab(index) {
21
56
  this.selectedTab = index;
22
- this.requestUpdate();
57
+ this.updateComplete.then(() => this._equalizeRows());
58
+ }
59
+
60
+ async firstUpdated() {
61
+ await this._measureMaxHeight();
62
+ }
63
+
64
+ _equalizeRows() {
65
+ const container = this.renderRoot.querySelector(".bd-columns-container");
66
+ if (!container) return;
67
+
68
+ const icons = container.querySelectorAll(".col-icon-wrapper");
69
+ const titles = container.querySelectorAll(".col-title-wrapper");
70
+ const descriptions = container.querySelectorAll(".col-description");
71
+
72
+ // Reset
73
+ icons.forEach(el => el.style.height = "");
74
+ titles.forEach(el => el.style.height = "");
75
+ descriptions.forEach(el => el.style.height = "");
76
+
77
+ // Egalizează icon-uri
78
+ let maxH = 0;
79
+ icons.forEach(el => { if (el.scrollHeight > maxH) maxH = el.scrollHeight; });
80
+ icons.forEach(el => el.style.height = `${maxH}px`);
81
+
82
+ // Egalizează titluri
83
+ maxH = 0;
84
+ titles.forEach(el => { if (el.scrollHeight > maxH) maxH = el.scrollHeight; });
85
+ titles.forEach(el => el.style.height = `${maxH}px`);
86
+
87
+ // Egalizează descrieri
88
+ maxH = 0;
89
+ descriptions.forEach(el => { if (el.scrollHeight > maxH) maxH = el.scrollHeight; });
90
+ descriptions.forEach(el => el.style.height = `${maxH}px`);
91
+ }
92
+
93
+ async _measureMaxHeight() {
94
+ const panels = this._getPanels();
95
+ const container = this.renderRoot.querySelector(".bd-columns-container");
96
+ if (!container || panels.length === 0) return;
97
+
98
+ let maxHeight = 0;
99
+ const originalTab = this.selectedTab;
100
+
101
+ for (let i = 0; i < panels.length; i++) {
102
+ this.selectedTab = i;
103
+ await this.updateComplete;
104
+ this._equalizeRows();
105
+ await new Promise(resolve => requestAnimationFrame(resolve));
106
+ const h = container.scrollHeight;
107
+ if (h > maxHeight) maxHeight = h;
108
+ }
109
+
110
+ this.selectedTab = originalTab;
111
+ this._minHeight = maxHeight;
112
+ await this.updateComplete;
113
+ this._equalizeRows();
114
+ }
115
+
116
+ _renderCol(col) {
117
+ const descEl = col.querySelector("[slot='description']");
118
+ const iconEl = col.querySelector("[slot='icon']");
119
+ const accordionEl = col.querySelector("bd-accordion-section");
120
+ const title = col.getAttribute("title");
121
+
122
+ return html`
123
+ <div class="bd-col">
124
+ <div class="col-icon-wrapper">
125
+ ${iconEl ? iconEl.cloneNode(true) : ""}
126
+ </div>
127
+ <div class="col-title-wrapper">
128
+ <bd-h as="h4">${title}</bd-h>
129
+ </div>
130
+ ${descEl
131
+ ? html`<div class="col-description"><bd-p kind="regular">${descEl.textContent}</bd-p></div>`
132
+ : ""}
133
+ ${accordionEl
134
+ ? accordionEl.cloneNode(true)
135
+ : ""}
136
+ </div>
137
+ `;
23
138
  }
24
139
 
25
140
  render() {
26
- const tabs = [
27
- {
28
- title : "Privacy Protection",
29
- imageBase: "/assets/tabs-img1.png",
30
- icon : "/assets/pic1.png"
31
- },
32
- {
33
- title : "Identity Protection",
34
- imageBase: "/assets/tabs-img2.png",
35
- icon : "/assets/pic2.png"
36
- },
37
- {
38
- title : "Device Protection",
39
- imageBase: "/assets/tabs-img3.png",
40
- icon : "/assets/pic3.png"
41
- },
42
- {
43
- title : "Financial Insurance",
44
- imageBase: "/assets/tabs-img3.png",
45
- icon : "/assets/pic3.png"
46
- }
47
- ];
48
-
49
- const content = [
50
- {
51
- heading : "Take back your family's digital privacy",
52
- description: () => html`
53
- <bd-list type="unordered" spacing="md" variant="default" orientation="vertical">
54
- <bd-li kind="bullet" size="md">
55
- <bd-p kind="small">Enjoy complete data privacy with
56
- <strong>Bitdefender Premium VPN</strong>.</bd-p>
57
- </bd-li>
58
- <bd-li kind="bullet" size="md">
59
- <bd-p kind="small">Escape intrusive ads and trackers with our advanced
60
- <strong>Ad-Blocker</strong> and
61
- <strong>Anti-Tracker</strong> features.</bd-p>
62
- </bd-li>
63
- <bd-li kind="bullet" size="md">
64
- <bd-p kind="small">Shield yourself with robust
65
- <strong>Webcam and Microphone Protection</strong>.</bd-p>
66
- </bd-li>
67
- </bd-list>`,
68
- href: "#1"
69
- },
70
- {
71
- heading : "Safe data. Safe you.",
72
- description: () => html`
73
- <bd-p kind="small">Get instant alerts for potential threats to your personal data and
74
- finances.</bd-p>
75
- <bd-p kind="small">Receive clear and concise advice on handling data breaches.</bd-p>
76
- <bd-p kind="small">Reduce the risk of online ID theft by scanning your digital
77
- footprint.</bd-p>`,
78
- href: "#2"
79
- },
80
- {
81
- heading : "We secure what you hold dear",
82
- description: () => html`
83
- <bd-p kind="small">Enjoy seamless security across all devices.</bd-p>
84
- <bd-p kind="small">Protection from corrupt websites, risky downloads, and phishing
85
- attempts.</bd-p>
86
- <bd-p kind="small">Lightweight performance ensures smooth device operation.</bd-p>`,
87
- href: "#3"
88
- },
89
- {
90
- heading : "Financial Insurance",
91
- description: () => html`
92
- <bd-p kind="small">Enjoy seamless security across all devices.</bd-p>
93
- <bd-p kind="small">Protection from corrupt websites, risky downloads, and phishing
94
- attempts.</bd-p>
95
- <bd-p kind="small">Lightweight performance ensures smooth device operation.</bd-p>`,
96
- href: "#4"
97
- }
98
- ];
99
-
100
- const selectedTabData = tabs[this.selectedTab];
141
+ const panels = this._getPanels();
142
+ const activePanel = panels[this.selectedTab];
143
+ const cols = activePanel ? this._getCols(activePanel) : [];
144
+ const colCount = cols.length;
101
145
 
102
146
  return html`
103
147
  <div class="bd-tabs-component">
104
- <bd-highlight highlight-text="family member">
105
- Customized online protection for each family member - Smart, Private &
106
- Safe
107
- </bd-highlight>
108
- <bd-p kind="regular" class="bd-tabs-subtitle">Your family is unique and so should be your online security. Our family protection plan adapts to each member's needs, offering the ideal mix of privacy, freedom, and safety.</bd-p>
109
- <div class="bd-tabs-container">
110
- ${tabs.map(
111
- (tab, index) => html`
112
- <button
113
- role="tab"
114
- aria-selected="${this.selectedTab === index ? "true" : "false"}"
115
- class=${this.selectedTab === index
116
- ? "bd-selected bd-tab-button"
117
- : "bd-tab-button"}
118
- @click=${() => this.selectTab(index)}
119
- aria-controls="tab-content-${index}"
120
- id="tab-${index}"
121
- >
122
- <img src="${tab.icon}" alt="${tab.title} Icon" />
123
- <bd-p kind="small"><strong>${tab.title}</strong></bd-p>
124
- </button>
125
- `
126
- )}
127
- </div>
128
- <div class="bd-card-container">
129
- <div class="bd-left">
130
- <picture>
131
- ${["webp", "png"].map((format) =>
132
- ["2000", "750"].map(
133
- (width) => html`
134
- <source
135
- type="image/${format}"
136
- srcset="
137
- ${selectedTabData.imageBase}?width=${width}&format=${format}&optimize=medium
138
- "
139
- media="${width === "2000" ? "(min-width: 600px)" : ""}"
140
- />
141
- `
142
- )
143
- )}
144
- <img
145
- loading="lazy"
146
- alt="${selectedTabData.title} Image"
147
- src="${selectedTabData.imageBase}?width=750&format=png&optimize=medium"
148
- />
149
- </picture>
150
- </div>
151
- <div class="bd-right">
152
- <bd-h as="h3" id="tab-content-heading-${this.selectedTab}">
153
- ${content[this.selectedTab].heading}
154
- </bd-h>
155
- <div
156
- id="tab-content-${this.selectedTab}"
157
- aria-labelledby="tab-content-heading-${this.selectedTab}"
158
- >
159
- ${content[this.selectedTab].description()}
160
- </div>
161
- <a
162
- href="${content[this.selectedTab].href}"
163
- class="bd-find-out-more"
164
- aria-label="Find out more about ${content[this.selectedTab]
165
- .heading}"
148
+
149
+ ${this.title
150
+ ? html`<bd-highlight highlight-text="">${this.title}</bd-highlight>`
151
+ : ""}
152
+
153
+ ${this.subtitle
154
+ ? html`<bd-p kind="large" class="bd-tabs-subtitle">${this.subtitle}</bd-p>`
155
+ : ""}
156
+
157
+ <!-- Tab buttons -->
158
+ <div class="bd-tabs-container" role="tablist">
159
+ ${panels.map((panel, index) => html`
160
+ <button
161
+ role="tab"
162
+ aria-selected="${this.selectedTab === index ? "true" : "false"}"
163
+ aria-controls="tab-panel-${index}"
164
+ id="tab-btn-${index}"
165
+ class="bd-tab-button ${this.selectedTab === index ? "bd-selected" : ""}"
166
+ @click=${() => this.selectTab(index)}
166
167
  >
167
- <bd-p kind="small">Find out more</bd-p>
168
- <span class="bd-arrow">
169
- <svg
170
- width="14"
171
- height="14"
172
- viewBox="0 0 24 24"
173
- fill="none"
174
- stroke="currentColor"
175
- stroke-width="3"
176
- stroke-linecap="round"
177
- stroke-linejoin="round"
178
- >
179
- <path d="M5 12h14"></path>
180
- <path d="M12 5l7 7-7 7"></path>
181
- </svg>
182
- </span>
183
- </a>
184
- </div>
168
+ <span class="bd-tab-label">${panel.getAttribute("title")}</span>
169
+ </button>
170
+ `)}
171
+ </div>
172
+
173
+ <!-- Active panel — grid cu coloane -->
174
+ <div
175
+ id="tab-panel-${this.selectedTab}"
176
+ role="tabpanel"
177
+ aria-labelledby="tab-btn-${this.selectedTab}"
178
+ class="bd-columns-container"
179
+ style="grid-template-columns: repeat(${colCount}, 300px); ${this._minHeight ? `min-height: ${this._minHeight}px;` : ""}"
180
+ >
181
+ ${cols.map(col => this._renderCol(col))}
185
182
  </div>
183
+
186
184
  </div>
187
185
  `;
188
186
  }
189
187
  }
190
188
 
191
- TabsComponent.styles = [tokens, tabsCSS];
192
-
193
- customElements.define("bd-tabs-component", TabsComponent);
189
+ customElements.define("bd-tabs", BdTabs);