@hashicorp/design-system-components 0.7.1 → 0.9.1

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
@@ -1,5 +1,29 @@
1
1
  # @hashicorp/design-system-components
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @hashicorp/ember-flight-icons@2.0.5
9
+
10
+ ## 0.9.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#66](https://github.com/hashicorp/design-system/pull/66) [`29e2ce55`](https://github.com/hashicorp/design-system/commit/29e2ce55bab74cc02dd511794dfadd2b3ac40a14) Thanks [@MelSumner](https://github.com/MelSumner)! - Adds dropdown component to the design system
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies []:
19
+ - @hashicorp/ember-flight-icons@2.0.4
20
+
21
+ ## 0.8.0
22
+
23
+ ### Minor Changes
24
+
25
+ - [#115](https://github.com/hashicorp/design-system/pull/115) [`caff569b`](https://github.com/hashicorp/design-system/commit/caff569b46a9a46940eab94d263816dd7d046c56) Thanks [@didoo](https://github.com/didoo)! - added “Link::CTA“ and “LinkTo::CTA” components
26
+
3
27
  ## 0.7.1
4
28
 
5
29
  ### Patch Changes
@@ -160,7 +160,6 @@ export default class HdsButtonIndexComponent extends Component {
160
160
  * @method Button#classNames
161
161
  * @return {string} The "class" attribute to apply to the component.
162
162
  */
163
- // "hds-button {{this.sizeClass}} {{this.colorClass}} {{this.widthClass}}"
164
163
  get classNames() {
165
164
  let classes = ['hds-button'];
166
165
 
@@ -0,0 +1,27 @@
1
+ <Hds::Disclosure class="hds-dropdown">
2
+ <:toggle as |t|>
3
+ {{yield
4
+ (hash
5
+ ToggleButton=(component "hds/dropdown/toggle-button" isOpen=t.isActive onClick=t.onClickToggle)
6
+ ToggleIcon=(component "hds/dropdown/toggle-icon" isOpen=t.isActive onClick=t.onClickToggle)
7
+ )
8
+ }}
9
+ </:toggle>
10
+ <:content>
11
+ <ul
12
+ class="hds-dropdown-list
13
+ {{if (eq @listPosition 'left') 'hds-dropdown-list--position-left' 'hds-dropdown-list--position-right'}}"
14
+ >
15
+ {{yield
16
+ (hash
17
+ CopyItem=(component "hds/dropdown/list-item" item="copy-item")
18
+ Description=(component "hds/dropdown/list-item" item="description")
19
+ Generic=(component "hds/dropdown/list-item" item="generic")
20
+ Interactive=(component "hds/dropdown/list-item")
21
+ Separator=(component "hds/dropdown/list-item" item="separator")
22
+ Title=(component "hds/dropdown/list-item" item="title")
23
+ )
24
+ }}
25
+ </ul>
26
+ </:content>
27
+ </Hds::Disclosure>
@@ -0,0 +1,84 @@
1
+ {{#if (eq this.item "title")}}
2
+ <li class={{this.classNames}}>
3
+ {{this.text}}
4
+ </li>
5
+
6
+ {{else if (eq this.item "generic")}}
7
+ <li class={{this.classNames}}>
8
+ {{yield}}
9
+ </li>
10
+
11
+ {{else if (eq this.item "copy-item")}}
12
+ <li class={{this.classNames}}>
13
+ {{#if @copyItemTitle}}
14
+ <div class="hds-dropdown-list-item__copy-item-title">{{@copyItemTitle}}</div>
15
+ {{/if}}
16
+ <button
17
+ type="button"
18
+ class="{{if @state (concat 'is-' @state)}} {{if this.isSuccess 'is-success'}}"
19
+ {{on "click" this.copyCode}}
20
+ >
21
+ <div class="hds-dropdown-list-item__copy-item-text">
22
+ {{this.text}}
23
+ </div>
24
+ <FlightIcon
25
+ @name="{{if this.isSuccess 'clipboard-checked' 'clipboard-copy'}}"
26
+ class="hds-dropdown-list-item__copy-item-icon"
27
+ />
28
+ </button>
29
+ </li>
30
+
31
+ {{else if (eq this.item "description")}}
32
+ <li class={{this.classNames}}>
33
+ {{this.text}}
34
+ </li>
35
+
36
+ {{else if (eq this.item "separator")}}
37
+ <li class={{this.classNames}} role="separator"></li>
38
+
39
+ {{else if (eq this.item "interactive")}}
40
+ <li class={{this.classNames}}>
41
+ {{#if @route}}
42
+ <LinkTo
43
+ class="{{if @state (concat 'is-' @state)}}"
44
+ @current-when={{@current-when}}
45
+ @models={{hds-link-to-models @model @models}}
46
+ @query={{hds-link-to-query @query}}
47
+ @replace={{@replace}}
48
+ @route={{@route}}
49
+ ...attributes
50
+ >
51
+ {{#if @icon}}
52
+ <div class="hds-dropdown-list-item__interactive-icon">
53
+ <FlightIcon @name={{this.icon}} @isInlineBlock={{false}} />
54
+ </div>
55
+ {{/if}}
56
+ <div class="hds-dropdown-list-item__interactive-text">
57
+ {{this.text}}
58
+ </div>
59
+ </LinkTo>
60
+ {{else if @href}}
61
+ <a target="_blank" rel="noopener noreferrer" href={{@href}} class="{{if @state (concat 'is-' @state)}}">
62
+ {{#if this.icon}}
63
+ <div class="hds-dropdown-list-item__interactive-icon">
64
+ <FlightIcon @name={{this.icon}} @isInlineBlock={{false}} />
65
+ </div>
66
+ {{/if}}
67
+ <div class="hds-dropdown-list-item__interactive-text">
68
+ {{this.text}}
69
+ </div>
70
+ </a>
71
+ {{else}}
72
+ <button class="{{if @state (concat 'is-' @state)}}" type="button" ...attributes>
73
+ {{#if this.icon}}
74
+ <div class="hds-dropdown-list-item__interactive-icon">
75
+ <FlightIcon @name={{this.icon}} @isInlineBlock={{false}} />
76
+ </div>
77
+ {{/if}}
78
+ <div class="hds-dropdown-list-item__interactive-text">
79
+ {{this.text}}
80
+ </div>
81
+ </button>
82
+ {{/if}}
83
+ </li>
84
+ {{/if}}
@@ -0,0 +1,120 @@
1
+ import Component from '@glimmer/component';
2
+ import { assert } from '@ember/debug';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+
6
+ export const DEFAULT_COLOR = 'action';
7
+ export const DEFAULT_ITEM = 'interactive';
8
+ export const COLORS = ['action', 'critical'];
9
+ export const ITEMS = [
10
+ 'copy-item',
11
+ 'description',
12
+ 'generic',
13
+ 'interactive',
14
+ 'separator',
15
+ 'title',
16
+ ];
17
+
18
+ export default class HdsDropdownListItemComponent extends Component {
19
+ @tracked isSuccess = this.args.isSuccess ?? false;
20
+
21
+ /**
22
+ * @param item
23
+ * @type {string}
24
+ * @default interactive
25
+ * @description Determines the type of item to show
26
+ */
27
+ get item() {
28
+ let { item = DEFAULT_ITEM } = this.args;
29
+
30
+ assert(
31
+ `@item for "Hds::Dropdown::ListItem" must be one of the following: ${ITEMS.join(
32
+ ', '
33
+ )}; received: ${item}`,
34
+ ITEMS.includes(item)
35
+ );
36
+
37
+ return item;
38
+ }
39
+
40
+ /**
41
+ * @param text
42
+ * @type {string}
43
+ * @description The text of the item. If no text value is defined an error will be thrown unless it is the generic or separator item type.
44
+ */
45
+ get text() {
46
+ let { text } = this.args;
47
+
48
+ assert(
49
+ '@text for "Hds::Dropdown::ListItem" must have a valid value',
50
+ text !== undefined
51
+ );
52
+
53
+ return text;
54
+ }
55
+
56
+ /**
57
+ * @param color
58
+ * @type {string}
59
+ * @default primary
60
+ * @description Determines the color of the item (when item is set to interactive)
61
+ */
62
+ get color() {
63
+ let { color = DEFAULT_COLOR } = this.args;
64
+
65
+ assert(
66
+ `@color for "Hds::Dropdown::ListItem" must be one of the following: ${COLORS.join(
67
+ ', '
68
+ )}; received: ${color}`,
69
+ COLORS.includes(color)
70
+ );
71
+
72
+ return color;
73
+ }
74
+
75
+ /**
76
+ * @param icon
77
+ * @type {string}
78
+ * @default null
79
+ * @description The name of the icon to be used.
80
+ */
81
+ get icon() {
82
+ assert(
83
+ `when the "Hds::ListItem" @color is "critical" an @icon is required`,
84
+ !(this.color === 'critical' && !this.args.icon)
85
+ );
86
+
87
+ return this.args.icon ?? null;
88
+ }
89
+
90
+ /**
91
+ * Get the class names to apply to the component.
92
+ * @method classNames
93
+ * @return {string} The "class" attribute to apply to the component.
94
+ */
95
+ get classNames() {
96
+ let classes = ['hds-dropdown-list-item'];
97
+
98
+ // add a class based on the @item argument
99
+ if (this.item) {
100
+ classes.push(`hds-dropdown-list-item--${this.item}`);
101
+ }
102
+
103
+ // add a class based on the @color argument
104
+ if (this.item === 'interactive') {
105
+ classes.push(`hds-dropdown-list-item--color-${this.color}`);
106
+ }
107
+
108
+ return classes.join(' ');
109
+ }
110
+
111
+ @action
112
+ copyCode() {
113
+ navigator.clipboard.writeText(this.args.text);
114
+ // this if statement resolves to [object Promise] so maybe some improvements
115
+ // could be made here
116
+ if (navigator.clipboard.readText()) {
117
+ this.isSuccess = true;
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,23 @@
1
+ {{!
2
+ // >>>>>>>>>>> WARNING <<<<<<<<<<
3
+ //
4
+ // Notice: in this component we're directly using the `Hds::Button` component
5
+ // (and adding a specialized class for the "toggle-button" variant, see below)
6
+ // If you need to change the styling of the `Button` component, remember that this will impact also
7
+ // this component too.
8
+ // If instead you need to change only the styling of the `toggle-button` component, you can do it
9
+ // in the CSS file using the specialized class declared there.
10
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
11
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
12
+ // `Button (primary)` with other components.
13
+ }}
14
+
15
+ <Hds::Button
16
+ @text={{this.text}}
17
+ @icon="chevron-{{if @isOpen 'up' 'down'}}"
18
+ @iconPosition="trailing"
19
+ @color={{this.color}}
20
+ class="hds-dropdown-toggle--with-button-component"
21
+ {{on "click" this.onClick}}
22
+ ...attributes
23
+ />
@@ -0,0 +1,59 @@
1
+ import Component from '@glimmer/component';
2
+ import { assert } from '@ember/debug';
3
+
4
+ export const DEFAULT_COLOR = 'primary';
5
+ export const COLORS = ['primary', 'secondary'];
6
+
7
+ export default class HdsDropdownToggleButtonComponent extends Component {
8
+ /**
9
+ * @param text
10
+ * @type {string}
11
+ * @description The text of the button. If no text value is defined an error will be thrown.
12
+ */
13
+ get text() {
14
+ let { text } = this.args;
15
+
16
+ assert(
17
+ '@text for "Hds::Dropdown::ToggleButton" must have a valid value',
18
+ text !== undefined
19
+ );
20
+
21
+ return text;
22
+ }
23
+
24
+ /**
25
+ * @param color
26
+ * @type {string}
27
+ * @default primary
28
+ * @description Determines the color of button to be used; acceptable values are `primary` and `secondary`
29
+ */
30
+ get color() {
31
+ let { color = DEFAULT_COLOR } = this.args;
32
+
33
+ assert(
34
+ `@color for "Hds::Dropdown::ToggleButton" must be one of the following: ${COLORS.join(
35
+ ', '
36
+ )}; received: ${color}`,
37
+ COLORS.includes(color)
38
+ );
39
+
40
+ return color;
41
+ }
42
+
43
+ /**
44
+ * @param onClick
45
+ * @type {function}
46
+ * @default () => {}
47
+ */
48
+ get onClick() {
49
+ let { onClick } = this.args;
50
+
51
+ // notice: this is a guard used in case the toggle is used as standalone element (eg. in the showcase)
52
+ // in reality it's always used inside the Dropdown main component as yielded component, so the onClick handler is always defined
53
+ if (typeof onClick === 'function') {
54
+ return onClick;
55
+ } else {
56
+ return () => {};
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,22 @@
1
+ <button
2
+ class="hds-dropdown-toggle-icon"
3
+ aria-label={{this.text}}
4
+ ...attributes
5
+ {{on "click" this.onClick}}
6
+ type="button"
7
+ >
8
+ <div class="hds-dropdown-toggle-icon__wrapper">
9
+ {{#if @imageSrc}}
10
+ <img src={{@imageSrc}} alt="" role="presentation" height="32" width="32" />
11
+ {{else}}
12
+ <FlightIcon @name={{this.icon}} @size="24" />
13
+ {{/if}}
14
+ </div>
15
+ {{#if this.hasChevron}}
16
+ <FlightIcon
17
+ @name="chevron-{{if @isOpen 'up' 'down'}}"
18
+ class="hds-dropdown-toggle-icon__chevron"
19
+ @isInlineBlock={{false}}
20
+ />
21
+ {{/if}}
22
+ </button>
@@ -0,0 +1,59 @@
1
+ import Component from '@glimmer/component';
2
+ import { assert } from '@ember/debug';
3
+
4
+ export default class HdsDropdownToggleIconComponent extends Component {
5
+ /**
6
+ * @param text
7
+ * @type {string}
8
+ * @description The text of the `aria-label` applied to the toggle
9
+ */
10
+ get text() {
11
+ let { text } = this.args;
12
+
13
+ assert(
14
+ '@text for "Hds::Dropdown::ToggleIcon" must have a valid value',
15
+ text !== undefined
16
+ );
17
+
18
+ return text;
19
+ }
20
+
21
+ /**
22
+ * Indicates if a dropdown chevron icon should be displayed; should be displayed unless the "more-horizontal" icon is used.
23
+ *
24
+ * @param hasChevron
25
+ * @type {boolean}
26
+ * @default true
27
+ */
28
+ get hasChevron() {
29
+ return this.args.hasChevron ?? true;
30
+ }
31
+
32
+ /**
33
+ * Sets the icon name
34
+ *
35
+ * @param icon
36
+ * @type {string}
37
+ * @default user
38
+ */
39
+ get icon() {
40
+ return this.args.icon ?? 'user';
41
+ }
42
+
43
+ /**
44
+ * @param onClick
45
+ * @type {function}
46
+ * @default () => {}
47
+ */
48
+ get onClick() {
49
+ let { onClick } = this.args;
50
+
51
+ // notice: this is a guard used in case the toggle is used as standalone element (eg. in the showcase)
52
+ // in reality it's always used inside the Dropdown main component as yielded component, so the onClick handler is always defined
53
+ if (typeof onClick === 'function') {
54
+ return onClick;
55
+ } else {
56
+ return () => {};
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,50 @@
1
+ {{!
2
+ // ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████
3
+ // ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██
4
+ // ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███
5
+ // ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
6
+ // ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████
7
+ //
8
+ // Notice: in this component we're using directly the styles from the `Hds::Button` component
9
+ // using the `hds-button` class names (and adding a specialized class for the "cta", see below)
10
+ // If you need to change the styling of the `Button` component, remember that this will impact also
11
+ // this component too.
12
+ // If instead you need to change only the styling of the `CTA` component, you can do it
13
+ // in the CSS file using the specialized class declared there.
14
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
15
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
16
+ // `Button (primary)` with other components.
17
+ }}
18
+
19
+ {{! template-lint-disable link-href-attributes }}
20
+ {{! we can disable this linting rule because the developer will add the html attribute themselves }}
21
+ <a
22
+ class={{this.classNames}}
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ ...attributes
26
+ {{did-insert this.didInsert}}
27
+ {{on-key "Space" this.onKeySpace}}
28
+ >
29
+ {{#if this.icon}}
30
+ {{#if (eq this.iconPosition "leading")}}
31
+ <div class="hds-button__icon">
32
+ <FlightIcon @name={{this.icon}} @size={{this.iconSize}} @stretched={{true}} />
33
+ </div>
34
+ <div class="hds-button__text">
35
+ {{this.text}}
36
+ </div>
37
+ {{else}}
38
+ <div class="hds-button__text">
39
+ {{this.text}}
40
+ </div>
41
+ <div class="hds-button__icon">
42
+ <FlightIcon @name={{this.icon}} @size={{this.iconSize}} @stretched={{true}} />
43
+ </div>
44
+ {{/if}}
45
+ {{else}}
46
+ <div class="hds-button__text">
47
+ {{this.text}}
48
+ </div>
49
+ {{/if}}
50
+ </a>
@@ -0,0 +1,150 @@
1
+ // ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████
2
+ // ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██
3
+ // ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███
4
+ // ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
5
+ // ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████
6
+ //
7
+ // Notice: in this component we're using directly the styles from the `Hds::Button` component
8
+ // using the `hds-button` class names (and adding a specialized class for the "cta", see below)
9
+ // If you need to change the styling of the `Button` component, remember that this will impact also
10
+ // this component too.
11
+ // If instead you need to change only the styling of the `CTA` component, you can do it
12
+ // in the CSS file using the specialized class declared there.
13
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
14
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
15
+ // `Button (primary)` with other components.
16
+
17
+ import Component from '@glimmer/component';
18
+ import { assert } from '@ember/debug';
19
+ import { action } from '@ember/object';
20
+
21
+ export const DEFAULT_SIZE = 'medium';
22
+ export const DEFAULT_ICONPOSITION = 'leading';
23
+ export const SIZES = ['small', 'medium', 'large'];
24
+ export const ICONPOSITIONS = ['leading', 'trailing'];
25
+
26
+ export default class HdsLinkCtaComponent extends Component {
27
+ /**
28
+ * @param text
29
+ * @type {string}
30
+ * @description The text of the component. If no text value is defined an error will be thrown.
31
+ */
32
+ get text() {
33
+ let { text } = this.args;
34
+
35
+ assert(
36
+ '@text for "Hds::Link::Cta" must have a valid value',
37
+ text !== undefined
38
+ );
39
+
40
+ return text;
41
+ }
42
+
43
+ /**
44
+ * @param size
45
+ * @type {string}
46
+ * @default medium
47
+ * @description The size of the component; acceptable values are `small`, `medium`, and `large`
48
+ */
49
+ get size() {
50
+ let { size = DEFAULT_SIZE } = this.args;
51
+
52
+ assert(
53
+ `@size for "Hds::Link::Cta" must be one of the following: ${SIZES.join(
54
+ ', '
55
+ )}; received: ${size}`,
56
+ SIZES.includes(size)
57
+ );
58
+
59
+ return size;
60
+ }
61
+
62
+ /**
63
+ * @param icon
64
+ * @type {string}
65
+ * @default null
66
+ * @description The name of the icon to be used.
67
+ */
68
+ get icon() {
69
+ return this.args.icon ?? null;
70
+ }
71
+
72
+ /**
73
+ * @param iconPosition
74
+ * @type {string}
75
+ * @default leading
76
+ * @description Positions the icon before or after the text; allowed values are `leading` or `trailing`
77
+ */
78
+ get iconPosition() {
79
+ let { iconPosition = DEFAULT_ICONPOSITION } = this.args;
80
+
81
+ assert(
82
+ `@iconPosition for "Hds::Link::Cta" must be one of the following: ${ICONPOSITIONS.join(
83
+ ', '
84
+ )}; received: ${iconPosition}`,
85
+ ICONPOSITIONS.includes(iconPosition)
86
+ );
87
+
88
+ return iconPosition;
89
+ }
90
+
91
+ /**
92
+ * @param iconSize
93
+ * @type {string}
94
+ * @default 16
95
+ * @description ensures that the correct icon size is used. Automatically calculated.
96
+ */
97
+ get iconSize() {
98
+ if (this.args.size === 'large') {
99
+ return '24';
100
+ } else {
101
+ return '16';
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param isFullWidth
107
+ * @type {boolean}
108
+ * @default false
109
+ * @description Indicates that the component should take up the full width of the parent container. The default is false.
110
+ */
111
+ get isFullWidth() {
112
+ return this.args.isFullWidth ?? false;
113
+ }
114
+
115
+ /**
116
+ * Get the class names to apply to the component.
117
+ * @method classNames
118
+ * @return {string} The "class" attribute to apply to the component.
119
+ */
120
+ get classNames() {
121
+ let classes = [
122
+ 'hds-button',
123
+ 'hds-button--color-primary',
124
+ 'hds-link-cta--inherit-button-styles',
125
+ ];
126
+
127
+ // add a class based on the @size argument
128
+ classes.push(`hds-button--size-${this.size}`);
129
+
130
+ // add a class based on the @isFullWidth argument
131
+ if (this.isFullWidth) {
132
+ classes.push('hds-button--width-full');
133
+ }
134
+
135
+ return classes.join(' ');
136
+ }
137
+
138
+ @action
139
+ didInsert(el) {
140
+ // we need to register the element to compare it with the one that triggered the "key/space" event
141
+ this.el = el;
142
+ }
143
+
144
+ @action
145
+ onKeySpace(event) {
146
+ if (event.target === this.el) {
147
+ event.target.click();
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,51 @@
1
+ {{!
2
+ // ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████
3
+ // ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██
4
+ // ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███
5
+ // ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
6
+ // ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████
7
+ //
8
+ // Notice: in this component we're using directly the styles from the `Hds::Button` component
9
+ // using the `hds-button` class names (and adding a specialized class for the "cta", see below)
10
+ // If you need to change the styling of the `Button` component, remember that this will impact also
11
+ // this component too.
12
+ // If instead you need to change only the styling of the `CTA` component, you can do it
13
+ // in the CSS file using the specialized class declared there.
14
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
15
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
16
+ // `Button (primary)` with other components.
17
+ }}
18
+
19
+ <LinkTo
20
+ class={{this.classNames}}
21
+ @current-when={{@current-when}}
22
+ @models={{hds-link-to-models @model @models}}
23
+ @query={{hds-link-to-query @query}}
24
+ @replace={{@replace}}
25
+ @route={{this.route}}
26
+ ...attributes
27
+ {{did-insert this.didInsert}}
28
+ {{on-key "Space" this.onKeySpace}}
29
+ >
30
+ {{#if this.icon}}
31
+ {{#if (eq this.iconPosition "leading")}}
32
+ <div class="hds-button__icon">
33
+ <FlightIcon @name={{this.icon}} @size={{this.iconSize}} @stretched={{true}} />
34
+ </div>
35
+ <div class="hds-button__text">
36
+ {{this.text}}
37
+ </div>
38
+ {{else}}
39
+ <div class="hds-button__text">
40
+ {{this.text}}
41
+ </div>
42
+ <div class="hds-button__icon">
43
+ <FlightIcon @name={{this.icon}} @size={{this.iconSize}} @stretched={{true}} />
44
+ </div>
45
+ {{/if}}
46
+ {{else}}
47
+ <div class="hds-button__text">
48
+ {{this.text}}
49
+ </div>
50
+ {{/if}}
51
+ </LinkTo>
@@ -0,0 +1,165 @@
1
+ // ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████
2
+ // ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██
3
+ // ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███
4
+ // ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
5
+ // ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████
6
+ //
7
+ // Notice: in this component we're using directly the styles from the `Hds::Button` component
8
+ // using the `hds-button` class names (and adding a specialized class for the "cta", see below)
9
+ // If you need to change the styling of the `Button` component, remember that this will impact also
10
+ // this component too.
11
+ // If instead you need to change only the styling of the `CTA` component, you can do it
12
+ // in the CSS file using the specialized class declared there.
13
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
14
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
15
+ // `Button (primary)` with other components.
16
+
17
+ import Component from '@glimmer/component';
18
+ import { assert } from '@ember/debug';
19
+ import { action } from '@ember/object';
20
+
21
+ export const DEFAULT_SIZE = 'medium';
22
+ export const DEFAULT_ICONPOSITION = 'leading';
23
+ export const SIZES = ['small', 'medium', 'large'];
24
+ export const ICONPOSITIONS = ['leading', 'trailing'];
25
+
26
+ export default class HdsLinkToCtaComponent extends Component {
27
+ /**
28
+ * @param text
29
+ * @type {string}
30
+ * @description The text of the component. If no text value is defined an error will be thrown.
31
+ */
32
+ get text() {
33
+ let { text } = this.args;
34
+
35
+ assert(
36
+ '@text for "Hds::LinkTo::Cta" must have a valid value',
37
+ text !== undefined
38
+ );
39
+
40
+ return text;
41
+ }
42
+
43
+ /**
44
+ * @param size
45
+ * @type {string}
46
+ * @default medium
47
+ * @description The size of the component; acceptable values are `small`, `medium`, and `large`
48
+ */
49
+ get size() {
50
+ let { size = DEFAULT_SIZE } = this.args;
51
+
52
+ assert(
53
+ `@size for "Hds::LinkTo::Cta" must be one of the following: ${SIZES.join(
54
+ ', '
55
+ )}; received: ${size}`,
56
+ SIZES.includes(size)
57
+ );
58
+
59
+ return size;
60
+ }
61
+
62
+ /**
63
+ * @param icon
64
+ * @type {string}
65
+ * @default null
66
+ * @description The name of the icon to be used.
67
+ */
68
+ get icon() {
69
+ return this.args.icon ?? null;
70
+ }
71
+
72
+ /**
73
+ * @param iconPosition
74
+ * @type {string}
75
+ * @default leading
76
+ * @description Positions the icon before or after the text; allowed values are `leading` or `trailing`
77
+ */
78
+ get iconPosition() {
79
+ let { iconPosition = DEFAULT_ICONPOSITION } = this.args;
80
+
81
+ assert(
82
+ `@iconPosition for "Hds::LinkTo::Cta" must be one of the following: ${ICONPOSITIONS.join(
83
+ ', '
84
+ )}; received: ${iconPosition}`,
85
+ ICONPOSITIONS.includes(iconPosition)
86
+ );
87
+
88
+ return iconPosition;
89
+ }
90
+
91
+ /**
92
+ * @param iconSize
93
+ * @type {string}
94
+ * @default 16
95
+ * @description ensures that the correct icon size is used. Automatically calculated.
96
+ */
97
+ get iconSize() {
98
+ if (this.args.size === 'large') {
99
+ return '24';
100
+ } else {
101
+ return '16';
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param isFullWidth
107
+ * @type {boolean}
108
+ * @default false
109
+ * @description Indicates that the component should take up the full width of the parent container. The default is false.
110
+ */
111
+ get isFullWidth() {
112
+ return this.args.isFullWidth ?? false;
113
+ }
114
+
115
+ /**
116
+ * @param route
117
+ * @type {string|null}
118
+ * @description Checks to make sure route is defined.
119
+ */
120
+ get route() {
121
+ let { route } = this.args;
122
+ assert(
123
+ '@route must be defined for "Hds::LinkTo::Cta"',
124
+ route !== undefined
125
+ );
126
+
127
+ return route;
128
+ }
129
+
130
+ /**
131
+ * Get the class names to apply to the component.
132
+ * @method classNames
133
+ * @return {string} The "class" attribute to apply to the component.
134
+ */
135
+ get classNames() {
136
+ let classes = [
137
+ 'hds-button',
138
+ 'hds-button--color-primary',
139
+ 'hds-link-cta--inherit-button-styles',
140
+ ];
141
+
142
+ // add a class based on the @size argument
143
+ classes.push(`hds-button--size-${this.size}`);
144
+
145
+ // add a class based on the @isFullWidth argument
146
+ if (this.isFullWidth) {
147
+ classes.push('hds-button--width-full');
148
+ }
149
+
150
+ return classes.join(' ');
151
+ }
152
+
153
+ @action
154
+ didInsert(el) {
155
+ // we need to register the element to compare it with the one that triggered the "key/space" event
156
+ this.el = el;
157
+ }
158
+
159
+ @action
160
+ onKeySpace(event) {
161
+ if (event.target === this.el) {
162
+ event.target.click();
163
+ }
164
+ }
165
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/dropdown/index';
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/dropdown/list-item';
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/dropdown/toggle-button';
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/dropdown/toggle-icon';
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/link/cta';
@@ -0,0 +1 @@
1
+ export { default } from '@hashicorp/design-system-components/components/hds/link-to/cta';
@@ -11,7 +11,9 @@
11
11
  @use "../components/button";
12
12
  @use "../components/card";
13
13
  @use "../components/disclosure";
14
+ @use "../components/dropdown";
14
15
  @use "../components/icon-tile";
16
+ @use "../components/link/cta";
15
17
  @use "../components/link/standalone";
16
18
 
17
19
  .sr-only {
@@ -0,0 +1,392 @@
1
+ // >>>>>>>>>> WARNING <<<<<<<<<<<
2
+ //
3
+ // Notice: in this component we're using directly the `Hds::Button` component
4
+ // (and adding a specialized class for the "toggle-button" variant, see below)
5
+ // If you need to change the styling of the `Button` component, remember that this will impact also
6
+ // this component too.
7
+ // If instead you need to change only the styling of the `toggle-button` component, you can do it here using
8
+ // the specialized class declared below.
9
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
10
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
11
+ // `Button (primary)` with other components.
12
+
13
+ //
14
+ // DROPDOWN COMPONENT
15
+ //
16
+ // properties within each class are sorted alphabetically
17
+ // notice: pseudo-classes for the states *must* follow the order link > visited > hover > focus > active
18
+ // see https://github.com/hashicorp/design-system-components/issues/132
19
+ //
20
+ //
21
+
22
+ @use '../mixins/focus-ring' as *;
23
+
24
+ // TOGGLE/ICON
25
+ .hds-dropdown-toggle-icon {
26
+ align-items: center;
27
+ background-color: transparent;
28
+ border: 1px solid transparent; // We need this to be transparent for a11y
29
+ border-radius: 5px;
30
+ display: flex;
31
+ height: 36px;
32
+ justify-content: center;
33
+ outline-style: solid; // used to avoid double outline+focus-ring in Safari (see https://github.com/hashicorp/design-system-components/issues/161#issuecomment-1031548656)
34
+ outline-color: transparent; // We need this to be transparent for a11y
35
+ padding: 1px;
36
+ min-width: 36px;
37
+
38
+ &:hover,
39
+ &.is-hover {
40
+ background-color: var(--token-color-surface-interactive);
41
+ border-color: var(--token-color-border-strong);
42
+ cursor: pointer;
43
+ }
44
+
45
+ // this is the :focus
46
+ @include hds-focus-ring-with-pseudo-element($top: -1px, $right: -1px, $bottom: -1px, $left: -1px, $radius: 5px);
47
+
48
+ &:active,
49
+ &.is-active {
50
+ background-color: var(--token-color-surface-interactive-active);
51
+ border-color: var(--token-color-border-strong);
52
+ }
53
+ }
54
+
55
+ .hds-dropdown-toggle-icon__wrapper {
56
+ align-items: center;
57
+ border-radius: 3px; // 5px- 1px padding - 1px border
58
+ display: flex;
59
+ justify-content: center;
60
+ height: 32px;
61
+ padding: 1px;
62
+ width: 32px;
63
+
64
+ img {
65
+ border-radius: inherit;
66
+ object-fit: cover; // this will make sure it's correct even if the item isn't square
67
+ }
68
+ }
69
+
70
+ .hds-dropdown-toggle-icon__chevron {
71
+ margin-left: 0.25rem;
72
+ }
73
+
74
+ // TOGGLE/BUTTON
75
+ .hds-dropdown-toggle--with-button-component {
76
+ box-shadow: none; // we override this to remove the elevation style
77
+
78
+ .hds-button__icon {
79
+ margin-right: -6px; // we apply a negative margin to counter the padding-right of the button and reduce the visual space between the icon and the right border
80
+ margin-left: 0.5rem; // this overrides the rule `.hds-button__text + .hds-button__icon`
81
+ }
82
+ }
83
+
84
+
85
+ // LIST
86
+ // UL ELEMENT
87
+ // GOES INSIDE HDS::DISCLOSURE's :content block
88
+
89
+ .hds-dropdown-list {
90
+ background-color: var(--token-color-surface-primary);
91
+ border-radius: 6px;
92
+ box-shadow: var(--token-surface-high-box-shadow);
93
+ list-style: none;
94
+ margin: 0;
95
+ max-width: 25rem;
96
+ min-width: 12.5rem;
97
+ padding: 4px 0;
98
+ &.hds-dropdown-list--position-right {
99
+ position: absolute;
100
+ right: 0;
101
+ top: calc(100% + 0.25rem);
102
+ z-index: 2; // https://github.com/hashicorp/design-system/issues/114
103
+ }
104
+
105
+ &.hds-dropdown-list--position-left {
106
+ position: absolute;
107
+ left: 0;
108
+ top: calc(100% + 0.25rem);
109
+ z-index: 2; // https://github.com/hashicorp/design-system/issues/114
110
+ }
111
+ }
112
+
113
+ // LIST > LIST-ITEM
114
+ // HDS::DROPDOWN::LIST-ITEM
115
+
116
+ .hds-dropdown-list-item--title {
117
+ color: var(--token-color-foreground-strong);
118
+ font-family: var(--token-typography-body-100-font-family);
119
+ font-size: var(--token-typography-body-100-font-size);
120
+ font-weight: var(--token-typography-font-weight-semibold);
121
+ line-height: var(--token-typography-body-100-line-height);
122
+ padding: 10px 16px 4px;
123
+ }
124
+
125
+ .hds-dropdown-list-item--generic {
126
+ padding-left: 16px;
127
+ padding-right: 16px;
128
+ }
129
+
130
+ .hds-dropdown-list-item--description {
131
+ color: var(--token-color-foreground-faint);
132
+ font-family: var(--token-typography-body-100-font-family);
133
+ font-size: var(--token-typography-body-100-font-size);
134
+ font-weight: var(--token-typography-font-weight-regular);
135
+ line-height: var(--token-typography-body-100-line-height);
136
+ padding: 2px 16px 4px;
137
+ }
138
+
139
+ .hds-dropdown-list-item--separator {
140
+ position: relative;
141
+ height: 4px;
142
+ width: 100%;
143
+
144
+ &::before {
145
+ position: absolute;
146
+ right: 6px;
147
+ left: 6px;
148
+ bottom: 0;
149
+ border-bottom: 1px solid var(--token-color-border-primary);
150
+ content: '';
151
+ }
152
+ }
153
+
154
+ .hds-dropdown-list-item__copy-item-title {
155
+ color: var(--token-color-foreground-faint);
156
+ font-family: var(--token-typography-body-100-font-family);
157
+ font-size: var(--token-typography-body-100-font-size); // 13
158
+ font-weight: var(--token-typography-font-weight-semibold);
159
+ line-height: var(--token-typography-body-100-line-height); // 18
160
+ padding: 2px 0 4px;
161
+ }
162
+
163
+ .hds-dropdown-list-item--copy-item {
164
+ padding: 10px 16px 12px;
165
+ width: 100%;
166
+
167
+ button {
168
+ background-color: transparent;
169
+ border-radius: 5px;
170
+ border: 1px solid var(--token-color-border-primary);
171
+ color: var(--token-color-foreground-primary);
172
+ display: flex;
173
+ font-family: var(--token-typography-font-stack-code);
174
+ justify-content: space-between;
175
+ padding: 12px 8px;
176
+ width: 100%;
177
+
178
+ &:hover,
179
+ &.is-hover {
180
+ background-color: var(--token-color-surface-interactive-hover);
181
+ cursor: pointer;
182
+ }
183
+
184
+ @include hds-focus-ring-basic();
185
+
186
+ &:focus,
187
+ &.is-focus {
188
+ //TODO this focus is just way too complex
189
+ background-color: var(--token-color-surface-action);
190
+ border-color: var(--token-color-focus-action-internal);
191
+ }
192
+
193
+ &:active,
194
+ &.is-active {
195
+ background-color: var(--token-color-surface-interactive-active);
196
+ }
197
+
198
+ &.is-success {
199
+ border-color: var(--token-color-border-success);
200
+ background-color: var(--token-color-surface-success);
201
+
202
+ .hds-dropdown-list-item__copy-item-icon {
203
+ color: var(--token-color-foreground-success);
204
+ }
205
+ }
206
+
207
+ .hds-dropdown-list-item__copy-item-text {
208
+ font-size: var(--token-typography-code-100-font-size);
209
+ font-weight: var(--token-typography-font-weight-regular);
210
+ line-height: var(--token-typography-code-100-line-height);
211
+ // max-width: 250px; // TODO we should be able to figure out the proportions here
212
+ overflow: hidden;
213
+ text-align: left;
214
+ text-overflow: ellipsis;
215
+ white-space: nowrap;
216
+ }
217
+
218
+ .hds-dropdown-list-item__copy-item-icon {
219
+ color: var(--token-color-foreground-action);
220
+ margin-left: 0.5rem;
221
+ }
222
+ }
223
+ }
224
+
225
+ .hds-dropdown-list-item--interactive {
226
+ position: relative;
227
+ isolation: isolate; // used to create a new stacking context (needed to have the pseudo element below text/icon but not the parent container)
228
+ min-height: 36px;
229
+
230
+ // need to reset a few extra things to make the button visually appear the same as the links
231
+ // TODO this is 0.125rem taller than the link...
232
+ button {
233
+ border: 1px inset transparent; // cause of the extra height
234
+ background-color: transparent;
235
+ width: 100%;
236
+
237
+ &:hover {
238
+ cursor: pointer;
239
+ }
240
+
241
+ }
242
+
243
+ // shared styles for links and buttons
244
+ a, button {
245
+ align-items: center;
246
+ display: flex;
247
+ outline-style: solid; // used to avoid double outline+focus-ring in Safari (see https://github.com/hashicorp/design-system-components/issues/161#issuecomment-1031548656)
248
+ outline-color: transparent;
249
+ padding: 8px 10px 8px 16px;
250
+ text-decoration: none;
251
+
252
+ // this is used for the left "hover" indicator
253
+ &::before {
254
+ border-radius: 1px;
255
+ bottom: 6px;
256
+ content: '';
257
+ left: 0.25rem;
258
+ position: absolute;
259
+ top: 6px;
260
+ width: 2px;
261
+ z-index: -1;
262
+ }
263
+
264
+ // Notice: this is used for the active/focus states which have very specific positions
265
+ // and also has a background color, so we can't use the existing focus-ring mixins
266
+ &::after {
267
+ border-radius: 5px;
268
+ bottom: 0px;
269
+ content: '';
270
+ left: 0.625rem;
271
+ position: absolute;
272
+ right: 0.25rem;
273
+ z-index: -1;
274
+ top: 0;
275
+ }
276
+
277
+ // Notice: to avoid too much duplication we define two local CSS variables
278
+ // and define their values in the color variants below
279
+
280
+ // default focus for browsers that still rely on ":focus"
281
+ &:focus,
282
+ &.is-focus {
283
+ &::after {
284
+ background-color: var(--current-background-color);
285
+ box-shadow: var(--current-focus-ring-box-shadow);
286
+ left: 0.25rem;
287
+ }
288
+ }
289
+ // undo the previous declaration for browsers that support ":focus-visible" but wouldn't normally show default focus styles
290
+ &:focus:not(:focus-visible) {
291
+ &::after {
292
+ box-shadow: none;
293
+ }
294
+ }
295
+ // set focus for browsers that support ":focus-visible"
296
+ &:focus-visible {
297
+ &::after {
298
+ background-color: var(--current-background-color);
299
+ box-shadow: var(--current-focus-ring-box-shadow);
300
+ left: 0.25rem;
301
+ }
302
+ }
303
+ // remove the focus ring on "active + focused" state (by design)
304
+ &:focus:active,
305
+ &.is-focus.is-active {
306
+ &::after {
307
+ box-shadow: none;
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ .hds-dropdown-list-item__interactive-text {
314
+ font-family: var(--token-typography-body-200-font-family);
315
+ font-size: var(--token-typography-body-200-font-size);
316
+ font-weight: var(--token-typography-font-weight-medium);
317
+ line-height: var(--token-typography-body-200-line-height);
318
+ text-align: left; // the button element was centering text
319
+ }
320
+
321
+ .hds-dropdown-list-item__interactive-icon {
322
+ margin-right: 0.5rem;
323
+ }
324
+
325
+ .hds-dropdown-list-item--color-action {
326
+ a, button {
327
+ color: var(--token-color-foreground-primary);
328
+
329
+ // assign the values to the local CSS variables used above
330
+ &::after {
331
+ --current-background-color: var(--token-color-surface-action);
332
+ --current-focus-ring-box-shadow: var(
333
+ --token-focus-ring-action-box-shadow
334
+ );
335
+ }
336
+
337
+ &:hover,
338
+ &.is-hover {
339
+ color: var(--token-color-foreground-action-hover);
340
+ &::before {
341
+ background-color: currentColor;
342
+ }
343
+ }
344
+
345
+ &:active,
346
+ &.is-active {
347
+ color: var(--token-color-foreground-action-active);
348
+ &::before {
349
+ background-color: currentColor;
350
+ }
351
+ &::after {
352
+ background-color: var(--token-color-surface-action);
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ .hds-dropdown-list-item--color-critical {
359
+ a, button {
360
+ color: var(--token-color-foreground-critical);
361
+
362
+ // assign the values to the local CSS variables used above
363
+ &::after {
364
+ --current-background-color: var(--token-color-surface-critical);
365
+ --current-focus-ring-box-shadow: var(
366
+ --token-focus-ring-critical-box-shadow
367
+ );
368
+ }
369
+
370
+ &:hover,
371
+ &.is-hover {
372
+ color: var(
373
+ --token-color-palette-red-300
374
+ ); // TODO we need to add this token to the design system
375
+ &::before {
376
+ background-color: currentColor;
377
+ }
378
+ }
379
+ &:active,
380
+ &.is-active {
381
+ color: var(
382
+ --token-color-palette-red-400
383
+ ); // TODO we need to add this token to the design system
384
+ &::before {
385
+ background-color: currentColor;
386
+ }
387
+ &::after {
388
+ background-color: var(--token-color-surface-critical);
389
+ }
390
+ }
391
+ }
392
+ }
@@ -0,0 +1,28 @@
1
+ // ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████
2
+ // ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██
3
+ // ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███
4
+ // ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
5
+ // ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████
6
+ //
7
+ // Notice: in this component we're using directly the styles from the `Hds::Button` component
8
+ // using the `hds-button` class names (and adding a specialized class for the "cta", see below)
9
+ // If you need to change the styling of the `Button` component, remember that this will impact also
10
+ // this component too.
11
+ // If instead you need to change only the styling of the `CTA` component, you can do it here using
12
+ // the specialized class declared below.
13
+ // This is NOT a standard approach that we use in the HDS design system implementation, but it's been
14
+ // the least worst option we could find to solve the problem of sharing the exact same style of the
15
+ // `Button (primary)` with other components.
16
+
17
+ //
18
+ // LINK > CTA COMPONENT
19
+ //
20
+ // properties within each class are sorted alphabetically
21
+ //
22
+ //
23
+
24
+ .hds-link-cta--inherit-button-styles {
25
+ isolation: isolate;
26
+ text-decoration: none;
27
+ width: fit-content;
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashicorp/design-system-components",
3
- "version": "0.7.1",
3
+ "version": "0.9.1",
4
4
  "description": "HashiCorp Design System Components",
5
5
  "keywords": [
6
6
  "hashicorp",
@@ -35,12 +35,13 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@hashicorp/design-system-tokens": "^0.8.0",
38
- "@hashicorp/ember-flight-icons": "^2.0.1",
38
+ "@hashicorp/ember-flight-icons": "^2.0.5",
39
39
  "ember-auto-import": "^2.2.3",
40
40
  "ember-cli-babel": "^7.26.6",
41
41
  "ember-cli-htmlbars": "^5.7.1",
42
42
  "ember-cli-sass": "^10.0.1",
43
43
  "ember-focus-trap": "^1.0.1",
44
+ "ember-keyboard": "^8.0.0",
44
45
  "ember-named-blocks-polyfill": "^0.2.5",
45
46
  "sass": "^1.43.4"
46
47
  },