@hashicorp/design-system-components 2.12.2 → 2.14.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 +29 -0
- package/addon/components/hds/app-footer/copyright.hbs +9 -0
- package/addon/components/hds/app-footer/copyright.js +18 -0
- package/addon/components/hds/app-footer/index.hbs +20 -0
- package/addon/components/hds/app-footer/index.js +41 -0
- package/addon/components/hds/app-footer/item.hbs +8 -0
- package/addon/components/hds/app-footer/legal-links.hbs +14 -0
- package/addon/components/hds/app-footer/legal-links.js +66 -0
- package/addon/components/hds/app-footer/link.hbs +22 -0
- package/addon/components/hds/app-footer/status-link.hbs +20 -0
- package/addon/components/hds/app-footer/status-link.js +133 -0
- package/addon/components/hds/pagination/compact/index.hbs +9 -0
- package/addon/components/hds/pagination/compact/index.js +23 -0
- package/addon/components/hds/side-nav/index.hbs +5 -8
- package/addon/components/hds/side-nav/index.js +22 -11
- package/addon/components/hds/side-nav/toggle-button.hbs +7 -0
- package/addon/components/hds/tabs/index.hbs +7 -2
- package/addon/components/hds/tabs/index.js +101 -46
- package/addon/components/hds/tabs/panel.hbs +3 -3
- package/addon/components/hds/tabs/panel.js +18 -11
- package/addon/components/hds/tabs/tab.hbs +2 -2
- package/addon/components/hds/tabs/tab.js +23 -25
- package/app/components/hds/app-footer/copyright.js +6 -0
- package/app/components/hds/app-footer/index.js +6 -0
- package/app/components/hds/app-footer/item.js +6 -0
- package/app/components/hds/app-footer/legal-links.js +6 -0
- package/app/components/hds/app-footer/link.js +6 -0
- package/app/components/hds/app-footer/status-link.js +6 -0
- package/app/components/hds/side-nav/toggle-button.js +6 -0
- package/app/styles/@hashicorp/design-system-components.scss +1 -0
- package/app/styles/components/app-footer.scss +154 -0
- package/app/styles/components/link/standalone.scss +3 -1
- package/app/styles/components/side-nav/header.scss +10 -0
- package/app/styles/components/side-nav/index.scss +1 -0
- package/app/styles/components/side-nav/main.scss +21 -72
- package/app/styles/components/side-nav/toggle-button.scss +106 -0
- package/app/styles/components/side-nav/vars.scss +2 -4
- package/app/styles/components/tag.scss +1 -1
- package/blueprints/hds-component-test/files/tests/acceptance/components/hds/__name__.js +21 -0
- package/package.json +2 -2
|
@@ -7,45 +7,74 @@ import Component from '@glimmer/component';
|
|
|
7
7
|
import { tracked } from '@glimmer/tracking';
|
|
8
8
|
import { action } from '@ember/object';
|
|
9
9
|
import { assert } from '@ember/debug';
|
|
10
|
-
import { schedule } from '@ember/runloop';
|
|
10
|
+
import { next, schedule } from '@ember/runloop';
|
|
11
11
|
|
|
12
12
|
export default class HdsTabsIndexComponent extends Component {
|
|
13
13
|
@tracked tabNodes = [];
|
|
14
14
|
@tracked tabIds = [];
|
|
15
15
|
@tracked panelNodes = [];
|
|
16
16
|
@tracked panelIds = [];
|
|
17
|
-
@tracked selectedTabIndex;
|
|
17
|
+
@tracked _selectedTabIndex = this.args.selectedTabIndex ?? 0;
|
|
18
|
+
@tracked selectedTabId;
|
|
19
|
+
@tracked isControlled;
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// default starting tab index
|
|
22
|
-
let initialTabIndex = 0;
|
|
23
|
-
let selectedCount = 0;
|
|
24
|
-
|
|
25
|
-
this.tabNodes.forEach((tabElement, index) => {
|
|
26
|
-
if (tabElement.hasAttribute('data-is-selected')) {
|
|
27
|
-
initialTabIndex = index;
|
|
28
|
-
selectedCount++;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
this.selectedTabIndex = initialTabIndex;
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
// this is to determine if the "selected" tab logic is controlled in the consumers' code or is maintained as an internal state
|
|
25
|
+
this.isControlled = this.args.selectedTabIndex !== undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get selectedTabIndex() {
|
|
29
|
+
if (this.isControlled) {
|
|
30
|
+
return this.args.selectedTabIndex;
|
|
31
|
+
} else {
|
|
32
|
+
return this._selectedTabIndex;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
set selectedTabIndex(value) {
|
|
37
|
+
if (this.isControlled) {
|
|
38
|
+
// noop
|
|
39
|
+
} else {
|
|
40
|
+
this._selectedTabIndex = value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
38
43
|
|
|
44
|
+
@action
|
|
45
|
+
didInsert() {
|
|
39
46
|
assert(
|
|
40
47
|
'The number of Tabs must be equal to the number of Panels',
|
|
41
48
|
this.tabNodes.length === this.panelNodes.length
|
|
42
49
|
);
|
|
50
|
+
|
|
51
|
+
if (this.selectedTabId) {
|
|
52
|
+
this.selectedTabIndex = this.tabIds.indexOf(this.selectedTabId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
schedule('afterRender', () => {
|
|
56
|
+
this.setTabIndicator();
|
|
57
|
+
});
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
@action
|
|
46
|
-
didInsertTab(element) {
|
|
61
|
+
didInsertTab(element, isSelected) {
|
|
47
62
|
this.tabNodes = [...this.tabNodes, element];
|
|
48
63
|
this.tabIds = [...this.tabIds, element.id];
|
|
64
|
+
if (isSelected) {
|
|
65
|
+
if (this.selectedTabId) {
|
|
66
|
+
assert('Only one tab may use isSelected argument');
|
|
67
|
+
}
|
|
68
|
+
this.selectedTabId = element.id;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@action
|
|
73
|
+
didUpdateTab(tabIndex, isSelected) {
|
|
74
|
+
if (isSelected) {
|
|
75
|
+
this.selectedTabIndex = tabIndex;
|
|
76
|
+
}
|
|
77
|
+
this.setTabIndicator();
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
@action
|
|
@@ -55,7 +84,7 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
55
84
|
}
|
|
56
85
|
|
|
57
86
|
@action
|
|
58
|
-
didInsertPanel(
|
|
87
|
+
didInsertPanel(element, panelId) {
|
|
59
88
|
this.panelNodes = [...this.panelNodes, element];
|
|
60
89
|
this.panelIds = [...this.panelIds, panelId];
|
|
61
90
|
}
|
|
@@ -67,40 +96,39 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
67
96
|
}
|
|
68
97
|
|
|
69
98
|
@action
|
|
70
|
-
onClick(
|
|
99
|
+
onClick(event, tabIndex) {
|
|
71
100
|
this.selectedTabIndex = tabIndex;
|
|
72
|
-
this.setTabIndicator(
|
|
73
|
-
|
|
74
|
-
// Scroll Tab into view if it's out of view
|
|
75
|
-
this.tabNodes[tabIndex].parentNode.scrollIntoView({
|
|
76
|
-
behavior: 'smooth',
|
|
77
|
-
block: 'nearest',
|
|
78
|
-
inline: 'nearest',
|
|
79
|
-
});
|
|
101
|
+
this.setTabIndicator();
|
|
80
102
|
|
|
81
103
|
// invoke the callback function if it's provided as argument
|
|
82
104
|
if (typeof this.args.onClickTab === 'function') {
|
|
83
|
-
this.args.onClickTab(event);
|
|
105
|
+
this.args.onClickTab(event, tabIndex);
|
|
84
106
|
}
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
@action
|
|
88
|
-
onKeyUp(tabIndex,
|
|
110
|
+
onKeyUp(tabIndex, event) {
|
|
89
111
|
const leftArrow = 37;
|
|
90
112
|
const rightArrow = 39;
|
|
91
113
|
const enterKey = 13;
|
|
92
114
|
const spaceKey = 32;
|
|
93
115
|
|
|
94
|
-
if (
|
|
116
|
+
if (event.keyCode === rightArrow) {
|
|
95
117
|
const nextTabIndex = (tabIndex + 1) % this.tabIds.length;
|
|
96
|
-
this.focusTab(nextTabIndex,
|
|
97
|
-
} else if (
|
|
118
|
+
this.focusTab(nextTabIndex, event);
|
|
119
|
+
} else if (event.keyCode === leftArrow) {
|
|
98
120
|
const prevTabIndex =
|
|
99
121
|
(tabIndex + this.tabIds.length - 1) % this.tabIds.length;
|
|
100
|
-
this.focusTab(prevTabIndex,
|
|
101
|
-
} else if (
|
|
122
|
+
this.focusTab(prevTabIndex, event);
|
|
123
|
+
} else if (event.keyCode === enterKey || event.keyCode === spaceKey) {
|
|
102
124
|
this.selectedTabIndex = tabIndex;
|
|
103
125
|
}
|
|
126
|
+
// scroll selected tab into view (it may be out of view when activated using a keyboard with `prev/next`)
|
|
127
|
+
this.tabNodes[this.selectedTabIndex].parentNode.scrollIntoView({
|
|
128
|
+
behavior: 'smooth',
|
|
129
|
+
block: 'nearest',
|
|
130
|
+
inline: 'nearest',
|
|
131
|
+
});
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
// Focus tab for keyboard & mouse navigation:
|
|
@@ -114,15 +142,42 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
114
142
|
this.panelNodes[tabIndex].focus();
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
setTabIndicator(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
setTabIndicator() {
|
|
146
|
+
next(() => {
|
|
147
|
+
const tabElem = this.tabNodes[this.selectedTabIndex];
|
|
148
|
+
|
|
149
|
+
if (tabElem) {
|
|
150
|
+
const tabsParentElem = tabElem.closest('.hds-tabs__tablist');
|
|
151
|
+
|
|
152
|
+
// this condition is `null` if any of the parents has `display: none`
|
|
153
|
+
if (tabElem.parentNode.offsetParent) {
|
|
154
|
+
const tabLeftPos = tabElem.parentNode.offsetLeft;
|
|
155
|
+
const tabWidth = tabElem.parentNode.offsetWidth;
|
|
156
|
+
|
|
157
|
+
// Set CSS custom properties for indicator
|
|
158
|
+
tabsParentElem.style.setProperty(
|
|
159
|
+
'--indicator-left-pos',
|
|
160
|
+
tabLeftPos + 'px'
|
|
161
|
+
);
|
|
162
|
+
tabsParentElem.style.setProperty(
|
|
163
|
+
'--indicator-width',
|
|
164
|
+
tabWidth + 'px'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
assert(
|
|
169
|
+
`"Hds::Tabs" has tried to set the indicator for an element that doesn't exist (the value ${
|
|
170
|
+
this.selectedTabIndex
|
|
171
|
+
} of \`this.selectedTabIndex\` is out of bound for the array \`this.tabNodes\`, whose index range is [0-${
|
|
172
|
+
this.tabNodes.length - 1
|
|
173
|
+
}])`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
123
178
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
@action
|
|
180
|
+
updateTabIndicator() {
|
|
181
|
+
this.setTabIndicator();
|
|
127
182
|
}
|
|
128
183
|
}
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
class="hds-tabs__panel"
|
|
7
7
|
...attributes
|
|
8
8
|
role="tabpanel"
|
|
9
|
-
aria-labelledby={{this.tabId}}
|
|
10
9
|
id={{this.panelId}}
|
|
11
|
-
hidden={{not this.
|
|
10
|
+
hidden={{not this.isVisible}}
|
|
11
|
+
aria-labelledby={{this.coupledTabId}}
|
|
12
12
|
{{did-insert this.didInsertNode}}
|
|
13
13
|
{{will-destroy this.willDestroyNode}}
|
|
14
14
|
>
|
|
15
|
-
{{yield}}
|
|
15
|
+
{{yield (hash isVisible=this.isVisible)}}
|
|
16
16
|
</section>
|
|
@@ -10,9 +10,8 @@ import { action } from '@ember/object';
|
|
|
10
10
|
|
|
11
11
|
export default class HdsTabsIndexComponent extends Component {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @param panelId
|
|
13
|
+
* Generate a unique ID for the Panel
|
|
14
|
+
* @return {string}
|
|
16
15
|
*/
|
|
17
16
|
panelId = 'panel-' + guidFor(this);
|
|
18
17
|
|
|
@@ -23,32 +22,40 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
23
22
|
: undefined;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Check the condition if the panel is visible (because the coupled/associated tab is selected) or not
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
get isVisible() {
|
|
30
|
+
return this.nodeIndex === this.args.selectedTabIndex;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the ID of the tab coupled/associated with the panel (it's used by the `aria-labelledby` attribute)
|
|
35
|
+
* @returns string}
|
|
36
|
+
*/
|
|
37
|
+
get coupledTabId() {
|
|
27
38
|
return this.nodeIndex !== undefined
|
|
28
39
|
? this.args.tabIds[this.nodeIndex]
|
|
29
40
|
: undefined;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
get isSelected() {
|
|
33
|
-
return this.nodeIndex === this.args.selectedTabIndex;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
43
|
@action
|
|
37
44
|
didInsertNode(element) {
|
|
38
45
|
let { didInsertNode } = this.args;
|
|
39
46
|
|
|
40
47
|
if (typeof didInsertNode === 'function') {
|
|
41
48
|
this.elementId = element.id;
|
|
42
|
-
didInsertNode(this.elementId
|
|
49
|
+
didInsertNode(element, this.elementId);
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
@action
|
|
47
|
-
willDestroyNode() {
|
|
54
|
+
willDestroyNode(element) {
|
|
48
55
|
let { willDestroyNode } = this.args;
|
|
49
56
|
|
|
50
57
|
if (typeof willDestroyNode === 'function') {
|
|
51
|
-
willDestroyNode(
|
|
58
|
+
willDestroyNode(element);
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
id={{this.tabId}}
|
|
12
12
|
aria-selected={{if this.isSelected "true" "false"}}
|
|
13
13
|
tabindex={{unless this.isSelected "-1"}}
|
|
14
|
-
|
|
15
|
-
{{did-
|
|
14
|
+
{{did-insert this.didInsertNode @isSelected}}
|
|
15
|
+
{{did-update this.didUpdateNode @count @isSelected}}
|
|
16
16
|
{{will-destroy this.willDestroyNode}}
|
|
17
17
|
{{on "click" this.onClick}}
|
|
18
18
|
{{on "keyup" this.onKeyUp}}
|
|
@@ -10,9 +10,8 @@ import { action } from '@ember/object';
|
|
|
10
10
|
|
|
11
11
|
export default class HdsTabsIndexComponent extends Component {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @param tabId
|
|
13
|
+
* Generate a unique ID for the Tab
|
|
14
|
+
* @return {string}
|
|
16
15
|
*/
|
|
17
16
|
tabId = 'tab-' + guidFor(this);
|
|
18
17
|
|
|
@@ -21,17 +20,10 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
21
20
|
return this.args.tabIds ? this.args.tabIds.indexOf(this.tabId) : undefined;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
get panelId() {
|
|
25
|
-
return this.nodeIndex !== undefined
|
|
26
|
-
? this.args.panelIds[this.nodeIndex]
|
|
27
|
-
: undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
23
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
24
|
+
* Determine if the tab is the selected tab
|
|
25
|
+
* @return {boolean}
|
|
33
26
|
* @default false (1st tab is selected by default)
|
|
34
|
-
* @description Determines if the tab is the selected tab
|
|
35
27
|
*/
|
|
36
28
|
get isSelected() {
|
|
37
29
|
return (
|
|
@@ -40,46 +32,52 @@ export default class HdsTabsIndexComponent extends Component {
|
|
|
40
32
|
);
|
|
41
33
|
}
|
|
42
34
|
|
|
43
|
-
get isInitialTab() {
|
|
44
|
-
let { isSelected } = this.args;
|
|
45
|
-
return isSelected;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
35
|
@action
|
|
49
|
-
didInsertNode() {
|
|
36
|
+
didInsertNode(element, positional) {
|
|
50
37
|
let { didInsertNode } = this.args;
|
|
51
38
|
|
|
39
|
+
const isSelected = positional[0];
|
|
40
|
+
|
|
52
41
|
if (typeof didInsertNode === 'function') {
|
|
53
|
-
didInsertNode(
|
|
42
|
+
didInsertNode(element, isSelected);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@action
|
|
47
|
+
didUpdateNode() {
|
|
48
|
+
let { didUpdateNode } = this.args;
|
|
49
|
+
|
|
50
|
+
if (typeof didUpdateNode === 'function') {
|
|
51
|
+
didUpdateNode(this.nodeIndex, this.args.isSelected);
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
@action
|
|
58
|
-
willDestroyNode() {
|
|
56
|
+
willDestroyNode(element) {
|
|
59
57
|
let { willDestroyNode } = this.args;
|
|
60
58
|
|
|
61
59
|
if (typeof willDestroyNode === 'function') {
|
|
62
|
-
willDestroyNode(
|
|
60
|
+
willDestroyNode(element);
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
@action
|
|
67
|
-
onClick() {
|
|
65
|
+
onClick(event) {
|
|
68
66
|
let { onClick } = this.args;
|
|
69
67
|
|
|
70
68
|
if (typeof onClick === 'function') {
|
|
71
|
-
onClick(this.nodeIndex
|
|
69
|
+
onClick(event, this.nodeIndex);
|
|
72
70
|
} else {
|
|
73
71
|
return false;
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
@action
|
|
78
|
-
onKeyUp() {
|
|
76
|
+
onKeyUp(event) {
|
|
79
77
|
let { onKeyUp } = this.args;
|
|
80
78
|
|
|
81
79
|
if (typeof onKeyUp === 'function') {
|
|
82
|
-
onKeyUp(this.nodeIndex,
|
|
80
|
+
onKeyUp(this.nodeIndex, event);
|
|
83
81
|
} else {
|
|
84
82
|
return false;
|
|
85
83
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) HashiCorp, Inc.
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
// app-footer
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
$app-footer-gap: 24px;
|
|
11
|
+
$app-footer-icon-text-gap: 6px;
|
|
12
|
+
|
|
13
|
+
.hds-app-footer {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
gap: $app-footer-gap;
|
|
17
|
+
justify-content: flex-end;
|
|
18
|
+
padding: 24px;
|
|
19
|
+
color: var(--app-footer-foreground-color);
|
|
20
|
+
border-top: 1px solid var(--app-footer-border-top-color);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// SUB-ELEMENTS
|
|
24
|
+
|
|
25
|
+
// hide list if no children
|
|
26
|
+
.hds-app-footer__list:not(:has(li)) { display: none; }
|
|
27
|
+
|
|
28
|
+
.hds-app-footer__list,
|
|
29
|
+
.hds-app-footer__legal-links {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
gap: $app-footer-gap;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: flex-end;
|
|
35
|
+
width: fit-content;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
list-style-type: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.hds-app-footer__status-link {
|
|
43
|
+
// custom spacing for the status link (internally is using the "AppFooter::Link")
|
|
44
|
+
// Note: we increase specificity because otherwise is overwritten by the order of imports of components scss files
|
|
45
|
+
&.hds-link-inline--icon-leading > .hds-link-inline__icon { margin-right: $app-footer-icon-text-gap; }
|
|
46
|
+
|
|
47
|
+
.flight-icon {
|
|
48
|
+
fill: var(--hds-app-footer-status-icon-color, currentColor);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// preset status types
|
|
53
|
+
.hds-app-footer__status-link--operational {
|
|
54
|
+
.flight-icon { fill: var(--app-footer-status-link-icon-operational-color); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.hds-app-footer__status-link--degraded {
|
|
58
|
+
.flight-icon { fill: var(--app-footer-status-link-icon-degraded-color); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.hds-app-footer__status-link--maintenance {
|
|
62
|
+
.flight-icon { fill: var(--app-footer-status-link-icon-maintenance-color); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.hds-app-footer__status-link--critical {
|
|
66
|
+
.flight-icon { fill: var(--app-footer-status-link-icon-critical-color); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
.hds-app-footer__link.hds-link-inline--color-secondary,
|
|
71
|
+
.hds-app-footer__status-link {
|
|
72
|
+
// Overriding default colors
|
|
73
|
+
color: var(--app-footer-link-default-color);
|
|
74
|
+
text-align: right;
|
|
75
|
+
|
|
76
|
+
&:hover,
|
|
77
|
+
&.mock-hover {
|
|
78
|
+
color: var(--app-footer-link-hover-color);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:active,
|
|
82
|
+
&.mock-active {
|
|
83
|
+
color: var(--app-footer-link-active-color);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&:focus,
|
|
87
|
+
&.mock-focus,
|
|
88
|
+
&:focus-visible {
|
|
89
|
+
color: var(--app-footer-link-focus-color);
|
|
90
|
+
outline-color: var(--app-footer-link-focus-outline-color);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hds-app-footer__list-item {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.hds-app-footer__copyright {
|
|
100
|
+
display: flex;
|
|
101
|
+
gap: $app-footer-icon-text-gap;
|
|
102
|
+
align-items: center;
|
|
103
|
+
color: var(--app-footer-copyright-text-color);
|
|
104
|
+
|
|
105
|
+
.flight-icon { fill: var(--app-footer-copyright-icon-color); }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// THEMING
|
|
109
|
+
|
|
110
|
+
// Light
|
|
111
|
+
.hds-app-footer--theme-light {
|
|
112
|
+
--app-footer-foreground-color: var(--token-color-foreground-primary);
|
|
113
|
+
--app-footer-border-top-color: var(--token-color-border-primary);
|
|
114
|
+
|
|
115
|
+
// Overriding default Secondary Link colors
|
|
116
|
+
--app-footer-link-default-color: var(--token-color-foreground-faint);
|
|
117
|
+
--app-footer-link-hover-color: var(--token-color-palette-neutral-600);
|
|
118
|
+
--app-footer-link-active-color: var(--token-color-palette-neutral-700);
|
|
119
|
+
--app-footer-link-focus-color: var(--token-color-foreground-faint);
|
|
120
|
+
--app-footer-link-focus-outline-color: var(--token-color-focus-action-internal);
|
|
121
|
+
|
|
122
|
+
// Copyright
|
|
123
|
+
--app-footer-copyright-text-color: var(--token-color-foreground-primary);
|
|
124
|
+
--app-footer-copyright-icon-color: var(--token-color-hashicorp-brand);
|
|
125
|
+
|
|
126
|
+
// StatusLink icon colors
|
|
127
|
+
--app-footer-status-link-icon-operational-color: var(--token-color-foreground-success);
|
|
128
|
+
--app-footer-status-link-icon-degraded-color: var(--token-color-foreground-warning);
|
|
129
|
+
--app-footer-status-link-icon-maintenance-color: var(--token-color-foreground-warning);
|
|
130
|
+
--app-footer-status-link-icon-critical-color: var(--token-color-foreground-critical);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Dark
|
|
134
|
+
.hds-app-footer--theme-dark {
|
|
135
|
+
--app-footer-foreground-color: #b2b6bd;
|
|
136
|
+
--app-footer-border-top-color: #b2b6bd66; // rgba(#b2b6bd, 0.4) - Sass color functions don't work with CSS variables
|
|
137
|
+
|
|
138
|
+
// Overriding default Secondary Link colors
|
|
139
|
+
--app-footer-link-default-color: #b2b6bd;
|
|
140
|
+
--app-footer-link-hover-color: #d5d7db;
|
|
141
|
+
--app-footer-link-active-color: #efeff1;
|
|
142
|
+
--app-footer-link-focus-color: #b2b6bd;
|
|
143
|
+
--app-footer-link-focus-outline-color: #389aff;
|
|
144
|
+
|
|
145
|
+
// Copyright
|
|
146
|
+
--app-footer-copyright-text-color: #b2b6bd;
|
|
147
|
+
--app-footer-copyright-icon-color: #fff;
|
|
148
|
+
|
|
149
|
+
// StatusLink Icon colors
|
|
150
|
+
--app-footer-status-link-icon-operational-color: #009241;
|
|
151
|
+
--app-footer-status-link-icon-degraded-color: #e88c03;
|
|
152
|
+
--app-footer-status-link-icon-maintenance-color: #e88c03;
|
|
153
|
+
--app-footer-status-link-icon-critical-color: #ef3016;
|
|
154
|
+
}
|
|
@@ -21,6 +21,8 @@ $hds-link-standalone-border-width: 1px;
|
|
|
21
21
|
align-items: center;
|
|
22
22
|
justify-content: center;
|
|
23
23
|
width: fit-content;
|
|
24
|
+
padding-top: 4px;
|
|
25
|
+
padding-bottom: 4px;
|
|
24
26
|
font-weight: var(--token-typography-font-weight-regular);
|
|
25
27
|
font-family: var(--token-typography-font-stack-text);
|
|
26
28
|
background-color: transparent; // needs to exist for a11y
|
|
@@ -137,7 +139,7 @@ $hds-link-standalone-focus-shift: 4px;
|
|
|
137
139
|
$shift-extra: $shift + 2px;
|
|
138
140
|
|
|
139
141
|
// notice: this is used not only for the focus, but also to increase the clickable area
|
|
140
|
-
@include hds-focus-ring-with-pseudo-element($
|
|
142
|
+
@include hds-focus-ring-with-pseudo-element($right: -$shift, $left: -$shift, $radius: $hds-link-standalone-focus-border-radius);
|
|
141
143
|
|
|
142
144
|
// we need to override a couple of values for better visual alignment
|
|
143
145
|
&.hds-link-standalone--icon-position-leading::before {
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
justify-content: center;
|
|
61
61
|
width: var(--token-side-nav-header-home-link-logo-size);
|
|
62
62
|
height: var(--token-side-nav-header-home-link-logo-size);
|
|
63
|
+
transition:
|
|
64
|
+
width var(--hds-app-sidenav-animation-duration)
|
|
65
|
+
var(--hds-app-sidenav-animation-easing),
|
|
66
|
+
height var(--hds-app-sidenav-animation-duration)
|
|
67
|
+
var(--hds-app-sidenav-animation-easing);
|
|
68
|
+
|
|
69
|
+
.hds-side-nav--is-minimized & {
|
|
70
|
+
width: var(--token-side-nav-header-home-link-logo-size-minimized);
|
|
71
|
+
height: var(--token-side-nav-header-home-link-logo-size-minimized);
|
|
72
|
+
}
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
// "home-link"
|