edu-webcomponents 1.31.2 → 1.32.0

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
@@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v1.32.0](https://github.com/eduardocruzpalacios/edu-webcomponents/compare/v1.31.2...v1.32.0)
8
+
9
+ - feat: add EduBanner component [`8f6ac6d`](https://github.com/eduardocruzpalacios/edu-webcomponents/commit/8f6ac6dfd7e987b6a444a5e6903d7288acd231f7)
10
+
7
11
  #### [v1.31.2](https://github.com/eduardocruzpalacios/edu-webcomponents/compare/v1.31.1...v1.31.2)
8
12
 
13
+ > 23 January 2026
14
+
15
+ - chore: release v1.31.2 [`0f2f3f1`](https://github.com/eduardocruzpalacios/edu-webcomponents/commit/0f2f3f1015279e63b67900b5a2d48538a6f460c9)
9
16
  - chore: improve Storybook docs of EduTooltip [`1c90062`](https://github.com/eduardocruzpalacios/edu-webcomponents/commit/1c90062d8fc979dbccb295ac76bdc549799cb99d)
10
17
 
11
18
  #### [v1.31.1](https://github.com/eduardocruzpalacios/edu-webcomponents/compare/v1.31.0...v1.31.1)
package/README.md CHANGED
@@ -48,6 +48,95 @@ Or import individual components:
48
48
 
49
49
  ## 📚 Components
50
50
 
51
+ ### đŸ“ĸ Banner
52
+
53
+ A full-width notification banner for displaying important messages and alerts with multiple types and positioning options.
54
+
55
+ **Properties:**
56
+ - `type` (String) - Banner type: `'info'`, `'success'`, `'warning'`, `'error'`, or `'neutral'` (default: `'info'`)
57
+ - `message` (String) - Banner message content (default: `''`)
58
+ - `dismissible` (Boolean) - Shows close button to dismiss the banner (default: `false`)
59
+ - `position` (String) - Position variant: `'static'`, `'sticky'`, or `'fixed'` (default: `'static'`)
60
+ - `icon` (String) - Custom icon (uses default icon per type if not provided)
61
+ - `aria-label` (String) - Accessibility label
62
+
63
+ **Accessibility:**
64
+ - Uses `role="alert"` for immediate screen reader announcements
65
+ - Customizable ARIA labels for context
66
+ - Keyboard-accessible close button with visible focus indicators
67
+ - Semantic color choices with sufficient contrast ratios
68
+ - Full dark mode support
69
+
70
+ **Usage:**
71
+
72
+ ```html
73
+ <!-- Basic info banner -->
74
+ <edu-banner
75
+ type="info"
76
+ message="This is an informational message">
77
+ </edu-banner>
78
+
79
+ <!-- Success notification -->
80
+ <edu-banner
81
+ type="success"
82
+ message="Your changes have been saved successfully!">
83
+ </edu-banner>
84
+
85
+ <!-- Warning with dismiss button -->
86
+ <edu-banner
87
+ type="warning"
88
+ message="Your session will expire in 5 minutes"
89
+ dismissible>
90
+ </edu-banner>
91
+
92
+ <!-- Error banner -->
93
+ <edu-banner
94
+ type="error"
95
+ message="Unable to process your request. Please try again.">
96
+ </edu-banner>
97
+
98
+ <!-- Sticky banner (sticks to top when scrolling) -->
99
+ <edu-banner
100
+ type="info"
101
+ message="New features available!"
102
+ position="sticky"
103
+ dismissible>
104
+ </edu-banner>
105
+
106
+ <!-- Fixed banner (always at top of viewport) -->
107
+ <edu-banner
108
+ type="warning"
109
+ message="Scheduled maintenance tonight at 10 PM"
110
+ position="fixed">
111
+ </edu-banner>
112
+
113
+ <!-- Custom icon -->
114
+ <edu-banner
115
+ type="success"
116
+ message="Achievement unlocked!"
117
+ icon="🏆">
118
+ </edu-banner>
119
+ ```
120
+
121
+ **JavaScript:**
122
+
123
+ ```javascript
124
+ import { EduBanner } from 'edu-webcomponents';
125
+
126
+ const banner = document.querySelector('edu-banner');
127
+
128
+ // Listen for close event
129
+ banner.addEventListener('banner-close', () => {
130
+ console.log('Banner dismissed');
131
+ });
132
+
133
+ // Programmatically update banner
134
+ banner.message = 'Updated message';
135
+ banner.type = 'error';
136
+ ```
137
+
138
+ ---
139
+
51
140
  ### đŸˇī¸ Badge
52
141
 
53
142
  A small status indicator or label component perfect for displaying counts, categories, or status information.
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { EduBadge } from './src/edu-badge/index.js';
2
+ export { EduBanner } from './src/edu-banner/index.js';
2
3
  export { EduButton } from './src/edu-button/index.js';
3
4
  export { EduDivider } from './src/edu-divider/index.js';
4
5
  export { EduLoadingSpinner } from './src/edu-loading-spinner/index.js';
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "author": "edu-webcomponents",
14
- "version": "1.31.2",
14
+ "version": "1.32.0",
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "https://github.com/eduardocruzpalacios/edu-webcomponents"
@@ -0,0 +1,182 @@
1
+ import { html } from 'lit';
2
+ import './index.js';
3
+
4
+ export default {
5
+ title: 'Edu web components/EduBanner',
6
+ tags: ['autodocs'],
7
+ };
8
+
9
+ const createStory = args => {
10
+ const story = ({ type, message, dismissible, position, icon } = args) => html`
11
+ <edu-banner
12
+ .type=${type}
13
+ .message=${message}
14
+ ?dismissible=${dismissible}
15
+ .position=${position}
16
+ .icon=${icon}
17
+ ></edu-banner>
18
+ `;
19
+ story.parameters = {
20
+ controls: { expanded: true },
21
+ docs: { source: { type: 'code' } },
22
+ };
23
+ story.argTypes = {
24
+ type: {
25
+ control: 'select',
26
+ options: ['info', 'success', 'warning', 'error', 'neutral'],
27
+ description: 'Banner type: info / success / warning / error / neutral',
28
+ name: 'type',
29
+ },
30
+ message: {
31
+ control: 'text',
32
+ description: 'Banner message content',
33
+ name: 'message',
34
+ },
35
+ dismissible: {
36
+ control: 'boolean',
37
+ description: 'Show close button to dismiss the banner',
38
+ name: 'dismissible',
39
+ },
40
+ position: {
41
+ control: 'select',
42
+ options: ['static', 'sticky', 'fixed'],
43
+ description: 'Banner position: static / sticky / fixed',
44
+ name: 'position',
45
+ },
46
+ icon: {
47
+ control: 'text',
48
+ description: 'Custom icon (leave empty for default icon based on type)',
49
+ name: 'icon',
50
+ },
51
+ };
52
+ story.args = args;
53
+ return story;
54
+ };
55
+
56
+ const defaultArgs = {
57
+ type: 'info',
58
+ message: 'This is an informational banner message.',
59
+ dismissible: false,
60
+ position: 'static',
61
+ icon: '',
62
+ };
63
+
64
+ export const EduBannerInfo = createStory({ ...defaultArgs });
65
+
66
+ export const EduBannerSuccess = createStory({
67
+ ...defaultArgs,
68
+ type: 'success',
69
+ message: 'Operation completed successfully!',
70
+ });
71
+
72
+ export const EduBannerWarning = createStory({
73
+ ...defaultArgs,
74
+ type: 'warning',
75
+ message: 'Please review your settings before continuing.',
76
+ });
77
+
78
+ export const EduBannerError = createStory({
79
+ ...defaultArgs,
80
+ type: 'error',
81
+ message: 'An error occurred while processing your request.',
82
+ });
83
+
84
+ export const EduBannerNeutral = createStory({
85
+ ...defaultArgs,
86
+ type: 'neutral',
87
+ message: 'This is a neutral banner for general information.',
88
+ });
89
+
90
+ export const EduBannerDismissible = createStory({
91
+ ...defaultArgs,
92
+ type: 'info',
93
+ message: 'You can close this banner by clicking the X button.',
94
+ dismissible: true,
95
+ });
96
+
97
+ export const EduBannerSticky = createStory({
98
+ ...defaultArgs,
99
+ type: 'warning',
100
+ message: 'This banner sticks to the top when you scroll.',
101
+ position: 'sticky',
102
+ });
103
+
104
+ export const EduBannerFixed = createStory({
105
+ ...defaultArgs,
106
+ type: 'info',
107
+ message: 'This banner is fixed to the top of the viewport.',
108
+ position: 'fixed',
109
+ });
110
+
111
+ export const EduBannerCustomIcon = createStory({
112
+ ...defaultArgs,
113
+ type: 'success',
114
+ message: 'Custom icon example with a star.',
115
+ icon: '⭐',
116
+ });
117
+
118
+ export const AllTypes = () => {
119
+ const story = () => html`
120
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
121
+ <edu-banner
122
+ type="info"
123
+ message="This is an informational banner."
124
+ ></edu-banner>
125
+ <edu-banner
126
+ type="success"
127
+ message="Operation completed successfully!"
128
+ ></edu-banner>
129
+ <edu-banner
130
+ type="warning"
131
+ message="Please review your settings."
132
+ ></edu-banner>
133
+ <edu-banner type="error" message="An error occurred."></edu-banner>
134
+ <edu-banner
135
+ type="neutral"
136
+ message="This is a neutral banner."
137
+ ></edu-banner>
138
+ </div>
139
+ `;
140
+ story.parameters = {
141
+ controls: { disable: true },
142
+ docs: { source: { type: 'code' } },
143
+ };
144
+ return story();
145
+ };
146
+
147
+ export const AllTypesDismissible = () => {
148
+ const story = () => html`
149
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
150
+ <edu-banner
151
+ type="info"
152
+ message="Dismissible info banner."
153
+ dismissible
154
+ ></edu-banner>
155
+ <edu-banner
156
+ type="success"
157
+ message="Dismissible success banner."
158
+ dismissible
159
+ ></edu-banner>
160
+ <edu-banner
161
+ type="warning"
162
+ message="Dismissible warning banner."
163
+ dismissible
164
+ ></edu-banner>
165
+ <edu-banner
166
+ type="error"
167
+ message="Dismissible error banner."
168
+ dismissible
169
+ ></edu-banner>
170
+ <edu-banner
171
+ type="neutral"
172
+ message="Dismissible neutral banner."
173
+ dismissible
174
+ ></edu-banner>
175
+ </div>
176
+ `;
177
+ story.parameters = {
178
+ controls: { disable: true },
179
+ docs: { source: { type: 'code' } },
180
+ };
181
+ return story();
182
+ };
@@ -0,0 +1 @@
1
+ export { EduBanner } from './src/EduBanner.js';
@@ -0,0 +1,236 @@
1
+ import { html, css, LitElement } from 'lit';
2
+ import { colorsConstants, typographyConstants } from '../../stylesConstants.js';
3
+
4
+ export class EduBanner extends LitElement {
5
+ static styles = [
6
+ colorsConstants,
7
+ typographyConstants,
8
+ css`
9
+ :host {
10
+ display: block;
11
+ width: 100%;
12
+ }
13
+
14
+ .banner {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ gap: 1rem;
19
+ padding: 1rem 1.5rem;
20
+ font-family: inherit;
21
+ line-height: var(--lineHeight);
22
+ border-left: 4px solid transparent;
23
+ position: relative;
24
+ }
25
+
26
+ .banner__content {
27
+ flex: 1;
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 0.75rem;
31
+ }
32
+
33
+ .banner__icon {
34
+ flex-shrink: 0;
35
+ font-size: 1.25rem;
36
+ line-height: 1;
37
+ }
38
+
39
+ .banner__message {
40
+ flex: 1;
41
+ }
42
+
43
+ .banner__close {
44
+ flex-shrink: 0;
45
+ background: none;
46
+ border: none;
47
+ padding: 0.25rem;
48
+ cursor: pointer;
49
+ font-size: 1.25rem;
50
+ line-height: 1;
51
+ opacity: 0.7;
52
+ transition: opacity 0.2s ease-in-out;
53
+ min-height: 24px;
54
+ min-width: 24px;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ border-radius: 4px;
59
+ }
60
+
61
+ .banner__close:hover {
62
+ opacity: 1;
63
+ }
64
+
65
+ .banner__close:focus-visible {
66
+ outline: 2px solid currentColor;
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ /* Hidden state */
71
+ :host([hidden]) {
72
+ display: none;
73
+ }
74
+
75
+ /* Type variants - Light mode */
76
+ .banner--info {
77
+ background-color: #e3f2fd;
78
+ color: #01579b;
79
+ border-left-color: #2196f3;
80
+ }
81
+
82
+ .banner--success {
83
+ background-color: #e8f5e9;
84
+ color: #1b5e20;
85
+ border-left-color: #4caf50;
86
+ }
87
+
88
+ .banner--warning {
89
+ background-color: #fff3e0;
90
+ color: #e65100;
91
+ border-left-color: #ff9800;
92
+ }
93
+
94
+ .banner--error {
95
+ background-color: #ffebee;
96
+ color: #b71c1c;
97
+ border-left-color: #f44336;
98
+ }
99
+
100
+ .banner--neutral {
101
+ background-color: var(--greyLight);
102
+ color: var(--blackLight);
103
+ border-left-color: var(--greyDark);
104
+ }
105
+
106
+ /* Dark mode support */
107
+ @media (prefers-color-scheme: dark) {
108
+ .banner--info {
109
+ background-color: #0d47a1;
110
+ color: #bbdefb;
111
+ border-left-color: #42a5f5;
112
+ }
113
+
114
+ .banner--success {
115
+ background-color: #1b5e20;
116
+ color: #c8e6c9;
117
+ border-left-color: #66bb6a;
118
+ }
119
+
120
+ .banner--warning {
121
+ background-color: #e65100;
122
+ color: #ffe0b2;
123
+ border-left-color: #ffa726;
124
+ }
125
+
126
+ .banner--error {
127
+ background-color: #b71c1c;
128
+ color: #ffcdd2;
129
+ border-left-color: #ef5350;
130
+ }
131
+
132
+ .banner--neutral {
133
+ background-color: var(--greyLight);
134
+ color: var(--blackLight);
135
+ border-left-color: var(--greyDark);
136
+ }
137
+
138
+ .banner__close {
139
+ color: currentColor;
140
+ }
141
+ }
142
+
143
+ /* Position variants */
144
+ .banner--sticky {
145
+ position: sticky;
146
+ top: 0;
147
+ z-index: 1000;
148
+ }
149
+
150
+ .banner--fixed {
151
+ position: fixed;
152
+ top: 0;
153
+ left: 0;
154
+ right: 0;
155
+ z-index: 1000;
156
+ }
157
+ `,
158
+ ];
159
+
160
+ static properties = {
161
+ type: { type: String },
162
+ message: { type: String },
163
+ dismissible: { type: Boolean },
164
+ position: { type: String },
165
+ icon: { type: String },
166
+ ariaLabel: { type: String, attribute: 'aria-label' },
167
+ };
168
+
169
+ constructor() {
170
+ super();
171
+ this.type = 'info';
172
+ this.message = '';
173
+ this.dismissible = false;
174
+ this.position = 'static';
175
+ this.icon = '';
176
+ this.ariaLabel = '';
177
+ }
178
+
179
+ _getDefaultIcon() {
180
+ const icons = {
181
+ info: 'â„šī¸',
182
+ success: '✓',
183
+ warning: 'âš ī¸',
184
+ error: '✕',
185
+ neutral: 'â€ĸ',
186
+ };
187
+ return icons[this.type] || icons.info;
188
+ }
189
+
190
+ _handleClose() {
191
+ this.dispatchEvent(
192
+ new CustomEvent('banner-close', {
193
+ bubbles: true,
194
+ composed: true,
195
+ })
196
+ );
197
+ this.style.display = 'none';
198
+ }
199
+
200
+ render() {
201
+ const iconToDisplay = this.icon || this._getDefaultIcon();
202
+ const computedAriaLabel =
203
+ this.ariaLabel || `${this.type} banner: ${this.message}`;
204
+
205
+ return html`
206
+ <div
207
+ class="banner banner--${this.type} banner--${this.position}"
208
+ role="alert"
209
+ aria-label=${computedAriaLabel}
210
+ >
211
+ <div class="banner__content">
212
+ ${iconToDisplay
213
+ ? html`<span class="banner__icon" aria-hidden="true"
214
+ >${iconToDisplay}</span
215
+ >`
216
+ : ''}
217
+ <div class="banner__message">${this.message}</div>
218
+ </div>
219
+ ${this.dismissible
220
+ ? html`
221
+ <button
222
+ class="banner__close"
223
+ @click=${this._handleClose}
224
+ aria-label="Close banner"
225
+ type="button"
226
+ >
227
+ ✕
228
+ </button>
229
+ `
230
+ : ''}
231
+ </div>
232
+ `;
233
+ }
234
+ }
235
+
236
+ customElements.define('edu-banner', EduBanner);