@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 +14 -0
- package/package.json +2 -2
- package/src/components/Button/Button.js +1 -1
- package/src/components/Button/button.css.js +11 -11
- package/src/components/anchor/anchor-nav.css.js +105 -93
- package/src/components/anchor/anchor-nav.js +127 -10
- package/src/components/anchor/anchor.stories.js +243 -96
- package/src/components/link/link.css.js +38 -2
- package/src/components/link/link.js +20 -14
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.
|
|
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": "
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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(--
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
292
|
+
.bd-anchor-nav__cta::after {
|
|
293
|
+
display: none;
|
|
294
|
+
}
|
|
266
295
|
|
|
267
296
|
@keyframes bd-anchor-nav-dropdown-label-swap {
|
|
268
|
-
from {
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
404
|
+
<bd-link
|
|
291
405
|
href="${href}"
|
|
292
|
-
|
|
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}</
|
|
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
|
-
<
|
|
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**
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
<bd-compare-section>...</bd-compare-section>
|
|
43
|
+
<section id="overview">...</section>
|
|
44
|
+
</div>
|
|
45
|
+
\`\`\`
|
|
22
46
|
|
|
23
|
-
###
|
|
47
|
+
### Utilizare cu slot CTA customizat
|
|
24
48
|
\`\`\`html
|
|
25
|
-
<bd-anchor-nav
|
|
26
|
-
<bd-anchor-nav-item title="Overview" href="#
|
|
27
|
-
<bd-anchor-nav-item title="
|
|
28
|
-
|
|
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
|
-
###
|
|
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: "#
|
|
39
|
-
{ title: "Features", href: "#
|
|
40
|
-
{ title: "Pricing", href: "#
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
| \`cta-label\` | String | \`"Buy now"\`
|
|
51
|
-
| \`cta-href\` | String | \`"#section-pricing"\`
|
|
52
|
-
| \`aria-label\` | String | \`"Section navigation"\` |
|
|
53
|
-
| \`items\` | Array | —
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
| \`title\`
|
|
59
|
-
| \`href\`
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
110
|
-
render : () =>
|
|
188
|
+
name : "Default — fallback CTA",
|
|
189
|
+
render : () => renderSimple(["Overview", "Features", "Reviews", "Pricing"]),
|
|
111
190
|
parameters: {
|
|
112
191
|
docs: {
|
|
113
192
|
description: {
|
|
114
|
-
story: "
|
|
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: () =>
|
|
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
|
|
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
|
|
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: "
|
|
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: () =>
|
|
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
|
|
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 : () =>
|
|
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 : () =>
|
|
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
|
|
198
|
-
render : () =>
|
|
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
|
|
208
|
-
render: () =>
|
|
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
|
|
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
|
-
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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:
|
|
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 : () =>
|
|
409
|
+
render : () => renderWithSlotCta(["Overview", "Features", "Reviews", "Pricing"]),
|
|
265
410
|
parameters: {
|
|
266
411
|
viewport: { defaultViewport: "mobile1" },
|
|
267
412
|
docs : {
|
|
268
|
-
description: {
|
|
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: "
|
|
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
|
|
8
|
-
target
|
|
9
|
-
kind
|
|
10
|
-
underline: { type: Boolean, reflect: true },
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
20
|
-
this.kind
|
|
21
|
-
this.underline
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
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
|
}
|