Skip to content

Commit 920f7e3

Browse files
authored
Merge pull request #124 from hashicorp/amy/alert-page
Alert (Page) and Alert (Inline)
2 parents fd7c25c + e01372d commit 920f7e3

File tree

10 files changed

+675
-91
lines changed

10 files changed

+675
-91
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
<div class={{this.classNames}} ...attributes role="alert">
2-
{{#if @icon}}
2+
{{#if this.icon}}
33
<div class="hds-alert__icon">
4-
<FlightIcon
5-
@name={{this.icon}}
6-
@size="24"
7-
{{! TODO: Replace w/dynamic color, once color prop implemented }}
8-
@color="var(--token-color-foreground-warning-on-surface)"
9-
@isInlineBlock={{false}}
10-
/>
4+
<FlightIcon @name={{this.icon}} @size="24" @isInlineBlock={{false}} />
115
</div>
126
{{/if}}
137

14-
<div class="hds-alert__text">
15-
{{#if @title}}
16-
<div class="hds-alert__title">{{@title}}</div>
17-
{{/if}}
8+
<div class="hds-alert__content">
9+
<div class="hds-alert__text">
10+
{{#if @title}}
11+
<div class="hds-alert__title">{{@title}}</div>
12+
{{/if}}
13+
{{#if @description}}
14+
<div class="hds-alert__description">{{html-safe @description}}</div>
15+
{{/if}}
16+
</div>
1817

19-
{{#if @description}}
20-
<div class="hds-alert__description">{{html-safe @description}}</div>
18+
{{#if (has-block "actions")}}
19+
{{! IMPORTANT: don't change the formatting or it will add empty space inside the element (we're using ":empty" in CSS to hide it when it's empty!) }}
20+
<div class="hds-alert__actions">
21+
{{yield
22+
(hash
23+
Button=(component "hds/button")
24+
Link::Standalone=(component "hds/link/standalone")
25+
LinkTo::Standalone=(component "hds/link-to/standalone")
26+
)
27+
to="actions"
28+
}}
29+
</div>
2130
{{/if}}
2231
</div>
2332
</div>
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
import Component from '@glimmer/component';
22
import { assert } from '@ember/debug';
33

4+
export const DEFAULT_TYPE = 'page';
5+
export const TYPES = ['page', 'inline', 'compact', 'toast'];
6+
export const DEFAULT_COLOR = 'neutral';
7+
export const COLORS = [
8+
'neutral',
9+
'highlight',
10+
'success',
11+
'warning',
12+
'critical',
13+
];
14+
export const MAPPING_COLORS_TO_ICONS = {
15+
critical: 'alert-octagon',
16+
warning: 'alert-triangle',
17+
neutral: 'info',
18+
highlight: 'info',
19+
success: 'check-circle',
20+
};
21+
422
export default class HdsAlertIndexComponent extends Component {
523
constructor() {
624
super(...arguments);
@@ -11,26 +29,79 @@ export default class HdsAlertIndexComponent extends Component {
1129
);
1230
}
1331

32+
/**
33+
* @param type
34+
* @type {enum}
35+
* @default page
36+
* @description Determines the type of the alert.
37+
*/
38+
get type() {
39+
let { type = DEFAULT_TYPE } = this.args;
40+
41+
assert(
42+
`@type for "Hds::Alert" must be one of the following: ${TYPES.join(
43+
', '
44+
)}; received: ${type}`,
45+
TYPES.includes(type)
46+
);
47+
48+
return type;
49+
}
50+
51+
/**
52+
* @param color
53+
* @type {enum}
54+
* @default neutral
55+
* @description Determines the color scheme for the alert.
56+
*/
57+
get color() {
58+
let { color = DEFAULT_COLOR } = this.args;
59+
60+
assert(
61+
`@color for "Hds::Alert" must be one of the following: ${COLORS.join(
62+
', '
63+
)}; received: ${color}`,
64+
COLORS.includes(color)
65+
);
66+
67+
return color;
68+
}
69+
1470
/**
1571
* @param icon
1672
* @type {string}
1773
* @default null
1874
* @description The name of the icon to be used.
1975
*/
2076
get icon() {
21-
return this.args.icon ?? null;
77+
let { icon } = this.args;
78+
79+
// If `icon` isn't passed, use the pre-defined one from `color`
80+
if (icon === undefined) {
81+
return MAPPING_COLORS_TO_ICONS[this.color];
82+
// If `icon` is set explicitly to false, user doesn't want any icon in the alert
83+
} else if (icon === false) {
84+
return false;
85+
} else {
86+
// If a name for `icon` is passed, set FlightIcon to that name
87+
return icon;
88+
}
2289
}
2390

2491
/**
2592
* Get the class names to apply to the component.
2693
* @method Alert#classNames
2794
* @return {string} The "class" attribute to apply to the component.
2895
*/
29-
// "hds-alert"
3096
get classNames() {
3197
let classes = ['hds-alert'];
3298

33-
// TODO: Add type classes, once type implemented
34-
return classes;
99+
// Add a class based on the @type argument
100+
classes.push(`hds-alert--type-${this.type}`);
101+
102+
// Add a class based on the @color argument
103+
classes.push(`hds-alert--color-${this.color}`);
104+
105+
return classes.join(' ');
35106
}
36107
}

packages/components/app/styles/components/alert.scss

+110-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77

88
.hds-alert {
99
align-content: flex-start;
10-
background-color: var(--token-color-surface-warning);
1110
display: flex;
12-
padding: 1.25rem; // 20px
11+
padding: 1rem; // 16px
1312
}
1413

1514
.hds-alert__icon {
@@ -27,12 +26,12 @@
2726
font-weight: var(--token-typography-font-weight-semibold);
2827
}
2928

30-
3129
.hds-alert__description {
3230
font-weight: var(--token-typography-font-weight-regular);
31+
color: var(--token-color-foreground-primary);
3332

3433
.hds-alert__title + & {
35-
margin-top: 0.75rem; // 12px
34+
margin-top: 0.25rem; // 4px
3635
}
3736

3837
strong {
@@ -46,7 +45,7 @@
4645
// Notice: in the future this may become a "Link::Inline" component (for now we declare the styles directly here)
4746
a {
4847
color: var(--token-color-foreground-action);
49-
// at the moment the "focus" state is not well defined in design (the one that is in Figma does not work) so we just apply a simple color to the default outline
48+
// At the moment the "focus" state is not well defined in design (the one that is in Figma does not work) so we just apply a simple color to the default outline
5049
outline-color: var(--token-color-focus-action-external);
5150

5251
&:hover {
@@ -58,3 +57,109 @@
5857
}
5958
}
6059
}
60+
61+
//
62+
// COLOR & TYPE
63+
//
64+
.hds-alert--color-neutral {
65+
background-color: var(--token-color-surface-faint);
66+
67+
&.hds-alert--type-page {
68+
box-shadow: 0px 1px 0px 0px var(--token-color-palette-alpha-300);
69+
}
70+
71+
&.hds-alert--type-inline {
72+
border-color: var(--token-color-border-strong);
73+
}
74+
75+
.hds-alert__icon, .hds-alert__title {
76+
color: var(--token-color-foreground-primary);
77+
}
78+
}
79+
80+
.hds-alert--color-highlight {
81+
background-color: var(--token-color-surface-highlight);
82+
83+
&.hds-alert--type-page {
84+
box-shadow: 0px 1px 0px 0px var(--token-color-border-highlight);
85+
}
86+
87+
&.hds-alert--type-inline {
88+
border-color: var(--token-color-border-highlight);
89+
}
90+
91+
.hds-alert__icon, .hds-alert__title {
92+
color: var(--token-color-foreground-highlight-on-surface);
93+
}
94+
}
95+
96+
.hds-alert--color-success {
97+
background-color: var(--token-color-surface-success);
98+
99+
&.hds-alert--type-page {
100+
box-shadow: 0px 1px 0px 0px var(--token-color-border-success);
101+
}
102+
103+
&.hds-alert--type-inline {
104+
border-color: var(--token-color-border-success);
105+
}
106+
107+
.hds-alert__icon, .hds-alert__title {
108+
color: var(--token-color-foreground-success-on-surface);
109+
}
110+
}
111+
112+
.hds-alert--color-warning {
113+
background-color: var(--token-color-surface-warning);
114+
115+
&.hds-alert--type-page {
116+
box-shadow: 0px 1px 0px 0px var(--token-color-border-warning);
117+
}
118+
119+
&.hds-alert--type-inline {
120+
border-color: var(--token-color-border-warning);
121+
}
122+
123+
.hds-alert__icon, .hds-alert__title {
124+
color: var(--token-color-foreground-warning-on-surface);
125+
}
126+
}
127+
128+
.hds-alert--color-critical {
129+
background-color: var(--token-color-surface-critical);
130+
131+
&.hds-alert--type-page {
132+
box-shadow: 0px 1px 0px 0px var(--token-color-border-critical);
133+
}
134+
135+
&.hds-alert--type-inline {
136+
border-color: var(--token-color-border-critical);
137+
}
138+
139+
.hds-alert__icon, .hds-alert__title {
140+
color: var(--token-color-foreground-critical-on-surface);
141+
}
142+
}
143+
144+
.hds-alert--type-inline {
145+
border-width: 1px;
146+
border-style: solid;
147+
border-radius: 6px;
148+
}
149+
150+
//
151+
// ACTIONS
152+
//
153+
.hds-alert__actions {
154+
align-items: center;
155+
display: flex;
156+
margin-top: 1rem; // 16px
157+
158+
> * + * {
159+
margin-left: 1rem; // 16px
160+
}
161+
162+
&:empty {
163+
display: none;
164+
}
165+
}

packages/components/tests/acceptance/percy-test.js

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ module('Acceptance | Percy test', function (hooks) {
1919
await visit('/foundations/focus-ring');
2020
await percySnapshot('FocusRing');
2121

22+
await visit('/components/alert');
23+
await percySnapshot('Alert');
24+
2225
await visit('/components/badge');
2326
await percySnapshot('Badge');
2427

packages/components/tests/dummy/app/components/dummy-placeholder/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default class DummyPlaceholderIndexComponent extends Component {
1111
get width() {
1212
let { width = '100%' } = this.args;
1313

14-
if (typeof width === 'string' && width.match(/[\d]+/)) {
14+
if (typeof width === 'string' && width.match(/^[\d]+$/)) {
1515
width = `${parseInt(width, 10)}px`;
1616
}
1717

@@ -28,7 +28,7 @@ export default class DummyPlaceholderIndexComponent extends Component {
2828
get height() {
2929
let { height = '100%' } = this.args;
3030

31-
if (typeof height === 'string' && height.match(/[\d]+/)) {
31+
if (typeof height === 'string' && height.match(/^[\d]+$/)) {
3232
height = `${parseInt(height, 10)}px`;
3333
}
3434

packages/components/tests/dummy/app/styles/app.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
@import "./_typography";
55

6+
@import "./pages/db-alert";
67
@import "./pages/db-badge";
78
@import "./pages/db-breadcrumb";
89
@import "./pages/db-button";
@@ -208,7 +209,6 @@ body.dummy-app {
208209
border-color: #f5c6cb;
209210
}
210211

211-
212212
// Percy (percySnapshot) doesn't allow to target specific DOM elements, so we have to "blacklist" the elements
213213
// that we want to exclude from the snapshots using their own "Percy-specific CSS".
214214
// see: https://docs.percy.io/docs/percy-specific-css#section-hiding-regions-with-percy-specific-css

packages/components/tests/dummy/app/styles/components/dummy-component-props.scss

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
line-height: 1.2;
77
max-width: 64rem;
88

9+
.dummy-indent {
10+
margin-left: 30px;
11+
}
12+
913
dt {
1014
color: #1F69FF;
1115
font-size: 1rem;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// ALERT
2+
3+
.dummy-alert-sample-custom-actions__actions {
4+
display: flex;
5+
gap: 16px;
6+
align-items: center;
7+
}
8+
9+
.dummy-alert-sample-custom-actions__text {
10+
@include dummyFontFamily();
11+
@include dummyFontSize(0.8rem);
12+
display: block;
13+
color: #999;
14+
margin-top: 1rem;
15+
}

0 commit comments

Comments
 (0)