@repobit/dex-system-design 0.24.20 → 0.25.1
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/assets/images/badges/app-store-badge.png +0 -0
- package/src/assets/images/badges/google-play-badge.png +0 -0
- package/src/components/announcement-banner/announcement-banner.css.js +44 -12
- package/src/components/announcement-banner/announcement-banner.js +76 -16
- package/src/components/announcement-banner/announcement-banner.stories.js +101 -32
- package/src/components/cards/card.css.js +62 -0
- package/src/components/cards/card.js +59 -7
- package/src/components/cards/card.stories.js +88 -1
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.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
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **DEX-1014:** announcement banner component
|
|
11
|
+
* **DEX-1014:** announcement banner component
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.25.0](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.20...@repobit/dex-system-design@0.25.0) (2026-09-25)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **DEX-28731:** add align-rows subgrid option to card section
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
## [0.24.20](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.19...@repobit/dex-system-design@0.24.20) (2026-09-24)
|
|
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.
|
|
3
|
+
"version": "0.25.1",
|
|
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.38",
|
|
80
|
+
"@repobit/dex-store-elements": "2.1.8",
|
|
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": "252088eff23e9de9125bd60243283eba2f86a909"
|
|
98
98
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -20,22 +20,21 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
20
20
|
gap: var(--spacing-96);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
.bd-announcementbanner-
|
|
24
|
-
display: block;
|
|
25
|
-
margin: 0;
|
|
26
|
-
color: var(--color-neutral-0, #fff);
|
|
27
|
-
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
28
|
-
flex: 1 1 320px;
|
|
29
|
-
max-width: 560px;
|
|
30
|
-
}
|
|
31
|
-
|
|
23
|
+
.bd-announcementbanner-left,
|
|
32
24
|
.bd-announcementbanner-side {
|
|
33
25
|
display: flex;
|
|
34
26
|
flex-direction: column;
|
|
35
27
|
align-items: flex-start;
|
|
36
28
|
gap: var(--spacing-16);
|
|
37
|
-
flex: 1 1
|
|
38
|
-
max-width:
|
|
29
|
+
flex: 1 1 320px;
|
|
30
|
+
max-width: 560px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.bd-announcementbanner-title {
|
|
34
|
+
display: block;
|
|
35
|
+
margin: 0;
|
|
36
|
+
color: var(--color-neutral-0, #fff);
|
|
37
|
+
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
.bd-announcementbanner-icon {
|
|
@@ -45,6 +44,15 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
45
44
|
margin-bottom: var(--spacing-4);
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/* Collapses to nothing when the corresponding slot has no assigned
|
|
48
|
+
content — see _onSlotChange in announcement-banner.js. */
|
|
49
|
+
.bd-announcementbanner-icon[hidden],
|
|
50
|
+
.bd-announcementbanner-buttons[hidden] {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Left and right icon slots share the exact same sizing/style — the
|
|
55
|
+
left column is a mirror of the right one, not a different layout. */
|
|
48
56
|
.bd-announcementbanner-icon ::slotted(*) {
|
|
49
57
|
display: block;
|
|
50
58
|
max-width: 160px;
|
|
@@ -57,6 +65,14 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
57
65
|
color: rgba(255, 255, 255, 0.65);
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
.bd-announcementbanner-buttons {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: var(--spacing-12);
|
|
73
|
+
margin-top: var(--spacing-4);
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
@media (max-width: 699px) {
|
|
61
77
|
.bd-announcementbanner-container {
|
|
62
78
|
padding: var(--spacing-28) var(--spacing-24);
|
|
@@ -64,10 +80,26 @@ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-
|
|
|
64
80
|
align-items: flex-start;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
.bd-announcementbanner-
|
|
83
|
+
.bd-announcementbanner-left,
|
|
68
84
|
.bd-announcementbanner-side {
|
|
69
85
|
max-width: none;
|
|
70
86
|
flex-basis: auto;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
89
|
+
|
|
90
|
+
/* When left-icon is present, the left title shrinks to match the
|
|
91
|
+
default right-column description size — visual emphasis moves to
|
|
92
|
+
the right side instead. */
|
|
93
|
+
.bd-announcementbanner-title--small {
|
|
94
|
+
margin: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Mirror case: the right description grows to the default title size
|
|
98
|
+
when it's the "big" side. */
|
|
99
|
+
.bd-announcementbanner-description--large {
|
|
100
|
+
display: block;
|
|
101
|
+
margin: 0;
|
|
102
|
+
color: var(--color-neutral-0, #fff);
|
|
103
|
+
line-height: var(--typography-lineHeight-tight, 1.15);
|
|
104
|
+
}
|
|
73
105
|
`;
|
|
@@ -5,48 +5,108 @@ import "../paragraph/paragraph.js";
|
|
|
5
5
|
import announcementBannerCSS from "./announcement-banner.css.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Simple two-column banner on a blue gradient
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Simple two-column banner on a blue gradient. Two layout modes:
|
|
9
|
+
*
|
|
10
|
+
* - No left-icon (default): big title on the left, icon + small
|
|
11
|
+
* description on the right — for partnership/announcement callouts.
|
|
12
|
+
* - With left-icon: the left column becomes the "small" side (icon +
|
|
13
|
+
* small title, mirroring how the default right side looks) and the
|
|
14
|
+
* right column becomes the "big" side (large description text) — i.e.
|
|
15
|
+
* visual emphasis flips to the right when a left icon is present.
|
|
13
16
|
*
|
|
14
17
|
* <bd-announcement-banner
|
|
15
18
|
* title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
|
|
16
|
-
|
|
19
|
+
* description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
17
20
|
* >
|
|
18
|
-
* <bd-
|
|
21
|
+
* <bd-picture slot="left-icon" ...></bd-picture>
|
|
22
|
+
* <bd-button-link slot="left-buttons" ...>Learn more</bd-button-link>
|
|
19
23
|
* </bd-announcement-banner>
|
|
20
24
|
*/
|
|
21
25
|
class BdAnnouncementBanner extends LitElement {
|
|
22
26
|
static properties = {
|
|
23
|
-
title
|
|
24
|
-
description: { type: String }
|
|
27
|
+
title : { type: String },
|
|
28
|
+
description : { type: String },
|
|
29
|
+
_hasLeftIcon : { state: true },
|
|
30
|
+
_hasLeftButtons : { state: true },
|
|
31
|
+
_hasRightIcon : { state: true },
|
|
32
|
+
_hasRightButtons: { state: true }
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
static styles = [tokens, announcementBannerCSS];
|
|
28
36
|
|
|
29
37
|
constructor() {
|
|
30
38
|
super();
|
|
31
|
-
this.title
|
|
39
|
+
this.title = "";
|
|
32
40
|
this.description = "";
|
|
41
|
+
this._hasLeftIcon = false;
|
|
42
|
+
this._hasLeftButtons = false;
|
|
43
|
+
this._hasRightIcon = false;
|
|
44
|
+
this._hasRightButtons = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Each optional slot's wrapper is only rendered with real layout
|
|
48
|
+
// (gap/margin) when it actually has assigned content — checked via
|
|
49
|
+
// slotchange rather than CSS :empty, since a <slot> with no assigned
|
|
50
|
+
// nodes still isn't :empty as a DOM matter the same way a plain element
|
|
51
|
+
// is, and this keeps the collapse logic in one predictable place.
|
|
52
|
+
_onSlotChange(stateKey, event) {
|
|
53
|
+
const hasContent = event.target.assignedNodes({ flatten: true }).length > 0;
|
|
54
|
+
if (this[stateKey] !== hasContent) this[stateKey] = hasContent;
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
render() {
|
|
58
|
+
// When a left icon is slotted in, visual emphasis flips: the left
|
|
59
|
+
// column's title renders small (mirroring the right column's default
|
|
60
|
+
// small description), and the right column's description renders
|
|
61
|
+
// large instead (mirroring the left column's default large title).
|
|
62
|
+
const flipped = this._hasLeftIcon;
|
|
63
|
+
|
|
36
64
|
return html`
|
|
37
65
|
<div class="bd-announcementbanner-container">
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
66
|
+
<div class="bd-announcementbanner-left">
|
|
67
|
+
<div class="bd-announcementbanner-icon" ?hidden=${!this._hasLeftIcon}>
|
|
68
|
+
<slot name="left-icon" @slotchange=${(e) => this._onSlotChange("_hasLeftIcon", e)}></slot>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
${flipped
|
|
72
|
+
? html`
|
|
73
|
+
<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-title bd-announcementbanner-title--small">
|
|
74
|
+
<slot name="title">${this.title}</slot>
|
|
75
|
+
</bd-p>
|
|
76
|
+
`
|
|
77
|
+
: html`
|
|
78
|
+
<bd-h as="h2" fontWeight="400" class="bd-announcementbanner-title">
|
|
79
|
+
<slot name="title">${this.title}</slot>
|
|
80
|
+
</bd-h>
|
|
81
|
+
`}
|
|
82
|
+
|
|
83
|
+
<div class="bd-announcementbanner-buttons" ?hidden=${!this._hasLeftButtons}>
|
|
84
|
+
<slot name="left-buttons" @slotchange=${(e) => this._onSlotChange("_hasLeftButtons", e)}></slot>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
41
87
|
|
|
42
88
|
<div class="bd-announcementbanner-side">
|
|
43
|
-
<div class="bd-announcementbanner-icon">
|
|
44
|
-
<slot name="icon"></slot>
|
|
89
|
+
<div class="bd-announcementbanner-icon" ?hidden=${!this._hasRightIcon}>
|
|
90
|
+
<slot name="icon" @slotchange=${(e) => this._onSlotChange("_hasRightIcon", e)}></slot>
|
|
45
91
|
</div>
|
|
46
92
|
|
|
47
93
|
${this.description
|
|
48
|
-
?
|
|
94
|
+
? flipped
|
|
95
|
+
? html`
|
|
96
|
+
<bd-h as="h2" fontWeight="400" color="var(--color-neutral-0)" class="bd-announcementbanner-description bd-announcementbanner-description--large">
|
|
97
|
+
<slot name="description">${this.description}</slot>
|
|
98
|
+
</bd-h>
|
|
99
|
+
`
|
|
100
|
+
: html`
|
|
101
|
+
<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-description">
|
|
102
|
+
<slot name="description">${this.description}</slot>
|
|
103
|
+
</bd-p>
|
|
104
|
+
`
|
|
49
105
|
: nothing}
|
|
106
|
+
|
|
107
|
+
<div class="bd-announcementbanner-buttons" ?hidden=${!this._hasRightButtons}>
|
|
108
|
+
<slot name="right-buttons" @slotchange=${(e) => this._onSlotChange("_hasRightButtons", e)}></slot>
|
|
109
|
+
</div>
|
|
50
110
|
</div>
|
|
51
111
|
</div>
|
|
52
112
|
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { html } from "lit";
|
|
2
|
+
import "../button/button.js";
|
|
2
3
|
import "../picture/picture.js";
|
|
3
4
|
import "./announcement-banner.js";
|
|
4
5
|
|
|
@@ -14,24 +15,24 @@ export default {
|
|
|
14
15
|
component: `
|
|
15
16
|
**AnnouncementBanner** este un banner simplu, cu 2 coloane, pe fundal albastru
|
|
16
17
|
gradient — titlu mare în stânga, iar în dreapta o iconiță/logo + caption +
|
|
17
|
-
descriere.
|
|
18
|
-
|
|
18
|
+
descriere. Coloana stângă poate primi opțional o imagine deasupra titlului
|
|
19
|
+
(\`left-icon\`) și butoane sub titlu (\`left-buttons\`); coloana dreaptă poate
|
|
20
|
+
primi opțional butoane sub descriere (\`right-buttons\`), pe lângă iconița
|
|
21
|
+
(\`icon\`) și descrierea (\`description\`) deja existente.
|
|
19
22
|
|
|
20
|
-
Pentru varianta centrată, cu butoane, vezi \`bd-cta-banner\`.
|
|
23
|
+
Pentru varianta centrată, cu un singur set de butoane, vezi \`bd-cta-banner\`.
|
|
21
24
|
|
|
22
25
|
### Usage
|
|
23
26
|
\`\`\`html
|
|
24
27
|
<bd-announcement-banner
|
|
25
28
|
title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
|
|
26
|
-
caption="Global Anti-Scam Alliance"
|
|
27
29
|
description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
28
30
|
>
|
|
29
|
-
<bd-picture
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
></bd-picture>
|
|
31
|
+
<bd-picture slot="left-icon" ...></bd-picture>
|
|
32
|
+
<bd-button-link slot="left-buttons" href="#" kind="outline-white">Learn more</bd-button-link>
|
|
33
|
+
|
|
34
|
+
<bd-picture slot="icon" ...></bd-picture>
|
|
35
|
+
<bd-button-link slot="right-buttons" href="#" kind="white">Read the report</bd-button-link>
|
|
35
36
|
</bd-announcement-banner>
|
|
36
37
|
\`\`\`
|
|
37
38
|
|
|
@@ -39,23 +40,23 @@ Pentru varianta centrată, cu butoane, vezi \`bd-cta-banner\`.
|
|
|
39
40
|
| Attribute | Type | Default | Description |
|
|
40
41
|
|------------------|--------|----------|------------------------------------------------------------|
|
|
41
42
|
| \`title\` | String | \`""\` | Titlul mare, afișat în coloana stângă (sau slot-ul \`title\`) |
|
|
42
|
-
| \`caption\` | String | \`""\` | Eticheta scurtă de deasupra descrierii, coloana dreaptă (opțional) |
|
|
43
43
|
| \`description\` | String | \`""\` | Textul descriptiv din coloana dreaptă (sau slot-ul \`description\`) |
|
|
44
44
|
|
|
45
45
|
### Slots
|
|
46
|
-
| Slot
|
|
47
|
-
|
|
48
|
-
| \`icon\`
|
|
49
|
-
| \`title\`
|
|
50
|
-
| \`
|
|
51
|
-
| \`
|
|
46
|
+
| Slot | Description |
|
|
47
|
+
|-------------------|----------------------------------------------------------------------|
|
|
48
|
+
| \`left-icon\` | Imagine/iconiță opțională, deasupra titlului, coloana stângă |
|
|
49
|
+
| \`title\` | Suprascrie atributul \`title\` dacă are conținut |
|
|
50
|
+
| \`left-buttons\` | Butoane opționale, sub titlu, coloana stângă |
|
|
51
|
+
| \`icon\` | Iconița/logo-ul din coloana dreaptă (ex: \`bd-picture\`) |
|
|
52
|
+
| \`description\` | Suprascrie atributul \`description\` dacă are conținut |
|
|
53
|
+
| \`right-buttons\` | Butoane opționale, sub descriere, coloana dreaptă |
|
|
52
54
|
`
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
},
|
|
56
58
|
argTypes: {
|
|
57
59
|
title : { control: "text", table: { category: "Content" } },
|
|
58
|
-
caption : { control: "text", table: { category: "Content" } },
|
|
59
60
|
description: { control: "text", table: { category: "Content" } }
|
|
60
61
|
}
|
|
61
62
|
};
|
|
@@ -79,9 +80,9 @@ const gasaSources = [
|
|
|
79
80
|
|
|
80
81
|
const gasaFallback = "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=750&format=png&optimize=medium";
|
|
81
82
|
|
|
82
|
-
const gasaPicture = () => html`
|
|
83
|
+
const gasaPicture = (slot = "icon") => html`
|
|
83
84
|
<bd-picture
|
|
84
|
-
slot
|
|
85
|
+
slot=${slot}
|
|
85
86
|
.sources=${gasaSources}
|
|
86
87
|
.fallbackSrc=${gasaFallback}
|
|
87
88
|
alt="Global Anti-Scam Alliance logo"
|
|
@@ -97,13 +98,11 @@ export const Default = {
|
|
|
97
98
|
name: "1. Default",
|
|
98
99
|
args: {
|
|
99
100
|
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
100
|
-
caption : "Global Anti-Scam Alliance",
|
|
101
101
|
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
102
102
|
},
|
|
103
103
|
render: (args) => html`
|
|
104
104
|
<bd-announcement-banner
|
|
105
105
|
title=${args.title}
|
|
106
|
-
caption=${args.caption}
|
|
107
106
|
description=${args.description}
|
|
108
107
|
>
|
|
109
108
|
${gasaPicture()}
|
|
@@ -112,23 +111,21 @@ export const Default = {
|
|
|
112
111
|
parameters: {
|
|
113
112
|
docs: {
|
|
114
113
|
description: {
|
|
115
|
-
story: "Varianta completă: titlu,
|
|
114
|
+
story: "Varianta completă: titlu, descriere și iconiță în dreapta — identică cu versiunea originală, fără elementele noi opționale."
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
};
|
|
120
119
|
|
|
121
120
|
export const NoCaption = {
|
|
122
|
-
name: "2. Fără
|
|
121
|
+
name: "2. Fără descriere lungă",
|
|
123
122
|
args: {
|
|
124
123
|
title : "Bitdefender named a Leader in the 2026 Endpoint Protection report",
|
|
125
|
-
caption : "",
|
|
126
124
|
description: "Recognised for detection accuracy, low false-positive rate, and minimal performance impact across three consecutive evaluation cycles."
|
|
127
125
|
},
|
|
128
126
|
render: (args) => html`
|
|
129
127
|
<bd-announcement-banner
|
|
130
128
|
title=${args.title}
|
|
131
|
-
caption=${args.caption}
|
|
132
129
|
description=${args.description}
|
|
133
130
|
>
|
|
134
131
|
${gasaPicture()}
|
|
@@ -137,7 +134,7 @@ export const NoCaption = {
|
|
|
137
134
|
parameters: {
|
|
138
135
|
docs: {
|
|
139
136
|
description: {
|
|
140
|
-
story: "
|
|
137
|
+
story: "Titlu + descriere + iconiță, fără sloturile noi de imagine stânga sau butoane."
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
}
|
|
@@ -147,7 +144,6 @@ export const TitleOnly = {
|
|
|
147
144
|
name: "3. Doar titlu",
|
|
148
145
|
args: {
|
|
149
146
|
title : "Bitdefender now supports macOS Tahoe on day one",
|
|
150
|
-
caption : "",
|
|
151
147
|
description: ""
|
|
152
148
|
},
|
|
153
149
|
render: (args) => html`
|
|
@@ -158,7 +154,81 @@ export const TitleOnly = {
|
|
|
158
154
|
parameters: {
|
|
159
155
|
docs: {
|
|
160
156
|
description: {
|
|
161
|
-
story: "Minimal — doar titlu și iconiță, fără
|
|
157
|
+
story: "Minimal — doar titlu și iconiță, fără descriere sau butoane."
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const WithButtonsBothSides = {
|
|
164
|
+
name: "4. Butoane pe ambele coloane",
|
|
165
|
+
args: {
|
|
166
|
+
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
167
|
+
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
168
|
+
},
|
|
169
|
+
render: (args) => html`
|
|
170
|
+
<bd-announcement-banner
|
|
171
|
+
title=${args.title}
|
|
172
|
+
description=${args.description}
|
|
173
|
+
>
|
|
174
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Learn more</bd-button-link>
|
|
175
|
+
<bd-button-link slot="left-buttons" href="#" kind="outline-white" size="sm">Our commitment</bd-button-link>
|
|
176
|
+
|
|
177
|
+
${gasaPicture()}
|
|
178
|
+
|
|
179
|
+
<bd-button-link slot="right-buttons" href="#" kind="outline-white" size="sm">Read the report</bd-button-link>
|
|
180
|
+
</bd-announcement-banner>
|
|
181
|
+
`,
|
|
182
|
+
parameters: {
|
|
183
|
+
docs: {
|
|
184
|
+
description: {
|
|
185
|
+
story: "Butoane atât sub titlu (`left-buttons`) cât și sub descriere (`right-buttons`) — fiecare coloană poate avea mai multe butoane, aliniate pe rând cu wrap."
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const WithLeftImage = {
|
|
192
|
+
name: "5. Imagine și în stânga",
|
|
193
|
+
args: {
|
|
194
|
+
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
195
|
+
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
196
|
+
},
|
|
197
|
+
render: (args) => html`
|
|
198
|
+
<bd-announcement-banner
|
|
199
|
+
title=${args.title}
|
|
200
|
+
description=${args.description}
|
|
201
|
+
>
|
|
202
|
+
${gasaPicture("left-icon")}
|
|
203
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Learn more</bd-button-link>
|
|
204
|
+
|
|
205
|
+
</bd-announcement-banner>
|
|
206
|
+
`,
|
|
207
|
+
parameters: {
|
|
208
|
+
docs: {
|
|
209
|
+
description: {
|
|
210
|
+
story: "Imagine opțională deasupra titlului (`left-icon`), plus buton sub titlu — coloana stângă poate avea propriul set de imagine + text + buton, independent de coloana dreaptă."
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const LeftOnly = {
|
|
217
|
+
name: "6. Doar coloana stângă (fără icon/description dreapta)",
|
|
218
|
+
args: {
|
|
219
|
+
title : "Bitdefender now supports macOS Tahoe on day one",
|
|
220
|
+
description: ""
|
|
221
|
+
},
|
|
222
|
+
render: (args) => html`
|
|
223
|
+
<bd-announcement-banner title=${args.title}>
|
|
224
|
+
${gasaPicture("left-icon")}
|
|
225
|
+
<bd-button-link slot="left-buttons" href="#" kind="white" size="sm">Download now</bd-button-link>
|
|
226
|
+
</bd-announcement-banner>
|
|
227
|
+
`,
|
|
228
|
+
parameters: {
|
|
229
|
+
docs: {
|
|
230
|
+
description: {
|
|
231
|
+
story: "Doar imagine + titlu + buton în stânga — coloana dreaptă rămâne complet goală și nu ocupă spațiu inutil, pentru că niciun slot din ea nu are conținut."
|
|
162
232
|
}
|
|
163
233
|
}
|
|
164
234
|
}
|
|
@@ -168,22 +238,21 @@ export const Playground = {
|
|
|
168
238
|
name: "Playground",
|
|
169
239
|
args: {
|
|
170
240
|
title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
|
|
171
|
-
caption : "Global Anti-Scam Alliance",
|
|
172
241
|
description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
|
|
173
242
|
},
|
|
174
243
|
render: (args) => html`
|
|
175
244
|
<bd-announcement-banner
|
|
176
245
|
title=${args.title}
|
|
177
|
-
caption=${args.caption}
|
|
178
246
|
description=${args.description}
|
|
179
247
|
>
|
|
180
248
|
${gasaPicture()}
|
|
249
|
+
<bd-button-link slot="right-buttons" href="#" kind="outline-white" size="sm">Read the report</bd-button-link>
|
|
181
250
|
</bd-announcement-banner>
|
|
182
251
|
`,
|
|
183
252
|
parameters: {
|
|
184
253
|
docs: {
|
|
185
254
|
description: {
|
|
186
|
-
story: "Interactiv — modifică `title
|
|
255
|
+
story: "Interactiv — modifică `title` și `description` din Controls."
|
|
187
256
|
}
|
|
188
257
|
}
|
|
189
258
|
}
|
|
@@ -260,6 +260,68 @@ export default css`
|
|
|
260
260
|
display: none;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/* === ALIGN ROWS — opt-in with align-rows on bd-card-section. The track is
|
|
264
|
+
a grid and every card is a subgrid spanning the same rows (header, title,
|
|
265
|
+
one per content node, CTA), so rows line up across cards even when their
|
|
266
|
+
content wraps differently. Row counts come from _syncAlignRows(). Row gaps
|
|
267
|
+
between tracks stay 0 so spacing inside cards is unchanged; spacing
|
|
268
|
+
between wrapped card rows comes from the item margin instead. === */
|
|
269
|
+
@media (min-width: 769px) {
|
|
270
|
+
:host([align-rows]) .bd-light-carousel-track-s {
|
|
271
|
+
display: grid;
|
|
272
|
+
grid-template-columns: repeat(var(--bd-card-cols), minmax(0, 1fr));
|
|
273
|
+
column-gap: 1.5rem;
|
|
274
|
+
row-gap: 0;
|
|
275
|
+
margin-bottom: -1.5rem;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
:host([align-rows]) ::slotted(bd-card-item) {
|
|
279
|
+
display: grid;
|
|
280
|
+
grid-template-rows: subgrid;
|
|
281
|
+
grid-row: span var(--bd-card-rows, 4);
|
|
282
|
+
width: auto;
|
|
283
|
+
min-width: 0;
|
|
284
|
+
margin: 0 0 1.5rem !important;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
:host([align-rows]) .bd-light-box-s {
|
|
288
|
+
display: grid;
|
|
289
|
+
grid-template-rows: subgrid;
|
|
290
|
+
grid-row: 1 / -1;
|
|
291
|
+
row-gap: 0;
|
|
292
|
+
/* center/outline boxes force flex centering with !important; in grid
|
|
293
|
+
those would center items vertically and shrink the column */
|
|
294
|
+
align-items: start !important;
|
|
295
|
+
justify-content: normal !important;
|
|
296
|
+
align-content: start;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
:host([align-rows]) .bd-light-box-header-s {
|
|
300
|
+
grid-row: 1;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
:host([align-rows]) .bd-light-title-badge-wrapper-s {
|
|
304
|
+
grid-row: 2;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
:host([align-rows]) .bd-card-content-wrapper {
|
|
308
|
+
display: grid;
|
|
309
|
+
grid-template-rows: subgrid;
|
|
310
|
+
grid-row: 3 / span var(--bd-card-content-rows, 1);
|
|
311
|
+
row-gap: 0;
|
|
312
|
+
align-items: start;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
:host([align-rows]) .bd-light-box-center .bd-card-content-wrapper,
|
|
316
|
+
:host([align-rows]) .bd-light-box-outline .bd-card-content-wrapper {
|
|
317
|
+
justify-items: center;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
:host([align-rows]) .bd-card-cta-wrapper {
|
|
321
|
+
grid-row: -2;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
263
325
|
|
|
264
326
|
@media (max-width: 768px) {
|
|
265
327
|
.bd-light-carousel-track-s {
|
|
@@ -16,6 +16,10 @@ class Card extends LitElement {
|
|
|
16
16
|
// on the section, unless a specific item needs to opt out by setting
|
|
17
17
|
// its own variant attribute explicitly.
|
|
18
18
|
variant : { type: String, reflect: true },
|
|
19
|
+
// Opt-in: lays the cards out on a CSS grid and makes every card a subgrid,
|
|
20
|
+
// so icon, title, each content row and CTA line up across cards (desktop
|
|
21
|
+
// only). Replaces the JS height equalization for this section.
|
|
22
|
+
alignRows : { type: Boolean, reflect: true, attribute: "align-rows" },
|
|
19
23
|
_itemCount: { state: true }
|
|
20
24
|
};
|
|
21
25
|
|
|
@@ -25,8 +29,10 @@ class Card extends LitElement {
|
|
|
25
29
|
this.subtitle = "";
|
|
26
30
|
this.bgBlue = false;
|
|
27
31
|
this.variant = "";
|
|
32
|
+
this.alignRows = false;
|
|
28
33
|
this._itemCount = 0;
|
|
29
34
|
this._resizeObserver = null;
|
|
35
|
+
this._mutationObserver = null;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
_isVisible() {
|
|
@@ -72,16 +78,51 @@ class Card extends LitElement {
|
|
|
72
78
|
});
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
// Mirrors align-rows onto every item (their shadow styles key off it) and
|
|
82
|
+
// sizes the shared row tracks: header, title, the longest content list, CTA.
|
|
83
|
+
_syncAlignRows() {
|
|
84
|
+
const items = [...this.querySelectorAll("bd-card-item")];
|
|
85
|
+
items.forEach((item) => item.toggleAttribute("align-rows", this.alignRows));
|
|
86
|
+
if (!this.alignRows) return;
|
|
87
|
+
|
|
88
|
+
const contentRows = Math.max(0, ...items.map((item) => item._contentNodes?.().length ?? 0));
|
|
89
|
+
this.style.setProperty("--bd-card-content-rows", contentRows);
|
|
90
|
+
this.style.setProperty("--bd-card-rows", contentRows + 3);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
updated(changed) {
|
|
94
|
+
if (!changed.has("alignRows")) return;
|
|
95
|
+
this._syncAlignRows();
|
|
96
|
+
if (this.alignRows) this._clearEqualizedHeights();
|
|
97
|
+
else this._equalizeHeights();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Removes the inline heights written by _equalizeHeights (e.g. when
|
|
101
|
+
// align-rows is switched on after the section already rendered).
|
|
102
|
+
_clearEqualizedHeights() {
|
|
103
|
+
this.querySelectorAll("bd-card-item").forEach((item) => {
|
|
104
|
+
const root = item.renderRoot;
|
|
105
|
+
if (!root) return;
|
|
106
|
+
[".bd-light-box-s", ".bd-light-box-header-s", ".bd-light-title-badge-wrapper-s", ".bd-card-content-wrapper"]
|
|
107
|
+
.forEach((selector) => {
|
|
108
|
+
const el = root.querySelector(selector);
|
|
109
|
+
if (el) { el.style.height = ""; el.style.minHeight = ""; }
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
75
114
|
_onSlotChange = () => {
|
|
76
115
|
this._itemCount = this.querySelectorAll("bd-card-item").length;
|
|
77
116
|
this._propagateVariant();
|
|
117
|
+
this._syncAlignRows();
|
|
78
118
|
this._updateCardWidth();
|
|
79
119
|
this.requestUpdate();
|
|
80
120
|
this._equalizeHeights();
|
|
81
121
|
}
|
|
82
122
|
|
|
83
123
|
_equalizeHeights() {
|
|
84
|
-
|
|
124
|
+
// align-rows sizes cards with subgrid; inline heights would fight it
|
|
125
|
+
if (this.alignRows || !this._isVisible()) return;
|
|
85
126
|
|
|
86
127
|
const items = Array.from(this.querySelectorAll("bd-card-item"));
|
|
87
128
|
if (!items.length) return;
|
|
@@ -187,6 +228,7 @@ class Card extends LitElement {
|
|
|
187
228
|
|
|
188
229
|
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
189
230
|
this._propagateVariant();
|
|
231
|
+
this._syncAlignRows();
|
|
190
232
|
this._updateCardWidth();
|
|
191
233
|
this._equalizeHeights();
|
|
192
234
|
|
|
@@ -198,11 +240,17 @@ class Card extends LitElement {
|
|
|
198
240
|
});
|
|
199
241
|
this._resizeObserver.observe(track);
|
|
200
242
|
}
|
|
243
|
+
|
|
244
|
+
// Content added to or removed from an item changes its row count, which
|
|
245
|
+
// doesn't trigger slotchange on the section.
|
|
246
|
+
this._mutationObserver = new MutationObserver(() => this._syncAlignRows());
|
|
247
|
+
this._mutationObserver.observe(this, { childList: true, subtree: true });
|
|
201
248
|
}
|
|
202
249
|
|
|
203
250
|
disconnectedCallback() {
|
|
204
251
|
super.disconnectedCallback();
|
|
205
252
|
this._resizeObserver?.disconnect();
|
|
253
|
+
this._mutationObserver?.disconnect();
|
|
206
254
|
}
|
|
207
255
|
|
|
208
256
|
render() {
|
|
@@ -255,7 +303,7 @@ class CardItem extends LitElement {
|
|
|
255
303
|
this.icon = "";
|
|
256
304
|
this.align = "start";
|
|
257
305
|
this.bgBlue = false;
|
|
258
|
-
this.ctaAlign = "
|
|
306
|
+
this.ctaAlign = "";
|
|
259
307
|
this.variant = "";
|
|
260
308
|
}
|
|
261
309
|
|
|
@@ -310,16 +358,19 @@ class CardItem extends LitElement {
|
|
|
310
358
|
|
|
311
359
|
return "";
|
|
312
360
|
}
|
|
313
|
-
|
|
314
|
-
|
|
361
|
+
// Nodes rendered in the default slot — each one is a content row
|
|
362
|
+
_contentNodes() {
|
|
363
|
+
return [...this.childNodes].filter((node) => {
|
|
315
364
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
316
365
|
const slot = node.getAttribute("slot");
|
|
317
366
|
return slot !== "icon" && slot !== "cta";
|
|
318
367
|
}
|
|
319
368
|
return node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== "";
|
|
320
369
|
});
|
|
370
|
+
}
|
|
321
371
|
|
|
322
|
-
|
|
372
|
+
_hasContent() {
|
|
373
|
+
return this._contentNodes().length > 0;
|
|
323
374
|
}
|
|
324
375
|
|
|
325
376
|
render() {
|
|
@@ -327,9 +378,10 @@ class CardItem extends LitElement {
|
|
|
327
378
|
const isCentered = this.align === "center" || isOutline;
|
|
328
379
|
const hasContent = this._hasContent();
|
|
329
380
|
const hasCtaSlot = !!this.querySelector("[slot='cta']");
|
|
330
|
-
const
|
|
381
|
+
const ctaAlign = this.ctaAlign || (isCentered ? "center" : "start");
|
|
382
|
+
const ctaJustify = ctaAlign === "center"
|
|
331
383
|
? "justify-content: center;"
|
|
332
|
-
:
|
|
384
|
+
: ctaAlign === "end"
|
|
333
385
|
? "justify-content: flex-end;"
|
|
334
386
|
: "justify-content: flex-start;";
|
|
335
387
|
|
|
@@ -34,6 +34,13 @@ to a bordered, light-blue-tinted, centered card style (blue title, centered
|
|
|
34
34
|
icon/text) — set it once on the section, it propagates to all items
|
|
35
35
|
automatically. Leaving \`variant\` unset keeps the existing grey-box look.
|
|
36
36
|
An individual \`bd-card-item\` can opt out with \`variant=""\` explicitly.
|
|
37
|
+
|
|
38
|
+
### Align rows
|
|
39
|
+
\`align-rows\` on \`bd-card-section\` lays the cards out with CSS subgrid
|
|
40
|
+
(above 768px): icon, title, each content element and the CTA line up across
|
|
41
|
+
cards, even when one card's text wraps to more lines. It replaces the JS
|
|
42
|
+
height equalization for that section. Note: an incomplete last row is
|
|
43
|
+
left-aligned instead of centered.
|
|
37
44
|
`
|
|
38
45
|
}
|
|
39
46
|
}
|
|
@@ -250,6 +257,81 @@ export const WithCTA = {
|
|
|
250
257
|
parameters: { docs: { description: { story: 'Cards cu buton CTA via `slot="cta"`.' } } }
|
|
251
258
|
};
|
|
252
259
|
|
|
260
|
+
// ─── Align Rows ───────────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
const alignRowsIcons = [
|
|
263
|
+
html`<bd-individual-icon slot="icon" width="32" height="32" color="#006DFF"></bd-individual-icon>`,
|
|
264
|
+
html`<bd-globe-icon slot="icon" width="32" height="32" color="#006DFF"></bd-globe-icon>`,
|
|
265
|
+
html`<bd-machine-learning-icon slot="icon" width="32" height="32" color="#006DFF"></bd-machine-learning-icon>`,
|
|
266
|
+
html`<bd-analysis-icon slot="icon" width="32" height="32" color="#006DFF"></bd-analysis-icon>`
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
// Store badges rendered as linked pictures (a content row), like on product pages
|
|
270
|
+
const storeBadges = [
|
|
271
|
+
{ src: '/images/badges/google-play-badge.png', alt: 'Get it on Google Play' },
|
|
272
|
+
{ src: '/images/badges/app-store-badge.png', alt: 'Download on the App Store' }
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
const storeBadge = (n) => {
|
|
276
|
+
const badge = storeBadges[(n - 1) % storeBadges.length];
|
|
277
|
+
return html`
|
|
278
|
+
<a href="#" style="display: block;">
|
|
279
|
+
<img src="${badge.src}" alt="${badge.alt}" width="188" height="56" style="display: block; width: auto; height: 56px;" />
|
|
280
|
+
</a>
|
|
281
|
+
`;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const AlignRows = {
|
|
285
|
+
name: 'Align Rows (subgrid)',
|
|
286
|
+
args: {
|
|
287
|
+
alignRows : true,
|
|
288
|
+
align : 'center',
|
|
289
|
+
variant : '',
|
|
290
|
+
showCta : true,
|
|
291
|
+
showBadges: true,
|
|
292
|
+
card1Title: 'Antivirus Free for Android',
|
|
293
|
+
card1Text : 'Blocks malware on your phone.',
|
|
294
|
+
card1Extra: 'No cost, no catch.',
|
|
295
|
+
card2Title: 'Virus Scanner for Mac',
|
|
296
|
+
card2Text : 'Catches Mac and Windows malware alike, including threats hiding in email attachments and downloads.',
|
|
297
|
+
card2Extra: 'Free. Forever.',
|
|
298
|
+
card3Title: 'Security for iOS, iPhone and iPad devices',
|
|
299
|
+
card3Text : 'Web protection.',
|
|
300
|
+
card3Extra: 'Free for iPhone and iPad.',
|
|
301
|
+
card4Title: '',
|
|
302
|
+
card4Text : '',
|
|
303
|
+
card4Extra: ''
|
|
304
|
+
},
|
|
305
|
+
argTypes: {
|
|
306
|
+
alignRows : { control: 'boolean', description: '`align-rows` on bd-card-section — toggle to compare with the JS height equalization' },
|
|
307
|
+
align : { control: { type: 'select' }, options: ['start', 'center'], description: 'bd-card-item align' },
|
|
308
|
+
variant : { control: { type: 'select' }, options: ['', 'outline'], description: 'bd-card-section variant' },
|
|
309
|
+
showCta : { control: 'boolean', description: 'Render a CTA (slot="cta") in every card' },
|
|
310
|
+
showBadges: { control: 'boolean', description: 'Render a store badge (linked picture) as the last content row — Google Play / App Store alternating' },
|
|
311
|
+
...Object.fromEntries([1, 2, 3, 4].flatMap((n) => [
|
|
312
|
+
[`card${n}Title`, { control: 'text', table: { category: `Card ${n}` }, description: n === 4 ? 'Leave empty to hide card 4' : 'Card title' }],
|
|
313
|
+
[`card${n}Text`, { control: 'text', table: { category: `Card ${n}` }, description: 'First content row (bd-p)' }],
|
|
314
|
+
[`card${n}Extra`, { control: 'text', table: { category: `Card ${n}` }, description: 'Second content row (bd-p) — leave empty to omit' }]
|
|
315
|
+
]))
|
|
316
|
+
},
|
|
317
|
+
render: (args) => html`
|
|
318
|
+
<bd-card-section title="Also free" variant="${args.variant}" ?align-rows=${args.alignRows}>
|
|
319
|
+
${[1, 2, 3, 4]
|
|
320
|
+
.filter((n) => args[`card${n}Title`])
|
|
321
|
+
.map((n) => html`
|
|
322
|
+
<bd-card-item align="${args.align}" title="${args[`card${n}Title`]}">
|
|
323
|
+
${alignRowsIcons[n - 1]}
|
|
324
|
+
${args[`card${n}Text`] ? html`<bd-p>${args[`card${n}Text`]}</bd-p>` : ''}
|
|
325
|
+
${args[`card${n}Extra`] ? html`<bd-p>${args[`card${n}Extra`]}</bd-p>` : ''}
|
|
326
|
+
${args.showBadges ? storeBadge(n) : ''}
|
|
327
|
+
${args.showCta ? html`<bd-button-link slot="cta" href="#" kind="primary" size="sm">Get it free</bd-button-link>` : ''}
|
|
328
|
+
</bd-card-item>
|
|
329
|
+
`)}
|
|
330
|
+
</bd-card-section>
|
|
331
|
+
`,
|
|
332
|
+
parameters: { docs: { description: { story: 'With `align-rows`, each row (icon, title, every content element, CTA) has the same height across cards, via CSS subgrid. Use the controls to toggle it and change card content. Rows only align above 768px — widen the canvas.' } } }
|
|
333
|
+
};
|
|
334
|
+
|
|
253
335
|
// ─── No Slotted Content ───────────────────────────────────────────────────────
|
|
254
336
|
|
|
255
337
|
export const NoSlottedContent = {
|
|
@@ -390,13 +472,18 @@ export const Playground = {
|
|
|
390
472
|
itemTitle : 'AI Protection',
|
|
391
473
|
align : 'start',
|
|
392
474
|
bgBlue : false,
|
|
393
|
-
ctaAlign : '
|
|
475
|
+
ctaAlign : ''
|
|
394
476
|
},
|
|
395
477
|
argTypes: {
|
|
396
478
|
sectionVariant: {
|
|
397
479
|
control : { type: 'select' },
|
|
398
480
|
options : ['', 'outline'],
|
|
399
481
|
description: 'bd-card-section variant — propagates to all items'
|
|
482
|
+
},
|
|
483
|
+
ctaAlign: {
|
|
484
|
+
control : { type: 'select' },
|
|
485
|
+
options : ['', 'start', 'center', 'end'],
|
|
486
|
+
description: 'bd-card-item cta-align — empty follows align'
|
|
400
487
|
}
|
|
401
488
|
},
|
|
402
489
|
render: (args) => html`
|