@repobit/dex-system-design 0.23.40 → 0.23.42
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/CHANGELOG.md +15 -0
- package/package.json +4 -4
- package/src/components/highlight/highlight.css.js +1 -1
- package/src/components/highlight/highlight.js +21 -12
- package/src/components/tabs/tabs.css.js +87 -197
- package/src/components/tabs/tabs.js +160 -164
- package/src/components/tabs/tabs.stories.js +436 -103
|
@@ -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 "../
|
|
6
|
-
import "../
|
|
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 "
|
|
11
|
+
import tabsCSS from "./tabs.css.js";
|
|
9
12
|
|
|
10
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
customElements.define("bd-tabs-component", TabsComponent);
|
|
189
|
+
customElements.define("bd-tabs", BdTabs);
|