@repobit/dex-system-design 0.23.16 → 0.23.18

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,20 @@
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.23.18](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.23.17...@repobit/dex-system-design@0.23.18) (2026-04-30)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **DEX-1014:** css adjustments for anchor-nav
11
+
12
+
13
+ ## [0.23.17](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.23.16...@repobit/dex-system-design@0.23.17) (2026-04-29)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **DEX-1014:** adjustments for anchor-nav. Modified structure
18
+
19
+
6
20
  ## [0.23.16](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.23.15...@repobit/dex-system-design@0.23.16) (2026-04-23)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobit/dex-system-design",
3
- "version": "0.23.16",
3
+ "version": "0.23.18",
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",
@@ -89,5 +89,5 @@
89
89
  "volta": {
90
90
  "node": "24.14.0"
91
91
  },
92
- "gitHead": "0d063cda634ce56449d0dd9501bc86fc6d3d818e"
92
+ "gitHead": "46e6f636b408a309529072a2ed36ad57b7a0e5af"
93
93
  }
@@ -107,7 +107,7 @@ class ButtonLink extends LitElement {
107
107
  size : { type: String },
108
108
  customClass: { type: String },
109
109
  kind : { type: String },
110
- fullWidth : { type: Boolean },
110
+ fullWidth : { type: Boolean, attribute: "fullwidth" },
111
111
  strong : { type: Boolean },
112
112
  fontSize : { type: String, attribute: "font-size" },
113
113
  fontWeight : { type: String, attribute: "font-weight" },
@@ -29,8 +29,16 @@ export default css`
29
29
  --bd-accesibility-focus: var(--color-blue-500);
30
30
  }
31
31
 
32
- /* Double rAF focus pattern */
33
- :host([data-bd-button-focus]) {
32
+ :host([fullwidth]) {
33
+ display: block;
34
+ width: 100%;
35
+ }
36
+
37
+ .max--width {
38
+ width: 100%;
39
+ display: flex;
40
+ }
41
+ :host([data-bd-button-focus]) {
34
42
  outline: var(--spacing-2) solid var(--bd-accesibility-focus);
35
43
  outline-offset: var(--spacing-2);
36
44
  border-radius: var(--radius-md);
@@ -167,15 +175,7 @@ export default css`
167
175
  font-weight: var(--typography-fontWeight-semibold);
168
176
  }
169
177
 
170
- .max--width {
171
- width: 100%;
172
- display: block;
173
- }
174
-
175
- .button.max--width {
176
- width: 100%;
177
- }
178
-
178
+
179
179
  /* Asigură-te că slot-ul ocupă întreg spațiul */
180
180
  .button > ::slotted(*) {
181
181
  display: inline-block;
@@ -3,35 +3,35 @@ import { css } from "lit";
3
3
  export const anchorNavStyles = css`
4
4
  :host {
5
5
  display: block;
6
- position: sticky;
7
- top: var(--spacing-0);
8
- z-index: 1000;
9
- border-top: var(--border-width-1) solid var(--color-neutral-100);
10
- border-bottom: var(--border-width-1) solid var(--color-neutral-100);
11
- padding-block: var(--spacing-10);
12
- padding-inline: var(--layout-ensemble-inline-padding);
13
6
  width: 100%;
14
- box-sizing: border-box;
15
7
  background: var(--color-neutral-0);
16
- margin-bottom: var(--spacing-0);
17
8
  --bd-accesibility-focus: var(--color-blue-500);
9
+ --anchor-nav-max-width: 1290px;
18
10
  }
19
11
 
20
- a:focus-visible,
21
- .bd-anchor-nav__cta:focus-visible,
22
- .bd-anchor-nav__dropdown-toggle:focus-visible,
23
- .bd-anchor-nav__dropdown-option:focus-visible {
24
- outline: var(--spacing-2) solid var(--bd-accesibility-focus);
25
- outline-offset: var(--spacing-2);
26
- border-radius: var(--space-2xs);
12
+ :host(.is-sticky) {
13
+ position: fixed;
14
+ top: 0;
15
+ left: var(--sticky-left, 0);
16
+ width: var(--sticky-width, 100%);
17
+ box-shadow: var(--shadow-sm);
18
+ z-index: 1000;
27
19
  }
28
20
 
21
+ /* Border-urile sunt pe nav - full width */
29
22
  nav {
30
23
  display: block;
31
24
  margin: var(--spacing-0);
32
25
  padding: var(--spacing-0);
33
26
  width: 100%;
34
27
  box-sizing: border-box;
28
+ border-top: var(--border-width-1) solid var(--color-neutral-100);
29
+ border-bottom: var(--border-width-1) solid var(--color-neutral-100);
30
+ }
31
+
32
+ /* Când e sticky, păstrăm aceleași border-uri */
33
+ :host(.is-sticky) nav {
34
+ background: var(--color-neutral-0);
35
35
  }
36
36
 
37
37
  .bd-anchor-nav__inner {
@@ -41,11 +41,24 @@ export const anchorNavStyles = css`
41
41
  align-items: center;
42
42
  gap: var(--spacing-32);
43
43
  width: 100%;
44
- max-width: var(--layout-compare-grid-max);
44
+ max-width: var(--anchor-nav-max-width);
45
+ padding-block: var(--spacing-10);
46
+ padding-inline: var(--layout-ensemble-inline-padding, var(--spacing-24));
45
47
  margin-inline: auto;
46
48
  box-sizing: border-box;
47
49
  }
48
50
 
51
+ /* Restul codului tău CSS rămâne identic de aici în jos */
52
+
53
+ .anchor-link:focus-visible,
54
+ .bd-anchor-nav__cta:focus-visible,
55
+ .bd-anchor-nav__dropdown-toggle:focus-visible,
56
+ .bd-anchor-nav__dropdown-option:focus-visible {
57
+ outline: var(--spacing-2) solid var(--bd-accesibility-focus);
58
+ outline-offset: var(--spacing-2);
59
+ border-radius: var(--space-2xs);
60
+ }
61
+
49
62
  .anchor-links {
50
63
  position: relative;
51
64
  display: flex;
@@ -57,8 +70,67 @@ export const anchorNavStyles = css`
57
70
  min-width: 0;
58
71
  }
59
72
 
60
- .anchor-links--desktop {
61
- display: flex;
73
+ .anchor-links--desktop { display: flex; }
74
+
75
+ .anchor-link {
76
+ position: relative;
77
+ text-decoration: none;
78
+ color: var(--color-neutral-900);
79
+ font-weight: var(--typography-fontWeight-normal);
80
+ padding: var(--spacing-10) var(--spacing-0);
81
+ display: inline-block;
82
+ font-size: var(--typography-body-regular-fontSize);
83
+ font-family: var(--typography-body-regular-fontFamily);
84
+ transition: color var(--transition-duration-fast) var(--transition-easing-smooth);
85
+ }
86
+
87
+ .anchor-link.active {
88
+ font-weight: var(--typography-fontWeight-semibold);
89
+ color: var(--color-neutral-900);
90
+ }
91
+
92
+ .anchor-link,
93
+ .anchor-link.bd-link,
94
+ .anchor-link.bd-link--primary,
95
+ .anchor-link.bd-link--secondary {
96
+ text-decoration: none !important;
97
+ }
98
+
99
+ .anchor-link:hover,
100
+ .anchor-link.bd-link:hover,
101
+ .anchor-link.bd-link--primary:hover,
102
+ .anchor-link.bd-link--secondary:hover {
103
+ text-decoration: none !important;
104
+ }
105
+
106
+ .anchor-link.bd-link--primary:hover {
107
+ color: var(--color-blue-500) !important;
108
+ }
109
+
110
+ .anchor-link.bd-link--secondary:hover {
111
+ color: var(--color-neutral-900) !important;
112
+ }
113
+
114
+ .anchor-link.active:hover,
115
+ .anchor-link.active.bd-link--primary:hover,
116
+ .anchor-link.active.bd-link--secondary:hover {
117
+ color: var(--color-neutral-900) !important;
118
+ text-decoration: none !important;
119
+ }
120
+
121
+ .anchor-link::after {
122
+ content: "";
123
+ position: absolute;
124
+ left: var(--spacing-0);
125
+ bottom: calc(-1 * var(--spacing-14));
126
+ width: 0%;
127
+ height: var(--border-width-3);
128
+ background-color: var(--color-blue-500);
129
+ transition: width var(--transition-duration-slow) var(--transition-easing-ease-out);
130
+ }
131
+
132
+ .anchor-link.active::after {
133
+ width: 100%;
62
134
  }
63
135
 
64
136
  .bd-anchor-nav__dropdown {
@@ -69,8 +141,6 @@ export const anchorNavStyles = css`
69
141
  width: 100%;
70
142
  }
71
143
 
72
- /* ── Dropdown toggle ── */
73
-
74
144
  .bd-anchor-nav__dropdown-toggle {
75
145
  display: flex;
76
146
  align-items: center;
@@ -104,7 +174,6 @@ export const anchorNavStyles = css`
104
174
  .bd-anchor-nav__dropdown-toggle[aria-expanded="true"] {
105
175
  background: var(--color-neutral-50);
106
176
  border-color: var(--color-blue-200);
107
- border-radius: var(--radius-lg) var(--radius-lg) var(--spacing-0) var(--spacing-0);
108
177
  }
109
178
 
110
179
  .bd-anchor-nav__dropdown-label {
@@ -128,8 +197,6 @@ export const anchorNavStyles = css`
128
197
  color: var(--color-blue-500);
129
198
  }
130
199
 
131
- /* ── Dropdown panel ── */
132
-
133
200
  .bd-anchor-nav__dropdown-panel {
134
201
  position: absolute;
135
202
  left: var(--spacing-0);
@@ -154,8 +221,6 @@ export const anchorNavStyles = css`
154
221
  gap: var(--spacing-2);
155
222
  }
156
223
 
157
- /* ── Dropdown options ── */
158
-
159
224
  .bd-anchor-nav__dropdown-option {
160
225
  display: flex;
161
226
  align-items: center;
@@ -194,52 +259,10 @@ export const anchorNavStyles = css`
194
259
  background: var(--color-blue-50);
195
260
  }
196
261
 
197
- /* ── Desktop links ── */
198
-
199
- a {
200
- position: relative;
201
- text-decoration: none;
202
- color: var(--color-neutral-900);
203
- font-weight: var(--typography-fontWeight-normal);
204
- padding: var(--spacing-10) var(--spacing-0);
205
- display: inline-block;
206
- font-size: var(--typography-body-regular-fontSize);
207
- font-family: var(--typography-body-regular-fontFamily);
208
- transition: color var(--transition-duration-fast) var(--transition-easing-smooth);
209
- }
210
-
211
- a.active {
212
- font-weight: var(--typography-fontWeight-semibold);
213
- color: var(--color-neutral-900);
214
- }
215
-
216
- a:hover:not(.active) {
217
- color: var(--color-blue-500);
218
- }
219
- .bd-anchor-nav__cta:hover {
220
- color: var(--color-neutral-0) !important;
221
- }
222
- a.active:hover {
223
- color: var(--color-neutral-900);
262
+ ::slotted([slot="cta"]) {
263
+ flex-shrink: 0;
224
264
  }
225
265
 
226
- a::after {
227
- content: "";
228
- position: absolute;
229
- left: var(--spacing-0);
230
- bottom: calc(-1 * var(--spacing-14));
231
- width: 0%;
232
- height: var(--border-width-3);
233
- background-color: var(--color-blue-500);
234
- transition: width var(--transition-duration-slow) var(--transition-easing-ease-out);
235
- }
236
-
237
- a.active::after {
238
- width: 100%;
239
- }
240
-
241
- /* ── CTA button ── */
242
-
243
266
  .bd-anchor-nav__cta {
244
267
  font-family: var(--typography-fontFamily-sans);
245
268
  font-size: var(--typography-fontSize-lg);
@@ -251,32 +274,32 @@ export const anchorNavStyles = css`
251
274
  cursor: pointer;
252
275
  background: var(--color-red-500);
253
276
  color: var(--color-neutral-0);
277
+ text-decoration: none !important;
254
278
  transition: background var(--transition-duration-fast) var(--transition-easing-smooth);
255
279
  }
256
280
 
257
281
  .bd-anchor-nav__cta:hover {
258
282
  background: var(--color-red-600);
283
+ color: var(--color-neutral-0);
284
+ text-decoration: none !important;
259
285
  }
260
286
 
261
287
  .bd-anchor-nav__cta:active {
262
288
  background: var(--color-red-700);
289
+ color: var(--color-neutral-0);
263
290
  }
264
291
 
265
- /* ── Animations ── */
292
+ .bd-anchor-nav__cta::after {
293
+ display: none;
294
+ }
266
295
 
267
296
  @keyframes bd-anchor-nav-dropdown-label-swap {
268
- from {
269
- opacity: 0.35;
270
- transform: translateY(var(--spacing-4));
271
- }
272
- to {
273
- opacity: 1;
274
- transform: translateY(var(--spacing-0));
275
- }
297
+ from { opacity: 0.35; transform: translateY(var(--spacing-4)); }
298
+ to { opacity: 1; transform: translateY(var(--spacing-0)); }
276
299
  }
277
300
 
278
301
  @media (prefers-reduced-motion: reduce) {
279
- a::after,
302
+ .anchor-link::after,
280
303
  .bd-anchor-nav__dropdown-chevron,
281
304
  .bd-cta,
282
305
  .bd-anchor-nav__dropdown-label--swap {
@@ -284,27 +307,16 @@ export const anchorNavStyles = css`
284
307
  }
285
308
  }
286
309
 
287
- /* ── Mobile ── */
288
-
289
310
  @media (max-width: 768px) {
290
- :host {
291
- padding-block: var(--spacing-10);
292
- padding-inline: var(--layout-ensemble-inline-padding);
293
- }
294
-
295
311
  .bd-anchor-nav__inner {
296
312
  flex-direction: column;
297
313
  align-items: stretch;
298
314
  gap: var(--spacing-16);
299
315
  }
300
316
 
301
- .anchor-links--desktop {
302
- display: none !important;
303
- }
317
+ .anchor-links--desktop { display: none !important; }
304
318
 
305
- .bd-anchor-nav__dropdown {
306
- display: block;
307
- }
319
+ .bd-anchor-nav__dropdown { display: block; }
308
320
 
309
321
  .bd-anchor-nav__dropdown-label.bd-anchor-nav__dropdown-label--swap {
310
322
  animation: bd-anchor-nav-dropdown-label-swap var(--transition-duration-normal)
@@ -1,5 +1,7 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
2
  import { tokens } from "../../tokens/tokens.js";
3
+ import "../Button/Button.js";
4
+ import "../link/link.js";
3
5
  import { anchorNavStyles } from "./anchor-nav.css.js";
4
6
 
5
7
  function parseLengthToPx(value) {
@@ -30,7 +32,8 @@ class BdAnchorNav extends LitElement {
30
32
  _mobileCtaRevealed: { type: Boolean, state: true },
31
33
  ctaLabel : { type: String, attribute: "cta-label" },
32
34
  ctaHref : { type: String, attribute: "cta-href" },
33
- items : { type: Array }
35
+ items : { type: Array },
36
+ maxWidth : { type: String, attribute: "max-width" }
34
37
  };
35
38
 
36
39
  static styles = [tokens, anchorNavStyles];
@@ -44,6 +47,7 @@ class BdAnchorNav extends LitElement {
44
47
  this.ctaLabel = "Buy now";
45
48
  this.ctaHref = "";
46
49
  this.items = null;
50
+ this.maxWidth = "";
47
51
  this._panelId = `bd-anchor-nav-panel-${Math.random().toString(36)
48
52
  .slice(2, 11)}`;
49
53
  this._onScrollSpy = this._onScrollSpy.bind(this);
@@ -53,15 +57,21 @@ class BdAnchorNav extends LitElement {
53
57
  this._programmaticScrollTargetId = null;
54
58
  this._unlockScrollSpyTimer = null;
55
59
  this._scrollEndUnlockHandler = null;
60
+ this._stickyOriginalTop = null;
61
+ this._stickyPlaceholder = null;
62
+ this._onStickyScroll = null;
63
+ this._handleResize = this._handleResize.bind(this);
56
64
  }
57
65
 
58
66
  connectedCallback() {
59
67
  super.connectedCallback();
60
68
  window.addEventListener("scroll", this._onScrollSpy, { passive: true });
61
69
  window.addEventListener("resize", this._onScrollSpy, { passive: true });
70
+ window.addEventListener("resize", this._handleResize);
62
71
  document.addEventListener("pointerdown", this._onDocumentPointerDown, true);
63
72
  document.addEventListener("keydown", this._onKeydown, true);
64
73
  queueMicrotask(() => this._runScrollSpy());
74
+ this._initSticky();
65
75
  }
66
76
 
67
77
  disconnectedCallback() {
@@ -69,8 +79,111 @@ class BdAnchorNav extends LitElement {
69
79
  this._cancelScrollSpyUnlock();
70
80
  window.removeEventListener("scroll", this._onScrollSpy);
71
81
  window.removeEventListener("resize", this._onScrollSpy);
82
+ window.removeEventListener("resize", this._handleResize);
72
83
  document.removeEventListener("pointerdown", this._onDocumentPointerDown, true);
73
84
  document.removeEventListener("keydown", this._onKeydown, true);
85
+ this._destroySticky();
86
+ }
87
+
88
+ _handleResize() {
89
+ this._handleMobileFullWidth();
90
+ }
91
+
92
+ _handleMobileFullWidth() {
93
+ const isMobile = window.matchMedia("(max-width: 768px)").matches;
94
+ const ctaSlot = this.shadowRoot?.querySelector('slot[name="cta"]');
95
+
96
+ if (!ctaSlot) return;
97
+
98
+ const assignedElements = ctaSlot.assignedElements();
99
+ const buttonLink = assignedElements.find(el => el.tagName === 'BD-BUTTON-LINK');
100
+
101
+ if (buttonLink) {
102
+ if (isMobile) {
103
+ buttonLink.setAttribute('fullwidth', '');
104
+ } else {
105
+ buttonLink.removeAttribute('fullwidth');
106
+ }
107
+ }
108
+ }
109
+
110
+ _initSticky() {
111
+ this._stickyOriginalTop = null;
112
+
113
+ this._onStickyScroll = () => {
114
+ const parent = this.parentElement;
115
+ if (!parent) return;
116
+
117
+ const parentRect = parent.getBoundingClientRect();
118
+ const navHeight = this._stickyPlaceholder
119
+ ? this._stickyPlaceholder.offsetHeight
120
+ : this.offsetHeight;
121
+
122
+ if (this._stickyOriginalTop === null) {
123
+ const rect = this.getBoundingClientRect();
124
+ this._stickyOriginalTop = rect.top + window.scrollY;
125
+ }
126
+
127
+ const passedTop = window.scrollY >= this._stickyOriginalTop;
128
+ const parentExited = parentRect.bottom <= navHeight;
129
+
130
+ if (passedTop && !parentExited) {
131
+ if (!this.classList.contains("is-sticky")) {
132
+ if (!this._stickyPlaceholder) {
133
+ this._stickyPlaceholder = document.createElement("div");
134
+ this._stickyPlaceholder.style.height = `${this.offsetHeight}px`;
135
+ this._stickyPlaceholder.style.display = "block";
136
+ this.parentNode.insertBefore(this._stickyPlaceholder, this);
137
+ }
138
+ this.classList.add("is-sticky");
139
+ }
140
+ this.style.setProperty("--sticky-left", `${parentRect.left}px`);
141
+ this.style.setProperty("--sticky-width", `${parentRect.width}px`);
142
+ } else {
143
+ if (this.classList.contains("is-sticky")) {
144
+ this.classList.remove("is-sticky");
145
+ this.style.removeProperty("--sticky-left");
146
+ this.style.removeProperty("--sticky-width");
147
+ if (this._stickyPlaceholder) {
148
+ this._stickyPlaceholder.remove();
149
+ this._stickyPlaceholder = null;
150
+ }
151
+ }
152
+ }
153
+ };
154
+
155
+ this._onStickyResize = () => {
156
+ this._stickyOriginalTop = null;
157
+ if (this.classList.contains("is-sticky")) {
158
+ const parent = this.parentElement;
159
+ const parentRect = parent.getBoundingClientRect();
160
+ this.style.setProperty("--sticky-left", `${parentRect.left}px`);
161
+ this.style.setProperty("--sticky-width", `${parentRect.width}px`);
162
+ }
163
+ this._onStickyScroll();
164
+ };
165
+
166
+ window.addEventListener("scroll", this._onStickyScroll, { passive: true });
167
+ window.addEventListener("resize", this._onStickyResize, { passive: true });
168
+ requestAnimationFrame(() => this._onStickyScroll());
169
+ }
170
+
171
+ _destroySticky() {
172
+ if (this._onStickyScroll) {
173
+ window.removeEventListener("scroll", this._onStickyScroll);
174
+ this._onStickyScroll = null;
175
+ }
176
+ if (this._onStickyResize) {
177
+ window.removeEventListener("resize", this._onStickyResize);
178
+ this._onStickyResize = null;
179
+ }
180
+ if (this._stickyPlaceholder) {
181
+ this._stickyPlaceholder.remove();
182
+ this._stickyPlaceholder = null;
183
+ }
184
+ this.style.removeProperty("--sticky-left");
185
+ this.style.removeProperty("--sticky-width");
186
+ this.classList.remove("is-sticky");
74
187
  }
75
188
 
76
189
  _cancelScrollSpyUnlock() {
@@ -191,6 +304,7 @@ class BdAnchorNav extends LitElement {
191
304
  this.requestUpdate();
192
305
  }
193
306
  queueMicrotask(() => this._runScrollSpy());
307
+ this._handleMobileFullWidth();
194
308
  }
195
309
 
196
310
  updated(changedProperties) {
@@ -287,22 +401,29 @@ class BdAnchorNav extends LitElement {
287
401
  ${resolved.map(({ id, title, href }) => {
288
402
  const isActive = this.activeId === id;
289
403
  return html`
290
- <a
404
+ <bd-link
291
405
  href="${href}"
292
- class="${isActive ? "active" : ""}"
406
+ kind="${isActive ? "primary" : "secondary"}"
407
+ no-underline
408
+ class="anchor-link ${isActive ? "active" : ""}"
409
+ color="var(--color-neutral-900)"
410
+ ?active=${isActive}
293
411
  aria-current=${isActive ? "true" : nothing}
294
412
  @click=${(e) => this.handleClick(e, id, href)}
295
- >${title}</a>
413
+ >${title}</bd-link>
296
414
  `;
297
415
  })}
298
416
  </div>
299
417
  `;
300
418
 
419
+ const innerStyle = this.maxWidth ? `max-width: ${this.maxWidth};` : '';
420
+
301
421
  return html`
302
422
  <nav aria-label=${this.navLabel}>
303
423
  <div
304
424
  class="bd-anchor-nav__inner"
305
425
  part="inner"
426
+ style="${innerStyle}"
306
427
  ?data-mobile-cta-visible=${this._mobileCtaRevealed}
307
428
  >
308
429
  ${linkRow("anchor-links anchor-links--desktop")}
@@ -350,11 +471,7 @@ class BdAnchorNav extends LitElement {
350
471
  </div>
351
472
 
352
473
  <div class="bd-cta">
353
- <a
354
- class="bd-anchor-nav__cta"
355
- href="${this.ctaHref || "#section-pricing"}"
356
- part="buy-button"
357
- >${this.ctaLabel}</a>
474
+ <slot name="cta"></slot>
358
475
  </div>
359
476
  </div>
360
477
  </nav>
@@ -363,4 +480,4 @@ class BdAnchorNav extends LitElement {
363
480
  }
364
481
 
365
482
  customElements.define("bd-anchor-nav-item", BdAnchorNavItem);
366
- customElements.define("bd-anchor-nav", BdAnchorNav);
483
+ customElements.define("bd-anchor-nav", BdAnchorNav);
@@ -3,9 +3,8 @@ import "../Button/Button.js";
3
3
  import "../heading/heading.js";
4
4
  import "../image/image.js";
5
5
  import "../link/link.js";
6
- import "./anchor-nav.js";
7
-
8
6
  import "../paragraph/paragraph.js";
7
+ import "./anchor-nav.js";
9
8
 
10
9
  export default {
11
10
  title : "Components/AnchorNav",
@@ -14,49 +13,88 @@ export default {
14
13
  docs: {
15
14
  description: {
16
15
  component: `
17
- **BdAnchorNav** is a sticky navigation bar with anchor links and a customizable CTA button.
16
+ **BdAnchorNav** este un navbar sticky cu linkuri de ancorare și un buton CTA customizabil.
17
+
18
+ Lățimea conținutului interior se ajustează **automat** la cel mai lat sibling din containerul părinte,
19
+ prin \`ResizeObserver\` — fără a fi necesară setarea manuală a \`max-width\`.
20
+
21
+ ### Elemente
22
+ - \`<bd-anchor-nav>\` — containerul nav
23
+ - \`<bd-anchor-nav-item>\` — item declarativ (via atributul \`title\` și opțional \`href\`)
24
+ - \`slot="cta"\` — slot opțional pentru butonul CTA (fallback: \`<a>\` simplu)
25
+
26
+ ### Sticky behavior
27
+ - Când navul iese din viewport, devine \`position: fixed\` full-width
28
+ - Border-ul și background-ul sunt full-width
29
+ - Conținutul (linkuri + CTA) rămâne centrat și aliniat cu cel mai lat sibling
30
+
31
+ ---
32
+
33
+ ### Utilizare simplă (fallback CTA)
34
+ \`\`\`html
35
+ <div>
36
+ <bd-anchor-nav cta-label="Buy now" cta-href="https://www.bitdefender.com/buy">
37
+ <bd-anchor-nav-item title="Overview" href="#overview"></bd-anchor-nav-item>
38
+ <bd-anchor-nav-item title="Features" href="#features"></bd-anchor-nav-item>
39
+ <bd-anchor-nav-item title="Pricing" href="#pricing"></bd-anchor-nav-item>
40
+ </bd-anchor-nav>
18
41
 
19
- ### Elements
20
- - \`<bd-anchor-nav>\` — the nav container
21
- - \`<bd-anchor-nav-item>\` — declarative items (via \`title\` and optional \`href\` attributes)
42
+ <bd-compare-section>...</bd-compare-section>
43
+ <section id="overview">...</section>
44
+ </div>
45
+ \`\`\`
22
46
 
23
- ### Usage child elements
47
+ ### Utilizare cu slot CTA customizat
24
48
  \`\`\`html
25
- <bd-anchor-nav cta-label="Buy now" cta-href="https://www.bitdefender.com/buy">
26
- <bd-anchor-nav-item title="Overview" href="#anchor-1-section"></bd-anchor-nav-item>
27
- <bd-anchor-nav-item title="Features" href="#anchor-2-section"></bd-anchor-nav-item>
28
- <bd-anchor-nav-item title="Pricing" href="#section-pricing"></bd-anchor-nav-item>
49
+ <bd-anchor-nav>
50
+ <bd-anchor-nav-item title="Overview" href="#overview"></bd-anchor-nav-item>
51
+ <bd-anchor-nav-item title="Pricing" href="#pricing"></bd-anchor-nav-item>
52
+
53
+ <bd-button-link
54
+ slot="cta"
55
+ kind="danger"
56
+ size="md"
57
+ href="https://www.bitdefender.com/buy"
58
+ label="Cumpără acum"
59
+ buyLink
60
+ >
61
+ Cumpără acum
62
+ </bd-button-link>
29
63
  </bd-anchor-nav>
30
64
  \`\`\`
31
65
 
32
- ### Usage programmatic items prop
66
+ ### Utilizare programatică cu \`.items\`
33
67
  \`\`\`html
34
68
  <bd-anchor-nav
35
69
  cta-label="Buy now"
36
70
  cta-href="https://www.bitdefender.com/buy"
37
- .items=${[
38
- { title: "Overview", href: "#anchor-1-section" },
39
- { title: "Features", href: "#anchor-2-section" },
40
- { title: "Pricing", href: "#section-pricing" }
71
+ .items=\${[
72
+ { title: "Overview", href: "#overview" },
73
+ { title: "Features", href: "#features" },
74
+ { title: "Pricing", href: "#pricing" }
41
75
  ]}
42
76
  ></bd-anchor-nav>
43
77
  \`\`\`
44
78
 
45
- ### Attributes
46
-
47
- #### \`bd-anchor-nav\`
48
- | Attribute | Type | Default | Description |
49
- |--------------|--------|----------------------|------------------------------------------------------|
50
- | \`cta-label\` | String | \`"Buy now"\` | Text label for the CTA button |
51
- | \`cta-href\` | String | \`"#section-pricing"\` | href for the CTA button |
52
- | \`aria-label\` | String | \`"Section navigation"\` | Accessible label for the \`<nav>\` landmark |
53
- | \`items\` | Array | — | \`{ title, href }[]\` — overrides child elements |
54
-
55
- #### \`bd-anchor-nav-item\`
56
- | Attribute | Type | Description |
57
- |-----------|--------|----------------------------------------------------------|
58
- | \`title\` | String | Link label |
59
- | \`href\` | String | Target URL or anchor (default: \`#anchor-N-section\`) |
79
+ ---
80
+
81
+ ### Atribute \`bd-anchor-nav\`
82
+ | Atribut | Tip | Default | Descriere |
83
+ |--------------|--------|--------------------------|--------------------------------------------------|
84
+ | \`cta-label\` | String | \`"Buy now"\` | Textul butonului CTA fallback |
85
+ | \`cta-href\` | String | \`"#section-pricing"\` | href-ul butonului CTA fallback |
86
+ | \`aria-label\` | String | \`"Section navigation"\` | Label accesibil pentru \`<nav>\` |
87
+ | \`items\` | Array | — | \`{ title, href }[]\` — suprascrie child items |
88
+
89
+ ### Atribute \`bd-anchor-nav-item\`
90
+ | Atribut | Tip | Descriere |
91
+ |---------|--------|--------------------------------------------------------|
92
+ | \`title\` | String | Textul linkului |
93
+ | \`href\` | String | URL sau ancoră (default: \`#anchor-N-section\`) |
94
+
95
+ ### Slot \`cta\`
96
+ Orice element cu \`slot="cta"\` înlocuiește butonul CTA fallback.
97
+ Pe mobile devine automat full-width prin \`::slotted([slot="cta"])\`.
60
98
  `
61
99
  }
62
100
  }
@@ -64,17 +102,17 @@ export default {
64
102
  argTypes: {
65
103
  ctaLabel: {
66
104
  control : "text",
67
- description: "Text label for the CTA button",
105
+ description: "Textul butonului CTA fallback",
68
106
  table : { type: { summary: "string" }, defaultValue: { summary: "Buy now" }, category: "CTA" }
69
107
  },
70
108
  ctaHref: {
71
109
  control : "text",
72
- description: "href for the CTA button",
110
+ description: "href-ul butonului CTA fallback",
73
111
  table : { type: { summary: "string" }, defaultValue: { summary: "#section-pricing" }, category: "CTA" }
74
112
  },
75
113
  items: {
76
114
  control : "object",
77
- description: "Array of { title, href } objects (overrides child elements)",
115
+ description: "Array de { title, href } suprascrie child elements",
78
116
  table : { type: { summary: "{ title: string, href: string }[]" }, category: "Content" }
79
117
  }
80
118
  }
@@ -83,18 +121,36 @@ export default {
83
121
 
84
122
  // ─── Helpers ─────────────────────────────────────────────────────────────────
85
123
 
86
- const renderNavChildren = (labels, ctaLabel = "Buy now", ctaHref = "#section-pricing") => html`
124
+ const navItems = (labels) => labels.map((item) => html`
125
+ <bd-anchor-nav-item
126
+ title=${typeof item === "string" ? item : item.title}
127
+ href=${typeof item === "string" ? "" : (item.href || "")}
128
+ ></bd-anchor-nav-item>
129
+ `);
130
+
131
+ const renderSimple = (labels, ctaLabel = "Buy now", ctaHref = "#section-pricing") => html`
87
132
  <bd-anchor-nav cta-label=${ctaLabel} cta-href=${ctaHref}>
88
- ${labels.map((item) => html`
89
- <bd-anchor-nav-item
90
- title=${typeof item === "string" ? item : item.title}
91
- href=${typeof item === "string" ? "" : (item.href || "")}
92
- ></bd-anchor-nav-item>
93
- `)}
133
+ ${navItems(labels)}
94
134
  </bd-anchor-nav>
95
135
  `;
96
136
 
97
- const renderNavItems = (items, ctaLabel = "Buy now", ctaHref = "#section-pricing") => html`
137
+ const renderWithSlotCta = (labels, ctaLabel = "Cumpără acum", ctaHref = "#section-pricing") => html`
138
+ <bd-anchor-nav>
139
+ ${navItems(labels)}
140
+ <bd-button-link
141
+ slot="cta"
142
+ kind="danger"
143
+ size="md"
144
+ href=${ctaHref}
145
+ label=${ctaLabel}
146
+ font-size="16px"
147
+ font-weight="600"
148
+ buyLink
149
+ >${ctaLabel}</bd-button-link>
150
+ </bd-anchor-nav>
151
+ `;
152
+
153
+ const renderWithItems = (items, ctaLabel = "Buy now", ctaHref = "#section-pricing") => html`
98
154
  <bd-anchor-nav
99
155
  cta-label=${ctaLabel}
100
156
  cta-href=${ctaHref}
@@ -102,16 +158,92 @@ const renderNavItems = (items, ctaLabel = "Buy now", ctaHref = "#section-pricing
102
158
  ></bd-anchor-nav>
103
159
  `;
104
160
 
161
+ /** Wrapper cu conținut sibling pentru a demonstra auto max-width */
162
+ const withSiblingContent = (navTemplate) => html`
163
+ <div style="margin: 0 auto;">
164
+ ${navTemplate}
165
+ <div style="
166
+ max-width: 900px;
167
+ margin: 0 auto;
168
+ padding: 40px 24px;
169
+ background: #f0f9ff;
170
+ border: 2px dashed #bfdbfe;
171
+ border-radius: 8px;
172
+ text-align: center;
173
+ color: #1e40af;
174
+ font-family: sans-serif;
175
+ ">
176
+ <strong>Sibling content — 900px max-width</strong><br>
177
+ <span style="font-size: 0.875rem; opacity: 0.8;">
178
+ Navul se aliniază automat la lățimea acestui element
179
+ </span>
180
+ </div>
181
+ </div>
182
+ `;
183
+
105
184
 
106
185
  // ─── Stories ─────────────────────────────────────────────────────────────────
107
186
 
108
187
  export const Default = {
109
- name : "Default (4 items)",
110
- render : () => renderNavChildren(["Overview", "Features", "Reviews", "Pricing"]),
188
+ name : "Default fallback CTA",
189
+ render : () => renderSimple(["Overview", "Features", "Reviews", "Pricing"]),
111
190
  parameters: {
112
191
  docs: {
113
192
  description: {
114
- story: "Default nav with four anchor links. Each link auto-targets `#anchor-N-section`. The CTA defaults to `#section-pricing`."
193
+ story: "Nav cu 4 linkuri și butonul CTA fallback (`<a>` simplu). Fiecare link targhetează automat `#anchor-N-section`."
194
+ }
195
+ }
196
+ }
197
+ };
198
+
199
+ export const WithSlotCta = {
200
+ name : "Cu slot CTA — bd-button-link danger",
201
+ render : () => renderWithSlotCta(["Overview", "Features", "Reviews", "Pricing"]),
202
+ parameters: {
203
+ docs: {
204
+ description: {
205
+ story: "CTA customizat prin `slot=\"cta\"` cu `bd-button-link kind=\"danger\"`. Pe mobile devine automat full-width."
206
+ }
207
+ }
208
+ }
209
+ };
210
+
211
+ export const WithSlotCtaOutline = {
212
+ name : "Cu slot CTA — bd-button-link outline",
213
+ render: () => html`
214
+ <bd-anchor-nav>
215
+ ${navItems(["Overview", "Features", "Pricing"])}
216
+ <bd-button-link
217
+ slot="cta"
218
+ kind="outline"
219
+ size="md"
220
+ href="https://www.bitdefender.com/buy"
221
+ label="Cumpără acum"
222
+ font-size="16px"
223
+ font-weight="600"
224
+ >Cumpără acum</bd-button-link>
225
+ </bd-anchor-nav>
226
+ `,
227
+ parameters: {
228
+ docs: {
229
+ description: {
230
+ story: "CTA cu `kind=\"outline\"` — demonstrează flexibilitatea slot-ului CTA."
231
+ }
232
+ }
233
+ }
234
+ };
235
+
236
+ export const AutoMaxWidth = {
237
+ name : "Auto max-width — aliniere la sibling",
238
+ render: () => withSiblingContent(
239
+ renderSimple(["Overview", "Features", "Pricing"])
240
+ ),
241
+ parameters: {
242
+ docs: {
243
+ description: {
244
+ story: `Navul detectează automat lățimea celui mai lat sibling din containerul părinte
245
+ (via \`ResizeObserver\`) și centrează conținutul la acea lățime.
246
+ Border-ul rămâne full-width. Nu este necesară nicio configurare manuală.`
115
247
  }
116
248
  }
117
249
  }
@@ -119,7 +251,7 @@ export const Default = {
119
251
 
120
252
  export const CustomCtaHref = {
121
253
  name : "Custom CTA href",
122
- render: () => renderNavChildren(
254
+ render: () => renderSimple(
123
255
  ["Overview", "Features", "Reviews", "Pricing"],
124
256
  "Get Bitdefender",
125
257
  "https://www.bitdefender.com/buy"
@@ -127,14 +259,14 @@ export const CustomCtaHref = {
127
259
  parameters: {
128
260
  docs: {
129
261
  description: {
130
- story: "CTA button uses a full external URL via `cta-href`. The button label is overridden with `cta-label`."
262
+ story: "CTA fallback cu URL extern via `cta-href` și label customizat via `cta-label`."
131
263
  }
132
264
  }
133
265
  }
134
266
  };
135
267
 
136
268
  export const CustomItemHrefs = {
137
- name : "Custom item hrefs (child elements)",
269
+ name : "Custom item hrefs",
138
270
  render: () => html`
139
271
  <bd-anchor-nav cta-label="Buy now" cta-href="#section-pricing">
140
272
  <bd-anchor-nav-item title="Overview" href="#overview"></bd-anchor-nav-item>
@@ -146,15 +278,15 @@ export const CustomItemHrefs = {
146
278
  parameters: {
147
279
  docs: {
148
280
  description: {
149
- story: "Each `bd-anchor-nav-item` has a custom `href` attribute pointing to a specific section ID."
281
+ story: "Fiecare `bd-anchor-nav-item` are `href` customizat către un ID de secțiune specific."
150
282
  }
151
283
  }
152
284
  }
153
285
  };
154
286
 
155
287
  export const ProgrammaticItems = {
156
- name : "Programmatic items prop",
157
- render: () => renderNavItems(
288
+ name : "Programmatic .items prop",
289
+ render: () => renderWithItems(
158
290
  [
159
291
  { title: "Overview", href: "#anchor-1-section" },
160
292
  { title: "Features", href: "#anchor-2-section" },
@@ -167,7 +299,7 @@ export const ProgrammaticItems = {
167
299
  parameters: {
168
300
  docs: {
169
301
  description: {
170
- story: "Items defined via the `.items` property array instead of child elements. Useful for dynamic rendering from CMS data."
302
+ story: "Items definite via proprietatea `.items` util pentru date din CMS sau config externă."
171
303
  }
172
304
  }
173
305
  }
@@ -175,37 +307,31 @@ export const ProgrammaticItems = {
175
307
 
176
308
  export const TwoItems = {
177
309
  name : "Two Items",
178
- render : () => renderNavChildren(["Overview", "Pricing"]),
310
+ render : () => renderSimple(["Overview", "Pricing"]),
179
311
  parameters: {
180
- docs: {
181
- description: { story: "Minimal nav with only two items." }
182
- }
312
+ docs: { description: { story: "Nav minimal cu 2 items." } }
183
313
  }
184
314
  };
185
315
 
186
316
  export const ManyItems = {
187
317
  name : "Many Items (7)",
188
- render : () => renderNavChildren(["Overview", "Features", "Security", "Performance", "Reviews", "FAQ", "Pricing"]),
318
+ render : () => renderSimple(["Overview", "Features", "Security", "Performance", "Reviews", "FAQ", "Pricing"]),
189
319
  parameters: {
190
- docs: {
191
- description: { story: "Seven items — tests overflow and truncation on narrow viewports." }
192
- }
320
+ docs: { description: { story: "7 items — testează overflow și truncarea pe viewport-uri înguste." } }
193
321
  }
194
322
  };
195
323
 
196
324
  export const SingleItem = {
197
- name : "Single Item (edge case)",
198
- render : () => renderNavChildren(["Pricing"]),
325
+ name : "Single Item",
326
+ render : () => renderSimple(["Pricing"]),
199
327
  parameters: {
200
- docs: {
201
- description: { story: "Edge case: single anchor item. Should be active by default." }
202
- }
328
+ docs: { description: { story: "Edge case: un singur item — trebuie să fie activ by default." } }
203
329
  }
204
330
  };
205
331
 
206
332
  export const LongLabels = {
207
- name : "Long Item Labels",
208
- render: () => renderNavChildren([
333
+ name : "Long Labels",
334
+ render: () => renderSimple([
209
335
  "Product Overview",
210
336
  "Advanced Security Features",
211
337
  "Performance Benchmarks",
@@ -213,47 +339,66 @@ export const LongLabels = {
213
339
  "Plans & Pricing"
214
340
  ]),
215
341
  parameters: {
216
- docs: {
217
- description: { story: "Long label strings — tests text overflow and wrapping." }
218
- }
342
+ docs: { description: { story: "Labels lungi — testează text overflow și wrapping." } }
219
343
  }
220
344
  };
221
345
 
222
346
  export const WithScrollTargets = {
223
- name : "With Scroll Targets",
347
+ name : "With Scroll Targets — demo complet",
224
348
  render: () => html`
225
- <div style="position: sticky; top: 0; z-index: 100;">
226
- <bd-anchor-nav
227
- cta-label="Buy now"
228
- cta-href="#section-pricing"
229
- >
349
+ <div>
350
+ <bd-anchor-nav cta-label="Cumpără acum" cta-href="#section-pricing">
230
351
  <bd-anchor-nav-item title="Overview" href="#anchor-1-section"></bd-anchor-nav-item>
231
352
  <bd-anchor-nav-item title="Features" href="#anchor-2-section"></bd-anchor-nav-item>
232
353
  <bd-anchor-nav-item title="Pricing" href="#anchor-3-section"></bd-anchor-nav-item>
233
354
  </bd-anchor-nav>
234
- </div>
235
355
 
236
- <div id="anchor-1-section" style="padding: 80px 24px; background: #EDF9FF;">
237
- <bd-h as="h2">Overview Section</bd-h>
238
- <bd-p kind="regular">This is the overview content area. The nav scrolls here when "Overview" is clicked.</bd-p>
239
- </div>
240
- <div id="anchor-2-section" style="padding: 80px 24px; background: #fff;">
241
- <bd-h as="h2">Features Section</bd-h>
242
- <bd-p kind="regular">This is the features content area.</bd-p>
243
- </div>
244
- <div id="anchor-3-section" style="padding: 80px 24px; background: #EDF9FF;">
245
- <bd-h as="h2">Pricing Section</bd-h>
246
- <bd-p kind="regular">This is the pricing content area.</bd-p>
247
- </div>
248
- <div id="section-pricing" style="padding: 80px 24px; background: #fff;">
249
- <bd-h as="h2">Buy Now Target</bd-h>
250
- <bd-p kind="regular">The "Buy now" button scrolls to this element (#section-pricing).</bd-p>
356
+ <!-- Conținut sibling navul se aliniază automat la această lățime -->
357
+ <div style="max-width: 800px; margin: 0 auto;">
358
+
359
+ <div id="anchor-1-section" style="
360
+ min-height: 60vh; padding: 80px 24px;
361
+ background: #EDF9FF; border-bottom: 1px solid #e2e8f0;
362
+ ">
363
+ <bd-h as="h2">Overview Section</bd-h>
364
+ <bd-p kind="regular">
365
+ Navul de mai sus se aliniază automat la lățimea acestui container (800px).
366
+ Scroll în jos pentru a vedea sticky și scroll spy în acțiune.
367
+ </bd-p>
368
+ </div>
369
+
370
+ <div id="anchor-2-section" style="
371
+ min-height: 60vh; padding: 80px 24px;
372
+ background: #fff; border-bottom: 1px solid #e2e8f0;
373
+ ">
374
+ <bd-h as="h2">Features Section</bd-h>
375
+ <bd-p kind="regular">Conținut Features. Link-ul din nav devine activ când ajungi aici.</bd-p>
376
+ </div>
377
+
378
+ <div id="anchor-3-section" style="
379
+ min-height: 60vh; padding: 80px 24px;
380
+ background: #EDF9FF; border-bottom: 1px solid #e2e8f0;
381
+ ">
382
+ <bd-h as="h2">Pricing Section</bd-h>
383
+ <bd-p kind="regular">Conținut Pricing.</bd-p>
384
+ </div>
385
+
386
+ <div id="section-pricing" style="
387
+ min-height: 40vh; padding: 80px 24px; background: #fff;
388
+ ">
389
+ <bd-h as="h2">Buy Now Target</bd-h>
390
+ <bd-p kind="regular">Butonul CTA scrollează până aici.</bd-p>
391
+ </div>
392
+
393
+ </div>
251
394
  </div>
252
395
  `,
253
396
  parameters: {
254
397
  docs: {
255
398
  description: {
256
- story: "Full scroll demo with actual target sections. Click nav links or Buy now to trigger smooth scroll."
399
+ story: `Demo complet cu secțiuni reale. Navul detectează automat lățimea sibling-ului (800px)
400
+ și centrează conținutul la acea lățime. Border-ul rămâne full-width.
401
+ Sticky se oprește când containerul iese din viewport.`
257
402
  }
258
403
  }
259
404
  }
@@ -261,11 +406,13 @@ export const WithScrollTargets = {
261
406
 
262
407
  export const MobileView = {
263
408
  name : "Mobile View",
264
- render : () => renderNavChildren(["Overview", "Features", "Reviews", "Pricing"]),
409
+ render : () => renderWithSlotCta(["Overview", "Features", "Reviews", "Pricing"]),
265
410
  parameters: {
266
411
  viewport: { defaultViewport: "mobile1" },
267
412
  docs : {
268
- description: { story: "Nav at 375px mobile width — dropdown replaces link row." }
413
+ description: {
414
+ story: "Nav la 375px — dropdown înlocuiește linkurile, CTA apare după primul scroll. Pe mobile butonul devine full-width automat."
415
+ }
269
416
  }
270
417
  }
271
418
  };
@@ -292,7 +439,7 @@ export const Playground = {
292
439
  parameters: {
293
440
  docs: {
294
441
  description: {
295
- story: "Fully interactive playground. Edit `items`, `ctaLabel`, and `ctaHref` in Controls."
442
+ story: "Playground interactiv. Editează `items`, `ctaLabel` și `ctaHref` din Controls."
296
443
  }
297
444
  }
298
445
  }
@@ -37,6 +37,39 @@ export default css`
37
37
  text-underline-offset: 2px;
38
38
  }
39
39
 
40
+ /* ============================================
41
+ ACTIVE STATE - FONT WEIGHT 700
42
+ ============================================ */
43
+
44
+ .bd-link--active {
45
+ font-weight: 700 !important;
46
+ }
47
+
48
+ /* Poți adăuga și alte stiluri pentru active dacă dorești */
49
+ .bd-link--active.bd-link--primary {
50
+ color: var(--color-neutral-900);
51
+ }
52
+
53
+ .bd-link--active.bd-link--secondary {
54
+ color: var(--color-neutral-900);
55
+ }
56
+
57
+
58
+ .bd-link--no-underline:hover,
59
+ .bd-link--no-underline.bd-link--primary:hover,
60
+ .bd-link--no-underline.bd-link--secondary:hover,
61
+ .bd-link--no-underline.bd-link--danger:hover,
62
+ .bd-link--no-underline.bd-link--bold:hover,
63
+ .bd-link--no-underline.bd-link--subtle:hover {
64
+ text-decoration: none !important;
65
+ }
66
+
67
+
68
+
69
+ .bd-link--no-underline.bd-link--secondary:hover {
70
+ color: var(--color-neutral-800) !important;
71
+ }
72
+
40
73
  /* Light variant for dark backgrounds */
41
74
  .bd-link--light {
42
75
  color: var(--color-neutral-0);
@@ -101,9 +134,12 @@ export default css`
101
134
  pointer-events: none;
102
135
  }
103
136
 
104
-
105
- /* Focus styles for all variants */
137
+ /* Focus styles */
106
138
  .bd-link:focus {
139
+ outline: none;
140
+ }
141
+
142
+ .bd-link:focus-visible {
107
143
  outline: 2px solid var(--color-blue-300);
108
144
  outline-offset: 2px;
109
145
  border-radius: 2px;
@@ -4,24 +4,28 @@ import linkCSS from "./link.css.js";
4
4
 
5
5
  class BdLink extends LitElement {
6
6
  static properties = {
7
- href : { type: String },
8
- target : { type: String },
9
- kind : { type: String, reflect: true },
10
- underline: { type: Boolean, reflect: true },
11
- disabled : { type: Boolean, reflect: true },
12
- fontSize : { type: String, attribute: "font-size" },
13
- color : { type: String }
7
+ href : { type: String },
8
+ target : { type: String },
9
+ kind : { type: String, reflect: true },
10
+ underline : { type: Boolean, reflect: true },
11
+ noUnderline: { type: Boolean, attribute: "no-underline" },
12
+ disabled : { type: Boolean, reflect: true },
13
+ fontSize : { type: String, attribute: "font-size" },
14
+ color : { type: String },
15
+ active : { type: Boolean, reflect: true }
14
16
  };
15
17
 
16
18
  constructor() {
17
19
  super();
18
- this.href = "#";
19
- this.target = "_self";
20
- this.kind = "primary";
21
- this.underline = false;
22
- this.disabled = false;
23
- this.fontSize = "";
24
- this.color = "";
20
+ this.href = "#";
21
+ this.target = "_self";
22
+ this.kind = "primary";
23
+ this.underline = false;
24
+ this.noUnderline = false;
25
+ this.disabled = false;
26
+ this.fontSize = "";
27
+ this.color = "";
28
+ this.active = false;
25
29
  }
26
30
 
27
31
  static styles = [tokens, linkCSS];
@@ -31,6 +35,8 @@ class BdLink extends LitElement {
31
35
  "bd-link",
32
36
  `bd-link--${this.kind}`,
33
37
  this.underline ? "bd-link--underline" : "",
38
+ this.noUnderline ? "bd-link--no-underline" : "",
39
+ this.active ? "bd-link--active" : "",
34
40
  this.disabled ? "bd-link--disabled" : ""
35
41
  ].filter(Boolean).join(" ");
36
42
  }