Skip to content

Commit 2abd893

Browse files
IsmaylRahmaouiIsmayl Toufik RAHMAOUI
authored andcommitted
feat: GAE-2413 add Message Bars component (#242)
Signed-off-by: Ismayl Toufik RAHMAOUI <[email protected]> Co-authored-by: Ismayl Toufik RAHMAOUI <[email protected]> Signed-off-by: Maude Laflamme <[email protected]>
1 parent 2967e2e commit 2abd893

File tree

10 files changed

+7506
-0
lines changed

10 files changed

+7506
-0
lines changed

build-storybook.log

Lines changed: 6688 additions & 0 deletions
Large diffs are not rendered by default.

projects/angular-ui/src/lib/hyperlink/hyperlink.component.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ export class BaoHyperlinkComponent implements AfterViewInit {
4949
public ngAfterViewInit() {
5050
this.setIcon();
5151
this.addIconClass();
52+
this.addTabIndex();
53+
}
54+
55+
private addTabIndex() {
56+
if (!this.nativeElement) return;
57+
const anchorElement = Array.from(this.nativeElement.children).find(
58+
el => el.localName === 'a'
59+
);
60+
if (!anchorElement) return;
61+
this.renderer.setAttribute(anchorElement, 'tabIndex', '0');
5262
}
5363

5464
private setIcon() {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright (c) 2025 Ville de Montreal. All rights reserved.
3+
* Licensed under the MIT license.
4+
* See LICENSE file in the project root for full license information.
5+
*/
6+
export * from './message-bar.component';
7+
export * from './module';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<bao-icon [svgIcon]="iconType" [title]="iconTitle"></bao-icon>
2+
3+
<div class="bao-message-content">
4+
<ng-content></ng-content>
5+
</div>
6+
7+
<button
8+
*ngIf="dismissible"
9+
class="bao-message-close"
10+
type="button"
11+
data-dismiss="message"
12+
role="button"
13+
[attr.aria-label]="dismissibleButtonAriaLabel"
14+
(click)="onDismissClicked()"
15+
>
16+
<bao-icon svgIcon="icon-x"></bao-icon>
17+
</button>
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
@import '../core/colors';
2+
@import '../core/radius';
3+
@import '../core/shadows';
4+
@import '../core/typography';
5+
@import '../core/mixins';
6+
7+
// Custom fade and show Animations
8+
@keyframes bao-fade-in {
9+
from {
10+
opacity: 0;
11+
}
12+
to {
13+
opacity: 1;
14+
}
15+
}
16+
17+
@keyframes bao-fade-out {
18+
from {
19+
opacity: 1;
20+
}
21+
to {
22+
opacity: 0;
23+
}
24+
}
25+
26+
.bao-fade-in {
27+
animation: bao-fade-in 0.3s ease-in-out;
28+
}
29+
30+
.bao-fade-out {
31+
animation: bao-fade-out 0.3s ease-in-out;
32+
pointer-events: none;
33+
}
34+
35+
// Colors per message type
36+
$message-colors: (
37+
info: (
38+
base: $informative-reversed,
39+
dark: $white,
40+
light: $informative-reversed,
41+
hover: darken($informative-reversed, 10%),
42+
focus: $warning-reversed
43+
),
44+
alert: (
45+
base: $warning-reversed,
46+
dark: $ground-reversed,
47+
light: $warning-reversed,
48+
hover: lighten($warning-reversed, 10%),
49+
focus: $white
50+
),
51+
urgent: (
52+
base: $negative-reversed,
53+
dark: $white,
54+
light: $negative-reversed,
55+
hover: darken($negative-reversed, 10%),
56+
focus: $warning-reversed
57+
),
58+
neutral: (
59+
base: $ground-reversed,
60+
dark: $white,
61+
light: $ground-reversed,
62+
hover: $neutral-secondary,
63+
focus: $warning-reversed
64+
)
65+
);
66+
67+
// General rules
68+
.bao-message-bar {
69+
display: flex;
70+
align-items: flex-start;
71+
padding: 1rem;
72+
width: 100%;
73+
position: relative;
74+
border-left-width: 4px;
75+
justify-content: flex-start;
76+
gap: 12px;
77+
padding-left: 32px;
78+
79+
/* Icon (Left) */
80+
.bao-message-icon {
81+
flex-shrink: 0;
82+
width: 1.5rem;
83+
height: 1.5rem;
84+
svg {
85+
fill: $white;
86+
width: 100%;
87+
height: 100%;
88+
}
89+
}
90+
@media (max-width: 768px) {
91+
padding-left: 16px;
92+
}
93+
94+
/* Message Content */
95+
.bao-message-content {
96+
display: inline;
97+
word-break: break-word;
98+
white-space: normal;
99+
max-width: calc(100% - 5rem);
100+
margin-right: 1rem;
101+
justify-content: space-between;
102+
}
103+
104+
.bao-message-content > * {
105+
display: inline;
106+
}
107+
108+
/* Link styles */
109+
a,
110+
bao-hyperlink {
111+
text-decoration: none;
112+
font-weight: $font-weight-bold;
113+
border-bottom: 1px solid currentColor;
114+
white-space: nowrap;
115+
display: inline-block;
116+
flex-shrink: 0;
117+
outline: none;
118+
padding: 2px;
119+
}
120+
121+
/* Close Button (Right) */
122+
.bao-message-close {
123+
background: $transparent;
124+
border: none;
125+
cursor: pointer;
126+
font-size: 1.5rem;
127+
color: $white;
128+
display: flex;
129+
align-items: center;
130+
justify-content: center;
131+
flex-shrink: 0;
132+
position: absolute;
133+
right: 1rem;
134+
top: 1rem;
135+
@include hover-focus {
136+
opacity: 0.75;
137+
}
138+
}
139+
}
140+
141+
@mixin focus-style($map, $is-close-button: false) {
142+
$color-base: map-get($map, base);
143+
$color-dark: map-get($map, dark);
144+
$color-focus: map-get($map, focus);
145+
146+
outline: 3px solid
147+
if($color-base == $warning-reversed, $informative-reversed, $color-focus);
148+
outline-offset: 3px;
149+
background-color: rgba($color-dark, 0.1);
150+
box-shadow: 0 0 0 3px if($color-base == $warning-reversed, $white, $black);
151+
152+
@if $is-close-button {
153+
border-radius: if($color-base == $warning-reversed, 2px, 4px);
154+
}
155+
}
156+
157+
// Apply correct colors per type
158+
@each $label, $map in $message-colors {
159+
$color-base: map-get($map, base);
160+
$color-light: map-get($map, light);
161+
$color-dark: map-get($map, dark);
162+
$color-hover: map-get($map, hover);
163+
$color-focus: map-get($map, focus);
164+
165+
.bao-message-bar-#{$label} {
166+
background-color: $color-light;
167+
border-left: 4px solid $color-base;
168+
color: $color-dark;
169+
170+
a,
171+
bao-hyperlink {
172+
color: $color-dark;
173+
margin: 3px;
174+
&:hover {
175+
background-color: $color-hover;
176+
}
177+
&:focus {
178+
@include focus-style($map);
179+
}
180+
}
181+
182+
.bao-message-icon svg {
183+
fill: $color-dark;
184+
}
185+
186+
.bao-message-close {
187+
color: $color-dark;
188+
background-color: $transparent;
189+
border-radius: $radius-none;
190+
height: 2.5rem;
191+
width: 2.5rem;
192+
margin-left: auto;
193+
margin-right: -0.5rem;
194+
margin-top: -0.5rem;
195+
196+
&:hover {
197+
opacity: 0.8;
198+
}
199+
200+
&:focus {
201+
@include focus-style($map, true);
202+
}
203+
}
204+
}
205+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2025 Ville de Montreal. All rights reserved.
3+
* Licensed under the MIT license.
4+
* See LICENSE file in the project root for full license information.
5+
*/
6+
import {
7+
Component,
8+
Input,
9+
Output,
10+
EventEmitter,
11+
OnChanges,
12+
SimpleChanges,
13+
ViewEncapsulation,
14+
ElementRef,
15+
Directive
16+
} from '@angular/core';
17+
18+
/**
19+
* The BaoMessageBarContent directive is used within the <bao-message-bar>
20+
* It ensures consistency in text formatting and spacing.
21+
*
22+
* This directive is purely visual and does not provide any additional behaviors.
23+
*/
24+
@Directive({
25+
selector: 'bao-message-content',
26+
host: { class: 'bao-message-content' }
27+
})
28+
export class BaoMessageBarContent {}
29+
30+
@Component({
31+
selector: 'bao-message-bar',
32+
templateUrl: './message-bar.component.html',
33+
styleUrls: ['./message-bar.component.scss'],
34+
encapsulation: ViewEncapsulation.None,
35+
host: {
36+
class: 'bao-message-bar message-bar-dismissible bao-fade-in',
37+
'[class.bao-message-bar-info]': 'type === "info"',
38+
'[class.bao-message-bar-alert]': 'type === "alert"',
39+
'[class.bao-message-bar-urgent]': 'type === "urgent"',
40+
'[class.bao-message-bar-neutral]': 'type === "neutral"'
41+
}
42+
})
43+
export class BaoMessageBarComponent implements OnChanges {
44+
@Input() type: 'info' | 'alert' | 'urgent' | 'neutral' = 'info';
45+
@Input() dismissible = false;
46+
@Input() dismissibleButtonAriaLabel = 'Cacher le message';
47+
48+
@Output() dismiss = new EventEmitter<void>();
49+
50+
iconType: string;
51+
iconTitle: string;
52+
53+
constructor(private elementRef: ElementRef) {}
54+
55+
ngOnChanges(changes: SimpleChanges): void {
56+
if (changes['type']) {
57+
this.iconType = this.getIconType(changes['type'].currentValue);
58+
this.iconTitle = this.getIconTitle(changes['type'].currentValue);
59+
}
60+
}
61+
62+
onDismissClicked(): void {
63+
const messageBar = this.elementRef.nativeElement;
64+
messageBar.classList.add('bao-fade-out');
65+
66+
setTimeout(() => {
67+
this.dismiss.emit();
68+
}, 300);
69+
}
70+
71+
private getIconType(value: string): string {
72+
const icons = {
73+
info: 'icon-info',
74+
alert: 'icon-warning',
75+
urgent: 'icon-emergency',
76+
neutral: 'icon-info'
77+
};
78+
return (icons[value] as string) || 'icon-info';
79+
}
80+
81+
private getIconTitle(value: string): string {
82+
const titles = {
83+
info: 'Information',
84+
alert: 'Alerte',
85+
urgent: 'Urgence',
86+
neutral: 'Information'
87+
};
88+
return (titles[value] as string) || 'Information';
89+
}
90+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2025 Ville de Montreal. All rights reserved.
3+
* Licensed under the MIT license.
4+
* See LICENSE file in the project root for full license information.
5+
*/
6+
import { NgModule } from '@angular/core';
7+
import { CommonModule } from '@angular/common';
8+
import { BaoIconModule } from '../icon/module';
9+
import { BaoButtonModule } from '../button/module';
10+
import {
11+
BaoMessageBarComponent,
12+
BaoMessageBarContent
13+
} from './message-bar.component';
14+
import { BaoHyperlinkModule } from '../hyperlink/module';
15+
16+
@NgModule({
17+
imports: [CommonModule, BaoIconModule, BaoButtonModule, BaoHyperlinkModule],
18+
declarations: [BaoMessageBarComponent, BaoMessageBarContent],
19+
exports: [BaoMessageBarComponent, BaoMessageBarContent]
20+
})
21+
export class BaoMessageBarModule {}

0 commit comments

Comments
 (0)