@repobit/dex-system-design 0.25.0 → 0.25.2
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 +16 -0
- package/package.json +4 -4
- package/src/components/ai-guardian-demo.stories.js +4 -4
- package/src/components/anchor/anchor-nav.css.js +5 -6
- package/src/components/anchor/anchor-nav.js +37 -27
- package/src/components/announcement-banner/announcement-banner.css.js +44 -12
- package/src/components/announcement-banner/announcement-banner.js +76 -16
- package/src/components/announcement-banner/announcement-banner.stories.js +101 -32
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.25.2](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.25.1...@repobit/dex-system-design@0.25.2) (2026-10-01)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **DEX-1014:** anchor nav component adjustments
|
|
11
|
+
* **DEX-1014:** anchor nav component adjustments
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.25.1](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.25.0...@repobit/dex-system-design@0.25.1) (2026-09-25)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **DEX-1014:** announcement banner component
|
|
19
|
+
* **DEX-1014:** announcement banner component
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [0.25.0](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.20...@repobit/dex-system-design@0.25.0) (2026-09-25)
|
|
7
23
|
|
|
8
24
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repobit/dex-system-design",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"description": "Design system based on Web Components.",
|
|
5
5
|
"author": "Iordache Matei Cezar <miordache@bitdefender.com>",
|
|
6
6
|
"homepage": "https://github.com/bitdefender/dex-core#readme",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"url": "https://github.com/bitdefender/dex-core/issues"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@repobit/dex-store": "1.6.
|
|
80
|
-
"@repobit/dex-store-elements": "2.1.
|
|
79
|
+
"@repobit/dex-store": "1.6.39",
|
|
80
|
+
"@repobit/dex-store-elements": "2.1.9",
|
|
81
81
|
"lit": "^3.3.2"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"volta": {
|
|
95
95
|
"node": "24.14.0"
|
|
96
96
|
},
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "14ac669a586110a33a6f8d470e6d9ce8c11a4bd7"
|
|
98
98
|
}
|
|
@@ -93,7 +93,7 @@ function aiGuardianBody() {
|
|
|
93
93
|
aria-label="Page sections"
|
|
94
94
|
cta-label="Download BETA Today"
|
|
95
95
|
cta-href="#download"
|
|
96
|
-
sticky-top="
|
|
96
|
+
sticky-top="76px"
|
|
97
97
|
.items=${[
|
|
98
98
|
{ title: "Overview" },
|
|
99
99
|
{ title: "Compatibility" },
|
|
@@ -500,7 +500,7 @@ function skillScannerBody() {
|
|
|
500
500
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
501
501
|
<bd-anchor-nav
|
|
502
502
|
aria-label="Skill Scanner sections"
|
|
503
|
-
sticky-top="
|
|
503
|
+
sticky-top="76px"
|
|
504
504
|
.items=${[
|
|
505
505
|
{ title: "Install" },
|
|
506
506
|
{ title: "Overview" },
|
|
@@ -958,7 +958,7 @@ function vpnBody() {
|
|
|
958
958
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
959
959
|
<bd-anchor-nav
|
|
960
960
|
aria-label="VPN for AI Agents sections"
|
|
961
|
-
sticky-top="
|
|
961
|
+
sticky-top="76px"
|
|
962
962
|
.items=${[
|
|
963
963
|
{ title: "Overview" },
|
|
964
964
|
{ title: "Protection" },
|
|
@@ -1444,7 +1444,7 @@ Interactive demo: sticky top-level product nav (\`AI Guardian\` / \`Skill Scanne
|
|
|
1444
1444
|
navigation — clicking a tab intercepts the anchor click and swaps local state instead
|
|
1445
1445
|
of following the href (which has no route in Storybook and would 404).
|
|
1446
1446
|
|
|
1447
|
-
Both/all anchor-nav bars use \`sticky-top="
|
|
1447
|
+
Both/all anchor-nav bars use \`sticky-top="76px"\` so they pin themselves *below*
|
|
1448
1448
|
the sticky page-switcher instead of overlapping it once scrolled. That value
|
|
1449
1449
|
is an estimate — measure bd-page-switcher's real rendered height in DevTools
|
|
1450
1450
|
and adjust if the bars still overlap or leave a gap.
|
|
@@ -7,6 +7,8 @@ export const anchorNavStyles = css`
|
|
|
7
7
|
background: var(--color-neutral-0);
|
|
8
8
|
--bd-accesibility-focus: var(--color-blue-500);
|
|
9
9
|
--anchor-nav-max-width: 1290px;
|
|
10
|
+
border-top: var(--border-width-1) solid var(--color-neutral-100);
|
|
11
|
+
border-bottom: var(--border-width-1) solid var(--color-neutral-100);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
:host(.is-sticky) {
|
|
@@ -28,8 +30,6 @@ export const anchorNavStyles = css`
|
|
|
28
30
|
padding: var(--spacing-0);
|
|
29
31
|
width: 100%;
|
|
30
32
|
box-sizing: border-box;
|
|
31
|
-
border-top: var(--border-width-1) solid var(--color-neutral-100);
|
|
32
|
-
border-bottom: var(--border-width-1) solid var(--color-neutral-100);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.bd-anchor-nav__inner {
|
|
@@ -46,7 +46,6 @@ export const anchorNavStyles = css`
|
|
|
46
46
|
box-sizing: border-box;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
49
|
.anchor-link:focus-visible,
|
|
51
50
|
.bd-anchor-nav__cta:focus-visible,
|
|
52
51
|
.bd-anchor-nav__dropdown-toggle:focus-visible,
|
|
@@ -115,17 +114,17 @@ export const anchorNavStyles = css`
|
|
|
115
114
|
text-decoration: none !important;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
.anchor-indicator {
|
|
117
|
+
.anchor-indicator {
|
|
120
118
|
position: absolute;
|
|
121
119
|
left: 0;
|
|
122
|
-
|
|
120
|
+
top: 0;
|
|
123
121
|
width: 0;
|
|
124
122
|
height: var(--border-width-3);
|
|
125
123
|
background-color: var(--color-blue-500);
|
|
126
124
|
transform: translateX(0);
|
|
127
125
|
transition:
|
|
128
126
|
transform var(--transition-duration-slow) var(--transition-easing-ease-out),
|
|
127
|
+
top var(--transition-duration-slow) var(--transition-easing-ease-out),
|
|
129
128
|
width var(--transition-duration-slow) var(--transition-easing-ease-out);
|
|
130
129
|
pointer-events: none;
|
|
131
130
|
}
|
|
@@ -34,10 +34,6 @@ class BdAnchorNav extends LitElement {
|
|
|
34
34
|
ctaHref : { type: String, attribute: "cta-href" },
|
|
35
35
|
items : { type: Array },
|
|
36
36
|
maxWidth : { type: String, attribute: "max-width" },
|
|
37
|
-
// How far from the viewport top this nav sticks once pinned — set this
|
|
38
|
-
// to the rendered height of whatever sticky element sits above it (e.g.
|
|
39
|
-
// a page-switcher), so the two don't stack on top of each other at
|
|
40
|
-
// top:0. Accepts any CSS length, e.g. "64px", "4rem".
|
|
41
37
|
stickyTop : { type: String, attribute: "sticky-top" }
|
|
42
38
|
};
|
|
43
39
|
|
|
@@ -67,10 +63,8 @@ class BdAnchorNav extends LitElement {
|
|
|
67
63
|
this._stickyPlaceholder = null;
|
|
68
64
|
this._onStickyScroll = null;
|
|
69
65
|
this._handleResize = this._handleResize.bind(this);
|
|
70
|
-
// sliding underline indicator: false until the first successful
|
|
71
|
-
// measurement + placement, so it doesn't visibly animate in from the
|
|
72
|
-
// left edge on first paint.
|
|
73
66
|
this._indicatorReady = false;
|
|
67
|
+
this._indicatorResizeObserver = null;
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
connectedCallback() {
|
|
@@ -93,6 +87,7 @@ class BdAnchorNav extends LitElement {
|
|
|
93
87
|
document.removeEventListener("pointerdown", this._onDocumentPointerDown, true);
|
|
94
88
|
document.removeEventListener("keydown", this._onKeydown, true);
|
|
95
89
|
this._destroySticky();
|
|
90
|
+
this._indicatorResizeObserver?.disconnect();
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
updated(changedProperties) {
|
|
@@ -100,17 +95,11 @@ class BdAnchorNav extends LitElement {
|
|
|
100
95
|
|
|
101
96
|
if (changedProperties.has("stickyTop")) {
|
|
102
97
|
this.style.setProperty("--sticky-top", this.stickyTop || "0px");
|
|
103
|
-
// the scroll-spy offset (how far to scroll past the sticky bar when
|
|
104
|
-
// clicking a link) depends on this too, since the nav's own visible
|
|
105
|
-
// top now sits lower than the viewport top while stuck.
|
|
106
98
|
this._runScrollSpy();
|
|
107
99
|
}
|
|
108
100
|
|
|
109
101
|
if (changedProperties.has("activeId") || changedProperties.has("items")) {
|
|
110
|
-
|
|
111
|
-
// measuring its box — requestAnimationFrame is enough here, no need
|
|
112
|
-
// for updateComplete since nothing else in render() depends on this.
|
|
113
|
-
requestAnimationFrame(() => this._updateIndicator());
|
|
102
|
+
requestAnimationFrame(() => this._updateIndicator("updated"));
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
if (!changedProperties.has("activeId")) return;
|
|
@@ -128,13 +117,23 @@ class BdAnchorNav extends LitElement {
|
|
|
128
117
|
}
|
|
129
118
|
queueMicrotask(() => this._runScrollSpy());
|
|
130
119
|
this._handleMobileFullWidth();
|
|
131
|
-
requestAnimationFrame(() => this._updateIndicator());
|
|
120
|
+
requestAnimationFrame(() => this._updateIndicator("firstUpdated"));
|
|
121
|
+
|
|
122
|
+
const links = this.renderRoot?.querySelector(".anchor-links--desktop");
|
|
123
|
+
if (links && "ResizeObserver" in window) {
|
|
124
|
+
this._indicatorResizeObserver = new ResizeObserver(() => {
|
|
125
|
+
console.log("[anchor-nav] ResizeObserver fired", {
|
|
126
|
+
isSticky : this.classList.contains("is-sticky"),
|
|
127
|
+
navRect : this.getBoundingClientRect(),
|
|
128
|
+
linksRect: links.getBoundingClientRect()
|
|
129
|
+
});
|
|
130
|
+
this._updateIndicator("ResizeObserver");
|
|
131
|
+
});
|
|
132
|
+
this._indicatorResizeObserver.observe(links);
|
|
133
|
+
this._indicatorResizeObserver.observe(this);
|
|
134
|
+
}
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
// Positions the sliding underline under the currently active desktop
|
|
135
|
-
// link. Reads offsetLeft/offsetWidth directly off the active bd-link —
|
|
136
|
-
// as a flex item it's block-level regardless of its own :host display,
|
|
137
|
-
// so these are reliable without any extra math against a bounding rect.
|
|
138
137
|
_updateIndicator() {
|
|
139
138
|
const indicator = this.renderRoot?.querySelector(".anchor-indicator");
|
|
140
139
|
const activeLink = this.renderRoot?.querySelector(
|
|
@@ -144,14 +143,13 @@ class BdAnchorNav extends LitElement {
|
|
|
144
143
|
|
|
145
144
|
const left = activeLink.offsetLeft;
|
|
146
145
|
const width = activeLink.offsetWidth;
|
|
146
|
+
const top = activeLink.offsetTop + activeLink.offsetHeight + 14;
|
|
147
147
|
|
|
148
148
|
if (!this._indicatorReady) {
|
|
149
|
-
// first placement: skip the transition so it doesn't slide in from
|
|
150
|
-
// the left edge on initial paint, then re-enable it next frame.
|
|
151
149
|
indicator.style.transition = "none";
|
|
152
150
|
indicator.style.transform = `translateX(${left}px)`;
|
|
151
|
+
indicator.style.top = `${top}px`;
|
|
153
152
|
indicator.style.width = `${width}px`;
|
|
154
|
-
// force a style flush before removing the transition override
|
|
155
153
|
void indicator.offsetWidth;
|
|
156
154
|
indicator.style.transition = "";
|
|
157
155
|
this._indicatorReady = true;
|
|
@@ -159,12 +157,13 @@ class BdAnchorNav extends LitElement {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
indicator.style.transform = `translateX(${left}px)`;
|
|
160
|
+
indicator.style.top = `${top}px`;
|
|
162
161
|
indicator.style.width = `${width}px`;
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
_handleResize() {
|
|
166
165
|
this._handleMobileFullWidth();
|
|
167
|
-
this._updateIndicator();
|
|
166
|
+
this._updateIndicator("windowResize");
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
_handleMobileFullWidth() {
|
|
@@ -190,14 +189,16 @@ class BdAnchorNav extends LitElement {
|
|
|
190
189
|
this.style.setProperty("--sticky-top", this.stickyTop || "0px");
|
|
191
190
|
|
|
192
191
|
this._onStickyScroll = () => {
|
|
193
|
-
const parent
|
|
192
|
+
const parent = this.parentElement;
|
|
194
193
|
if (!parent) return;
|
|
195
194
|
|
|
196
195
|
const parentRect = parent.getBoundingClientRect();
|
|
197
196
|
const navHeight = this._stickyPlaceholder
|
|
198
197
|
? this._stickyPlaceholder.offsetHeight
|
|
199
198
|
: this.offsetHeight;
|
|
200
|
-
const stickyTopPx = parseLengthToPx(
|
|
199
|
+
const stickyTopPx = parseLengthToPx(
|
|
200
|
+
this.style.getPropertyValue("--sticky-top") || this.stickyTop
|
|
201
|
+
) || 0;
|
|
201
202
|
|
|
202
203
|
if (this._stickyOriginalTop === null) {
|
|
203
204
|
const rect = this.getBoundingClientRect();
|
|
@@ -217,8 +218,8 @@ class BdAnchorNav extends LitElement {
|
|
|
217
218
|
}
|
|
218
219
|
this.classList.add("is-sticky");
|
|
219
220
|
}
|
|
220
|
-
this.style.setProperty("--sticky-left",
|
|
221
|
-
this.style.setProperty("--sticky-width", `${
|
|
221
|
+
this.style.setProperty("--sticky-left", "0px");
|
|
222
|
+
this.style.setProperty("--sticky-width", `${document.documentElement.clientWidth}px`);
|
|
222
223
|
} else {
|
|
223
224
|
if (this.classList.contains("is-sticky")) {
|
|
224
225
|
this.classList.remove("is-sticky");
|
|
@@ -232,6 +233,15 @@ class BdAnchorNav extends LitElement {
|
|
|
232
233
|
}
|
|
233
234
|
};
|
|
234
235
|
|
|
236
|
+
this._onStickyResize = () => {
|
|
237
|
+
this._stickyOriginalTop = null;
|
|
238
|
+
if (this.classList.contains("is-sticky")) {
|
|
239
|
+
this.style.setProperty("--sticky-left", "0px");
|
|
240
|
+
this.style.setProperty("--sticky-width", `${document.documentElement.clientWidth}px`);
|
|
241
|
+
}
|
|
242
|
+
this._onStickyScroll();
|
|
243
|
+
};
|
|
244
|
+
|
|
235
245
|
this._onStickyResize = () => {
|
|
236
246
|
this._stickyOriginalTop = null;
|
|
237
247
|
if (this.classList.contains("is-sticky")) {
|
|
@@ -20,22 +20,21 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
20
20
|
gap: var(--spacing-96);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
.bd-announcementbanner-
|
|
24
|
-
display: block;
|
|
25
|
-
margin: 0;
|
|
26
|
-
color: var(--color-neutral-0, #fff);
|
|
27
|
-
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
28
|
-
flex: 1 1 320px;
|
|
29
|
-
max-width: 560px;
|
|
30
|
-
}
|
|
31
|
-
|
|
23
|
+
.bd-announcementbanner-left,
|
|
32
24
|
.bd-announcementbanner-side {
|
|
33
25
|
display: flex;
|
|
34
26
|
flex-direction: column;
|
|
35
27
|
align-items: flex-start;
|
|
36
28
|
gap: var(--spacing-16);
|
|
37
|
-
flex: 1 1
|
|
38
|
-
max-width:
|
|
29
|
+
flex: 1 1 320px;
|
|
30
|
+
max-width: 560px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.bd-announcementbanner-title {
|
|
34
|
+
display: block;
|
|
35
|
+
margin: 0;
|
|
36
|
+
color: var(--color-neutral-0, #fff);
|
|
37
|
+
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
.bd-announcementbanner-icon {
|
|
@@ -45,6 +44,15 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
45
44
|
margin-bottom: var(--spacing-4);
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/* Collapses to nothing when the corresponding slot has no assigned
|
|
48
|
+
content — see _onSlotChange in announcement-banner.js. */
|
|
49
|
+
.bd-announcementbanner-icon[hidden],
|
|
50
|
+
.bd-announcementbanner-buttons[hidden] {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Left and right icon slots share the exact same sizing/style — the
|
|
55
|
+
left column is a mirror of the right one, not a different layout. */
|
|
48
56
|
.bd-announcementbanner-icon ::slotted(*) {
|
|
49
57
|
display: block;
|
|
50
58
|
max-width: 160px;
|
|
@@ -57,6 +65,14 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
57
65
|
color: rgba(255, 255, 255, 0.65);
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
.bd-announcementbanner-buttons {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: var(--spacing-12);
|
|
73
|
+
margin-top: var(--spacing-4);
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
@media (max-width: 699px) {
|
|
61
77
|
.bd-announcementbanner-container {
|
|
62
78
|
padding: var(--spacing-28) var(--spacing-24);
|
|
@@ -64,10 +80,26 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
64
80
|
align-items: flex-start;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
.bd-announcementbanner-
|
|
83
|
+
.bd-announcementbanner-left,
|
|
68
84
|
.bd-announcementbanner-side {
|
|
69
85
|
max-width: none;
|
|
70
86
|
flex-basis: auto;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
89
|
+
|
|
90
|
+
/* When left-icon is present, the left title shrinks to match the
|
|
91
|
+
default right-column description size — visual emphasis moves to
|
|
92
|
+
the right side instead. */
|
|
93
|
+
.bd-announcementbanner-title--small {
|
|
94
|
+
margin: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Mirror case: the right description grows to the default title size
|
|
98
|
+
when it's the "big" side. */
|
|
99
|
+
.bd-announcementbanner-description--large {
|
|
100
|
+
display: block;
|
|
101
|
+
margin: 0;
|
|
102
|
+
color: var(--color-neutral-0, #fff);
|
|
103
|
+
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
104
|
+
}
|
|
73
105
|
`;
|
|
@@ -5,48 +5,108 @@ import "../paragraph/paragraph.js";
|
|
|
5
5
|
import announcementBannerCSS from "./announcement-banner.css.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Simple two-column banner on a blue gradient
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Simple two-column banner on a blue gradient. Two layout modes:
|
|
9
|
+
*
|
|
10
|
+
* - No left-icon (default): big title on the left, icon + small
|
|
11
|
+
* description on the right — for partnership/announcement callouts.
|
|
12
|
+
* - With left-icon: the left column becomes the "small" side (icon +
|
|
13
|
+
* small title, mirroring how the default right side looks) and the
|
|
14
|
+
* right column becomes the "big" side (large description text) — i.e.
|
|
15
|
+
* visual emphasis flips to the right when a left icon is present.
|
|
13
16
|
*
|
|
14
17
|
* <bd-announcement-banner
|
|
15
18
|
* title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
|
|
16
|
-
|
|
19
|
+
* description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
17
20
|
* >
|
|
18
|
-
* <bd-
|
|
21
|
+
* <bd-picture slot="left-icon" ...></bd-picture>
|
|
22
|
+
* <bd-button-link slot="left-buttons" ...>Learn more</bd-button-link>
|
|
19
23
|
* </bd-announcement-banner>
|
|
20
24
|
*/
|
|
21
25
|
class BdAnnouncementBanner extends LitElement {
|
|
22
26
|
static properties = {
|
|
23
|
-
title
|
|
24
|
-
description: { type: String }
|
|
27
|
+
title : { type: String },
|
|
28
|
+
description : { type: String },
|
|
29
|
+
_hasLeftIcon : { state: true },
|
|
30
|
+
_hasLeftButtons : { state: true },
|
|
31
|
+
_hasRightIcon : { state: true },
|
|
32
|
+
_hasRightButtons: { state: true }
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
static styles = [tokens, announcementBannerCSS];
|
|
28
36
|
|
|
29
37
|
constructor() {
|
|
30
38
|
super();
|
|
31
|
-
this.title
|
|
39
|
+
this.title = "";
|
|
32
40
|
this.description = "";
|
|
41
|
+
this._hasLeftIcon = false;
|
|
42
|
+
this._hasLeftButtons = false;
|
|
43
|
+
this._hasRightIcon = false;
|
|
44
|
+
this._hasRightButtons = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Each optional slot's wrapper is only rendered with real layout
|
|
48
|
+
// (gap/margin) when it actually has assigned content — checked via
|
|
49
|
+
// slotchange rather than CSS :empty, since a <slot> with no assigned
|
|
50
|
+
// nodes still isn't :empty as a DOM matter the same way a plain element
|
|
51
|
+
// is, and this keeps the collapse logic in one predictable place.
|
|
52
|
+
_onSlotChange(stateKey, event) {
|
|
53
|
+
const hasContent = event.target.assignedNodes({ flatten: true }).length > 0;
|
|
54
|
+
if (this[stateKey] !== hasContent) this[stateKey] = hasContent;
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
render() {
|
|
58
|
+
// When a left icon is slotted in, visual emphasis flips: the left
|
|
59
|
+
// column's title renders small (mirroring the right column's default
|
|
60
|
+
// small description), and the right column's description renders
|
|
61
|
+
// large instead (mirroring the left column's default large title).
|
|
62
|
+
const flipped = this._hasLeftIcon;
|
|
63
|
+
|
|
36
64
|
return html`
|
|
37
65
|
<div class="bd-announcementbanner-container">
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
66
|
+
<div class="bd-announcementbanner-left">
|
|
67
|
+
<div class="bd-announcementbanner-icon" ?hidden=${!this._hasLeftIcon}>
|
|
68
|
+
<slot name="left-icon" @slotchange=${(e) => this._onSlotChange("_hasLeftIcon", e)}></slot>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
${flipped
|
|
72
|
+
? html`
|
|
73
|
+
<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-title bd-announcementbanner-title--small">
|
|
74
|
+
<slot name="title">${this.title}</slot>
|
|
75
|
+
</bd-p>
|
|
76
|
+
`
|
|
77
|
+
: html`
|
|
78
|
+
<bd-h as="h2" fontWeight="400" class="bd-announcementbanner-title">
|
|
79
|
+
<slot name="title">${this.title}</slot>
|
|
80
|
+
</bd-h>
|
|
81
|
+
`}
|
|
82
|
+
|
|
83
|
+
<div class="bd-announcementbanner-buttons" ?hidden=${!this._hasLeftButtons}>
|
|
84
|
+
<slot name="left-buttons" @slotchange=${(e) => this._onSlotChange("_hasLeftButtons", e)}></slot>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
41
87
|
|
|
42
88
|
<div class="bd-announcementbanner-side">
|
|
43
|
-
<div class="bd-announcementbanner-icon">
|
|
44
|
-
<slot name="icon"></slot>
|
|
89
|
+
<div class="bd-announcementbanner-icon" ?hidden=${!this._hasRightIcon}>
|
|
90
|
+
<slot name="icon" @slotchange=${(e) => this._onSlotChange("_hasRightIcon", e)}></slot>
|
|
45
91
|
</div>
|
|
46
92
|
|
|
47
93
|
${this.description
|
|
48
|
-
?
|
|
94
|
+
? flipped
|
|
95
|
+
? html`
|
|
96
|
+
<bd-h as="h2" fontWeight="400" color="var(--color-neutral-0)" class="bd-announcementbanner-description bd-announcementbanner-description--large">
|
|
97
|
+
<slot name="description">${this.description}</slot>
|
|
98
|
+
</bd-h>
|
|
99
|
+
`
|
|
100
|
+
: html`
|
|
101
|
+
<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-description">
|
|
102
|
+
<slot name="description">${this.description}</slot>
|
|
103
|
+
</bd-p>
|
|
104
|
+
`
|
|
49
105
|
: nothing}
|
|
106
|
+
|
|
107
|
+
<div class="bd-announcementbanner-buttons" ?hidden=${!this._hasRightButtons}>
|
|
108
|
+
<slot name="right-buttons" @slotchange=${(e) => this._onSlotChange("_hasRightButtons", e)}></slot>
|
|
109
|
+
</div>
|
|
50
110
|
</div>
|
|
51
111
|
</div>
|
|
52
112
|
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { html } from "lit";
|
|
2
|
+
import "../button/button.js";
|
|
2
3
|
import "../picture/picture.js";
|
|
3
4
|
import "./announcement-banner.js";
|
|
4
5
|
|
|
@@ -14,24 +15,24 @@ export default {
|
|
|
14
15
|
component: `
|
|
15
16
|
**AnnouncementBanner** este un banner simplu, cu 2 coloane, pe fundal albastru
|
|
16
17
|
gradient — titlu mare în stânga, iar în dreapta o iconiță/logo + caption +
|
|
17
|
-
descriere.
|
|
18
|
-
|
|
18
|
+
descriere. Coloana stângă poate primi opțional o imagine deasupra titlului
|
|
19
|
+
(\`left-icon\`) și butoane sub titlu (\`left-buttons\`); coloana dreaptă poate
|
|
20
|
+
primi opțional butoane sub descriere (\`right-buttons\`), pe lângă iconița
|
|
21
|
+
(\`icon\`) și descrierea (\`description\`) deja existente.
|
|
19
22
|
|
|
20
|
-
Pentru varianta centrată, cu butoane, vezi \`bd-cta-banner\`.
|
|
23
|
+
Pentru varianta centrată, cu un singur set de butoane, vezi \`bd-cta-banner\`.
|
|
21
24
|
|
|
22
25
|
### Usage
|
|
23
26
|
\`\`\`html
|
|
24
27
|
<bd-announcement-banner
|
|
25
28
|
title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
|
|
26
|
-
caption="Global Anti-Scam Alliance"
|
|
27
29
|
description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
28
30
|
>
|
|
29
|
-
<bd-picture
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
></bd-picture>
|
|
31
|
+
<bd-picture slot="left-icon" ...></bd-picture>
|
|
32
|
+
<bd-button-link slot="left-buttons" href="#" kind="outline-white">Learn more</bd-button-link>
|
|
33
|
+
|
|
34
|
+
<bd-picture slot="icon" ...></bd-picture>
|
|
35
|
+
<bd-button-link slot="right-buttons" href="#" kind="white">Read the report</bd-button-link>
|
|
35
36
|
</bd-announcement-banner>
|
|
36
37
|
\`\`\`
|
|
37
38
|
|
|
@@ -39,23 +40,23 @@ Pentru varianta centrată, cu butoane, vezi \`bd-cta-banner\`.
|
|
|
39
40
|
| Attribute | Type | Default | Description |
|
|
40
41
|
|------------------|--------|----------|------------------------------------------------------------|
|
|
41
42
|
| \`title\` | String | \`""\` | Titlul mare, afișat în coloana stângă (sau slot-ul \`title\`) |
|
|
42
|
-
| \`caption\` | String | \`""\` | Eticheta scurtă de deasupra descrierii, coloana dreaptă (opțional) |
|
|
43
43
|
| \`description\` | String | \`""\` | Textul descriptiv din coloana dreaptă (sau slot-ul \`description\`) |
|
|
44
44
|
|
|
45
45
|
### Slots
|
|
46
|
-
| Slot
|
|
47
|
-
|
|
48
|
-
| \`icon\`
|
|
49
|
-
| \`title\`
|
|
50
|
-
| \`
|
|
51
|
-
| \`
|
|
46
|
+
| Slot | Description |
|
|
47
|
+
|-------------------|----------------------------------------------------------------------|
|
|
48
|
+
| \`left-icon\` | Imagine/iconiță opțională, deasupra titlului, coloana stângă |
|
|
49
|
+
| \`title\` | Suprascrie atributul \`title\` dacă are conținut |
|
|
50
|
+
| \`left-buttons\` | Butoane opționale, sub titlu, coloana stângă |
|
|
51
|
+
| \`icon\` | Iconița/logo-ul din coloana dreaptă (ex: \`bd-picture\`) |
|
|
52
|
+
| \`description\` | Suprascrie atributul \`description\` dacă are conținut |
|
|
53
|
+
| \`right-buttons\` | Butoane opționale, sub descriere, coloana dreaptă |
|
|
52
54
|
`
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
},
|
|
56
58
|
argTypes: {
|
|
57
59
|
title : { control: "text", table: { category: "Content" } },
|
|
58
|
-
caption : { control: "text", table: { category: "Content" } },
|
|
59
60
|
description: { control: "text", table: { category: "Content" } }
|
|
60
61
|
}
|
|
61
62
|
};
|
|
@@ -79,9 +80,9 @@ const gasaSources = [
|
|
|
79
80
|
|
|
80
81
|
const gasaFallback = "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=750&format=png&optimize=medium";
|
|
81
82
|
|
|
82
|
-
const gasaPicture = () => html`
|
|
83
|
+
const gasaPicture = (slot = "icon") => html`
|
|
83
84
|
<bd-picture
|
|
84
|
-
slot
|
|
85
|
+
slot=${slot}
|
|
85
86
|
.sources=${gasaSources}
|
|
86
87
|
.fallbackSrc=${gasaFallback}
|
|
87
88
|
alt="Global Anti-Scam Alliance logo"
|
|
@@ -97,13 +98,11 @@ export const Default = {
|
|
|
97
98
|
name: "1. Default",
|
|
98
99
|
args: {
|
|
99
100
|
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
100
|
-
caption : "Global Anti-Scam Alliance",
|
|
101
101
|
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
102
102
|
},
|
|
103
103
|
render: (args) => html`
|
|
104
104
|
<bd-announcement-banner
|
|
105
105
|
title=${args.title}
|
|
106
|
-
caption=${args.caption}
|
|
107
106
|
description=${args.description}
|
|
108
107
|
>
|
|
109
108
|
${gasaPicture()}
|
|
@@ -112,23 +111,21 @@ export const Default = {
|
|
|
112
111
|
parameters: {
|
|
113
112
|
docs: {
|
|
114
113
|
description: {
|
|
115
|
-
story: "Varianta completă: titlu,
|
|
114
|
+
story: "Varianta completă: titlu, descriere și iconiță în dreapta — identică cu versiunea originală, fără elementele noi opționale."
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
};
|
|
120
119
|
|
|
121
120
|
export const NoCaption = {
|
|
122
|
-
name: "2. Fără
|
|
121
|
+
name: "2. Fără descriere lungă",
|
|
123
122
|
args: {
|
|
124
123
|
title : "Bitdefender named a Leader in the 2026 Endpoint Protection report",
|
|
125
|
-
caption : "",
|
|
126
124
|
description: "Recognised for detection accuracy, low false-positive rate, and minimal performance impact across three consecutive evaluation cycles."
|
|
127
125
|
},
|
|
128
126
|
render: (args) => html`
|
|
129
127
|
<bd-announcement-banner
|
|
130
128
|
title=${args.title}
|
|
131
|
-
caption=${args.caption}
|
|
132
129
|
description=${args.description}
|
|
133
130
|
>
|
|
134
131
|
${gasaPicture()}
|
|
@@ -137,7 +134,7 @@ export const NoCaption = {
|
|
|
137
134
|
parameters: {
|
|
138
135
|
docs: {
|
|
139
136
|
description: {
|
|
140
|
-
story: "
|
|
137
|
+
story: "Titlu + descriere + iconiță, fără sloturile noi de imagine stânga sau butoane."
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
}
|
|
@@ -147,7 +144,6 @@ export const TitleOnly = {
|
|
|
147
144
|
name: "3. Doar titlu",
|
|
148
145
|
args: {
|
|
149
146
|
title : "Bitdefender now supports macOS Tahoe on day one",
|
|
150
|
-
caption : "",
|
|
151
147
|
description: ""
|
|
152
148
|
},
|
|
153
149
|
render: (args) => html`
|
|
@@ -158,7 +154,81 @@ export const TitleOnly = {
|
|
|
158
154
|
parameters: {
|
|
159
155
|
docs: {
|
|
160
156
|
description: {
|
|
161
|
-
story: "Minimal — doar titlu și iconiță, fără
|
|
157
|
+
story: "Minimal — doar titlu și iconiță, fără descriere sau butoane."
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const WithButtonsBothSides = {
|
|
164
|
+
name: "4. Butoane pe ambele coloane",
|
|
165
|
+
args: {
|
|
166
|
+
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
167
|
+
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
168
|
+
},
|
|
169
|
+
render: (args) => html`
|
|
170
|
+
<bd-announcement-banner
|
|
171
|
+
title=${args.title}
|
|
172
|
+
description=${args.description}
|
|
173
|
+
>
|
|
174
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Learn more</bd-button-link>
|
|
175
|
+
<bd-button-link slot="left-buttons" href="#" kind="outline-white" size="sm">Our commitment</bd-button-link>
|
|
176
|
+
|
|
177
|
+
${gasaPicture()}
|
|
178
|
+
|
|
179
|
+
<bd-button-link slot="right-buttons" href="#" kind="outline-white" size="sm">Read the report</bd-button-link>
|
|
180
|
+
</bd-announcement-banner>
|
|
181
|
+
`,
|
|
182
|
+
parameters: {
|
|
183
|
+
docs: {
|
|
184
|
+
description: {
|
|
185
|
+
story: "Butoane atât sub titlu (`left-buttons`) cât și sub descriere (`right-buttons`) — fiecare coloană poate avea mai multe butoane, aliniate pe rând cu wrap."
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const WithLeftImage = {
|
|
192
|
+
name: "5. Imagine și în stânga",
|
|
193
|
+
args: {
|
|
194
|
+
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
195
|
+
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
196
|
+
},
|
|
197
|
+
render: (args) => html`
|
|
198
|
+
<bd-announcement-banner
|
|
199
|
+
title=${args.title}
|
|
200
|
+
description=${args.description}
|
|
201
|
+
>
|
|
202
|
+
${gasaPicture("left-icon")}
|
|
203
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Learn more</bd-button-link>
|
|
204
|
+
|
|
205
|
+
</bd-announcement-banner>
|
|
206
|
+
`,
|
|
207
|
+
parameters: {
|
|
208
|
+
docs: {
|
|
209
|
+
description: {
|
|
210
|
+
story: "Imagine opțională deasupra titlului (`left-icon`), plus buton sub titlu — coloana stângă poate avea propriul set de imagine + text + buton, independent de coloana dreaptă."
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const LeftOnly = {
|
|
217
|
+
name: "6. Doar coloana stângă (fără icon/description dreapta)",
|
|
218
|
+
args: {
|
|
219
|
+
title : "Bitdefender now supports macOS Tahoe on day one",
|
|
220
|
+
description: ""
|
|
221
|
+
},
|
|
222
|
+
render: (args) => html`
|
|
223
|
+
<bd-announcement-banner title=${args.title}>
|
|
224
|
+
${gasaPicture("left-icon")}
|
|
225
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Download now</bd-button-link>
|
|
226
|
+
</bd-announcement-banner>
|
|
227
|
+
`,
|
|
228
|
+
parameters: {
|
|
229
|
+
docs: {
|
|
230
|
+
description: {
|
|
231
|
+
story: "Doar imagine + titlu + buton în stânga — coloana dreaptă rămâne complet goală și nu ocupă spațiu inutil, pentru că niciun slot din ea nu are conținut."
|
|
162
232
|
}
|
|
163
233
|
}
|
|
164
234
|
}
|
|
@@ -168,22 +238,21 @@ export const Playground = {
|
|
|
168
238
|
name: "Playground",
|
|
169
239
|
args: {
|
|
170
240
|
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
171
|
-
caption : "Global Anti-Scam Alliance",
|
|
172
241
|
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
173
242
|
},
|
|
174
243
|
render: (args) => html`
|
|
175
244
|
<bd-announcement-banner
|
|
176
245
|
title=${args.title}
|
|
177
|
-
caption=${args.caption}
|
|
178
246
|
description=${args.description}
|
|
179
247
|
>
|
|
180
248
|
${gasaPicture()}
|
|
249
|
+
<bd-button-link slot="right-buttons" href="#" kind="outline-white" size="sm">Read the report</bd-button-link>
|
|
181
250
|
</bd-announcement-banner>
|
|
182
251
|
`,
|
|
183
252
|
parameters: {
|
|
184
253
|
docs: {
|
|
185
254
|
description: {
|
|
186
|
-
story: "Interactiv — modifică `title
|
|
255
|
+
story: "Interactiv — modifică `title` și `description` din Controls."
|
|
187
256
|
}
|
|
188
257
|
}
|
|
189
258
|
}
|