@repobit/dex-system-design 0.24.9 → 0.24.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.24.11](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.10...@repobit/dex-system-design@0.24.11) (2026-09-09)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **DEX-1014:** announcement banner component
11
+ * **DEX-1014:** announcement banner component
12
+
13
+
14
+ ## [0.24.10](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.9...@repobit/dex-system-design@0.24.10) (2026-09-08)
15
+
16
+ **Note:** Version bump only for package @repobit/dex-system-design
17
+
18
+
19
+
20
+
21
+
6
22
  ## [0.24.9](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.8...@repobit/dex-system-design@0.24.9) (2026-09-08)
7
23
 
8
24
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobit/dex-system-design",
3
- "version": "0.24.9",
3
+ "version": "0.24.11",
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",
@@ -74,8 +74,8 @@
74
74
  "url": "https://github.com/bitdefender/dex-core/issues"
75
75
  },
76
76
  "dependencies": {
77
- "@repobit/dex-store": "1.6.25",
78
- "@repobit/dex-store-elements": "2.0.30",
77
+ "@repobit/dex-store": "1.6.27",
78
+ "@repobit/dex-store-elements": "2.0.32",
79
79
  "lit": "^3.3.2"
80
80
  },
81
81
  "devDependencies": {
@@ -92,5 +92,5 @@
92
92
  "volta": {
93
93
  "node": "24.14.0"
94
94
  },
95
- "gitHead": "1f08b0389af256faebe7db77b276d350c9be31ae"
95
+ "gitHead": "1090fb8b6f3075a6fbfc7a2d055c3d00bfe05a55"
96
96
  }
@@ -0,0 +1,73 @@
1
+ import { css } from "lit";
2
+
3
+ export default css`
4
+ :host {
5
+ display: block;
6
+ font-family: "Roboto", "roboto-normal-400-fallback", helvetica, ubuntu, roboto, noto, sans-serif;
7
+ max-width: 1290px;
8
+ margin: 0 auto;
9
+ }
10
+
11
+ .bd-announcementbanner-container {
12
+ background: linear-gradient(87.6deg, var(--color-blue-500) 0%, var(--color-blue-950) 100%);
13
+ border-radius: var(--radius-2xl);
14
+ padding: var(--spacing-48);
15
+ box-sizing: border-box;
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ align-items: center;
19
+ // justify-content: space-between;
20
+ gap: var(--spacing-96);
21
+ }
22
+
23
+ .bd-announcementbanner-title {
24
+ display: block;
25
+ margin: 0;
26
+ color: var(--color-neutral-0, #fff);
27
+ line-height: var(--typography-lineHeight-tight, 1.15);
28
+ flex: 1 1 320px;
29
+ max-width: 560px;
30
+ }
31
+
32
+ .bd-announcementbanner-side {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: flex-start;
36
+ gap: var(--spacing-16);
37
+ flex: 1 1 280px;
38
+ max-width: 360px;
39
+ }
40
+
41
+ .bd-announcementbanner-icon {
42
+ display: flex;
43
+ align-items: center;
44
+ color: var(--color-neutral-0, #fff);
45
+ margin-bottom: var(--spacing-4);
46
+ }
47
+
48
+ .bd-announcementbanner-icon ::slotted(*) {
49
+ display: block;
50
+ max-width: 160px;
51
+ width: 100%;
52
+ height: auto;
53
+ }
54
+
55
+ .bd-announcementbanner-description {
56
+ margin: 0;
57
+ color: rgba(255, 255, 255, 0.65);
58
+ }
59
+
60
+ @media (max-width: 699px) {
61
+ .bd-announcementbanner-container {
62
+ padding: var(--spacing-28) var(--spacing-24);
63
+ flex-direction: column;
64
+ align-items: flex-start;
65
+ }
66
+
67
+ .bd-announcementbanner-title,
68
+ .bd-announcementbanner-side {
69
+ max-width: none;
70
+ flex-basis: auto;
71
+ }
72
+ }
73
+ `;
@@ -0,0 +1,56 @@
1
+ import { LitElement, html, nothing } from "lit";
2
+ import { tokens } from "../../tokens/tokens.js";
3
+ import "../heading/heading.js";
4
+ import "../paragraph/paragraph.js";
5
+ import announcementBannerCSS from "./announcement-banner.css.js";
6
+
7
+ /**
8
+ * Simple two-column banner on a blue gradient — a title on the left, an
9
+ * icon + description on the right. No buttons; for
10
+ * partnership/announcement callouts (e.g. "Bitdefender joined the Global
11
+ * Anti-Scam Alliance"), not calls to action. See bd-cta-banner for the
12
+ * centred, button-driven variant.
13
+ *
14
+ * <bd-announcement-banner
15
+ * title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
16
+ = * description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
17
+ * >
18
+ * <bd-gasa-icon slot="icon" width="140" height="40" color="#fff"></bd-gasa-icon>
19
+ * </bd-announcement-banner>
20
+ */
21
+ class BdAnnouncementBanner extends LitElement {
22
+ static properties = {
23
+ title : { type: String },
24
+ description: { type: String }
25
+ };
26
+
27
+ static styles = [tokens, announcementBannerCSS];
28
+
29
+ constructor() {
30
+ super();
31
+ this.title = "";
32
+ this.description = "";
33
+ }
34
+
35
+ render() {
36
+ return html`
37
+ <div class="bd-announcementbanner-container">
38
+ <bd-h as="h2" fontWeight="400" class="bd-announcementbanner-title">
39
+ <slot name="title">${this.title}</slot>
40
+ </bd-h>
41
+
42
+ <div class="bd-announcementbanner-side">
43
+ <div class="bd-announcementbanner-icon">
44
+ <slot name="icon"></slot>
45
+ </div>
46
+
47
+ ${this.description
48
+ ? html`<bd-p kind="large" color="var(--color-neutral-0)" class="bd-announcementbanner-description"><slot name="description">${this.description}</slot></bd-p>`
49
+ : nothing}
50
+ </div>
51
+ </div>
52
+ `;
53
+ }
54
+ }
55
+
56
+ customElements.define("bd-announcement-banner", BdAnnouncementBanner);
@@ -0,0 +1,190 @@
1
+ import { html } from "lit";
2
+ import "../picture/picture.js";
3
+ import "./announcement-banner.js";
4
+
5
+
6
+ export default {
7
+ title : "Components/AnnouncementBanner",
8
+ component : "bd-announcement-banner",
9
+ tags : ["autodocs"],
10
+ parameters: {
11
+ layout: "padded",
12
+ docs : {
13
+ description: {
14
+ component: `
15
+ **AnnouncementBanner** este un banner simplu, cu 2 coloane, pe fundal albastru
16
+ gradient — titlu mare în stânga, iar în dreapta o iconiță/logo + caption +
17
+ descriere. Nu are butoane — e gândit pentru anunțuri de tip parteneriat sau
18
+ apartenență la o organizație, nu pentru CTA-uri.
19
+
20
+ Pentru varianta centrată, cu butoane, vezi \`bd-cta-banner\`.
21
+
22
+ ### Usage
23
+ \`\`\`html
24
+ <bd-announcement-banner
25
+ title="Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member"
26
+ caption="Global Anti-Scam Alliance"
27
+ description="Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
28
+ >
29
+ <bd-picture
30
+ slot="icon"
31
+ .sources=\${sources}
32
+ .fallbackSrc=\${fallbackSrc}
33
+ alt="Global Anti-Scam Alliance logo"
34
+ ></bd-picture>
35
+ </bd-announcement-banner>
36
+ \`\`\`
37
+
38
+ ### Attributes
39
+ | Attribute | Type | Default | Description |
40
+ |------------------|--------|----------|------------------------------------------------------------|
41
+ | \`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
+ | \`description\` | String | \`""\` | Textul descriptiv din coloana dreaptă (sau slot-ul \`description\`) |
44
+
45
+ ### Slots
46
+ | Slot | Description |
47
+ |-----------------|--------------------------------------------------------------------|
48
+ | \`icon\` | Iconița/logo-ul din coloana dreaptă (ex: \`bd-picture\`) |
49
+ | \`title\` | Suprascrie atributul \`title\` dacă are conținut |
50
+ | \`caption\` | Suprascrie atributul \`caption\` dacă are conținut |
51
+ | \`description\` | Suprascrie atributul \`description\` dacă are conținut |
52
+ `
53
+ }
54
+ }
55
+ },
56
+ argTypes: {
57
+ title : { control: "text", table: { category: "Content" } },
58
+ caption : { control: "text", table: { category: "Content" } },
59
+ description: { control: "text", table: { category: "Content" } }
60
+ }
61
+ };
62
+
63
+ const gasaSources = [
64
+ {
65
+ type : "image/webp",
66
+ srcset: "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=2000&format=webply&optimize=medium",
67
+ media : "(min-width: 600px)"
68
+ },
69
+ {
70
+ type : "image/webp",
71
+ srcset: "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=750&format=webply&optimize=medium"
72
+ },
73
+ {
74
+ type : "image/png",
75
+ srcset: "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=2000&format=png&optimize=medium",
76
+ media : "(min-width: 600px)"
77
+ }
78
+ ];
79
+
80
+ const gasaFallback = "https://main--www-websites--bitdefender.aem.page/en-us/consumer/media_1a19736be4554469877718d743401be3086dbb640.png?width=750&format=png&optimize=medium";
81
+
82
+ const gasaPicture = () => html`
83
+ <bd-picture
84
+ slot="icon"
85
+ .sources=${gasaSources}
86
+ .fallbackSrc=${gasaFallback}
87
+ alt="Global Anti-Scam Alliance logo"
88
+ .width=${393}
89
+ .height=${144}
90
+ fit="contain"
91
+ loading="lazy"
92
+ referrer-policy="no-referrer"
93
+ ></bd-picture>
94
+ `;
95
+
96
+ export const Default = {
97
+ name: "1. Default",
98
+ args: {
99
+ title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
100
+ caption : "Global Anti-Scam Alliance",
101
+ description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
102
+ },
103
+ render: (args) => html`
104
+ <bd-announcement-banner
105
+ title=${args.title}
106
+ caption=${args.caption}
107
+ description=${args.description}
108
+ >
109
+ ${gasaPicture()}
110
+ </bd-announcement-banner>
111
+ `,
112
+ parameters: {
113
+ docs: {
114
+ description: {
115
+ story: "Varianta completă: titlu, caption, descriere și iconiță."
116
+ }
117
+ }
118
+ }
119
+ };
120
+
121
+ export const NoCaption = {
122
+ name: "2. Fără caption",
123
+ args: {
124
+ title : "Bitdefender named a Leader in the 2026 Endpoint Protection report",
125
+ caption : "",
126
+ description: "Recognised for detection accuracy, low false-positive rate, and minimal performance impact across three consecutive evaluation cycles."
127
+ },
128
+ render: (args) => html`
129
+ <bd-announcement-banner
130
+ title=${args.title}
131
+ caption=${args.caption}
132
+ description=${args.description}
133
+ >
134
+ ${gasaPicture()}
135
+ </bd-announcement-banner>
136
+ `,
137
+ parameters: {
138
+ docs: {
139
+ description: {
140
+ story: "Fără caption — util când titlul e suficient de descriptiv."
141
+ }
142
+ }
143
+ }
144
+ };
145
+
146
+ export const TitleOnly = {
147
+ name: "3. Doar titlu",
148
+ args: {
149
+ title : "Bitdefender now supports macOS Tahoe on day one",
150
+ caption : "",
151
+ description: ""
152
+ },
153
+ render: (args) => html`
154
+ <bd-announcement-banner title=${args.title}>
155
+ ${gasaPicture()}
156
+ </bd-announcement-banner>
157
+ `,
158
+ parameters: {
159
+ docs: {
160
+ description: {
161
+ story: "Minimal — doar titlu și iconiță, fără caption sau descriere."
162
+ }
163
+ }
164
+ }
165
+ };
166
+
167
+ export const Playground = {
168
+ name: "Playground",
169
+ args: {
170
+ title : "Bitdefender joined the Global Anti-Scam Alliance as a Foundation Member",
171
+ caption : "Global Anti-Scam Alliance",
172
+ description: "Global Anti-Scam Alliance (GASA) unites organisations, experts, and law enforcement to protect consumers worldwide from scams."
173
+ },
174
+ render: (args) => html`
175
+ <bd-announcement-banner
176
+ title=${args.title}
177
+ caption=${args.caption}
178
+ description=${args.description}
179
+ >
180
+ ${gasaPicture()}
181
+ </bd-announcement-banner>
182
+ `,
183
+ parameters: {
184
+ docs: {
185
+ description: {
186
+ story: "Interactiv — modifică `title`, `caption` și `description` din Controls."
187
+ }
188
+ }
189
+ }
190
+ };
@@ -9,7 +9,8 @@ export class BdP extends LitElement {
9
9
  paddingBottom: { type: String },
10
10
  paddingTop : { type: String },
11
11
  fontFamily : { type: String, attribute: "font-family" },
12
- fontWeight : { type: String, attribute: "font-weight" }
12
+ fontWeight : { type: String, attribute: "font-weight" },
13
+ color : { type: String }
13
14
  };
14
15
 
15
16
  constructor() {
@@ -20,6 +21,7 @@ export class BdP extends LitElement {
20
21
  this.paddingTop = "";
21
22
  this.fontFamily = "";
22
23
  this.fontWeight = "";
24
+ this.color = "";
23
25
  }
24
26
 
25
27
  _getKindClass() {
@@ -35,6 +37,22 @@ export class BdP extends LitElement {
35
37
  }
36
38
  }
37
39
 
40
+ // Same validation as bd-h's _getValidColor — only accepts CSS token
41
+ // references or real colour syntax, so a typo or stray text never lands
42
+ // in the inline style attribute as literal (and potentially unsafe) CSS.
43
+ _getValidColor(color) {
44
+ if (!color) return "";
45
+ if (
46
+ color.startsWith("var(") ||
47
+ color.startsWith("#") ||
48
+ color.startsWith("rgb") ||
49
+ color.startsWith("hsl")
50
+ ) {
51
+ return color;
52
+ }
53
+ return "";
54
+ }
55
+
38
56
  render() {
39
57
  const styles = [];
40
58
  if (this.padding) styles.push(`padding: ${this.padding};`);
@@ -43,6 +61,9 @@ export class BdP extends LitElement {
43
61
  if (this.fontFamily) styles.push(`font-family: ${this.fontFamily};`);
44
62
  if (this.fontWeight) styles.push(`font-weight: ${this.fontWeight};`);
45
63
 
64
+ const validColor = this._getValidColor(this.color);
65
+ if (validColor) styles.push(`color: ${validColor};`);
66
+
46
67
  const kindClass = this._getKindClass();
47
68
 
48
69
  return html`
@@ -1,17 +1,19 @@
1
- import { LitElement, html } from "lit";
1
+ import { LitElement, html, nothing } from "lit";
2
2
  import { tokens } from "../../tokens/tokens.js";
3
3
  import pictureCSS from "./picture.css.js";
4
4
 
5
5
  export class BdPicture extends LitElement {
6
6
  static properties = {
7
- sources : { type: Array },
8
- alt : { type: String },
9
- fallbackSrc: { type: String },
10
- width : { type: Number },
11
- height : { type: Number },
12
- loading : { type: String },
13
- fit : { type: String },
14
- radius : { type: String }
7
+ sources : { type: Array },
8
+ alt : { type: String },
9
+ fallbackSrc : { type: String },
10
+ width : { type: Number },
11
+ height : { type: Number },
12
+ loading : { type: String },
13
+ fit : { type: String },
14
+ radius : { type: String },
15
+ referrerPolicy: { type: String, attribute: "referrer-policy" },
16
+ _loaded : { state: true }
15
17
  };
16
18
 
17
19
  constructor() {
@@ -24,6 +26,25 @@ export class BdPicture extends LitElement {
24
26
  this.loading = "lazy";
25
27
  this.fit = "cover";
26
28
  this.radius = "none";
29
+ this.referrerPolicy = "";
30
+ this._loaded = false;
31
+ }
32
+
33
+ _onImgLoad() {
34
+ this._loaded = true;
35
+ }
36
+
37
+ _onImgError() {
38
+ this._loaded = true;
39
+ }
40
+
41
+ updated(changedProperties) {
42
+ super.updated(changedProperties);
43
+ if (this._loaded) return;
44
+ const img = this.renderRoot?.querySelector("img");
45
+ if (img && img.complete && img.naturalWidth > 0) {
46
+ this._loaded = true;
47
+ }
27
48
  }
28
49
 
29
50
  render() {
@@ -34,7 +55,7 @@ export class BdPicture extends LitElement {
34
55
  ].filter(Boolean).join(' ');
35
56
 
36
57
  return html`
37
- <picture class="${classes}">
58
+ <picture class="${classes}" ?loaded=${this._loaded}>
38
59
  ${this.sources.map(source => html`
39
60
  <source
40
61
  type="${source.type}"
@@ -49,6 +70,10 @@ export class BdPicture extends LitElement {
49
70
  src="${this.fallbackSrc}"
50
71
  width="${this.width}"
51
72
  height="${this.height}"
73
+ referrerpolicy="${this.referrerPolicy || nothing}"
74
+ ?loaded=${this._loaded}
75
+ @load=${this._onImgLoad}
76
+ @error=${this._onImgError}
52
77
  >
53
78
  </picture>
54
79
  `;