@salesforcedevs/dx-components 0.16.1 → 0.18.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 (62) hide show
  1. package/lwc.config.json +2 -1
  2. package/package.json +3 -3
  3. package/src/assets/svg/sidebar-loading.svg +34 -0
  4. package/src/modules/dx/banner/__tests__/banner.test.ts +31 -0
  5. package/src/modules/dx/{feedbackBanner/feedbackBanner.css → banner/banner.css} +6 -6
  6. package/src/modules/dx/banner/banner.html +13 -0
  7. package/src/modules/dx/banner/banner.ts +14 -0
  8. package/src/modules/dx/dropdown/dropdown.html +2 -0
  9. package/src/modules/dx/dropdown/dropdown.ts +4 -2
  10. package/src/modules/dx/dropdownOption/__tests__/dropdownOption.test.ts +32 -2
  11. package/src/modules/dx/dropdownOption/dropdownOption.ts +9 -6
  12. package/src/modules/dx/footer/footer.ts +13 -1
  13. package/src/modules/dx/header/__tests__/header.test.ts +38 -21
  14. package/src/modules/dx/header/__tests__/mockProps.ts +29 -37
  15. package/src/modules/dx/header/header.html +4 -1
  16. package/src/modules/dx/header/header.stories.ts +30 -0
  17. package/src/modules/dx/header/header.ts +4 -1
  18. package/src/modules/dx/headerNav/headerNav.html +1 -0
  19. package/src/modules/dx/headerSearch/__tests__/headerSearch.test.ts +24 -39
  20. package/src/modules/dx/headerSearch/__tests__/mockProps.ts +2 -7
  21. package/src/modules/dx/headerSearch/headerSearch.ts +13 -19
  22. package/src/modules/dx/lazy/lazy.ts +0 -7
  23. package/src/modules/dx/newsletterForm/newsletterForm.ts +20 -11
  24. package/src/modules/dx/sidebar/__tests__/mockSearchChangeEmptyEvent.ts +6 -0
  25. package/src/modules/dx/sidebar/__tests__/mockSearchChangeEvent.ts +527 -0
  26. package/src/modules/dx/sidebar/__tests__/sidebar.test.ts +210 -247
  27. package/src/modules/dx/sidebar/sidebar.css +40 -17
  28. package/src/modules/dx/sidebar/sidebar.html +58 -34
  29. package/src/modules/dx/sidebar/sidebar.stories.ts +54 -4
  30. package/src/modules/dx/sidebar/sidebar.ts +141 -73
  31. package/src/modules/dx/sidebarOld/__tests__/mockProps.ts +178 -0
  32. package/src/modules/dx/sidebarOld/__tests__/sidebarOld.test.ts +574 -0
  33. package/src/modules/dx/sidebarOld/sidebarOld.css +158 -0
  34. package/src/modules/dx/sidebarOld/sidebarOld.html +70 -0
  35. package/src/modules/dx/sidebarOld/sidebarOld.stories.ts +139 -0
  36. package/src/modules/dx/sidebarOld/sidebarOld.ts +187 -0
  37. package/src/modules/dx/sidebarSearch/__tests__/mockPreloadedState.ts +8843 -0
  38. package/src/modules/dx/sidebarSearch/__tests__/sidebarSearch.test.ts +113 -0
  39. package/src/modules/dx/sidebarSearch/sidebarSearch.css +3 -0
  40. package/src/modules/dx/sidebarSearch/sidebarSearch.html +26 -0
  41. package/src/modules/dx/sidebarSearch/sidebarSearch.ts +421 -0
  42. package/src/modules/dx/sidebarSearchResult/__tests__/sidebarSearchResult.test.ts +55 -0
  43. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.css +36 -0
  44. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.html +14 -0
  45. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.ts +56 -0
  46. package/src/modules/dx/treeTile/__tests__/treeTile.test.ts +27 -38
  47. package/src/modules/dx/treeTile/treeTile.css +3 -19
  48. package/src/modules/dx/treeTile/treeTile.html +4 -1
  49. package/src/modules/dx/treeTile/treeTile.ts +2 -2
  50. package/src/modules/helpers/sidebar/sidebar.css +21 -0
  51. package/src/modules/utils/coveo/coveo.ts +69 -0
  52. package/src/modules/utils/headerBase/headerBase.ts +13 -2
  53. package/src/modules/utils/highlight/__tests__/highlight.test.ts +108 -0
  54. package/src/modules/utils/highlight/__tests__/mockProps.ts +45 -0
  55. package/src/modules/utils/highlight/highlight.ts +2 -1
  56. package/src/modules/utils/jest/jest.ts +35 -0
  57. package/src/modules/utils/recentSearches/__tests__/recentSearches.test.ts +79 -0
  58. package/src/modules/utils/recentSearches/recentSearches.ts +57 -0
  59. package/src/modules/utils/tests/tests.ts +18 -11
  60. package/src/modules/dx/feedbackBanner/__tests__/feedbackBanner.test.ts +0 -19
  61. package/src/modules/dx/feedbackBanner/feedbackBanner.html +0 -13
  62. package/src/modules/dx/feedbackBanner/feedbackBanner.ts +0 -3
@@ -0,0 +1,158 @@
1
+ @import "helpers/reset";
2
+ @import "helpers/text";
3
+
4
+ :host {
5
+ --dx-c-sidebar-width: 275px;
6
+ --dx-c-sidebar-button-color: var(--dx-g-blue-vibrant-50);
7
+ --dx-c-sidebar-button-background: white;
8
+ --dx-c-sidebar-button-border: white;
9
+ --dx-c-sidebar-button-color-hover: white;
10
+ --dx-c-sidebar-button-background-hover: var(--dx-g-blue-vibrant-50);
11
+ --dx-c-sidebar-button-border-hover: var(--dx-g-blue-vibrant-50);
12
+ --dx-c-sidebar-left-padding: var(--dx-g-page-padding-horizontal);
13
+ --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
14
+ }
15
+
16
+ dx-tree:not(:last-child) {
17
+ margin-bottom: var(--dx-g-spacing-sm);
18
+ }
19
+
20
+ .header {
21
+ box-sizing: content-box;
22
+ display: flex;
23
+ line-height: var(--dx-g-spacing-xl);
24
+ margin-bottom: 12px;
25
+ }
26
+
27
+ .padding-horizontal {
28
+ padding-left: var(--dx-g-spacing-md);
29
+ padding-right: var(--dx-g-spacing-md);
30
+ }
31
+
32
+ .header-title {
33
+ line-height: inherit;
34
+ }
35
+
36
+ .header-toggle-button {
37
+ position: absolute;
38
+ right: calc((-1 * (var(--dx-g-spacing-xl) / 2)) - 1px);
39
+
40
+ --dx-c-button-opacity: 0;
41
+ --dx-c-button-custom-color: var(--dx-c-sidebar-button-color);
42
+ --dx-c-button-custom-background: var(--dx-c-sidebar-button-background);
43
+ --dx-c-button-custom-border: var(--dx-c-sidebar-button-border);
44
+ --dx-c-button-custom-color-hover: var(--dx-c-sidebar-button-color-hover);
45
+ --dx-c-button-custom-background-hover: var(
46
+ --dx-c-sidebar-button-background-hover
47
+ );
48
+ --dx-c-button-custom-border-hover: var(--dx-c-sidebar-button-border-hover);
49
+ }
50
+
51
+ .header-toggle-icon {
52
+ margin: auto 0 auto auto;
53
+ color: var(--dx-g-blue-vibrant-30);
54
+ }
55
+
56
+ .header-toggle-icon dx-icon {
57
+ transition: var(--dx-g-transition-transform-2x);
58
+ }
59
+
60
+ .container.collapsed {
61
+ width: var(--dx-g-spacing-xl);
62
+ }
63
+
64
+ .collapsed .header-toggle-button {
65
+ transform: rotate(180deg);
66
+ }
67
+
68
+ .container {
69
+ background-color: var(--dx-g-gray-95);
70
+ display: flex;
71
+ flex-direction: column;
72
+ height: var(--dx-c-sidebar-height);
73
+ width: calc(var(--dx-c-sidebar-width) + var(--left-spacing));
74
+ }
75
+
76
+ .container:not(.container-mobile) {
77
+ --border-color: var(--dx-g-gray-95);
78
+ --left-spacing: calc(
79
+ var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-md)
80
+ );
81
+
82
+ border-right: 2px solid var(--border-color);
83
+ min-height: var(--dx-c-sidebar-min-height);
84
+ padding: var(--dx-c-sidebar-vertical-padding) 0
85
+ var(--dx-c-sidebar-vertical-padding) var(--left-spacing);
86
+ position: relative;
87
+ }
88
+
89
+ .collapsed.container:not(.container-mobile) {
90
+ background-color: transparent;
91
+ }
92
+
93
+ .collapsed.container:not(.container-border-active) {
94
+ --border-color: transparent;
95
+ }
96
+
97
+ .container:hover .header-toggle-button {
98
+ --dx-c-button-opacity: 1;
99
+ }
100
+
101
+ .container.container-border-active {
102
+ --border-color: var(--dx-c-sidebar-button-color);
103
+ }
104
+
105
+ .container.container-mobile {
106
+ width: 100%;
107
+ }
108
+
109
+ .container.container-mobile .header {
110
+ cursor: pointer;
111
+ line-height: var(--dx-g-spacing-2xl);
112
+ }
113
+
114
+ .container.container-mobile:not(.collapsed) .header {
115
+ background-color: #dddbda;
116
+ }
117
+
118
+ .container.container-mobile .header-title {
119
+ color: var(--dx-g-gray-10);
120
+ font-family: var(--dx-g-font-sans);
121
+ font-size: var(--dx-g-text-sm);
122
+ }
123
+
124
+ .container.container-mobile.collapsed {
125
+ height: unset;
126
+ padding-bottom: 0;
127
+ }
128
+
129
+ .container.container-mobile.collapsed .header {
130
+ margin-bottom: 0;
131
+ }
132
+
133
+ .container.container-mobile.collapsed .header-toggle-icon dx-icon {
134
+ transform: rotateZ(-90deg);
135
+ }
136
+
137
+ .sidebar-content {
138
+ overflow-y: auto;
139
+ }
140
+
141
+ .search {
142
+ padding-bottom: var(--dx-g-spacing-md);
143
+ }
144
+
145
+ .search-box {
146
+ --dx-c-input-width: 100%;
147
+ }
148
+
149
+ .truncate {
150
+ overflow: hidden;
151
+ text-overflow: ellipsis;
152
+ white-space: nowrap;
153
+ }
154
+
155
+ dx-empty-state {
156
+ --dx-c-empty-state-background: var(--sds-g-gray-2);
157
+ --dx-c-empty-state-image-width: 400px;
158
+ }
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class={containerClass}>
3
+ <div
4
+ if:true={mobile}
5
+ class="header padding-horizontal"
6
+ onclick={onToggleClick}
7
+ >
8
+ <span class="header-title truncate">{headerMobile}</span>
9
+ <div class="header-toggle-icon">
10
+ <dx-icon
11
+ aria-expanded={expanded}
12
+ aria-label="icon"
13
+ sprite="utility"
14
+ symbol="chevrondown"
15
+ ></dx-icon>
16
+ </div>
17
+ </div>
18
+
19
+ <dx-button
20
+ if:false={mobile}
21
+ aria-expanded={expanded}
22
+ aria-label="icon"
23
+ class="header-toggle-button"
24
+ icon-symbol="close_panel"
25
+ variant="custom"
26
+ onclick={onToggleClick}
27
+ onmouseenter={onToggleMouseEnter}
28
+ onmouseleave={onToggleMouseLeave}
29
+ ></dx-button>
30
+
31
+ <template if:true={expanded}>
32
+ <div class="padding-horizontal">
33
+ <div class="header" if:false={mobile}>
34
+ <h2 class="dx-text-heading-4 header-title">{header}</h2>
35
+ <slot name="header"></slot>
36
+ </div>
37
+ <div class="search">
38
+ <dx-input
39
+ class="search-box"
40
+ type="search"
41
+ placeholder="Search this list..."
42
+ role="search"
43
+ aria-label="Content Search"
44
+ icon-symbol="search"
45
+ size="small"
46
+ value={searchText}
47
+ onchange={onSearchChange}
48
+ shortcut-key="j"
49
+ ></dx-input>
50
+ </div>
51
+ </div>
52
+ <div class="sidebar-content" if:true={anyResultMatch}>
53
+ <dx-tree
54
+ for:each={filteredTrees}
55
+ for:item="tree"
56
+ key={tree.key}
57
+ tree-root={tree.tree}
58
+ onselect={onSelect}
59
+ value={value}
60
+ ></dx-tree>
61
+ </div>
62
+ <dx-empty-state
63
+ class="padding-horizontal"
64
+ size="small"
65
+ title="No Results"
66
+ if:false={anyResultMatch}
67
+ ></dx-empty-state>
68
+ </template>
69
+ </div>
70
+ </template>
@@ -0,0 +1,139 @@
1
+ import { SAMPLE_TREES, SAMPLE_TREES_WITH_METHODS } from "./__tests__/mockProps";
2
+
3
+ import { html } from "lit-html";
4
+
5
+ export default {
6
+ title: "dx/dx-sidebar-old",
7
+ component: "dx-sidebar-old",
8
+ argTypes: {
9
+ header: {
10
+ defaultValue: "Guides",
11
+ control: {
12
+ type: "text"
13
+ }
14
+ },
15
+ trees: {
16
+ defaultValue: SAMPLE_TREES,
17
+ control: {
18
+ type: "object"
19
+ }
20
+ },
21
+ value: {
22
+ defaultValue: "terminology",
23
+ control: {
24
+ type: "text"
25
+ }
26
+ }
27
+ }
28
+ };
29
+
30
+ const styles = (addStyles: string = "") => html`
31
+ <style>
32
+ dx-sidebar-old {
33
+ ${addStyles}
34
+ }
35
+
36
+ body.sb-main-padded.sb-show-main {
37
+ padding: 0;
38
+ }
39
+ </style>
40
+ `;
41
+
42
+ export const Base = (args: any) => html`
43
+ ${styles("--dx-c-sidebar-min-height: 100vh;")}
44
+
45
+ <dx-sidebar-old
46
+ header="${args.header}"
47
+ trees="${JSON.stringify(args.trees)}"
48
+ ></dx-sidebar-old>
49
+ `;
50
+
51
+ export const WidthAndMinHeightVariables = (args: any) => html`
52
+ ${styles(`
53
+ min-height: 500px;
54
+ --dx-c-sidebar-width: 400px;
55
+ --dx-c-sidebar-min-height: 500px;
56
+ `)}
57
+
58
+ <dx-sidebar-old
59
+ header="${args.header}"
60
+ trees="${JSON.stringify(args.trees)}"
61
+ ></dx-sidebar-old>
62
+ `;
63
+
64
+ export const WidthDrivenByContainer = (args: any) => html`
65
+ ${styles(`
66
+ display: block;
67
+ --dx-c-sidebar-min-height: 500px;
68
+ --dx-c-sidebar-width: unset;
69
+ `)}
70
+
71
+ <style>
72
+ @media screen and (min-width: 768px) {
73
+ .container {
74
+ width: 40%;
75
+ }
76
+ }
77
+ </style>
78
+ <div class="container">
79
+ <dx-sidebar-old
80
+ header="${args.header}"
81
+ trees="${JSON.stringify(args.trees)}"
82
+ value="${args.value}"
83
+ ></dx-sidebar-old>
84
+ </div>
85
+ `;
86
+
87
+ export const WithBadges = (args: any) => html`
88
+ ${styles("--dx-c-sidebar-min-height: 500px;")}
89
+
90
+ <div class="container">
91
+ <dx-sidebar-old
92
+ header="${args.header}"
93
+ trees="${JSON.stringify(SAMPLE_TREES_WITH_METHODS)}"
94
+ value="${args.value}"
95
+ ></dx-sidebar-old>
96
+ </div>
97
+ `;
98
+
99
+ export const PositionSticky = ({ header, trees, value }: any) => html`
100
+ ${styles(`
101
+ display: block;
102
+ position: sticky;
103
+ height: 100vh;
104
+
105
+ --dx-c-sidebar-height: 100%;
106
+ --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-lg);
107
+ `)}
108
+ <dx-sidebar-old
109
+ header="${header}"
110
+ trees="${JSON.stringify(trees)}"
111
+ value="${value}"
112
+ ></dx-sidebar-old>
113
+ `;
114
+
115
+ export const UseSlot = ({ header, trees, value }: any) => html`
116
+ ${styles(`
117
+ display: block;
118
+ position: sticky;
119
+ height: 100vh;
120
+
121
+ --dx-c-sidebar-height: 100%;
122
+ --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-lg);
123
+ --dx-c-sidebar-width: 300px;
124
+ `)}
125
+
126
+ <style>
127
+ span {
128
+ margin-left: auto;
129
+ }
130
+ </style>
131
+
132
+ <dx-sidebar-old
133
+ header="${header}"
134
+ trees="${JSON.stringify(trees)}"
135
+ value="${value}"
136
+ >
137
+ <span slot="header">I'm in the sidebar</span>
138
+ </dx-sidebar-old>
139
+ `;
@@ -0,0 +1,187 @@
1
+ import cx from "classnames";
2
+ import { track } from "dx/instrumentation";
3
+ import { LightningElement, api } from "lwc";
4
+
5
+ import { TreeNode, SidebarGtmAction } from "typings/custom";
6
+ import { toJson } from "utils/normalizers";
7
+
8
+ const WAIT_TIME = 500;
9
+ const MOBILE_SIZE_MATCH = "768px";
10
+ const TOGGLE_BUTTON_LABEL = "Toggle Sidebar";
11
+
12
+ export default class Sidebar extends LightningElement {
13
+ @api header: string = "";
14
+
15
+ @api
16
+ get value() {
17
+ return this._value;
18
+ }
19
+
20
+ set value(value) {
21
+ this._value = value;
22
+ }
23
+
24
+ @api
25
+ get trees() {
26
+ return this._trees;
27
+ }
28
+
29
+ set trees(value: string | Array<TreeNode>) {
30
+ let parsedValue = toJson(value);
31
+ if (parsedValue && !Array.isArray(parsedValue)) {
32
+ parsedValue = [parsedValue];
33
+ }
34
+
35
+ this._trees = parsedValue || [];
36
+
37
+ this._trees.forEach((tree) => this.assignValueToLabel(tree));
38
+ this.assignFilteredTrees();
39
+ }
40
+
41
+ private expanded: boolean = true;
42
+ private _toggleHovered: boolean = false;
43
+ private _searchTimeout?: number;
44
+ private _value?: string = undefined;
45
+ private _trees!: Array<TreeNode>;
46
+
47
+ private filteredTrees: Array<{ key: string; tree: TreeNode }> = [];
48
+ private mobile: boolean = true;
49
+ private searchText: string = "";
50
+ private matchMedia!: MediaQueryList;
51
+ private valueToLabel: { [key: string]: string } = {};
52
+
53
+ private get anyResultMatch() {
54
+ return this.filteredTrees.length > 0;
55
+ }
56
+
57
+ private get containerClass() {
58
+ return cx("container", {
59
+ collapsed: !this.expanded,
60
+ "container-border-active": this._toggleHovered,
61
+ "container-mobile": this.mobile
62
+ });
63
+ }
64
+
65
+ private get headerMobile() {
66
+ return (
67
+ (this.value && this.valueToLabel[this.value]) ||
68
+ Object.values(this.valueToLabel)[0] ||
69
+ ""
70
+ );
71
+ }
72
+
73
+ connectedCallback() {
74
+ this.matchMedia = window.matchMedia(
75
+ `(max-width: ${MOBILE_SIZE_MATCH})`
76
+ );
77
+
78
+ this.expanded = !this.matchMedia.matches;
79
+ this.onMediaChange(this.matchMedia);
80
+ this.matchMedia.addEventListener("change", this.onMediaChange);
81
+ }
82
+
83
+ disconnectedCallback() {
84
+ this.matchMedia.removeEventListener("change", this.onMediaChange);
85
+ }
86
+
87
+ private onMediaChange = (event: MediaQueryListEvent | MediaQueryList) =>
88
+ (this.mobile = event.matches);
89
+
90
+ private onSelect(event: CustomEvent) {
91
+ this._value = event.detail.name;
92
+ }
93
+
94
+ private onToggleClick(event: Event) {
95
+ this.expanded = !this.expanded;
96
+ this._toggleHovered = false;
97
+ const sideNavAction: SidebarGtmAction = this.expanded
98
+ ? "expand"
99
+ : "collapse";
100
+ track(event.currentTarget!, "custEv_leftNavLinkClick", {
101
+ sideNavAction,
102
+ clickText: TOGGLE_BUTTON_LABEL
103
+ });
104
+ }
105
+
106
+ private onToggleMouseEnter() {
107
+ this._toggleHovered = true;
108
+ }
109
+
110
+ private onToggleMouseLeave() {
111
+ this._toggleHovered = false;
112
+ }
113
+
114
+ /*
115
+ * Debouncing this method: Do not apply searchText filter as long as this
116
+ * function is being called to avoid running the recursivity logic very often.
117
+ **/
118
+ private onSearchChange(event: CustomEvent) {
119
+ this.searchText = event.detail;
120
+ window.clearTimeout(this._searchTimeout);
121
+ this._searchTimeout = window.setTimeout(() => {
122
+ this.assignFilteredTrees();
123
+ this.dispatchEvent(
124
+ new CustomEvent("highlightedtermchange", {
125
+ detail: this.searchText,
126
+ composed: true,
127
+ bubbles: true
128
+ })
129
+ );
130
+ }, WAIT_TIME);
131
+ }
132
+
133
+ private assignFilteredTrees() {
134
+ this.filteredTrees = this._trees
135
+ .map((singleTree) => {
136
+ return {
137
+ key: this.makeKey(),
138
+ tree: this.searchText
139
+ ? this.findMatch(
140
+ new RegExp(this.searchText.toLowerCase().trim()),
141
+ singleTree
142
+ )!
143
+ : singleTree
144
+ };
145
+ })
146
+ .filter((filteredTree) => filteredTree.tree);
147
+ }
148
+
149
+ private makeKey(): string {
150
+ return Math.random().toString(36).substring(7);
151
+ }
152
+
153
+ private findMatch(
154
+ valuePattern: RegExp,
155
+ node: TreeNode
156
+ ): TreeNode | undefined {
157
+ if (valuePattern.test(node.label.toLowerCase())) {
158
+ return { ...node, isExpanded: true };
159
+ }
160
+
161
+ const filteredChildren = this.filterChildren(
162
+ valuePattern,
163
+ node.children
164
+ );
165
+ return filteredChildren.length > 0
166
+ ? { ...node, children: filteredChildren, isExpanded: true }
167
+ : undefined;
168
+ }
169
+
170
+ private filterChildren(
171
+ valuePattern: RegExp,
172
+ children?: Array<TreeNode>
173
+ ): Array<TreeNode> {
174
+ return !children || children.length === 0
175
+ ? []
176
+ : (children
177
+ .map((child) => this.findMatch(valuePattern, child))
178
+ .filter((child) => child) as Array<TreeNode>);
179
+ }
180
+
181
+ private assignValueToLabel(node: TreeNode): void {
182
+ this.valueToLabel[node.name] = node.label;
183
+ if (node.children && node.children.length > 0) {
184
+ node.children.forEach((child) => this.assignValueToLabel(child));
185
+ }
186
+ }
187
+ }