@repobit/dex-system-design 0.25.1 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +4 -4
- package/src/components/ai-guardian-demo.stories.js +4 -4
- package/src/components/anchor/anchor-nav.css.js +5 -6
- package/src/components/anchor/anchor-nav.js +37 -27
- package/src/components/button/button.css.js +93 -23
- package/src/components/button/button.js +47 -6
- package/src/components/button/button.stories.js +79 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.3](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.25.2...@repobit/dex-system-design@0.25.3) (2026-10-01)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **DEX-1014:** button component - new variant
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [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)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **DEX-1014:** anchor nav component adjustments
|
|
18
|
+
* **DEX-1014:** anchor nav component adjustments
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
## [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)
|
|
7
22
|
|
|
8
23
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repobit/dex-system-design",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Design system based on Web Components.",
|
|
5
5
|
"author": "Iordache Matei Cezar <miordache@bitdefender.com>",
|
|
6
6
|
"homepage": "https://github.com/bitdefender/dex-core#readme",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"url": "https://github.com/bitdefender/dex-core/issues"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@repobit/dex-store": "1.6.
|
|
80
|
-
"@repobit/dex-store-elements": "2.1.
|
|
79
|
+
"@repobit/dex-store": "1.6.40",
|
|
80
|
+
"@repobit/dex-store-elements": "2.1.10",
|
|
81
81
|
"lit": "^3.3.2"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"volta": {
|
|
95
95
|
"node": "24.14.0"
|
|
96
96
|
},
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "bf65491b1abdfd2228891d548bcf46bd59643551"
|
|
98
98
|
}
|
|
@@ -93,7 +93,7 @@ function aiGuardianBody() {
|
|
|
93
93
|
aria-label="Page sections"
|
|
94
94
|
cta-label="Download BETA Today"
|
|
95
95
|
cta-href="#download"
|
|
96
|
-
sticky-top="
|
|
96
|
+
sticky-top="76px"
|
|
97
97
|
.items=${[
|
|
98
98
|
{ title: "Overview" },
|
|
99
99
|
{ title: "Compatibility" },
|
|
@@ -500,7 +500,7 @@ function skillScannerBody() {
|
|
|
500
500
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
501
501
|
<bd-anchor-nav
|
|
502
502
|
aria-label="Skill Scanner sections"
|
|
503
|
-
sticky-top="
|
|
503
|
+
sticky-top="76px"
|
|
504
504
|
.items=${[
|
|
505
505
|
{ title: "Install" },
|
|
506
506
|
{ title: "Overview" },
|
|
@@ -958,7 +958,7 @@ function vpnBody() {
|
|
|
958
958
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
959
959
|
<bd-anchor-nav
|
|
960
960
|
aria-label="VPN for AI Agents sections"
|
|
961
|
-
sticky-top="
|
|
961
|
+
sticky-top="76px"
|
|
962
962
|
.items=${[
|
|
963
963
|
{ title: "Overview" },
|
|
964
964
|
{ title: "Protection" },
|
|
@@ -1444,7 +1444,7 @@ Interactive demo: sticky top-level product nav (\`AI Guardian\` / \`Skill Scanne
|
|
|
1444
1444
|
navigation — clicking a tab intercepts the anchor click and swaps local state instead
|
|
1445
1445
|
of following the href (which has no route in Storybook and would 404).
|
|
1446
1446
|
|
|
1447
|
-
Both/all anchor-nav bars use \`sticky-top="
|
|
1447
|
+
Both/all anchor-nav bars use \`sticky-top="76px"\` so they pin themselves *below*
|
|
1448
1448
|
the sticky page-switcher instead of overlapping it once scrolled. That value
|
|
1449
1449
|
is an estimate — measure bd-page-switcher's real rendered height in DevTools
|
|
1450
1450
|
and adjust if the bars still overlap or leave a gap.
|
|
@@ -7,6 +7,8 @@ export const anchorNavStyles = css`
|
|
|
7
7
|
background: var(--color-neutral-0);
|
|
8
8
|
--bd-accesibility-focus: var(--color-blue-500);
|
|
9
9
|
--anchor-nav-max-width: 1290px;
|
|
10
|
+
border-top: var(--border-width-1) solid var(--color-neutral-100);
|
|
11
|
+
border-bottom: var(--border-width-1) solid var(--color-neutral-100);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
:host(.is-sticky) {
|
|
@@ -28,8 +30,6 @@ export const anchorNavStyles = css`
|
|
|
28
30
|
padding: var(--spacing-0);
|
|
29
31
|
width: 100%;
|
|
30
32
|
box-sizing: border-box;
|
|
31
|
-
border-top: var(--border-width-1) solid var(--color-neutral-100);
|
|
32
|
-
border-bottom: var(--border-width-1) solid var(--color-neutral-100);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.bd-anchor-nav__inner {
|
|
@@ -46,7 +46,6 @@ export const anchorNavStyles = css`
|
|
|
46
46
|
box-sizing: border-box;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
49
|
.anchor-link:focus-visible,
|
|
51
50
|
.bd-anchor-nav__cta:focus-visible,
|
|
52
51
|
.bd-anchor-nav__dropdown-toggle:focus-visible,
|
|
@@ -115,17 +114,17 @@ export const anchorNavStyles = css`
|
|
|
115
114
|
text-decoration: none !important;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
.anchor-indicator {
|
|
117
|
+
.anchor-indicator {
|
|
120
118
|
position: absolute;
|
|
121
119
|
left: 0;
|
|
122
|
-
|
|
120
|
+
top: 0;
|
|
123
121
|
width: 0;
|
|
124
122
|
height: var(--border-width-3);
|
|
125
123
|
background-color: var(--color-blue-500);
|
|
126
124
|
transform: translateX(0);
|
|
127
125
|
transition:
|
|
128
126
|
transform var(--transition-duration-slow) var(--transition-easing-ease-out),
|
|
127
|
+
top var(--transition-duration-slow) var(--transition-easing-ease-out),
|
|
129
128
|
width var(--transition-duration-slow) var(--transition-easing-ease-out);
|
|
130
129
|
pointer-events: none;
|
|
131
130
|
}
|
|
@@ -34,10 +34,6 @@ class BdAnchorNav extends LitElement {
|
|
|
34
34
|
ctaHref : { type: String, attribute: "cta-href" },
|
|
35
35
|
items : { type: Array },
|
|
36
36
|
maxWidth : { type: String, attribute: "max-width" },
|
|
37
|
-
// How far from the viewport top this nav sticks once pinned — set this
|
|
38
|
-
// to the rendered height of whatever sticky element sits above it (e.g.
|
|
39
|
-
// a page-switcher), so the two don't stack on top of each other at
|
|
40
|
-
// top:0. Accepts any CSS length, e.g. "64px", "4rem".
|
|
41
37
|
stickyTop : { type: String, attribute: "sticky-top" }
|
|
42
38
|
};
|
|
43
39
|
|
|
@@ -67,10 +63,8 @@ class BdAnchorNav extends LitElement {
|
|
|
67
63
|
this._stickyPlaceholder = null;
|
|
68
64
|
this._onStickyScroll = null;
|
|
69
65
|
this._handleResize = this._handleResize.bind(this);
|
|
70
|
-
// sliding underline indicator: false until the first successful
|
|
71
|
-
// measurement + placement, so it doesn't visibly animate in from the
|
|
72
|
-
// left edge on first paint.
|
|
73
66
|
this._indicatorReady = false;
|
|
67
|
+
this._indicatorResizeObserver = null;
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
connectedCallback() {
|
|
@@ -93,6 +87,7 @@ class BdAnchorNav extends LitElement {
|
|
|
93
87
|
document.removeEventListener("pointerdown", this._onDocumentPointerDown, true);
|
|
94
88
|
document.removeEventListener("keydown", this._onKeydown, true);
|
|
95
89
|
this._destroySticky();
|
|
90
|
+
this._indicatorResizeObserver?.disconnect();
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
updated(changedProperties) {
|
|
@@ -100,17 +95,11 @@ class BdAnchorNav extends LitElement {
|
|
|
100
95
|
|
|
101
96
|
if (changedProperties.has("stickyTop")) {
|
|
102
97
|
this.style.setProperty("--sticky-top", this.stickyTop || "0px");
|
|
103
|
-
// the scroll-spy offset (how far to scroll past the sticky bar when
|
|
104
|
-
// clicking a link) depends on this too, since the nav's own visible
|
|
105
|
-
// top now sits lower than the viewport top while stuck.
|
|
106
98
|
this._runScrollSpy();
|
|
107
99
|
}
|
|
108
100
|
|
|
109
101
|
if (changedProperties.has("activeId") || changedProperties.has("items")) {
|
|
110
|
-
|
|
111
|
-
// measuring its box — requestAnimationFrame is enough here, no need
|
|
112
|
-
// for updateComplete since nothing else in render() depends on this.
|
|
113
|
-
requestAnimationFrame(() => this._updateIndicator());
|
|
102
|
+
requestAnimationFrame(() => this._updateIndicator("updated"));
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
if (!changedProperties.has("activeId")) return;
|
|
@@ -128,13 +117,23 @@ class BdAnchorNav extends LitElement {
|
|
|
128
117
|
}
|
|
129
118
|
queueMicrotask(() => this._runScrollSpy());
|
|
130
119
|
this._handleMobileFullWidth();
|
|
131
|
-
requestAnimationFrame(() => this._updateIndicator());
|
|
120
|
+
requestAnimationFrame(() => this._updateIndicator("firstUpdated"));
|
|
121
|
+
|
|
122
|
+
const links = this.renderRoot?.querySelector(".anchor-links--desktop");
|
|
123
|
+
if (links && "ResizeObserver" in window) {
|
|
124
|
+
this._indicatorResizeObserver = new ResizeObserver(() => {
|
|
125
|
+
console.log("[anchor-nav] ResizeObserver fired", {
|
|
126
|
+
isSticky : this.classList.contains("is-sticky"),
|
|
127
|
+
navRect : this.getBoundingClientRect(),
|
|
128
|
+
linksRect: links.getBoundingClientRect()
|
|
129
|
+
});
|
|
130
|
+
this._updateIndicator("ResizeObserver");
|
|
131
|
+
});
|
|
132
|
+
this._indicatorResizeObserver.observe(links);
|
|
133
|
+
this._indicatorResizeObserver.observe(this);
|
|
134
|
+
}
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
// Positions the sliding underline under the currently active desktop
|
|
135
|
-
// link. Reads offsetLeft/offsetWidth directly off the active bd-link —
|
|
136
|
-
// as a flex item it's block-level regardless of its own :host display,
|
|
137
|
-
// so these are reliable without any extra math against a bounding rect.
|
|
138
137
|
_updateIndicator() {
|
|
139
138
|
const indicator = this.renderRoot?.querySelector(".anchor-indicator");
|
|
140
139
|
const activeLink = this.renderRoot?.querySelector(
|
|
@@ -144,14 +143,13 @@ class BdAnchorNav extends LitElement {
|
|
|
144
143
|
|
|
145
144
|
const left = activeLink.offsetLeft;
|
|
146
145
|
const width = activeLink.offsetWidth;
|
|
146
|
+
const top = activeLink.offsetTop + activeLink.offsetHeight + 14;
|
|
147
147
|
|
|
148
148
|
if (!this._indicatorReady) {
|
|
149
|
-
// first placement: skip the transition so it doesn't slide in from
|
|
150
|
-
// the left edge on initial paint, then re-enable it next frame.
|
|
151
149
|
indicator.style.transition = "none";
|
|
152
150
|
indicator.style.transform = `translateX(${left}px)`;
|
|
151
|
+
indicator.style.top = `${top}px`;
|
|
153
152
|
indicator.style.width = `${width}px`;
|
|
154
|
-
// force a style flush before removing the transition override
|
|
155
153
|
void indicator.offsetWidth;
|
|
156
154
|
indicator.style.transition = "";
|
|
157
155
|
this._indicatorReady = true;
|
|
@@ -159,12 +157,13 @@ class BdAnchorNav extends LitElement {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
indicator.style.transform = `translateX(${left}px)`;
|
|
160
|
+
indicator.style.top = `${top}px`;
|
|
162
161
|
indicator.style.width = `${width}px`;
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
_handleResize() {
|
|
166
165
|
this._handleMobileFullWidth();
|
|
167
|
-
this._updateIndicator();
|
|
166
|
+
this._updateIndicator("windowResize");
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
_handleMobileFullWidth() {
|
|
@@ -190,14 +189,16 @@ class BdAnchorNav extends LitElement {
|
|
|
190
189
|
this.style.setProperty("--sticky-top", this.stickyTop || "0px");
|
|
191
190
|
|
|
192
191
|
this._onStickyScroll = () => {
|
|
193
|
-
const parent
|
|
192
|
+
const parent = this.parentElement;
|
|
194
193
|
if (!parent) return;
|
|
195
194
|
|
|
196
195
|
const parentRect = parent.getBoundingClientRect();
|
|
197
196
|
const navHeight = this._stickyPlaceholder
|
|
198
197
|
? this._stickyPlaceholder.offsetHeight
|
|
199
198
|
: this.offsetHeight;
|
|
200
|
-
const stickyTopPx = parseLengthToPx(
|
|
199
|
+
const stickyTopPx = parseLengthToPx(
|
|
200
|
+
this.style.getPropertyValue("--sticky-top") || this.stickyTop
|
|
201
|
+
) || 0;
|
|
201
202
|
|
|
202
203
|
if (this._stickyOriginalTop === null) {
|
|
203
204
|
const rect = this.getBoundingClientRect();
|
|
@@ -217,8 +218,8 @@ class BdAnchorNav extends LitElement {
|
|
|
217
218
|
}
|
|
218
219
|
this.classList.add("is-sticky");
|
|
219
220
|
}
|
|
220
|
-
this.style.setProperty("--sticky-left",
|
|
221
|
-
this.style.setProperty("--sticky-width", `${
|
|
221
|
+
this.style.setProperty("--sticky-left", "0px");
|
|
222
|
+
this.style.setProperty("--sticky-width", `${document.documentElement.clientWidth}px`);
|
|
222
223
|
} else {
|
|
223
224
|
if (this.classList.contains("is-sticky")) {
|
|
224
225
|
this.classList.remove("is-sticky");
|
|
@@ -232,6 +233,15 @@ class BdAnchorNav extends LitElement {
|
|
|
232
233
|
}
|
|
233
234
|
};
|
|
234
235
|
|
|
236
|
+
this._onStickyResize = () => {
|
|
237
|
+
this._stickyOriginalTop = null;
|
|
238
|
+
if (this.classList.contains("is-sticky")) {
|
|
239
|
+
this.style.setProperty("--sticky-left", "0px");
|
|
240
|
+
this.style.setProperty("--sticky-width", `${document.documentElement.clientWidth}px`);
|
|
241
|
+
}
|
|
242
|
+
this._onStickyScroll();
|
|
243
|
+
};
|
|
244
|
+
|
|
235
245
|
this._onStickyResize = () => {
|
|
236
246
|
this._stickyOriginalTop = null;
|
|
237
247
|
if (this.classList.contains("is-sticky")) {
|
|
@@ -27,6 +27,11 @@ export default css`
|
|
|
27
27
|
--border-button-background-primary-outline-hover: var(--color-blue-500);
|
|
28
28
|
--button-background-primary-outline-focus: var(--color-blue-500);
|
|
29
29
|
--bd-accesibility-focus: var(--color-blue-500);
|
|
30
|
+
|
|
31
|
+
--button-arrow-size: 1.1em;
|
|
32
|
+
--button-arrow-shift: 0.65em;
|
|
33
|
+
--button-arrow-inset: 0.65em;
|
|
34
|
+
--button-arrow-offset: -0.4em;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/* ============================================
|
|
@@ -103,6 +108,10 @@ export default css`
|
|
|
103
108
|
box-sizing: border-box;
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
.button__label {
|
|
112
|
+
display: inline-block;
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
.button--primary {
|
|
107
116
|
background-color: var(--button-background-primary);
|
|
108
117
|
color: var(--color-neutral-0);
|
|
@@ -141,6 +150,62 @@ export default css`
|
|
|
141
150
|
.button--danger:active {
|
|
142
151
|
background-color: var(--button-background-danger-focus);
|
|
143
152
|
}
|
|
153
|
+
|
|
154
|
+
/* ============================================
|
|
155
|
+
HOVER CU SĂGEATĂ (implicit pentru kind="danger")
|
|
156
|
+
Lățimea butonului rămâne fixă: textul se translatează
|
|
157
|
+
spre stânga, iar săgeata (poziționată absolut) apare în dreapta.
|
|
158
|
+
============================================ */
|
|
159
|
+
|
|
160
|
+
.button--with-arrow {
|
|
161
|
+
position: relative;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.button--with-arrow .button__label {
|
|
166
|
+
transition: transform var(--transition-duration-normal) var(--transition-easing-smooth);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.button--with-arrow .button__arrow {
|
|
170
|
+
position: absolute;
|
|
171
|
+
top: 50%;
|
|
172
|
+
right: var(--button-arrow-inset);
|
|
173
|
+
width: var(--button-arrow-size);
|
|
174
|
+
height: var(--button-arrow-size);
|
|
175
|
+
display: inline-flex;
|
|
176
|
+
opacity: 0;
|
|
177
|
+
transform: translate(var(--button-arrow-offset), -50%);
|
|
178
|
+
transition: opacity var(--transition-duration-normal) var(--transition-easing-smooth),
|
|
179
|
+
transform var(--transition-duration-normal) var(--transition-easing-smooth);
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.button--with-arrow .button__arrow svg {
|
|
184
|
+
width: 100%;
|
|
185
|
+
height: 100%;
|
|
186
|
+
display: block;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.button--with-arrow:hover .button__label,
|
|
190
|
+
.button--with-arrow:focus-visible .button__label {
|
|
191
|
+
transform: translateX(calc(var(--button-arrow-shift) * -1));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.button--with-arrow:hover .button__arrow,
|
|
195
|
+
.button--with-arrow:focus-visible .button__arrow {
|
|
196
|
+
opacity: 1;
|
|
197
|
+
transform: translate(0, -50%);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Pe touch nu există hover real — nu lăsăm săgeata „blocată” după tap */
|
|
201
|
+
@media (hover: none) {
|
|
202
|
+
.button--with-arrow:hover .button__label {
|
|
203
|
+
transform: none;
|
|
204
|
+
}
|
|
205
|
+
.button--with-arrow:hover .button__arrow {
|
|
206
|
+
opacity: 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
144
209
|
|
|
145
210
|
.button--outline {
|
|
146
211
|
background-color: transparent;
|
|
@@ -208,41 +273,46 @@ export default css`
|
|
|
208
273
|
font-weight: var(--typography-fontWeight-semibold);
|
|
209
274
|
}
|
|
210
275
|
|
|
211
|
-
.button
|
|
276
|
+
/* slot-ul nu mai e copil direct al .button (e în .button__label) */
|
|
277
|
+
.button ::slotted(*) {
|
|
212
278
|
display: inline-block;
|
|
213
279
|
width: 100%;
|
|
214
280
|
text-align: center;
|
|
215
281
|
}
|
|
216
282
|
|
|
217
283
|
@media (prefers-reduced-motion: reduce) {
|
|
218
|
-
.button,
|
|
284
|
+
.button,
|
|
285
|
+
.bd-contact-btn,
|
|
286
|
+
.button__label,
|
|
287
|
+
.button__arrow {
|
|
219
288
|
transition: none !important;
|
|
220
289
|
}
|
|
221
290
|
}
|
|
222
|
-
.button--white {
|
|
223
|
-
background-color: var(--color-neutral-0);
|
|
224
|
-
color: var(--color-blue-500);
|
|
225
|
-
}
|
|
226
291
|
|
|
227
|
-
.button--white
|
|
228
|
-
|
|
229
|
-
|
|
292
|
+
.button--white {
|
|
293
|
+
background-color: var(--color-neutral-0);
|
|
294
|
+
color: var(--color-blue-500);
|
|
295
|
+
}
|
|
230
296
|
|
|
231
|
-
.button--white:
|
|
232
|
-
|
|
233
|
-
}
|
|
297
|
+
.button--white:hover {
|
|
298
|
+
background-color: var(--color-blue-50);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.button--white:active {
|
|
302
|
+
background-color: var(--color-blue-100);
|
|
303
|
+
}
|
|
234
304
|
|
|
235
|
-
.button--outline-white {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
305
|
+
.button--outline-white {
|
|
306
|
+
background-color: transparent;
|
|
307
|
+
box-shadow: inset 0 0 0 1.5px #ffffff8c;
|
|
308
|
+
color: var(--color-neutral-0);
|
|
309
|
+
}
|
|
240
310
|
|
|
241
|
-
.button--outline-white:hover {
|
|
242
|
-
|
|
243
|
-
}
|
|
311
|
+
.button--outline-white:hover {
|
|
312
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
313
|
+
}
|
|
244
314
|
|
|
245
|
-
.button--outline-white:active {
|
|
246
|
-
|
|
247
|
-
}
|
|
315
|
+
.button--outline-white:active {
|
|
316
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
317
|
+
}
|
|
248
318
|
`;
|
|
@@ -2,6 +2,24 @@ import { LitElement, html } from "lit";
|
|
|
2
2
|
import { tokens } from "../../tokens/tokens.js";
|
|
3
3
|
import buttonsCSS from "./button.css.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Săgeata afișată pe hover / focus pentru butoanele cu kind="danger".
|
|
7
|
+
* Folosește currentColor, deci preia culoarea textului butonului.
|
|
8
|
+
*/
|
|
9
|
+
const arrowIcon = html`
|
|
10
|
+
<span class="button__arrow" aria-hidden="true">
|
|
11
|
+
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
12
|
+
<path
|
|
13
|
+
d="M2.5 8h11M9 3.5 13.5 8 9 12.5"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
stroke-width="1.5"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
</span>
|
|
21
|
+
`;
|
|
22
|
+
|
|
5
23
|
class Button extends LitElement {
|
|
6
24
|
static properties = {
|
|
7
25
|
label : { type: String },
|
|
@@ -13,7 +31,8 @@ class Button extends LitElement {
|
|
|
13
31
|
strong : { type: Boolean },
|
|
14
32
|
fontSize : { type: String, attribute: "font-size" },
|
|
15
33
|
fontWeight : { type: String, attribute: "font-weight" },
|
|
16
|
-
align : { type: String }
|
|
34
|
+
align : { type: String },
|
|
35
|
+
noArrow : { type: Boolean, attribute: "no-arrow" }
|
|
17
36
|
};
|
|
18
37
|
|
|
19
38
|
constructor() {
|
|
@@ -24,6 +43,7 @@ class Button extends LitElement {
|
|
|
24
43
|
this.fontSize = "";
|
|
25
44
|
this.fontWeight = "";
|
|
26
45
|
this.align = "left";
|
|
46
|
+
this.noArrow = false;
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
_onFocusin(e) {
|
|
@@ -87,20 +107,30 @@ class Button extends LitElement {
|
|
|
87
107
|
return this.strong ? "button--bold" : "";
|
|
88
108
|
}
|
|
89
109
|
|
|
110
|
+
/** Săgeata pe hover e activă implicit pentru danger; se poate dezactiva cu no-arrow. */
|
|
111
|
+
get hasArrow() {
|
|
112
|
+
return this.kind === "danger" && !this.noArrow;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get arrowClass() {
|
|
116
|
+
return this.hasArrow ? "button--with-arrow" : "";
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
render() {
|
|
91
120
|
return html`
|
|
92
121
|
<button
|
|
93
122
|
@click=${this._handleClick}
|
|
94
123
|
@focusin=${this._onFocusin}
|
|
95
124
|
@focusout=${this._onFocusout}
|
|
96
|
-
class="button ${this.kindClass} ${this.sizeClass} ${this.fullWidthClass} ${this.strongClass}"
|
|
125
|
+
class="button ${this.kindClass} ${this.sizeClass} ${this.fullWidthClass} ${this.strongClass} ${this.arrowClass}"
|
|
97
126
|
aria-label="${this.label}"
|
|
98
127
|
style="
|
|
99
128
|
${this.fontSize ? `font-size: ${this.fontSize};` : ""}
|
|
100
129
|
${this.fontWeight ? `font-weight: ${this.fontWeight};` : ""}
|
|
101
130
|
"
|
|
102
131
|
>
|
|
103
|
-
<slot></slot>
|
|
132
|
+
<span class="button__label"><slot></slot></span>
|
|
133
|
+
${this.hasArrow ? arrowIcon : ""}
|
|
104
134
|
</button>
|
|
105
135
|
`;
|
|
106
136
|
}
|
|
@@ -119,7 +149,8 @@ class ButtonLink extends LitElement {
|
|
|
119
149
|
fontWeight : { type: String, attribute: "font-weight" },
|
|
120
150
|
buyLink : { type: Boolean },
|
|
121
151
|
shadow : { type: Boolean, reflect: true },
|
|
122
|
-
align : { type: String }
|
|
152
|
+
align : { type: String },
|
|
153
|
+
noArrow : { type: Boolean, attribute: "no-arrow" }
|
|
123
154
|
};
|
|
124
155
|
|
|
125
156
|
constructor() {
|
|
@@ -135,6 +166,7 @@ class ButtonLink extends LitElement {
|
|
|
135
166
|
this.buyLink = false;
|
|
136
167
|
this.shadow = true;
|
|
137
168
|
this.align = "left";
|
|
169
|
+
this.noArrow = false;
|
|
138
170
|
}
|
|
139
171
|
|
|
140
172
|
_onFocusin(e) {
|
|
@@ -194,11 +226,19 @@ class ButtonLink extends LitElement {
|
|
|
194
226
|
return this.strong ? "button--bold" : "";
|
|
195
227
|
}
|
|
196
228
|
|
|
229
|
+
get hasArrow() {
|
|
230
|
+
return this.kind === "danger" && !this.noArrow;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
get arrowClass() {
|
|
234
|
+
return this.hasArrow ? "button--with-arrow" : "";
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
render() {
|
|
198
238
|
return html`
|
|
199
239
|
<a
|
|
200
240
|
href="${this.href}"
|
|
201
|
-
class="button ${this.kindClass} ${this.sizeClass} ${this.fullWidthClass} ${this.strongClass}"
|
|
241
|
+
class="button ${this.kindClass} ${this.sizeClass} ${this.fullWidthClass} ${this.strongClass} ${this.arrowClass}"
|
|
202
242
|
aria-label="${this.label}"
|
|
203
243
|
@click=${this._handleClick}
|
|
204
244
|
@focusin=${this._onFocusin}
|
|
@@ -210,7 +250,8 @@ class ButtonLink extends LitElement {
|
|
|
210
250
|
?data-store-render="${this.buyLink}"
|
|
211
251
|
?data-store-buy-link="${this.buyLink}"
|
|
212
252
|
>
|
|
213
|
-
<slot>${this.label}</slot>
|
|
253
|
+
<span class="button__label"><slot>${this.label}</slot></span>
|
|
254
|
+
${this.hasArrow ? arrowIcon : ""}
|
|
214
255
|
</a>
|
|
215
256
|
`;
|
|
216
257
|
}
|
|
@@ -17,6 +17,7 @@ Three button components are available:
|
|
|
17
17
|
### Usage
|
|
18
18
|
\`\`\`html
|
|
19
19
|
<bd-button kind="primary" size="md">Click me</bd-button>
|
|
20
|
+
<bd-button kind="danger" size="md">Activez votre protection</bd-button>
|
|
20
21
|
<bd-button-link kind="danger" size="lg" href="/buy">Buy now</bd-button-link>
|
|
21
22
|
<bd-contact-button label="Contact us" href="/contact"></bd-contact-button>
|
|
22
23
|
\`\`\`
|
|
@@ -28,7 +29,22 @@ Three button components are available:
|
|
|
28
29
|
| \`primary-outline\` | \`button--primary--outline\` |
|
|
29
30
|
| \`outline\` | \`button--outline\` |
|
|
30
31
|
| \`secondary\` | \`button--secondary\` |
|
|
31
|
-
| \`danger\` | \`button--danger\` |
|
|
32
|
+
| \`danger\` | \`button--danger\` + \`button--with-arrow\` |
|
|
33
|
+
| \`white\` | \`button--white\` |
|
|
34
|
+
| \`outline-white\` | \`button--outline-white\` |
|
|
35
|
+
|
|
36
|
+
### Danger hover arrow
|
|
37
|
+
Every \`kind="danger"\` button (both \`bd-button\` and \`bd-button-link\`) has an arrow hover effect by default:
|
|
38
|
+
on hover (and on keyboard \`:focus-visible\`) the label slides to the left and an arrow fades in on the right.
|
|
39
|
+
The button width does not change.
|
|
40
|
+
|
|
41
|
+
To disable it on a specific button, add the \`no-arrow\` attribute:
|
|
42
|
+
\`\`\`html
|
|
43
|
+
<bd-button kind="danger" no-arrow>No arrow</bd-button>
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
The effect can be tuned from outside via CSS custom properties:
|
|
47
|
+
\`--button-arrow-size\`, \`--button-arrow-shift\`, \`--button-arrow-inset\`, \`--button-arrow-offset\`.
|
|
32
48
|
|
|
33
49
|
### Size Values
|
|
34
50
|
| Size | Class |
|
|
@@ -58,8 +74,8 @@ Three button components are available:
|
|
|
58
74
|
},
|
|
59
75
|
kind: {
|
|
60
76
|
control : { type: 'select' },
|
|
61
|
-
options : ['primary', 'primary-outline', 'outline', 'secondary', 'danger'],
|
|
62
|
-
description: 'Visual style variant of the button',
|
|
77
|
+
options : ['primary', 'primary-outline', 'outline', 'secondary', 'danger', 'white', 'outline-white'],
|
|
78
|
+
description: 'Visual style variant of the button. `danger` includes the hover arrow by default.',
|
|
63
79
|
table : { type: { summary: 'string' }, defaultValue: { summary: 'primary' }, category: 'Appearance' }
|
|
64
80
|
},
|
|
65
81
|
size: {
|
|
@@ -68,6 +84,11 @@ Three button components are available:
|
|
|
68
84
|
description: 'Size of the button',
|
|
69
85
|
table : { type: { summary: "'sm' | 'md' | 'lg'" }, defaultValue: { summary: 'md' }, category: 'Appearance' }
|
|
70
86
|
},
|
|
87
|
+
noArrow: {
|
|
88
|
+
control : 'boolean',
|
|
89
|
+
description: '(kind="danger" only) Disables the hover arrow. Attribute: `no-arrow`',
|
|
90
|
+
table : { type: { summary: 'boolean' }, defaultValue: { summary: 'false' }, category: 'Appearance' }
|
|
91
|
+
},
|
|
71
92
|
fullWidth: {
|
|
72
93
|
control : 'boolean',
|
|
73
94
|
description: 'When true, button stretches to full container width',
|
|
@@ -116,6 +137,7 @@ const defaultArgs = {
|
|
|
116
137
|
label : 'Buy now',
|
|
117
138
|
kind : 'primary',
|
|
118
139
|
size : 'md',
|
|
140
|
+
noArrow : false,
|
|
119
141
|
fullWidth : false,
|
|
120
142
|
strong : false,
|
|
121
143
|
align : 'left',
|
|
@@ -142,7 +164,40 @@ export const ButtonAllKinds = {
|
|
|
142
164
|
<bd-button kind="danger" size="md">Danger</bd-button>
|
|
143
165
|
</div>
|
|
144
166
|
`,
|
|
145
|
-
parameters: { docs: { description: { story: 'All five `kind` variants side by side.' } } }
|
|
167
|
+
parameters: { docs: { description: { story: 'All five `kind` variants side by side. Hover the danger button to see the arrow.' } } }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const ButtonDangerArrow = {
|
|
171
|
+
name : 'Button — Danger (Hover Arrow)',
|
|
172
|
+
render: () => html`
|
|
173
|
+
<div style="display:flex; flex-direction:column; gap:16px; align-items:flex-start;">
|
|
174
|
+
<p style="font-size:14px; margin:0;">Hover (or Tab to focus) any button to see the arrow.</p>
|
|
175
|
+
<bd-button kind="danger" size="md" strong>Activez votre protection</bd-button>
|
|
176
|
+
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
|
177
|
+
<bd-button kind="danger" size="sm" strong>Small</bd-button>
|
|
178
|
+
<bd-button kind="danger" size="md" strong>Medium</bd-button>
|
|
179
|
+
<bd-button kind="danger" size="lg" strong>Large</bd-button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
`,
|
|
183
|
+
parameters: {
|
|
184
|
+
docs: {
|
|
185
|
+
description: {
|
|
186
|
+
story: 'Default behaviour for every `kind="danger"` button: on hover / `:focus-visible` the label slides left and an arrow appears on the right, without changing the button width.'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const ButtonDangerNoArrow = {
|
|
193
|
+
name : 'Button — Danger without Arrow',
|
|
194
|
+
render: () => html`
|
|
195
|
+
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
|
196
|
+
<bd-button kind="danger" size="md" strong>With arrow (default)</bd-button>
|
|
197
|
+
<bd-button kind="danger" size="md" strong no-arrow>Without arrow</bd-button>
|
|
198
|
+
</div>
|
|
199
|
+
`,
|
|
200
|
+
parameters: { docs: { description: { story: 'Use the `no-arrow` attribute to opt out of the hover arrow on a specific danger button.' } } }
|
|
146
201
|
};
|
|
147
202
|
|
|
148
203
|
export const ButtonAllSizes = {
|
|
@@ -160,11 +215,12 @@ export const ButtonAllSizes = {
|
|
|
160
215
|
export const ButtonFullWidth = {
|
|
161
216
|
name : 'Button — Full Width',
|
|
162
217
|
render: () => html`
|
|
163
|
-
<div style="width:400px;">
|
|
218
|
+
<div style="width:400px; display:flex; flex-direction:column; gap:12px;">
|
|
164
219
|
<bd-button kind="primary" size="md" .fullWidth="${true}">Full Width Button</bd-button>
|
|
220
|
+
<bd-button kind="danger" size="md" strong .fullWidth="${true}">Full Width Danger</bd-button>
|
|
165
221
|
</div>
|
|
166
222
|
`,
|
|
167
|
-
parameters: { docs: { description: { story: '`fullWidth` set to true — applies `max--width` class.' } } }
|
|
223
|
+
parameters: { docs: { description: { story: '`fullWidth` set to true — applies `max--width` class. The danger arrow also works at full width.' } } }
|
|
168
224
|
};
|
|
169
225
|
|
|
170
226
|
export const ButtonStrong = {
|
|
@@ -223,7 +279,7 @@ export const ButtonCustomTypography = {
|
|
|
223
279
|
Buy now
|
|
224
280
|
</bd-button>
|
|
225
281
|
`,
|
|
226
|
-
parameters: { docs: { description: { story: 'Custom `font-size` and `font-weight` applied via inline styles.' } } }
|
|
282
|
+
parameters: { docs: { description: { story: 'Custom `font-size` and `font-weight` applied via inline styles. The arrow scales with the font size.' } } }
|
|
227
283
|
};
|
|
228
284
|
|
|
229
285
|
export const ButtonKeyboard = {
|
|
@@ -268,7 +324,18 @@ export const ButtonLinkAllSizes = {
|
|
|
268
324
|
<bd-button-link kind="danger" size="lg" href="#">Large</bd-button-link>
|
|
269
325
|
</div>
|
|
270
326
|
`,
|
|
271
|
-
parameters: { docs: { description: { story: 'All three sizes for `bd-button-link
|
|
327
|
+
parameters: { docs: { description: { story: 'All three sizes for `bd-button-link` (danger, with hover arrow).' } } }
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export const ButtonLinkDangerArrow = {
|
|
331
|
+
name : 'ButtonLink — Danger (Hover Arrow)',
|
|
332
|
+
render: () => html`
|
|
333
|
+
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
|
334
|
+
<bd-button-link kind="danger" size="md" href="#" strong>Activez votre protection</bd-button-link>
|
|
335
|
+
<bd-button-link kind="danger" size="md" href="#" strong no-arrow>Without arrow</bd-button-link>
|
|
336
|
+
</div>
|
|
337
|
+
`,
|
|
338
|
+
parameters: { docs: { description: { story: 'The hover arrow is also on by default for `bd-button-link` with `kind="danger"`; `no-arrow` disables it.' } } }
|
|
272
339
|
};
|
|
273
340
|
|
|
274
341
|
export const ButtonLinkBuyLink = {
|
|
@@ -397,15 +464,17 @@ export const Playground = {
|
|
|
397
464
|
size="${args.size}"
|
|
398
465
|
label="${args.label}"
|
|
399
466
|
align="${args.align}"
|
|
400
|
-
?
|
|
467
|
+
?fullwidth="${args.fullWidth}"
|
|
401
468
|
?strong="${args.strong}"
|
|
469
|
+
?no-arrow="${args.noArrow}"
|
|
402
470
|
font-size="${args.fontSize}"
|
|
403
471
|
font-weight="${args.fontWeight}"
|
|
404
472
|
>${args.label}</bd-button>
|
|
405
473
|
</div>
|
|
406
474
|
`,
|
|
407
|
-
parameters: { docs: { description: { story: 'Interactive playground for `bd-button`. Use Controls to adjust all props including alignment.' } } }
|
|
475
|
+
parameters: { docs: { description: { story: 'Interactive playground for `bd-button`. Use Controls to adjust all props including alignment and the danger hover arrow.' } } }
|
|
408
476
|
};
|
|
477
|
+
|
|
409
478
|
// ─── White / Outline-White (on dark background) ───────────────────────────
|
|
410
479
|
|
|
411
480
|
export const ButtonWhiteOnDark = {
|