@hashicorp/design-system-components 2.12.2 → 2.13.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/addon/components/hds/app-footer/copyright.hbs +9 -0
  3. package/addon/components/hds/app-footer/copyright.js +18 -0
  4. package/addon/components/hds/app-footer/index.hbs +20 -0
  5. package/addon/components/hds/app-footer/index.js +41 -0
  6. package/addon/components/hds/app-footer/item.hbs +8 -0
  7. package/addon/components/hds/app-footer/legal-links.hbs +14 -0
  8. package/addon/components/hds/app-footer/legal-links.js +66 -0
  9. package/addon/components/hds/app-footer/link.hbs +22 -0
  10. package/addon/components/hds/app-footer/status-link.hbs +20 -0
  11. package/addon/components/hds/app-footer/status-link.js +133 -0
  12. package/addon/components/hds/side-nav/index.hbs +5 -8
  13. package/addon/components/hds/side-nav/index.js +22 -11
  14. package/addon/components/hds/side-nav/toggle-button.hbs +7 -0
  15. package/app/components/hds/app-footer/copyright.js +6 -0
  16. package/app/components/hds/app-footer/index.js +6 -0
  17. package/app/components/hds/app-footer/item.js +6 -0
  18. package/app/components/hds/app-footer/legal-links.js +6 -0
  19. package/app/components/hds/app-footer/link.js +6 -0
  20. package/app/components/hds/app-footer/status-link.js +6 -0
  21. package/app/components/hds/side-nav/toggle-button.js +6 -0
  22. package/app/styles/@hashicorp/design-system-components.scss +1 -0
  23. package/app/styles/components/app-footer.scss +154 -0
  24. package/app/styles/components/link/standalone.scss +3 -1
  25. package/app/styles/components/side-nav/header.scss +10 -0
  26. package/app/styles/components/side-nav/index.scss +1 -0
  27. package/app/styles/components/side-nav/main.scss +21 -72
  28. package/app/styles/components/side-nav/toggle-button.scss +106 -0
  29. package/app/styles/components/side-nav/vars.scss +2 -4
  30. package/app/styles/components/tag.scss +1 -1
  31. package/blueprints/hds-component-test/files/tests/acceptance/components/hds/__name__.js +21 -0
  32. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @hashicorp/design-system-components
2
2
 
3
+ ## 2.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1623](https://github.com/hashicorp/design-system/pull/1623) [`2111a5439`](https://github.com/hashicorp/design-system/commit/2111a5439abea2951f12517354db662edd7c9cb9) Thanks [@KristinLBradley](https://github.com/KristinLBradley)! - `AppFooter` - Added new component
8
+
9
+ - [#1630](https://github.com/hashicorp/design-system/pull/1630) [`04da95443`](https://github.com/hashicorp/design-system/commit/04da95443290ee2d03d9bef23787a4ef10577247) Thanks [@alex-ju](https://github.com/alex-ju)! - `SideNav` - add `@isCollapsible` (to control if users can collapse the sidenav on 'desktop' viewports) and `@isMinimized` (to control the default state on 'desktop' viewports) arguments
10
+
11
+ ### Patch Changes
12
+
13
+ - [#1696](https://github.com/hashicorp/design-system/pull/1696) [`f3f3fb103`](https://github.com/hashicorp/design-system/commit/f3f3fb103a5aa1c6489d011b6820560df4c2ed88) Thanks [@MelSumner](https://github.com/MelSumner)! - `Tag` - Updated padding for dismiss button for WCAG conformance
14
+
15
+ - [#1678](https://github.com/hashicorp/design-system/pull/1678) [`a51976ded`](https://github.com/hashicorp/design-system/commit/a51976ded4f7939fe140a1abade0f98832ccc2d0) Thanks [@alex-ju](https://github.com/alex-ju)! - `Link::Standalone` – increase target size
16
+
17
+ - Updated dependencies [[`04da95443`](https://github.com/hashicorp/design-system/commit/04da95443290ee2d03d9bef23787a4ef10577247)]:
18
+ - @hashicorp/design-system-tokens@1.9.0
19
+
3
20
  ## 2.12.2
4
21
 
5
22
  ### Patch Changes
@@ -0,0 +1,9 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <div class="hds-app-footer__copyright" ...attributes>
7
+ <FlightIcon @name="hashicorp" />
8
+ <Hds::Text::Body @tag="span" @size="100">© {{this.year}} HashiCorp</Hds::Text::Body>
9
+ </div>
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import Component from '@glimmer/component';
7
+
8
+ export default class HdsAppFooterCopyrightComponent extends Component {
9
+ /**
10
+ * @param year
11
+ * @type {string}
12
+ * @description The copyright year
13
+ * @default The current year (calculated via `Date()`)
14
+ */
15
+ get year() {
16
+ return this.args.year ?? new Date().getFullYear();
17
+ }
18
+ }
@@ -0,0 +1,20 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <div class={{this.classNames}} ...attributes>
7
+ {{yield (hash ExtraBefore=(component "hds/yield"))}}
8
+ <ul class="hds-app-footer__list" aria-label={{this.ariaLabel}}>
9
+ {{yield (hash StatusLink=(component "hds/app-footer/status-link"))}}
10
+ {{yield
11
+ (hash
12
+ Link=(component "hds/app-footer/link")
13
+ LegalLinks=(component "hds/app-footer/legal-links")
14
+ Item=(component "hds/app-footer/item")
15
+ )
16
+ }}
17
+ </ul>
18
+ {{yield (hash ExtraAfter=(component "hds/yield"))}}
19
+ <Hds::AppFooter::Copyright @year={{@copyrightYear}} />
20
+ </div>
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import Component from '@glimmer/component';
7
+
8
+ export default class HdsAppFooterIndexComponent extends Component {
9
+ /**
10
+ * @param ariaLabel
11
+ * @type {string}
12
+ * @default 'Footer items'
13
+ */
14
+ get ariaLabel() {
15
+ return this.args.ariaLabel ?? 'Footer items';
16
+ }
17
+
18
+ /**
19
+ * @param theme
20
+ * @type {string}
21
+ * @description The component theme
22
+ * @default 'light'
23
+ */
24
+ get theme() {
25
+ return this.args.theme ?? 'light';
26
+ }
27
+
28
+ /**
29
+ * Get the class names to apply to the component.
30
+ * @method classNames
31
+ * @return {string} The "class" attribute to apply to the component.
32
+ */
33
+ get classNames() {
34
+ let classes = ['hds-app-footer'];
35
+
36
+ // add a class based on the @theme argument
37
+ classes.push(`hds-app-footer--theme-${this.theme}`);
38
+
39
+ return classes.join(' ');
40
+ }
41
+ }
@@ -0,0 +1,8 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <li class="hds-app-footer__list-item" ...attributes>
7
+ {{yield}}
8
+ </li>
@@ -0,0 +1,14 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <Hds::AppFooter::Item>
7
+ <ul class="hds-app-footer__legal-links" aria-label={{this.ariaLabel}} ...attributes>
8
+ <Hds::AppFooter::Link @href={{this.hrefForSupport}}>Support</Hds::AppFooter::Link>
9
+ <Hds::AppFooter::Link @href={{this.hrefForTerms}}>Terms</Hds::AppFooter::Link>
10
+ <Hds::AppFooter::Link @href={{this.hrefForPrivacy}}>Privacy</Hds::AppFooter::Link>
11
+ <Hds::AppFooter::Link @href={{this.hrefForSecurity}}>Security</Hds::AppFooter::Link>
12
+ <Hds::AppFooter::Link @href={{this.hrefForAccessibility}}>Accessibility</Hds::AppFooter::Link>
13
+ </ul>
14
+ </Hds::AppFooter::Item>
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import Component from '@glimmer/component';
7
+
8
+ export default class HdsAppFooterLegalLinksComponent extends Component {
9
+ /**
10
+ * @param ariaLabel
11
+ * @type {string}
12
+ * @default 'Legal links'
13
+ */
14
+ get ariaLabel() {
15
+ return this.args.ariaLabel ?? 'Legal links';
16
+ }
17
+
18
+ /**
19
+ * @param hrefForSupport
20
+ * @type {string}
21
+ * @description The href value of the "Support" link
22
+ */
23
+ get hrefForSupport() {
24
+ return this.args.hrefForSupport ?? 'https://www.hashicorp.com/support';
25
+ }
26
+
27
+ /**
28
+ * @param hrefForTerms
29
+ * @type {string}
30
+ * @description The href value of the "Terms" link
31
+ */
32
+ get hrefForTerms() {
33
+ return (
34
+ this.args.hrefForTerms ?? 'https://www.hashicorp.com/terms-of-service'
35
+ );
36
+ }
37
+
38
+ /**
39
+ * @param hrefForPrivacy
40
+ * @type {string}
41
+ * @description The href value of the "Privacy" link
42
+ */
43
+ get hrefForPrivacy() {
44
+ return this.args.hrefForPrivacy ?? 'https://www.hashicorp.com/privacy';
45
+ }
46
+
47
+ /**
48
+ * @param hrefForSecurity
49
+ * @type {string}
50
+ * @description The href value of the "Security" link
51
+ */
52
+ get hrefForSecurity() {
53
+ return this.args.hrefForSecurity ?? 'https://www.hashicorp.com/security';
54
+ }
55
+
56
+ /**
57
+ * @param hrefForAccessibility
58
+ * @type {string}
59
+ * @description The href value of the "Accessibility" link
60
+ */
61
+ get hrefForAccessibility() {
62
+ return (
63
+ this.args.hrefForAccessibility ?? 'mailto:accessibility@hashicorp.com'
64
+ );
65
+ }
66
+ }
@@ -0,0 +1,22 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <Hds::AppFooter::Item>
7
+ <Hds::Link::Inline
8
+ class="hds-app-footer__link"
9
+ @color="secondary"
10
+ @current-when={{@current-when}}
11
+ @models={{hds-link-to-models @model @models}}
12
+ @query={{hds-link-to-query @query}}
13
+ @replace={{@replace}}
14
+ @route={{@route}}
15
+ @isRouteExternal={{@isRouteExternal}}
16
+ @href={{@href}}
17
+ @isHrefExternal={{@isHrefExternal}}
18
+ @icon={{@icon}}
19
+ @iconPosition={{@iconPosition}}
20
+ ...attributes
21
+ ><Hds::Text::Body @tag="span" @size="100">{{yield}}</Hds::Text::Body></Hds::Link::Inline>
22
+ </Hds::AppFooter::Item>
@@ -0,0 +1,20 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+
6
+ <Hds::AppFooter::Link
7
+ class={{this.classNames}}
8
+ style={{this.itemStyle}}
9
+ @current-when={{@current-when}}
10
+ @models={{hds-link-to-models @model @models}}
11
+ @query={{hds-link-to-query @query}}
12
+ @replace={{@replace}}
13
+ @route={{@route}}
14
+ @isRouteExternal={{@isRouteExternal}}
15
+ @href={{this.href}}
16
+ @isHrefExternal={{@isHrefExternal}}
17
+ @icon={{this.statusIcon}}
18
+ @iconPosition="leading"
19
+ ...attributes
20
+ >{{this.text}}</Hds::AppFooter::Link>
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import Component from '@glimmer/component';
7
+ import { htmlSafe } from '@ember/template';
8
+ import { assert } from '@ember/debug';
9
+
10
+ export const STATUSES = {
11
+ operational: {
12
+ text: 'System operational',
13
+ iconName: 'check-circle',
14
+ },
15
+ degraded: {
16
+ text: 'System degraded',
17
+ iconName: 'alert-triangle',
18
+ },
19
+ maintenance: {
20
+ text: 'System maintenance',
21
+ iconName: 'alert-triangle',
22
+ },
23
+ critical: {
24
+ text: 'System critical',
25
+ iconName: 'x-circle',
26
+ },
27
+ };
28
+
29
+ export default class HdsAppFooterStatusLinkComponent extends Component {
30
+ constructor() {
31
+ super(...arguments);
32
+
33
+ assert(
34
+ 'Either @status or @text for "Hds::AppFooter::StatusLink" must have a valid value',
35
+ this.args.text !== undefined || this.args.status
36
+ );
37
+ }
38
+
39
+ /**
40
+ * @param status
41
+ * @type {string}
42
+ * @description The name of the status which the StatusLink is being set to
43
+ */
44
+ get status() {
45
+ let status;
46
+ if (this.args.status) {
47
+ status = this.args.status.toLowerCase();
48
+ assert(
49
+ `@status for "Hds::AppFooter" must be one of the following: ${Object.keys(
50
+ STATUSES
51
+ ).join(', ')} received: ${this.args.status}`,
52
+ Object.keys(STATUSES).includes(status)
53
+ );
54
+ }
55
+ return status;
56
+ }
57
+
58
+ /**
59
+ * @param statusIcon
60
+ * @type {string}
61
+ * @description The name for the StatusLink icon
62
+ */
63
+ get statusIcon() {
64
+ if (this.status && !this.args.statusIcon) {
65
+ return STATUSES[this.status].iconName;
66
+ }
67
+ return this.args.statusIcon;
68
+ }
69
+
70
+ /**
71
+ * @param statusIconColor
72
+ * @type {string}
73
+ * @description The color for the StatusLink icon
74
+ */
75
+ get statusIconColor() {
76
+ if (this.status && !this.args.statusIconColor) {
77
+ return STATUSES[this.status].iconColor;
78
+ }
79
+ return this.args.statusIconColor;
80
+ }
81
+
82
+ /**
83
+ * Get the inline style to apply to the item.
84
+ * @method StatusLink#itemStyle
85
+ * @return {string} The "style" attribute to apply to the item.
86
+ */
87
+ get itemStyle() {
88
+ if (this.args.statusIconColor) {
89
+ return htmlSafe(
90
+ `--hds-app-footer-status-icon-color: ${this.args.statusIconColor}`
91
+ );
92
+ } else {
93
+ return undefined;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * @param text
99
+ * @type {string}
100
+ * @description The text content of the StatusLink
101
+ */
102
+ get text() {
103
+ if (!this.args.text) {
104
+ return STATUSES[this.status].text;
105
+ }
106
+ return this.args.text;
107
+ }
108
+
109
+ /**
110
+ * @param href
111
+ * @type {string}
112
+ * @description The href value of the StatusLink
113
+ */
114
+ get href() {
115
+ return this.args.href ?? 'https://status.hashicorp.com';
116
+ }
117
+
118
+ /**
119
+ * Get the class names to apply to the component.
120
+ * @method classNames
121
+ * @return {string} The "class" attribute to apply to the component.
122
+ */
123
+ get classNames() {
124
+ let classes = ['hds-app-footer__status-link'];
125
+
126
+ // add a class based on the @status argument
127
+ if (this.args.status) {
128
+ classes.push(`hds-app-footer__status-link--${this.status}`);
129
+ }
130
+
131
+ return classes.join(' ');
132
+ }
133
+ }
@@ -19,18 +19,15 @@
19
19
  @navigationText={{@a11yRefocusNavigationText}}
20
20
  />
21
21
  {{/if}}
22
- {{#if this.isResponsive}}
22
+ {{#if this.showToggleButton}}
23
23
  {{! template-lint-disable no-invalid-interactive}}
24
24
  <div class="hds-side-nav__overlay" {{on "click" this.toggleMinimizedStatus}} />
25
25
  {{! template-lint-enable no-invalid-interactive}}
26
- <button
27
- class="hds-side-nav__menu-toggle-button"
28
- type="button"
29
- {{on "click" this.toggleMinimizedStatus}}
26
+ <Hds::SideNav::ToggleButton
30
27
  aria-label={{this.ariaLabel}}
31
- >
32
- <FlightIcon @name={{if this.isMinimized "menu" "x"}} @size="24" @stretched={{true}} />
33
- </button>
28
+ @icon={{if this.isMinimized "chevrons-right" "chevrons-left"}}
29
+ {{on "click" this.toggleMinimizedStatus}}
30
+ />
34
31
  {{/if}}
35
32
  </:root>
36
33
  <:header as |Header|>
@@ -10,8 +10,9 @@ import { assert } from '@ember/debug';
10
10
  import { registerDestructor } from '@ember/destroyable';
11
11
 
12
12
  export default class HdsSideNavComponent extends Component {
13
- @tracked isResponsive = this.args.isResponsive ?? true;
14
- @tracked isMinimized = this.isResponsive; // we set it minimized by default so that if we switch viewport from desktop to mobile its already minimized
13
+ @tracked isResponsive = this.args.isResponsive ?? true; // controls if the component reacts to viewport changes
14
+ @tracked isMinimized = this.args.isMinimized ?? false; // sets the default state on 'desktop' viewports
15
+ @tracked isCollapsible = this.args.isCollapsible ?? false; // controls if users can collapse the sidenav on 'desktop' viewports
15
16
  @tracked isAnimating = false;
16
17
  @tracked isDesktop = true;
17
18
  hasA11yRefocus = this.args.hasA11yRefocus ?? true;
@@ -39,8 +40,11 @@ export default class HdsSideNavComponent extends Component {
39
40
  addEventListeners() {
40
41
  document.addEventListener('keydown', this.escapePress, true);
41
42
  this.desktopMQ.addEventListener('change', this.updateDesktopVariable, true);
42
- // set initial state based on viewport
43
- this.updateDesktopVariable({ matches: this.desktopMQ.matches });
43
+ // if not instantiated as minimized via arguments
44
+ if (!this.args.isMinimized) {
45
+ // set initial state based on viewport
46
+ this.updateDesktopVariable({ matches: this.desktopMQ.matches });
47
+ }
44
48
  }
45
49
 
46
50
  removeEventListeners() {
@@ -56,6 +60,10 @@ export default class HdsSideNavComponent extends Component {
56
60
  return this.isResponsive && !this.isDesktop && !this.isMinimized;
57
61
  }
58
62
 
63
+ get showToggleButton() {
64
+ return (this.isResponsive && !this.isDesktop) || this.isCollapsible;
65
+ }
66
+
59
67
  /**
60
68
  * @param ariaLabel
61
69
  * @type {string}
@@ -72,15 +80,15 @@ export default class HdsSideNavComponent extends Component {
72
80
  let classes = []; // `hds-side-nav` is already set by the "Hds::SideNav::Base" component
73
81
 
74
82
  // add specific class names for the different possible states
75
- if (this.isDesktop) {
76
- classes.push('hds-side-nav--is-desktop');
77
- } else {
78
- classes.push('hds-side-nav--is-mobile');
79
- }
80
83
  if (this.isResponsive) {
81
84
  classes.push('hds-side-nav--is-responsive');
82
85
  }
83
- if (this.isMinimized) {
86
+ if (!this.isDesktop && this.isResponsive) {
87
+ classes.push('hds-side-nav--is-mobile');
88
+ } else {
89
+ classes.push('hds-side-nav--is-desktop');
90
+ }
91
+ if (this.isMinimized && this.isResponsive) {
84
92
  classes.push('hds-side-nav--is-minimized');
85
93
  } else {
86
94
  classes.push('hds-side-nav--is-not-minimized');
@@ -94,7 +102,7 @@ export default class HdsSideNavComponent extends Component {
94
102
 
95
103
  @action
96
104
  escapePress(event) {
97
- if (event.key === 'Escape' && !this.isMinimized) {
105
+ if (event.key === 'Escape' && !this.isMinimized && !this.isDesktop) {
98
106
  this.isMinimized = true;
99
107
  }
100
108
  }
@@ -127,6 +135,9 @@ export default class HdsSideNavComponent extends Component {
127
135
  updateDesktopVariable(event) {
128
136
  this.isDesktop = event.matches;
129
137
 
138
+ // automatically minimize on narrow viewports (when not in desktop mode)
139
+ this.isMinimized = !this.isDesktop;
140
+
130
141
  let { onDesktopViewportChange } = this.args;
131
142
 
132
143
  if (typeof onDesktopViewportChange === 'function') {
@@ -0,0 +1,7 @@
1
+ {{!
2
+ Copyright (c) HashiCorp, Inc.
3
+ SPDX-License-Identifier: MPL-2.0
4
+ }}
5
+ <button class="hds-side-nav__toggle-button" type="button" ...attributes>
6
+ <FlightIcon @name={{@icon}} />
7
+ </button>
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/copyright';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/index';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/item';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/legal-links';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/link';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/app-footer/status-link';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ export { default } from '@hashicorp/design-system-components/components/hds/side-nav/toggle-button';
@@ -14,6 +14,7 @@
14
14
  // START COMPONENTS CSS FILES IMPORTS
15
15
  @use "../components/accordion";
16
16
  @use "../components/alert";
17
+ @use "../components/app-footer";
17
18
  @use "../components/app-frame";
18
19
  @use "../components/application-state";
19
20
  @use "../components/avatar";
@@ -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($top: -$shift, $right: -$shift, $bottom: -$shift, $left: -$shift, $radius: $hds-link-standalone-focus-border-radius);
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"
@@ -7,4 +7,5 @@
7
7
  @use "./main.scss";
8
8
  @use "./header.scss";
9
9
  @use "./content.scss";
10
+ @use "./toggle-button.scss";
10
11
  @use "./a11y-refocus.scss";
@@ -31,7 +31,13 @@
31
31
 
32
32
  // desktop
33
33
  &.hds-side-nav--is-desktop {
34
- width: var(--hds-app-sidenav-width-expanded);
34
+ &.hds-side-nav--is-not-minimized {
35
+ width: var(--hds-app-sidenav-width-expanded);
36
+ }
37
+
38
+ &.hds-side-nav--is-minimized {
39
+ width: var(--hds-app-sidenav-width-minimized);
40
+ }
35
41
  }
36
42
  }
37
43
 
@@ -61,65 +67,6 @@
61
67
  }
62
68
  }
63
69
 
64
-
65
- // MENU/TOGGLE BUTTON
66
-
67
- .hds-side-nav__menu-toggle-button {
68
- position: absolute;
69
- z-index: 1;
70
- color: var(--token-color-foreground-high-contrast);
71
- background: none;
72
- border: 1px solid transparent;
73
- cursor: pointer;
74
- transition-timing-function: var(--hds-app-sidenav-animation-easing);
75
- transition-duration: var(--hds-app-sidenav-animation-duration);
76
- transition-property: width, height, padding, border-radius, transform;
77
-
78
- &:hover {
79
- background-color: var(--token-color-palette-neutral-600);
80
- border-color: transparent;
81
- }
82
-
83
- &:active {
84
- background-color: var(--token-color-palette-neutral-500);
85
- border-color: transparent;
86
- }
87
-
88
- &:focus {
89
- background-color: var(--token-color-palette-neutral-500);
90
- border-color: var(--token-color-focus-action-internal);
91
- outline: 3px solid var(--token-color-focus-action-external);
92
- }
93
-
94
- // by default it's styled as "close" button
95
- .hds-side-nav--is-mobile & {
96
- width: 24px;
97
- height: 24px;
98
- padding: 1px;
99
- background-color: var(--token-color-foreground-primary);
100
- border-radius: 3px;
101
- transform:
102
- translateX(calc(var(--hds-app-sidenav-width-expanded) + 8px))
103
- translateY(24px);
104
- // z-index: 15; // not needed anymore?
105
- }
106
-
107
- // when the sidenav is minimized it's styled as "menu" button
108
- .hds-side-nav--is-mobile.hds-side-nav--is-minimized & {
109
- width: 36px;
110
- height: 36px;
111
- padding: 5px;
112
- border-radius: 5px;
113
- transform: translateX(22px) translateY(var(--hds-app-sidenav-menu-button-y-shift));
114
- }
115
-
116
- // when it's desktop we _never_ want to show the menu/toggle button
117
- .hds-side-nav--is-desktop & {
118
- display: none;
119
- }
120
- }
121
-
122
-
123
70
  // RESPONSIVE WRAPPER
124
71
  // this container element is used to control the width of the sidebar at different viewports (desktop/mobile) and states (minimized/expanded)
125
72
 
@@ -129,6 +76,7 @@
129
76
  height: 100%;
130
77
  color: var(--token-side-nav-color-foreground-primary); // we set a default color (in case generic content is added to the header/body/footer of the sidenav)
131
78
  background: var(--token-side-nav-color-surface-primary);
79
+ border-right: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
132
80
 
133
81
  // RESPONSIVENESS - This controls the width of the "sidenav" container, and is independent (bur related) from the parent "sidebar" grid area
134
82
 
@@ -138,17 +86,11 @@
138
86
  var(--hds-app-sidenav-animation-easing);
139
87
  }
140
88
 
141
- // mobile
142
- .hds-side-nav--is-mobile.hds-side-nav--is-minimized & {
89
+ .hds-side-nav--is-minimized & {
143
90
  width: var(--hds-app-sidenav-width-minimized);
144
91
  }
145
92
 
146
- .hds-side-nav--is-mobile.hds-side-nav--is-not-minimized & {
147
- width: var(--hds-app-sidenav-width-expanded);
148
- }
149
-
150
- // desktop
151
- .hds-side-nav--is-desktop & {
93
+ .hds-side-nav--is-not-minimized & {
152
94
  width: var(--hds-app-sidenav-width-expanded);
153
95
  }
154
96
  }
@@ -160,6 +102,15 @@
160
102
  padding-right: var(--token-side-nav-wrapper-padding-horizontal);
161
103
  padding-bottom: 8px; // by design
162
104
  padding-left: var(--token-side-nav-wrapper-padding-horizontal);
105
+ transition:
106
+ padding var(--hds-app-sidenav-animation-duration)
107
+ var(--hds-app-sidenav-animation-easing);
108
+
109
+ .hds-side-nav--is-minimized & {
110
+ padding-top: var(--token-side-nav-wrapper-padding-vertical-minimized);
111
+ padding-right: var(--token-side-nav-wrapper-padding-horizontal-minimized);
112
+ padding-left: var(--token-side-nav-wrapper-padding-horizontal-minimized);
113
+ }
163
114
  }
164
115
 
165
116
  .hds-side-nav__wrapper-body {
@@ -187,10 +138,9 @@
187
138
  // - shown (transitioning their opacity) when the sidenav is "expanded"
188
139
 
189
140
  .hds-side-nav-hide-when-minimized {
190
- .hds-side-nav--is-mobile.hds-side-nav--is-minimized & {
141
+ .hds-side-nav--is-minimized & {
191
142
  visibility: hidden !important; // we need `!important` here to override the inline style applied to the single panels
192
143
  opacity: 0;
193
- transition: none;
194
144
  // this is needed because, despite the element having `visibility: hidden`,
195
145
  // the child elements ("panels") have their visibility dynamically managed via JS
196
146
  // and when they have "visibility: visible" applied, they are not visually visible
@@ -206,8 +156,7 @@
206
156
  pointer-events: none;
207
157
  }
208
158
 
209
- .hds-side-nav--is-mobile.hds-side-nav--is-not-minimized &,
210
- .hds-side-nav--is-desktop & {
159
+ .hds-side-nav--is-not-minimized & {
211
160
  visibility: visible;
212
161
  opacity: 1;
213
162
  transition:
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ //
7
+ // SIDE-NAV > TOGGLE BUTTON
8
+ //
9
+
10
+
11
+ .hds-side-nav__toggle-button {
12
+ position: absolute;
13
+ top: 22px;
14
+ left: calc(var(--token-side-nav-wrapper-border-width) * -1);
15
+ z-index: 1;
16
+ display: flex;
17
+ flex-direction: row-reverse;
18
+ align-items: center;
19
+ width: 26px;
20
+ height: 36px;
21
+ padding: 0 4px;
22
+ color: var(--token-color-foreground-high-contrast);
23
+ background: none;
24
+ background-color: var(--token-side-nav-color-surface-primary);
25
+ border: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
26
+ border-left-color: transparent;
27
+ border-top-right-radius: var(--token-side-nav-toggle-button-border-radius);
28
+ border-bottom-right-radius: var(--token-side-nav-toggle-button-border-radius);
29
+ transform: translateX(var(--hds-app-sidenav-width-expanded));
30
+ cursor: pointer;
31
+ transition:
32
+ transform var(--hds-app-sidenav-animation-duration)
33
+ var(--hds-app-sidenav-animation-easing),
34
+ width var(--hds-app-sidenav-animation-duration)
35
+ var(--hds-app-sidenav-animation-easing);
36
+
37
+ &::before {
38
+ position: absolute;
39
+ top: calc(var(--token-side-nav-toggle-button-border-radius) * -2);
40
+ left: calc(var(--token-side-nav-wrapper-border-width) * -1);
41
+ box-sizing: border-box;
42
+ width: calc(var(--token-side-nav-toggle-button-border-radius) * 2);
43
+ height: calc(var(--token-side-nav-toggle-button-border-radius) * 2);
44
+ border-bottom: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
45
+ border-left: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
46
+ border-bottom-left-radius: var(--token-side-nav-toggle-button-border-radius);
47
+ box-shadow: 0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-primary);
48
+ content: "";
49
+ }
50
+
51
+ &::after {
52
+ position: absolute;
53
+ bottom: calc(var(--token-side-nav-toggle-button-border-radius) * -2);
54
+ left: calc(var(--token-side-nav-wrapper-border-width) * -1);
55
+ box-sizing: border-box;
56
+ width: calc(var(--token-side-nav-toggle-button-border-radius) * 2);
57
+ height: calc(var(--token-side-nav-toggle-button-border-radius) * 2);
58
+ border-top: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
59
+ border-left: var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);
60
+ border-top-left-radius: var(--token-side-nav-toggle-button-border-radius);
61
+ box-shadow: 0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-primary);
62
+ content: "";
63
+ }
64
+
65
+ &:hover,
66
+ &.mock-hover {
67
+ width: 30px;
68
+ background-color: var(--token-side-nav-color-surface-interactive-hover);
69
+
70
+ &::before {
71
+ box-shadow: 0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-hover);
72
+ }
73
+
74
+ &::after {
75
+ box-shadow: 0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-hover);
76
+ }
77
+ }
78
+
79
+ &:active,
80
+ &.mock-active {
81
+ background-color: var(--token-side-nav-color-surface-interactive-active);
82
+
83
+ &::before {
84
+ box-shadow: 0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-active);
85
+ }
86
+
87
+ &::after {
88
+ box-shadow: 0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-active);
89
+ }
90
+ }
91
+
92
+ &:focus-visible,
93
+ &.mock-focus {
94
+ border-color: var(--token-color-focus-action-internal);
95
+ outline: 3px solid var(--token-color-focus-action-external);
96
+
97
+ &::before,
98
+ &::after {
99
+ display: none;
100
+ }
101
+ }
102
+
103
+ .hds-side-nav--is-minimized & {
104
+ transform: translateX(var(--hds-app-sidenav-width-minimized));
105
+ }
106
+ }
@@ -16,13 +16,11 @@
16
16
  // breakpoint
17
17
  --hds-app-desktop-breakpoint: 1088px; // this is used only to read its value via JS and set the `hds-side-nav--is-desktop` class
18
18
  // widths
19
- --hds-app-sidenav-width-minimized: 80px;
19
+ --hds-app-sidenav-width-minimized: 48px;
20
20
  --hds-app-sidenav-width-expanded: 280px;
21
21
  --hds-app-sidenav-width-fixed: var(--hds-app-sidenav-width-expanded);
22
- // sidebar menu
23
- --hds-app-sidenav-menu-button-y-shift: 84px;
24
22
  // animation
25
- --hds-app-sidenav-animation-duration: 250ms;
23
+ --hds-app-sidenav-animation-duration: 200ms;
26
24
  --hds-app-sidenav-animation-delay: var(--hds-app-sidenav-animation-duration);
27
25
  --hds-app-sidenav-animation-easing: cubic-bezier(0.65, 0, 0.35, 1);
28
26
  }
@@ -29,7 +29,7 @@ $hds-tag-border-radius: 50px;
29
29
  .hds-tag__dismiss {
30
30
  flex: 0 0 auto;
31
31
  margin: 0; // reset default button margin
32
- padding: 6px 2px 6px 8px;
32
+ padding: 6px 4px 6px 8px;
33
33
  border: none; // reset default button border
34
34
  border-radius: inherit;
35
35
  border-top-right-radius: 0;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import { module, test } from 'qunit';
7
+ import { visit } from '@ember/test-helpers';
8
+ import { setupApplicationTest } from 'dummy/tests/helpers';
9
+ import { a11yAudit } from 'ember-a11y-testing/test-support';
10
+
11
+ module('Acceptance | components/<%= dasherizedModuleName %>', function (hooks) {
12
+ setupApplicationTest(hooks);
13
+
14
+ test('Components/<%= dasherizedModuleName %> page passes automated a11y checks', async function (assert) {
15
+ await visit('/components/<%= dasherizedModuleName %>');
16
+
17
+ await a11yAudit();
18
+
19
+ assert.ok(true, 'a11y automation audit passed');
20
+ });
21
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashicorp/design-system-components",
3
- "version": "2.12.2",
3
+ "version": "2.13.0",
4
4
  "description": "Helios Design System Components",
5
5
  "keywords": [
6
6
  "hashicorp",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@ember/render-modifiers": "^2.0.5",
43
43
  "@ember/test-waiters": "^3.0.2",
44
- "@hashicorp/design-system-tokens": "^1.8.0",
44
+ "@hashicorp/design-system-tokens": "^1.9.0",
45
45
  "@hashicorp/ember-flight-icons": "^3.1.3",
46
46
  "dialog-polyfill": "^0.5.6",
47
47
  "ember-a11y-refocus": "^3.0.2",