@hashicorp/design-system-components 2.15.0 → 3.0.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-FIGMA-COMPONENTS.md +177 -0
- package/CHANGELOG-FIGMA-FOUNDATIONS.md +2 -0
- package/CHANGELOG.md +367 -59
- package/README.md +1 -1
- package/addon/components/hds/accordion/item/index.hbs +16 -5
- package/addon/components/hds/application-state/body.hbs +2 -2
- package/addon/components/hds/application-state/header.hbs +4 -4
- package/addon/components/hds/button/{index.js → index.ts} +29 -4
- package/addon/components/hds/copy/button/index.hbs +1 -1
- package/addon/components/hds/copy/button/index.js +14 -2
- package/addon/components/hds/copy/snippet/index.hbs +3 -3
- package/addon/components/hds/copy/snippet/index.js +19 -2
- package/addon/components/hds/dropdown/index.js +0 -3
- package/addon/components/hds/dropdown/list-item/checkmark.hbs +9 -3
- package/addon/components/hds/dropdown/list-item/copy-item.hbs +8 -4
- package/addon/components/hds/dropdown/list-item/copy-item.js +13 -0
- package/addon/components/hds/dropdown/list-item/description.hbs +9 -2
- package/addon/components/hds/dropdown/list-item/description.js +0 -18
- package/addon/components/hds/dropdown/list-item/interactive.hbs +4 -4
- package/addon/components/hds/dropdown/list-item/radio.hbs +6 -1
- package/addon/components/hds/dropdown/list-item/title.hbs +9 -2
- package/addon/components/hds/dropdown/list-item/title.js +0 -18
- package/addon/components/hds/flyout/description.hbs +2 -2
- package/addon/components/hds/flyout/header.hbs +4 -4
- package/addon/components/hds/form/error/index.hbs +2 -2
- package/addon/components/hds/form/error/index.js +0 -3
- package/addon/components/hds/form/error/message.hbs +2 -2
- package/addon/components/hds/form/helper-text/index.hbs +10 -2
- package/addon/components/hds/form/helper-text/index.js +0 -3
- package/addon/components/hds/form/indicator/index.hbs +1 -1
- package/addon/components/hds/form/indicator/index.js +0 -3
- package/addon/components/hds/form/masked-input/base.hbs +8 -10
- package/addon/components/hds/form/masked-input/base.js +14 -14
- package/addon/components/hds/form/masked-input/field.hbs +3 -1
- package/addon/components/hds/form/radio-card/description.hbs +6 -1
- package/addon/components/hds/form/radio-card/group.hbs +1 -2
- package/addon/components/hds/form/radio-card/index.js +5 -33
- package/addon/components/hds/form/radio-card/label.hbs +7 -1
- package/addon/components/hds/form/text-input/base.js +5 -0
- package/addon/components/hds/form/text-input/field.hbs +23 -11
- package/addon/components/hds/form/text-input/field.js +59 -0
- package/addon/components/hds/form/visibility-toggle/index.hbs +8 -0
- package/addon/components/hds/interactive/{index.js → index.ts} +28 -3
- package/addon/components/hds/modal/header.hbs +4 -4
- package/addon/components/hds/page-header/description.hbs +7 -1
- package/addon/components/hds/page-header/subtitle.hbs +7 -1
- package/addon/components/hds/page-header/title.hbs +7 -1
- package/addon/components/hds/pagination/info/index.hbs +2 -2
- package/addon/components/hds/pagination/nav/arrow.hbs +16 -4
- package/addon/components/hds/pagination/nav/arrow.js +0 -2
- package/addon/components/hds/pagination/nav/number.hbs +2 -1
- package/addon/components/hds/pagination/nav/number.js +1 -6
- package/addon/components/hds/side-nav/list/index.hbs +2 -2
- package/addon/components/hds/stepper/step/indicator.hbs +6 -1
- package/addon/components/hds/tag/index.hbs +2 -2
- package/addon/modifiers/hds-clipboard.js +163 -0
- package/addon/template-registry.ts +12 -0
- package/app/components/hds/form/visibility-toggle/index.js +6 -0
- package/app/modifiers/hds-clipboard.js +6 -0
- package/app/styles/components/button.scss +2 -0
- package/app/styles/components/copy/snippet.scss +22 -14
- package/app/styles/components/dropdown.scss +2 -5
- package/app/styles/components/flyout.scss +0 -2
- package/app/styles/components/form/group.scss +5 -0
- package/app/styles/components/form/index.scss +1 -0
- package/app/styles/components/form/masked-input.scss +0 -9
- package/app/styles/components/form/radio-card.scss +2 -4
- package/app/styles/components/form/text-input.scss +17 -0
- package/app/styles/components/form/visibility-toggle.scss +23 -0
- package/app/styles/components/stepper/step-indicator.scss +0 -3
- package/app/styles/components/tag.scss +1 -5
- package/index.js +3 -0
- package/package.json +33 -4
- package/tsconfig.json +46 -0
- package/types/dummy/index.d.ts +6 -0
- package/types/global.d.ts +12 -0
|
@@ -2,4 +2,10 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Display
|
|
6
|
+
class="hds-form-radio-card__label"
|
|
7
|
+
@tag="span"
|
|
8
|
+
@size="300"
|
|
9
|
+
@weight="bold"
|
|
10
|
+
...attributes
|
|
11
|
+
>{{yield}}</Hds::Text::Display>
|
|
@@ -57,6 +57,11 @@ export default class HdsFormTextInputBaseComponent extends Component {
|
|
|
57
57
|
classes.push(`hds-form-text-input--is-invalid`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// add a class based on the @hasVisibilityToggle argument
|
|
61
|
+
if (this.args.hasVisibilityToggle) {
|
|
62
|
+
classes.push(`hds-form-text-input--has-visibility-toggle`);
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
// add a class based on the @isLoading argument
|
|
61
66
|
if (this.args.isLoading) {
|
|
62
67
|
classes.push(`hds-form-text-input--is-loading`);
|
|
@@ -14,16 +14,28 @@
|
|
|
14
14
|
{{yield (hash Label=F.Label isRequired=F.isRequired isOptional=F.isOptional)}}
|
|
15
15
|
{{yield (hash HelperText=F.HelperText Error=F.Error)}}
|
|
16
16
|
<F.Control>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
<div class="hds-form-text-input__wrapper" {{style width=@width}}>
|
|
18
|
+
<Hds::Form::TextInput::Base
|
|
19
|
+
@type={{this.type}}
|
|
20
|
+
@value={{@value}}
|
|
21
|
+
@isInvalid={{@isInvalid}}
|
|
22
|
+
@isLoading={{@isLoading}}
|
|
23
|
+
@hasVisibilityToggle={{this.showVisibilityToggle}}
|
|
24
|
+
required={{@isRequired}}
|
|
25
|
+
...attributes
|
|
26
|
+
id={{F.id}}
|
|
27
|
+
aria-describedby={{F.ariaDescribedBy}}
|
|
28
|
+
/>
|
|
29
|
+
{{#if this.showVisibilityToggle}}
|
|
30
|
+
<Hds::Form::VisibilityToggle
|
|
31
|
+
@isVisible={{this.isPasswordMasked}}
|
|
32
|
+
@ariaLabel={{this.visibilityToggleAriaLabel}}
|
|
33
|
+
@ariaMessageText={{this.visibilityToggleAriaMessageText}}
|
|
34
|
+
aria-controls={{F.id}}
|
|
35
|
+
class="hds-form-text-input__visibility-toggle"
|
|
36
|
+
{{on "click" this.onClickTogglePasswordReadability}}
|
|
37
|
+
/>
|
|
38
|
+
{{/if}}
|
|
39
|
+
</div>
|
|
28
40
|
</F.Control>
|
|
29
41
|
</Hds::Form::Field>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) HashiCorp, Inc.
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Component from '@glimmer/component';
|
|
7
|
+
import { tracked } from '@glimmer/tracking';
|
|
8
|
+
import { action } from '@ember/object';
|
|
9
|
+
|
|
10
|
+
export default class HdsFormTextInputFieldComponent extends Component {
|
|
11
|
+
@tracked isPasswordMasked = true;
|
|
12
|
+
@tracked hasVisibilityToggle = this.args.hasVisibilityToggle ?? true;
|
|
13
|
+
@tracked type = this.args.type ?? 'text';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param showVisibilityToggle
|
|
17
|
+
* @type {boolean}
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
get showVisibilityToggle() {
|
|
21
|
+
return this.args.type === 'password' && this.hasVisibilityToggle;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param visibilityToggleAriaLabel
|
|
26
|
+
* @type {string}
|
|
27
|
+
* @default 'Show password'
|
|
28
|
+
*/
|
|
29
|
+
get visibilityToggleAriaLabel() {
|
|
30
|
+
if (this.args.visibilityToggleAriaLabel) {
|
|
31
|
+
return this.args.visibilityToggleAriaLabel;
|
|
32
|
+
} else if (this.isPasswordMasked) {
|
|
33
|
+
return 'Show password';
|
|
34
|
+
} else {
|
|
35
|
+
return 'Hide password';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param visibilityToggleAriaMessageText
|
|
41
|
+
* @type {string}
|
|
42
|
+
* @default 'Password is now hidden'
|
|
43
|
+
*/
|
|
44
|
+
get visibilityToggleAriaMessageText() {
|
|
45
|
+
if (this.args.visibilityToggleAriaMessageText) {
|
|
46
|
+
return this.args.visibilityToggleAriaMessageText;
|
|
47
|
+
} else if (this.isPasswordMasked) {
|
|
48
|
+
return 'Password is hidden';
|
|
49
|
+
} else {
|
|
50
|
+
return 'Password is visible';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@action
|
|
55
|
+
onClickTogglePasswordReadability() {
|
|
56
|
+
this.isPasswordMasked = !this.isPasswordMasked;
|
|
57
|
+
this.type = this.isPasswordMasked ? 'password' : 'text';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{{!
|
|
2
|
+
Copyright (c) HashiCorp, Inc.
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
}}
|
|
5
|
+
<button class="hds-form-visibility-toggle" type="button" aria-label={{@ariaLabel}} ...attributes>
|
|
6
|
+
<FlightIcon @name={{if @isVisible "eye" "eye-off"}} @size="16" @isInlineBlock={{false}} />
|
|
7
|
+
<span class="sr-only" aria-live="polite">{{@ariaMessageText}}</span>
|
|
8
|
+
</button>
|
|
@@ -6,7 +6,25 @@
|
|
|
6
6
|
import Component from '@glimmer/component';
|
|
7
7
|
import { action } from '@ember/object';
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export interface HdsInteractiveSignature {
|
|
10
|
+
Args: {
|
|
11
|
+
href?: string;
|
|
12
|
+
isHrefExternal?: boolean;
|
|
13
|
+
isRouteExternal?: boolean;
|
|
14
|
+
route?: unknown;
|
|
15
|
+
models?: unknown;
|
|
16
|
+
model?: unknown;
|
|
17
|
+
query?: unknown;
|
|
18
|
+
'current-when'?: unknown;
|
|
19
|
+
replace?: unknown;
|
|
20
|
+
};
|
|
21
|
+
Blocks: {
|
|
22
|
+
default: [];
|
|
23
|
+
};
|
|
24
|
+
Element: HTMLAnchorElement | HTMLButtonElement;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default class HdsInteractiveIndexComponent extends Component<HdsInteractiveSignature> {
|
|
10
28
|
/**
|
|
11
29
|
* Determines if a @href value is "external" (it adds target="_blank" rel="noopener noreferrer")
|
|
12
30
|
*
|
|
@@ -30,9 +48,16 @@ export default class HdsInteractiveComponent extends Component {
|
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
@action
|
|
33
|
-
onKeyUp(event) {
|
|
51
|
+
onKeyUp(event: KeyboardEvent) {
|
|
34
52
|
if (event.key === ' ' || event.code === 'Space') {
|
|
35
|
-
event.target.click();
|
|
53
|
+
(event.target as HTMLElement).click();
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
}
|
|
57
|
+
|
|
58
|
+
declare module '@glint/environment-ember-loose/registry' {
|
|
59
|
+
export default interface Registry {
|
|
60
|
+
'Hds::Interactive': typeof HdsInteractiveIndexComponent;
|
|
61
|
+
'hds/interactive': typeof HdsInteractiveIndexComponent;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
{{#if @icon}}
|
|
7
7
|
<FlightIcon class="hds-modal__icon" @name={{@icon}} @size="24" @isInlineBlock={{false}} />
|
|
8
8
|
{{/if}}
|
|
9
|
-
<
|
|
9
|
+
<Hds::Text::Display class="hds-modal__title" @tag="div" @size="300" @weight="semibold" id={{@id}}>
|
|
10
10
|
{{#if @tagline}}
|
|
11
|
-
<
|
|
11
|
+
<Hds::Text::Body class="hds-modal__tagline" @tag="div" @size="100" @weight="regular">
|
|
12
12
|
{{@tagline}}
|
|
13
|
-
</
|
|
13
|
+
</Hds::Text::Body>
|
|
14
14
|
{{/if}}
|
|
15
15
|
{{yield}}
|
|
16
|
-
</
|
|
16
|
+
</Hds::Text::Display>
|
|
17
17
|
<Hds::DismissButton class="hds-modal__dismiss" {{on "click" @onDismiss}} />
|
|
18
18
|
</div>
|
|
@@ -2,4 +2,10 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Body
|
|
6
|
+
class="hds-page-header__description"
|
|
7
|
+
@tag="p"
|
|
8
|
+
@size="200"
|
|
9
|
+
@color="primary"
|
|
10
|
+
...attributes
|
|
11
|
+
>{{yield}}</Hds::Text::Body>
|
|
@@ -2,4 +2,10 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Body
|
|
6
|
+
class="hds-page-header__subtitle"
|
|
7
|
+
@tag="p"
|
|
8
|
+
@size="200"
|
|
9
|
+
@color="faint"
|
|
10
|
+
...attributes
|
|
11
|
+
>{{yield}}</Hds::Text::Body>
|
|
@@ -2,4 +2,10 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Display
|
|
6
|
+
class="hds-page-header__title"
|
|
7
|
+
@tag="h1"
|
|
8
|
+
@size="500"
|
|
9
|
+
@color="strong"
|
|
10
|
+
...attributes
|
|
11
|
+
>{{yield}}</Hds::Text::Display>
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Body class="hds-pagination-info" @tag="div" @size="100" @weight="medium" ...attributes>
|
|
6
6
|
{{@itemsRangeStart}}–{{@itemsRangeEnd}}
|
|
7
7
|
{{#if this.showTotalItems}}
|
|
8
8
|
of
|
|
9
9
|
{{@totalItems}}
|
|
10
10
|
{{/if}}
|
|
11
|
-
</
|
|
11
|
+
</Hds::Text::Body>
|
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
<Hds::Interactive class={{this.classNames}} aria-label={{this.content.ariaLabel}} disabled={{true}} ...attributes>
|
|
7
7
|
<FlightIcon @name={{this.content.icon}} />
|
|
8
8
|
{{#if this.showLabel}}
|
|
9
|
-
<
|
|
9
|
+
<Hds::Text::Body
|
|
10
|
+
class="hds-pagination-nav__arrow-label"
|
|
11
|
+
@tag="span"
|
|
12
|
+
@size="100"
|
|
13
|
+
@weight="medium"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
>
|
|
10
16
|
{{this.content.label}}
|
|
11
|
-
</
|
|
17
|
+
</Hds::Text::Body>
|
|
12
18
|
{{/if}}
|
|
13
19
|
</Hds::Interactive>
|
|
14
20
|
{{else}}
|
|
@@ -24,9 +30,15 @@
|
|
|
24
30
|
>
|
|
25
31
|
<FlightIcon @name={{this.content.icon}} />
|
|
26
32
|
{{#if this.showLabel}}
|
|
27
|
-
<
|
|
33
|
+
<Hds::Text::Body
|
|
34
|
+
class="hds-pagination-nav__arrow-label"
|
|
35
|
+
@tag="span"
|
|
36
|
+
@size="100"
|
|
37
|
+
@weight="medium"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
>
|
|
28
40
|
{{this.content.label}}
|
|
29
|
-
</
|
|
41
|
+
</Hds::Text::Body>
|
|
30
42
|
{{/if}}
|
|
31
43
|
</Hds::Interactive>
|
|
32
44
|
{{/if}}
|
|
@@ -61,8 +61,6 @@ export default class HdsPaginationControlArrowComponent extends Component {
|
|
|
61
61
|
'hds-pagination-nav__control',
|
|
62
62
|
'hds-pagination-nav__arrow',
|
|
63
63
|
`hds-pagination-nav__arrow--direction-${this.args.direction}`,
|
|
64
|
-
'hds-typography-body-100',
|
|
65
|
-
'hds-font-weight-medium',
|
|
66
64
|
];
|
|
67
65
|
|
|
68
66
|
return classes.join(' ');
|
|
@@ -12,5 +12,6 @@
|
|
|
12
12
|
...attributes
|
|
13
13
|
aria-current={{if @isSelected "page" null}}
|
|
14
14
|
>
|
|
15
|
-
<span class="sr-only">page
|
|
15
|
+
<Hds::Text::Body @tag="span" @size="100" @weight="medium"><span class="sr-only">page
|
|
16
|
+
</span>{{this.page}}</Hds::Text::Body>
|
|
16
17
|
</Hds::Interactive>
|
|
@@ -25,12 +25,7 @@ export default class HdsPaginationControlNumberComponent extends Component {
|
|
|
25
25
|
* @return {string} The "class" attribute to apply to the component.
|
|
26
26
|
*/
|
|
27
27
|
get classNames() {
|
|
28
|
-
let classes = [
|
|
29
|
-
'hds-pagination-nav__control',
|
|
30
|
-
'hds-pagination-nav__number',
|
|
31
|
-
'hds-typography-body-100',
|
|
32
|
-
'hds-font-weight-medium"',
|
|
33
|
-
];
|
|
28
|
+
let classes = ['hds-pagination-nav__control', 'hds-pagination-nav__number'];
|
|
34
29
|
|
|
35
30
|
if (this.args.isSelected) {
|
|
36
31
|
classes.push(`hds-pagination-nav__number--is-selected`);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
}}
|
|
5
5
|
|
|
6
6
|
<nav class="hds-side-nav__list-wrapper" ...attributes>
|
|
7
|
-
{{yield (hash
|
|
7
|
+
{{yield (hash ExtraBefore=(component "hds/yield"))}}
|
|
8
8
|
<ul class="hds-side-nav__list" role="list">
|
|
9
9
|
{{yield
|
|
10
10
|
(hash
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
)
|
|
16
16
|
}}
|
|
17
17
|
</ul>
|
|
18
|
-
{{yield (hash
|
|
18
|
+
{{yield (hash ExtraAfter=(component "hds/yield"))}}
|
|
19
19
|
</nav>
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
{{else if (eq @status "complete")}}
|
|
18
18
|
<FlightIcon class="hds-stepper-indicator-step__icon" @name="check" @size="16" />
|
|
19
19
|
{{else}}
|
|
20
|
-
<
|
|
20
|
+
<Hds::Text::Body
|
|
21
|
+
class="hds-stepper-indicator-step__text"
|
|
22
|
+
@tag="span"
|
|
23
|
+
@size="100"
|
|
24
|
+
@weight="medium"
|
|
25
|
+
>{{@text}}</Hds::Text::Body>
|
|
21
26
|
{{/if}}
|
|
22
27
|
</div>
|
|
23
28
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Copyright (c) HashiCorp, Inc.
|
|
3
3
|
SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
}}
|
|
5
|
-
<
|
|
5
|
+
<Hds::Text::Body class={{this.classNames}} @tag="span" @size="100" @weight="medium" @color="primary" ...attributes>
|
|
6
6
|
{{#if this.onDismiss}}
|
|
7
7
|
<button class="hds-tag__dismiss" type="button" aria-label={{this.ariaLabel}} {{on "click" this.onDismiss}}>
|
|
8
8
|
<FlightIcon class="hds-tag__dismiss-icon" @name="x" @size="16" @isInlineBlock={{false}} />
|
|
@@ -27,4 +27,4 @@
|
|
|
27
27
|
{{this.text}}
|
|
28
28
|
</span>
|
|
29
29
|
{{/if}}
|
|
30
|
-
</
|
|
30
|
+
</Hds::Text::Body>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) HashiCorp, Inc.
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { modifier } from 'ember-modifier';
|
|
7
|
+
import { assert, warn } from '@ember/debug';
|
|
8
|
+
|
|
9
|
+
export const getTextToCopy = (text) => {
|
|
10
|
+
let textToCopy;
|
|
11
|
+
|
|
12
|
+
if (text) {
|
|
13
|
+
if (typeof text === 'string') {
|
|
14
|
+
textToCopy = text;
|
|
15
|
+
} else if (
|
|
16
|
+
// context: https://github.com/hashicorp/design-system/pull/1564
|
|
17
|
+
typeof text === 'number' ||
|
|
18
|
+
typeof text === 'bigint'
|
|
19
|
+
) {
|
|
20
|
+
textToCopy = text.toString();
|
|
21
|
+
} else {
|
|
22
|
+
assert(
|
|
23
|
+
`\`hds-clipboard\` modifier - \`text\` argument must be a string - provided: ${typeof text}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return textToCopy;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getTargetElement = (target) => {
|
|
31
|
+
let targetElement;
|
|
32
|
+
if (typeof target === 'string') {
|
|
33
|
+
targetElement = document.querySelector(target);
|
|
34
|
+
|
|
35
|
+
if (!targetElement) {
|
|
36
|
+
console.error(
|
|
37
|
+
'`hds-clipboard` modifier - `target` selector provided does not point to an existing DOM node, check your selector string',
|
|
38
|
+
targetElement
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
} else if (target instanceof Node && target.nodeType === Node.ELEMENT_NODE) {
|
|
43
|
+
targetElement = target;
|
|
44
|
+
} else {
|
|
45
|
+
if (target instanceof NodeList) {
|
|
46
|
+
assert(
|
|
47
|
+
'`hds-clipboard` modifier - `target` argument must be a string or a DOM node - provided: a list of DOM nodes'
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
assert(
|
|
51
|
+
`\`hds-clipboard\` modifier - \`target\` argument must be a string or a DOM node - provided: ${typeof target}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return targetElement;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const getTextToCopyFromTargetElement = (targetElement) => {
|
|
59
|
+
let textToCopy;
|
|
60
|
+
if (
|
|
61
|
+
targetElement instanceof Node &&
|
|
62
|
+
targetElement.nodeType === Node.ELEMENT_NODE
|
|
63
|
+
) {
|
|
64
|
+
if (
|
|
65
|
+
targetElement instanceof HTMLInputElement || // targetElement.nodeName === 'INPUT' ||
|
|
66
|
+
targetElement instanceof HTMLTextAreaElement || // targetElement.nodeName === 'TEXTAREA' ||
|
|
67
|
+
targetElement instanceof HTMLSelectElement // targetElement.nodeName === 'SELECT'
|
|
68
|
+
) {
|
|
69
|
+
textToCopy = targetElement.value;
|
|
70
|
+
} else {
|
|
71
|
+
// simplest approach
|
|
72
|
+
textToCopy = targetElement.innerText;
|
|
73
|
+
|
|
74
|
+
// approach based on text selection (left for backup just in case)
|
|
75
|
+
// var selection = window.getSelection();
|
|
76
|
+
// var range = document.createRange();
|
|
77
|
+
// selection.removeAllRanges();
|
|
78
|
+
// range.selectNodeContents(targetElement);
|
|
79
|
+
// selection.addRange(range);
|
|
80
|
+
// textToCopy = selection.toString();
|
|
81
|
+
// selection.removeAllRanges();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return textToCopy;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const writeTextToClipboard = async (textToCopy) => {
|
|
88
|
+
// finally copy the text to the clipboard using the Clipboard API
|
|
89
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
|
|
90
|
+
if (textToCopy) {
|
|
91
|
+
try {
|
|
92
|
+
// notice: the "clipboard-write" permission is granted automatically to pages when they are in the active tab
|
|
93
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
|
|
94
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
95
|
+
// DEBUG uncomment this for easy debugging
|
|
96
|
+
// console.log('success', textToCopy);
|
|
97
|
+
return true;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// clipboard write failed
|
|
100
|
+
// this probably never happens (see comment above) or happens only for very old browsers that don't for which `navigator.clipboard` is undefined
|
|
101
|
+
warn('copy action failed, please check your browser‘s permissions', {
|
|
102
|
+
id: 'hds-clipboard.write-text-to-clipboard.catch-error',
|
|
103
|
+
});
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const copyToClipboard = async (text, target) => {
|
|
112
|
+
let textToCopy;
|
|
113
|
+
|
|
114
|
+
if (text) {
|
|
115
|
+
textToCopy = getTextToCopy(text);
|
|
116
|
+
} else if (target) {
|
|
117
|
+
const targetElement = getTargetElement(target);
|
|
118
|
+
textToCopy = getTextToCopyFromTargetElement(targetElement);
|
|
119
|
+
} else {
|
|
120
|
+
assert(
|
|
121
|
+
'`hds-clipboard` modifier - either a `text` or a `target` argument is required'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const success = await writeTextToClipboard(textToCopy);
|
|
125
|
+
return success;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Notice: we use a function-based modifier here instead of a class-based one
|
|
129
|
+
// because it's quite simple in its logic, and doesn't require injecting services
|
|
130
|
+
// see: https://github.com/ember-modifier/ember-modifier#function-based-modifiers
|
|
131
|
+
|
|
132
|
+
export default modifier((element, positional, named) => {
|
|
133
|
+
assert(
|
|
134
|
+
'`hds-clipboard` modifier - the modifier must be applied to an element',
|
|
135
|
+
element
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const { text, target, onSuccess, onError } = named;
|
|
139
|
+
|
|
140
|
+
const onClick = async (event) => {
|
|
141
|
+
const trigger = event.currentTarget;
|
|
142
|
+
const success = await copyToClipboard(text, target);
|
|
143
|
+
|
|
144
|
+
// fire the `onSuccess/onError` callbacks (if provided)
|
|
145
|
+
if (success) {
|
|
146
|
+
if (typeof onSuccess === 'function') {
|
|
147
|
+
onSuccess({ trigger, text, target });
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
if (typeof onError === 'function') {
|
|
151
|
+
onError({ trigger, text, target });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// add the "onClick" event listener to the element
|
|
157
|
+
element.addEventListener('click', onClick);
|
|
158
|
+
|
|
159
|
+
// this (teardown) function is run when the element is removed
|
|
160
|
+
return () => {
|
|
161
|
+
element.removeEventListener('click', onClick);
|
|
162
|
+
};
|
|
163
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) HashiCorp, Inc.
|
|
3
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type HdsButtonIndexComponent from '@hashicorp/design-system-components/components/hds/button';
|
|
7
|
+
import type HdsInteractiveIndexComponent from '@hashicorp/design-system-components/components/hds/interactive';
|
|
8
|
+
|
|
9
|
+
export default interface HdsComponentsRegistry {
|
|
10
|
+
HdsButtonComponent: typeof HdsButtonIndexComponent;
|
|
11
|
+
HdsInteractiveComponent: typeof HdsInteractiveIndexComponent;
|
|
12
|
+
}
|
|
@@ -15,11 +15,8 @@
|
|
|
15
15
|
gap: 8px;
|
|
16
16
|
align-items: center;
|
|
17
17
|
justify-content: space-between;
|
|
18
|
-
width: 100%;
|
|
19
18
|
padding: 6px 4px;
|
|
20
|
-
white-space: normal;
|
|
21
19
|
text-align: left;
|
|
22
|
-
overflow-wrap: anywhere;
|
|
23
20
|
border: 1px solid transparent;
|
|
24
21
|
border-radius: 5px;
|
|
25
22
|
cursor: pointer;
|
|
@@ -27,11 +24,12 @@
|
|
|
27
24
|
|
|
28
25
|
.hds-copy-snippet--color-primary {
|
|
29
26
|
color: var(--token-color-foreground-action);
|
|
30
|
-
background-color:
|
|
27
|
+
background-color: transparent;
|
|
31
28
|
|
|
32
29
|
&:hover,
|
|
33
30
|
&.mock-hover {
|
|
34
31
|
color: var(--token-color-foreground-action-hover);
|
|
32
|
+
background-color: var(--token-color-surface-interactive);
|
|
35
33
|
border-color: var(--token-color-border-strong);
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -41,14 +39,19 @@
|
|
|
41
39
|
background-color: var(--token-color-surface-interactive-active);
|
|
42
40
|
border-color: var(--token-color-border-strong);
|
|
43
41
|
}
|
|
42
|
+
|
|
43
|
+
&:focus {
|
|
44
|
+
background-color: transparent;
|
|
45
|
+
}
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
.hds-copy-snippet--color-secondary {
|
|
47
49
|
color: var(--token-color-foreground-primary);
|
|
48
|
-
background-color:
|
|
50
|
+
background-color: transparent;
|
|
49
51
|
|
|
50
52
|
&:hover,
|
|
51
53
|
&.mock-hover {
|
|
54
|
+
background-color: var(--token-color-surface-interactive);
|
|
52
55
|
border-color: var(--token-color-border-strong);
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -76,12 +79,16 @@
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
.hds-copy-snippet--status-success {
|
|
82
|
+
background-color: var(--token-color-surface-interactive);
|
|
83
|
+
|
|
79
84
|
.hds-copy-snippet__icon {
|
|
80
85
|
color: var(--token-color-foreground-success);
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
.hds-copy-snippet--status-error {
|
|
90
|
+
background-color: var(--token-color-surface-interactive);
|
|
91
|
+
|
|
85
92
|
.hds-copy-snippet__icon {
|
|
86
93
|
color: var(--token-color-foreground-critical);
|
|
87
94
|
}
|
|
@@ -89,7 +96,6 @@
|
|
|
89
96
|
|
|
90
97
|
.hds-copy-snippet__text {
|
|
91
98
|
flex: 1 0 0;
|
|
92
|
-
max-width: calc(100% - 24px);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
.hds-copy-snippet__icon {
|
|
@@ -97,15 +103,17 @@
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
.hds-copy-snippet--width-full {
|
|
100
|
-
|
|
106
|
+
width: 100%;
|
|
107
|
+
max-width: 100%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.hds-copy-snippet--is-truncated {
|
|
111
|
+
width: 100%;
|
|
112
|
+
max-width: 100%;
|
|
101
113
|
|
|
102
114
|
.hds-copy-snippet__text {
|
|
103
|
-
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
text-overflow: ellipsis;
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
|
-
|
|
107
|
-
.hds-copy-snippet__text--truncated {
|
|
108
|
-
overflow: hidden;
|
|
109
|
-
white-space: nowrap;
|
|
110
|
-
text-overflow: ellipsis;
|
|
111
|
-
}
|