@brightspace-ui/labs 2.7.0 → 2.9.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 (44) hide show
  1. package/package.json +21 -1
  2. package/src/components/card-overlay/README.md +12 -0
  3. package/src/components/card-overlay/card-overlay.js +79 -0
  4. package/src/components/navigation/README.md +192 -0
  5. package/src/components/navigation/navigation-band.js +134 -0
  6. package/src/components/navigation/navigation-button-icon.js +120 -0
  7. package/src/components/navigation/navigation-dropdown-button-custom.js +35 -0
  8. package/src/components/navigation/navigation-dropdown-button-icon.js +86 -0
  9. package/src/components/navigation/navigation-immersive.js +265 -0
  10. package/src/components/navigation/navigation-iterator.js +97 -0
  11. package/src/components/navigation/navigation-link-back.js +39 -0
  12. package/src/components/navigation/navigation-link-icon.js +127 -0
  13. package/src/components/navigation/navigation-link-image.js +73 -0
  14. package/src/components/navigation/navigation-link.js +62 -0
  15. package/src/components/navigation/navigation-main-footer.js +27 -0
  16. package/src/components/navigation/navigation-main-header.js +67 -0
  17. package/src/components/navigation/navigation-notification-icon.js +54 -0
  18. package/src/components/navigation/navigation-separator.js +30 -0
  19. package/src/components/navigation/navigation-shared-styles.js +32 -0
  20. package/src/components/navigation/navigation-skip-main.js +31 -0
  21. package/src/components/navigation/navigation-skip.js +59 -0
  22. package/src/components/navigation/navigation-styles.js +107 -0
  23. package/src/components/navigation/navigation.js +60 -0
  24. package/src/lang/ar.js +4 -0
  25. package/src/lang/cy.js +4 -0
  26. package/src/lang/da.js +4 -0
  27. package/src/lang/de.js +4 -0
  28. package/src/lang/en-gb.js +4 -0
  29. package/src/lang/en.js +4 -0
  30. package/src/lang/es-es.js +4 -0
  31. package/src/lang/es.js +4 -0
  32. package/src/lang/fr-fr.js +4 -0
  33. package/src/lang/fr-on.js +4 -0
  34. package/src/lang/fr.js +4 -0
  35. package/src/lang/haw.js +4 -0
  36. package/src/lang/hi.js +4 -0
  37. package/src/lang/ja.js +4 -0
  38. package/src/lang/ko.js +4 -0
  39. package/src/lang/nl.js +4 -0
  40. package/src/lang/pt.js +4 -0
  41. package/src/lang/sv.js +4 -0
  42. package/src/lang/tr.js +4 -0
  43. package/src/lang/zh-cn.js +4 -0
  44. package/src/lang/zh-tw.js +4 -0
@@ -0,0 +1,86 @@
1
+ import '@brightspace-ui/core/components/icons/icon.js';
2
+ import '@brightspace-ui/core/components/tooltip/tooltip.js';
3
+ import './navigation-notification-icon.js';
4
+ import { css, html, LitElement, nothing } from 'lit';
5
+ import { highlightBorderStyles, highlightButtonStyles } from './navigation-styles.js';
6
+ import { DropdownOpenerMixin } from '@brightspace-ui/core/components/dropdown/dropdown-opener-mixin.js';
7
+ import { getUniqueId } from '@brightspace-ui/core/helpers/uniqueId.js';
8
+ import { ifDefined } from 'lit/directives/if-defined.js';
9
+ import { offscreenStyles } from '@brightspace-ui/core/components/offscreen/offscreen.js';
10
+
11
+ class NavigationDropdownButtonIcon extends DropdownOpenerMixin(LitElement) {
12
+
13
+ static get properties() {
14
+ return {
15
+ icon: { type: String },
16
+ hasNotification: { attribute: 'has-notification', reflect: true, type: Boolean },
17
+ text: { type: String },
18
+ notificationText: { attribute: 'notification-text', type: String },
19
+ tooltipOffset: { attribute: 'tooltip-offset', type: Number }
20
+ };
21
+ }
22
+
23
+ static get styles() {
24
+ return [highlightBorderStyles, highlightButtonStyles, offscreenStyles, css`
25
+ :host {
26
+ display: inline-block;
27
+ height: 100%;
28
+ position: relative;
29
+ }
30
+ :host([hidden]) {
31
+ display: none;
32
+ }
33
+ .icon-container {
34
+ display: inline-block;
35
+ position: relative;
36
+ }
37
+ `];
38
+ }
39
+
40
+ constructor() {
41
+ super();
42
+ this.hasNotification = false;
43
+ this._buttonId = getUniqueId();
44
+ this._describedById = getUniqueId();
45
+ }
46
+
47
+ render() {
48
+ const { ariaDescribedBy, ariaDescription, contents } = this._getRenderSettings();
49
+ const highlightBorder = !this.disabled ? html`<span class="d2l-labs-navigation-highlight-border"></span>` : nothing;
50
+ const tooltip = !this.dropdownOpened ? html`<d2l-tooltip close-on-click for="${this._buttonId}" for-type="label" position="bottom" offset="${ifDefined(this.tooltipOffset)}" class="vdiff-target">${this.text}</d2l-tooltip>` : nothing;
51
+ return html`
52
+ <button
53
+ aria-describedby="${ifDefined(ariaDescribedBy)}"
54
+ aria-label="${this.text}"
55
+ ?disabled="${this.disabled}"
56
+ id="${this._buttonId}"
57
+ type="button">${highlightBorder}${contents}</button>
58
+ ${ariaDescription}
59
+ ${tooltip}
60
+ <slot></slot>
61
+ `;
62
+ }
63
+
64
+ getOpenerElement() {
65
+ return this.shadowRoot?.querySelector('button');
66
+ }
67
+
68
+ _getRenderSettings() {
69
+ const icon = html`<d2l-icon icon="${this.icon}"></d2l-icon>`;
70
+ if (this.hasNotification) {
71
+ return {
72
+ ariaDescribedBy: this._describedById,
73
+ ariaDescription: html`<span class="d2l-offscreen" id="${this._describedById}">${this.notificationText}</span>`,
74
+ contents: html`<span class="icon-container">${icon}<d2l-labs-navigation-notification-icon></d2l-labs-navigation-notification-icon></span>`
75
+ };
76
+ }
77
+ return {
78
+ ariaDescribedBy: undefined,
79
+ ariaDescription: nothing,
80
+ contents: icon
81
+ };
82
+ }
83
+
84
+ }
85
+
86
+ customElements.define('d2l-labs-navigation-dropdown-button-icon', NavigationDropdownButtonIcon);
@@ -0,0 +1,265 @@
1
+ import '@brightspace-ui/core/components/colors/colors.js';
2
+ import './navigation.js';
3
+ import './navigation-link-back.js';
4
+ import { css, html, LitElement } from 'lit';
5
+ import { bodyCompactStyles } from '@brightspace-ui/core/components/typography/styles.js';
6
+ import { classMap } from 'lit/directives/class-map.js';
7
+ import { navigationSharedStyle } from './navigation-shared-styles.js';
8
+ import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
9
+
10
+ const mediaQueryList = window.matchMedia('(max-width: 615px)');
11
+
12
+ class NavigationImmersive extends LitElement {
13
+
14
+ static get properties() {
15
+ return {
16
+ allowOverflow: {
17
+ attribute: 'allow-overflow',
18
+ type: Boolean,
19
+ reflect: true
20
+ },
21
+ backLinkHref: {
22
+ attribute: 'back-link-href',
23
+ type: String
24
+ },
25
+ backLinkText: {
26
+ attribute: 'back-link-text',
27
+ type: String
28
+ },
29
+ backLinkTextShort: {
30
+ attribute: 'back-link-text-short',
31
+ type: String
32
+ },
33
+ widthType: {
34
+ attribute: 'width-type',
35
+ type: String,
36
+ reflect: true
37
+ },
38
+ _middleHidden: { state: true },
39
+ _middleNoRightBorder: { state: true },
40
+ _smallWidth: { state: true }
41
+ };
42
+ }
43
+
44
+ static get styles() {
45
+ return [bodyCompactStyles, navigationSharedStyle, css`
46
+ :host {
47
+ --d2l-labs-navigation-immersive-height-main: 3.1rem;
48
+ --d2l-labs-navigation-immersive-height-responsive: 2.8rem;
49
+ }
50
+ .d2l-navigiation-immersive-fixed {
51
+ background-color: white;
52
+ left: 0;
53
+ position: fixed;
54
+ right: 0;
55
+ top: 0;
56
+ z-index: 2; /* higher than skeletons which could scroll behind immersive nav */
57
+ }
58
+ d2l-labs-navigation {
59
+ border-bottom: 1px solid var(--d2l-color-mica);
60
+ }
61
+ .d2l-labs-navigation-immersive-margin {
62
+ display: flex;
63
+ justify-content: center;
64
+ margin: 0 30px;
65
+ }
66
+
67
+ .d2l-labs-navigation-immersive-container {
68
+ display: flex;
69
+ height: var(--d2l-labs-navigation-immersive-height-main);
70
+ justify-content: space-between;
71
+ margin: 0 -7px;
72
+ max-width: 100%;
73
+ overflow: hidden;
74
+ width: 100%;
75
+ }
76
+
77
+ :host([width-type="normal"]) .d2l-labs-navigation-immersive-container {
78
+ max-width: 1230px;
79
+ }
80
+
81
+ :host([allow-overflow]) .d2l-labs-navigation-immersive-container {
82
+ overflow: visible;
83
+ }
84
+
85
+ .d2l-labs-navigation-immersive-left ::slotted(*),
86
+ .d2l-labs-navigation-immersive-middle ::slotted(*),
87
+ .d2l-labs-navigation-immersive-right ::slotted(*) {
88
+ height: var(--d2l-labs-navigation-immersive-height-main);
89
+ }
90
+
91
+ .d2l-labs-navigation-immersive-left {
92
+ color: var(--d2l-color-tungsten);
93
+ letter-spacing: 0.2px;
94
+ padding-left: 7px;
95
+ }
96
+
97
+ .d2l-labs-navigation-immersive-right {
98
+ padding-right: 7px;
99
+ }
100
+
101
+ .d2l-labs-navigation-immersive-left,
102
+ .d2l-labs-navigation-immersive-right {
103
+ flex: 0 0 auto;
104
+ }
105
+
106
+ .d2l-labs-navigation-immersive-middle {
107
+ border-inline-end: 1px solid var(--d2l-color-gypsum);
108
+ border-inline-start: 1px solid var(--d2l-color-gypsum);
109
+ flex: 0 1 auto;
110
+ margin: 0 24px;
111
+ min-width: 0;
112
+ padding: 0 24px;
113
+ width: 100%;
114
+ }
115
+
116
+ .d2l-labs-navigation-immersive-middle.d2l-labs-navigation-immersive-middle-no-right-border {
117
+ border-inline-end: none;
118
+ }
119
+
120
+ div.d2l-labs-navigation-immersive-middle-observer,
121
+ div.d2l-labs-navigation-immersive-right-observer {
122
+ height: auto;
123
+ }
124
+
125
+ .d2l-labs-navigation-immersive-middle-hidden {
126
+ visibility: hidden;
127
+ }
128
+
129
+ .d2l-labs-navigation-immersive-spacing {
130
+ height: calc(var(--d2l-labs-navigation-immersive-height-main) + 5px);
131
+ position: unset;
132
+ }
133
+
134
+ @media (max-width: 929px) {
135
+ .d2l-labs-navigation-immersive-margin {
136
+ margin: 0 24px;
137
+ }
138
+ }
139
+
140
+ @media (max-width: 767px) {
141
+ .d2l-labs-navigation-immersive-margin {
142
+ margin: 0 18px;
143
+ }
144
+ }
145
+
146
+ @media (max-width: 615px) {
147
+ .d2l-labs-navigation-immersive-container {
148
+ height: var(--d2l-labs-navigation-immersive-height-responsive);
149
+ }
150
+ .d2l-labs-navigation-immersive-left ::slotted(*),
151
+ .d2l-labs-navigation-immersive-middle ::slotted(*),
152
+ .d2l-labs-navigation-immersive-right ::slotted(*) {
153
+ height: var(--d2l-labs-navigation-immersive-height-responsive);
154
+ }
155
+ .d2l-labs-navigation-immersive-spacing {
156
+ height: calc(var(--d2l-labs-navigation-immersive-height-responsive) + 5px);
157
+ }
158
+ .d2l-labs-navigation-immersive-middle {
159
+ margin: 0 18px;
160
+ padding: 0 18px;
161
+ }
162
+ }
163
+ `];
164
+ }
165
+
166
+ constructor() {
167
+ super();
168
+ this._handlePageResize = this._handlePageResize.bind(this);
169
+ this._middleHidden = false;
170
+ this._middleNoRightBorder = true;
171
+ this._middleObserver = new ResizeObserver(this._onMiddleResize.bind(this));
172
+ this._rightObserver = new ResizeObserver(this._onRightResize.bind(this));
173
+ this._smallWidth = false;
174
+ }
175
+
176
+ connectedCallback() {
177
+ super.connectedCallback();
178
+ this._startObserving();
179
+ if (mediaQueryList.addEventListener) mediaQueryList.addEventListener('change', this._handlePageResize);
180
+ }
181
+
182
+ disconnectedCallback() {
183
+ super.disconnectedCallback();
184
+ if (this._middleObserver) this._middleObserver.disconnect();
185
+ if (this._rightObserver) this._rightObserver.disconnect();
186
+ if (mediaQueryList.removeEventListener) mediaQueryList.removeEventListener('change', this._handlePageResize);
187
+ }
188
+
189
+ firstUpdated(changedProperties) {
190
+ super.firstUpdated(changedProperties);
191
+ this._startObserving();
192
+ this._smallWidth = mediaQueryList.matches;
193
+ }
194
+
195
+ render() {
196
+ const middleContainerClasses = {
197
+ 'd2l-labs-navigation-immersive-middle': true,
198
+ 'd2l-labs-navigation-immersive-middle-hidden': this._middleHidden,
199
+ 'd2l-labs-navigation-immersive-middle-no-right-border': this._middleNoRightBorder
200
+ };
201
+ const backLinkText = this._smallWidth ? (this.backLinkTextShort || this.backLinkText) : this.backLinkText;
202
+ return html`
203
+ <div class="d2l-navigiation-immersive-fixed">
204
+ <d2l-labs-navigation>
205
+ <div class="d2l-labs-navigation-immersive-margin">
206
+ <div class="d2l-labs-navigation-immersive-container">
207
+ <div class="d2l-labs-navigation-immersive-left d2l-body-compact">
208
+ <slot name="left">
209
+ <d2l-labs-navigation-link-back text="${backLinkText}" href="${this.backLinkHref}" @click="${this._handleBackClick}"></d2l-labs-navigation-link-back>
210
+ </slot>
211
+ </div>
212
+ <div class="${classMap(middleContainerClasses)}">
213
+ <div class="d2l-labs-navigation-immersive-middle-observer">
214
+ <slot name="middle"></slot>
215
+ </div>
216
+ </div>
217
+ <div class="d2l-labs-navigation-immersive-right"><div class="d2l-labs-navigation-immersive-right-observer"><slot name="right"></slot></div></div>
218
+ </div>
219
+ </div>
220
+ </d2l-labs-navigation>
221
+ </div>
222
+ <div class="d2l-labs-navigation-immersive-spacing"></div>
223
+ `;
224
+ }
225
+
226
+ _handleBackClick() {
227
+ this.dispatchEvent(
228
+ new CustomEvent(
229
+ 'd2l-labs-navigation-immersive-back-click',
230
+ { bubbles: false, composed: false }
231
+ )
232
+ );
233
+ }
234
+
235
+ _handlePageResize(e) {
236
+ this._smallWidth = e.matches;
237
+ }
238
+
239
+ _onMiddleResize(entries) {
240
+ if (!entries || entries.length === 0) {
241
+ return;
242
+ }
243
+ this._middleHidden = (entries[0].contentRect.height < 1);
244
+ }
245
+
246
+ _onRightResize(entries) {
247
+ if (!entries || entries.length === 0) {
248
+ return;
249
+ }
250
+ this._middleNoRightBorder = (entries[0].contentRect.height < 1);
251
+ }
252
+
253
+ _startObserving() {
254
+ const middle = this.shadowRoot?.querySelector('.d2l-labs-navigation-immersive-middle-observer');
255
+ if (middle) {
256
+ this._middleObserver.observe(middle);
257
+ }
258
+ const right = this.shadowRoot?.querySelector('.d2l-labs-navigation-immersive-right-observer');
259
+ if (right) {
260
+ this._rightObserver.observe(right);
261
+ }
262
+ }
263
+
264
+ }
265
+ customElements.define('d2l-labs-navigation-immersive', NavigationImmersive);
@@ -0,0 +1,97 @@
1
+ import './navigation-button-icon.js';
2
+ import { css, html, LitElement } from 'lit';
3
+ import { bodyCompactStyles } from '@brightspace-ui/core/components/typography/styles.js';
4
+ import { LocalizeLabsElement } from '../localize-labs-element.js';
5
+
6
+ class NavigationIterator extends LocalizeLabsElement(LitElement) {
7
+
8
+ static get properties() {
9
+ return {
10
+ hideText: { attribute: 'hide-text', type: Boolean },
11
+ previousDisabled: { attribute: 'previous-disabled', type: Boolean },
12
+ previousText: { attribute: 'previous-text', type: String },
13
+ nextDisabled: { attribute: 'next-disabled', type: Boolean },
14
+ nextText: { attribute: 'next-text', type: String },
15
+ };
16
+ }
17
+
18
+ static get styles() {
19
+ return [bodyCompactStyles, css`
20
+ :host {
21
+ align-items: center;
22
+ display: flex;
23
+ gap: 1.2rem;
24
+ height: 3.3rem;
25
+ justify-content: space-between;
26
+ max-width: 20rem;
27
+ }
28
+ :host([hidden]) {
29
+ display: none;
30
+ }
31
+ `];
32
+ }
33
+
34
+ constructor() {
35
+ super();
36
+ this.hideText = false;
37
+ this.nextDisabled = false;
38
+ this.nextText = '';
39
+ this.previousDisabled = false;
40
+ this.previousText = '';
41
+ }
42
+
43
+ render() {
44
+ const previousText = this.previousText ? this.previousText : this.localize('components:navigation:previous');
45
+ const nextText = this.nextText ? this.nextText : this.localize('components:navigation:next');
46
+ return html`
47
+ <d2l-labs-navigation-button-icon
48
+ class="d2l-body-compact"
49
+ icon="tier3:chevron-left-circle"
50
+ icon-position="start"
51
+ text="${previousText}"
52
+ ?text-hidden="${this.hideText}"
53
+ ?disabled="${this.previousDisabled}"
54
+ @click="${this._dispatchPreviousClicked}"></d2l-labs-navigation-button-icon>
55
+ <slot class="d2l-body-compact"></slot>
56
+ <d2l-labs-navigation-button-icon
57
+ class="d2l-body-compact"
58
+ icon="tier3:chevron-right-circle"
59
+ icon-position="end"
60
+ text="${nextText}"
61
+ ?text-hidden="${this.hideText}"
62
+ ?disabled="${this.nextDisabled}"
63
+ @click="${this._dispatchNextClicked}"></d2l-labs-navigation-button-icon>
64
+ `;
65
+ }
66
+
67
+ _dispatchNextClicked(e) {
68
+ if (this.nextDisabled) {
69
+ return;
70
+ }
71
+
72
+ e.stopPropagation();
73
+
74
+ this.dispatchEvent(new CustomEvent('next-click', {
75
+ detail: {
76
+ type: e.currentTarget.type
77
+ }
78
+ }));
79
+ }
80
+
81
+ _dispatchPreviousClicked(e) {
82
+ if (this.previousDisabled) {
83
+ return;
84
+ }
85
+
86
+ e.stopPropagation();
87
+
88
+ this.dispatchEvent(new CustomEvent('previous-click', {
89
+ detail: {
90
+ type: e.currentTarget.type
91
+ }
92
+ }));
93
+ }
94
+
95
+ }
96
+
97
+ window.customElements.define('d2l-labs-navigation-iterator', NavigationIterator);
@@ -0,0 +1,39 @@
1
+ import './navigation-link-icon.js';
2
+ import { css, html, LitElement } from 'lit';
3
+ import { FocusMixin } from '@brightspace-ui/core/mixins/focus-mixin.js';
4
+ import { LocalizeLabsElement } from '../localize-labs-element.js';
5
+
6
+ class NavigationLinkBack extends LocalizeLabsElement(FocusMixin(LitElement)) {
7
+
8
+ static get properties() {
9
+ return {
10
+ text: { type: String },
11
+ href: { type: String }
12
+ };
13
+ }
14
+
15
+ static get styles() {
16
+ return css`
17
+ :host {
18
+ display: inline-block;
19
+ height: 100%;
20
+ }
21
+ :host([hidden]) {
22
+ display: none;
23
+ }
24
+ `;
25
+ }
26
+
27
+ static get focusElementSelector() {
28
+ return 'd2l-labs-navigation-link-icon';
29
+ }
30
+
31
+ render() {
32
+ const href = this.href ? this.href : 'javascript:void(0);'; // backwards-compatible for uses before missing "href" threw exception
33
+ const text = this.text ? this.text : this.localize('components:navigation:back');
34
+ return html`<d2l-labs-navigation-link-icon href="${href}" icon="tier1:chevron-left" text="${text}"></d2l-labs-navigation-link-icon>`;
35
+ }
36
+
37
+ }
38
+
39
+ customElements.define('d2l-labs-navigation-link-back', NavigationLinkBack);
@@ -0,0 +1,127 @@
1
+ import '@brightspace-ui/core/components/icons/icon.js';
2
+ import '@brightspace-ui/core/components/tooltip/tooltip.js';
3
+ import { css, html, LitElement, nothing } from 'lit';
4
+ import { highlightBorderStyles, highlightLinkStyles } from './navigation-styles.js';
5
+ import { FocusMixin } from '@brightspace-ui/core/mixins/focus-mixin.js';
6
+ import { getUniqueId } from '@brightspace-ui/core/helpers/uniqueId.js';
7
+ import { ifDefined } from 'lit/directives/if-defined.js';
8
+ import { offscreenStyles } from '@brightspace-ui/core/components/offscreen/offscreen.js';
9
+
10
+ /**
11
+ * Navigation link with an icon and text.
12
+ */
13
+ class NavigationLinkIcon extends FocusMixin(LitElement) {
14
+
15
+ static get properties() {
16
+ return {
17
+ /**
18
+ * REQUIRED: URL or URL fragment of the link
19
+ * @type {string}
20
+ */
21
+ href: { type: String },
22
+ /**
23
+ * REQUIRED: Preset icon key (e.g. "tier1:gear")
24
+ * @type {string}
25
+ */
26
+ icon: { type: String },
27
+ /**
28
+ * REQUIRED: Text for the link
29
+ * @type {string}
30
+ */
31
+ text: { type: String },
32
+ /**
33
+ * Visually hides the text but still accessible
34
+ * @type {boolean}
35
+ */
36
+ textHidden: { attribute: 'text-hidden', type: Boolean },
37
+ /**
38
+ * Offset of the tooltip
39
+ * @type {Number}
40
+ */
41
+ tooltipOffset: { attribute: 'tooltip-offset', type: Number }
42
+ };
43
+ }
44
+
45
+ static get styles() {
46
+ return [highlightBorderStyles, highlightLinkStyles, offscreenStyles, css`
47
+ :host {
48
+ display: inline-block;
49
+ height: 100%;
50
+ }
51
+ :host([hidden]) {
52
+ display: none;
53
+ }
54
+ `];
55
+ }
56
+
57
+ constructor() {
58
+ super();
59
+ this.textHidden = false;
60
+ this._linkId = getUniqueId();
61
+ this._missingHrefErrorHasBeenThrown = false;
62
+ this._validatingHrefTimeout = null;
63
+ }
64
+
65
+ static get focusElementSelector() {
66
+ return 'a';
67
+ }
68
+
69
+ firstUpdated(changedProperties) {
70
+ super.firstUpdated(changedProperties);
71
+ this._validateHref();
72
+ }
73
+
74
+ render() {
75
+ const { ariaLabel, id, text, tooltip } = this._getRenderSettings();
76
+ return html`
77
+ <a id="${ifDefined(id)}" href="${ifDefined(this.href)}" aria-label="${ifDefined(ariaLabel)}">
78
+ <span class="d2l-labs-navigation-highlight-border"></span>
79
+ <d2l-icon icon="${this.icon}"></d2l-icon>
80
+ ${text}
81
+ </a>
82
+ ${tooltip}
83
+ `;
84
+ }
85
+
86
+ updated(changedProperties) {
87
+
88
+ super.updated(changedProperties);
89
+
90
+ if (changedProperties.has('href')) this._validateHref();
91
+
92
+ }
93
+
94
+ _getRenderSettings() {
95
+ if (this.textHidden) {
96
+ return {
97
+ ariaLabel: this.text,
98
+ id: this._linkId,
99
+ text: nothing,
100
+ tooltip: html`<d2l-tooltip for="${this._linkId}" for-type="label" position="bottom" offset="${ifDefined(this.tooltipOffset)}" class="vdiff-target">${this.text}</d2l-tooltip>`
101
+ };
102
+ }
103
+ return {
104
+ ariaLabel: undefined,
105
+ id: undefined,
106
+ text: this.text,
107
+ tooltip: nothing
108
+ };
109
+ }
110
+
111
+ _validateHref() {
112
+ clearTimeout(this._validatingHrefTimeout);
113
+ // don't error immediately in case it doesn't get set immediately
114
+ this._validatingHrefTimeout = setTimeout(() => {
115
+ this._validatingHrefTimeout = null;
116
+ const hasHref = (typeof this.href === 'string') && this.href.length > 0;
117
+ if (!hasHref && !this._missingHrefErrorHasBeenThrown) {
118
+ this._missingHrefErrorHasBeenThrown = true;
119
+ // we don't want to prevent rendering
120
+ setTimeout(() => { throw new Error('<d2l-labs-navigation-link-icon>: missing required "href" attribute. If this component performs an action and not a navigation, consider using <d2l-labs-navigation-button-icon> instead.'); });
121
+ }
122
+ }, 3000);
123
+ }
124
+
125
+ }
126
+
127
+ customElements.define('d2l-labs-navigation-link-icon', NavigationLinkIcon);
@@ -0,0 +1,73 @@
1
+ import '@brightspace-ui/core/components/tooltip/tooltip.js';
2
+ import { css, html, LitElement, nothing } from 'lit';
3
+ import { highlightBorderStyles, highlightLinkStyles } from './navigation-styles.js';
4
+ import { FocusMixin } from '@brightspace-ui/core/mixins/focus-mixin.js';
5
+ import { getUniqueId } from '@brightspace-ui/core/helpers/uniqueId.js';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+
8
+ class NavigationLinkImage extends FocusMixin(LitElement) {
9
+
10
+ static get properties() {
11
+ return {
12
+ href: { type: String },
13
+ slim: { reflect: true, type: Boolean },
14
+ src: { type: String },
15
+ text: { type: String },
16
+ tooltipOffset: { attribute: 'tooltip-offset', type: Number }
17
+ };
18
+ }
19
+
20
+ static get styles() {
21
+ return [highlightBorderStyles, highlightLinkStyles, css`
22
+ :host {
23
+ display: inline-block;
24
+ height: 100%;
25
+ }
26
+ :host([hidden]) {
27
+ display: none;
28
+ }
29
+ img {
30
+ max-height: 60px;
31
+ max-width: 260px;
32
+ vertical-align: middle;
33
+ }
34
+ :host([slim]) img {
35
+ max-height: 40px;
36
+ max-width: 173px;
37
+ }
38
+ .d2l-labs-navigation-link-image-container {
39
+ align-items: center;
40
+ display: inline-flex;
41
+ height: 100%;
42
+ vertical-align: middle;
43
+ }
44
+ `];
45
+ }
46
+
47
+ constructor() {
48
+ super();
49
+ this.slim = false;
50
+ this.text = '';
51
+ this._linkId = getUniqueId();
52
+ }
53
+
54
+ static get focusElementSelector() {
55
+ return 'a';
56
+ }
57
+
58
+ render() {
59
+ const image = html`<img src="${this.src}" alt="${this.text}">`;
60
+ if (this.href) {
61
+ return html`
62
+ <a href="${this.href}" id="${this._linkId}">
63
+ <span class="d2l-labs-navigation-highlight-border"></span>
64
+ <span class="d2l-labs-navigation-link-image-container">${image}</span>
65
+ </a>
66
+ ${this.text ? html`<d2l-tooltip for="${this._linkId}" for-type="label" position="bottom" offset="${ifDefined(this.tooltipOffset)}" class="vdiff-target">${this.text}</d2l-tooltip>` : nothing}
67
+ `;
68
+ }
69
+ return html`<span class="d2l-labs-navigation-link-image-container">${image}</span>`;
70
+ }
71
+ }
72
+
73
+ customElements.define('d2l-labs-navigation-link-image', NavigationLinkImage);