@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 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.0",
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.37",
80
- "@repobit/dex-store-elements": "2.1.7",
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": "1e626106ffdf4df5443c8da6fa45fa4dae48a2c7"
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="65px"
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="65px"
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="65px"
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="65px"\` so they pin themselves *below*
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
- bottom: calc(-1 * var(--spacing-14));
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
- // wait a frame so the .active class has actually painted before
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 = this.parentElement;
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(this.stickyTop) || 0;
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", `${parentRect.left}px`);
221
- this.style.setProperty("--sticky-width", `${parentRect.width}px`);
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-title {
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 280px;
38
- max-width: 360px;
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-title,
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 — a title on the left, an
9
- * icon + description on the right. No buttons; for
10
- * partnership/announcement callouts (e.g. "Bitdefender joined the Global
11
- * Anti-Scam Alliance"), not calls to action. See bd-cta-banner for the
12
- * centred, button-driven variant.
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
- = * description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
19
+ * description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
17
20
  * >
18
- * <bd-gasa-icon slot="icon" width="140" height="40" color="#fff"></bd-gasa-icon>
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 : { type: String },
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
- <bd-h as="h2" fontWeight="400" class="bd-announcementbanner-title">
39
- <slot name="title">${this.title}</slot>
40
- </bd-h>
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
- ? html`<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-description"><slot name="description">${this.description}</slot></bd-p>`
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. Nu are butoane — e gândit pentru anunțuri de tip parteneriat sau
18
- apartenență la o organizație, nu pentru CTA-uri.
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
- slot="icon"
31
- .sources=\${sources}
32
- .fallbackSrc=\${fallbackSrc}
33
- alt="Global Anti-Scam Alliance logo"
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 | Description |
47
- |-----------------|--------------------------------------------------------------------|
48
- | \`icon\` | Iconița/logo-ul din coloana dreaptă (ex: \`bd-picture\`) |
49
- | \`title\` | Suprascrie atributul \`title\` dacă are conținut |
50
- | \`caption\` | Suprascrie atributul \`caption\` dacă are conținut |
51
- | \`description\` | Suprascrie atributul \`description\` dacă are conținut |
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="icon"
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, caption, descriere și iconiță."
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ă caption",
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: "Fără caption — util când titlul e suficient de descriptiv."
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ă caption sau descriere."
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`, `caption` și `description` din Controls."
255
+ story: "Interactiv — modifică `title` și `description` din Controls."
187
256
  }
188
257
  }
189
258
  }