@salesforcedevs/dx-components 1.40.11 → 1.42.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.
package/lwc.config.json CHANGED
@@ -131,6 +131,7 @@
131
131
  "dxUtils/analytics",
132
132
  "dxUtils/async",
133
133
  "dxUtils/constants",
134
+ "dxUtils/openSearch",
134
135
  "dxUtils/contentTypes",
135
136
  "dxUtils/coveo",
136
137
  "dxUtils/dates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "1.40.11",
3
+ "version": "1.42.0",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -44,5 +44,5 @@
44
44
  "luxon": "3.4.4",
45
45
  "msw": "^2.12.4"
46
46
  },
47
- "gitHead": "5fe30fa8dd59e05dac6be5f8f91d22fe76254d8a"
47
+ "gitHead": "0923ae80f486a87b3ab0f07cd3c9d8ea9abbff8d"
48
48
  }
@@ -30,6 +30,7 @@
30
30
  transition: var(--dx-g-transition-hue-1x);
31
31
  align-items: center;
32
32
  outline: none !important;
33
+ background: white;
33
34
  }
34
35
 
35
36
  .option:active {
@@ -132,3 +132,22 @@ svg,
132
132
  display: inline-block;
133
133
  content: "(.\00a0\00a0)";
134
134
  }
135
+
136
+ .single-option {
137
+ display: block;
138
+ width: 100%;
139
+ padding: 6px 0;
140
+ text-align: left;
141
+ font-family: var(--dx-g-font-sans);
142
+ font-size: 14px;
143
+ color: rgb(24 24 24);
144
+ }
145
+
146
+ .single-option:hover,
147
+ .single-option-selected {
148
+ color: var(--dx-g-blue-vibrant-50);
149
+ }
150
+
151
+ .single-option-selected {
152
+ font-weight: bold;
153
+ }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <form class="container">
3
3
  <span class="title-group-main title-group" onclick={dropdownToggle}>
4
- <p class="filter-menu-title">{header}</p>
4
+ <p class="filter-menu-title">{displayTitle}</p>
5
5
  <svg
6
6
  xmlns="http://www.w3.org/2000/svg"
7
7
  width="28"
@@ -18,7 +18,22 @@
18
18
  </span>
19
19
  <ul class="nested first-layer always-visible-scrollbar">
20
20
  <li>
21
- <template if:true={isBaseVariant}>
21
+ <template if:true={singleSelect}>
22
+ <template for:each={singleOptions} for:item="option">
23
+ <button
24
+ key={option.id}
25
+ type="button"
26
+ class={option.className}
27
+ role="option"
28
+ aria-selected={option.selected}
29
+ data-id={option.id}
30
+ onclick={onSingleSelect}
31
+ >
32
+ {option.name}
33
+ </button>
34
+ </template>
35
+ </template>
36
+ <template if:true={isCheckboxList}>
22
37
  <template for:each={filterMenuOptions} for:item="option">
23
38
  <dx-checkbox
24
39
  key={option.id}
@@ -11,6 +11,10 @@ export default class FilterMenu extends LightningElement {
11
11
  @api header: string = "Filter Menu";
12
12
  @api variant: string = "base";
13
13
  @api filtersDatesLoading?: boolean;
14
+ /** Render a single-choice list; the header shows the selected option. */
15
+ @api singleSelect: boolean = false;
16
+ /** Id of the selected option when singleSelect is set. */
17
+ @api value?: string;
14
18
 
15
19
  @api
16
20
  get options(): Option[] {
@@ -31,6 +35,31 @@ export default class FilterMenu extends LightningElement {
31
35
  return this.variant === "nested";
32
36
  }
33
37
 
38
+ get isCheckboxList() {
39
+ return this.isBaseVariant && !this.singleSelect;
40
+ }
41
+
42
+ get selectedOption() {
43
+ return this.options.find((o) => String(o.id) === this.value);
44
+ }
45
+
46
+ get displayTitle() {
47
+ return (this.singleSelect && this.selectedOption?.name) || this.header;
48
+ }
49
+
50
+ get singleOptions() {
51
+ return this.options.map((o) => {
52
+ const selected = String(o.id) === this.value;
53
+ return {
54
+ ...o,
55
+ selected,
56
+ className: selected
57
+ ? "single-option single-option-selected"
58
+ : "single-option"
59
+ };
60
+ });
61
+ }
62
+
34
63
  get showViewMoreBtn() {
35
64
  return this.options.length > 20;
36
65
  }
@@ -93,6 +122,20 @@ export default class FilterMenu extends LightningElement {
93
122
  this.showMore = !this.showMore;
94
123
  }
95
124
 
125
+ private onSingleSelect(e: any) {
126
+ const id = e.currentTarget.dataset.id;
127
+ const value = this.options.find((o) => String(o.id) === id);
128
+ this.template.querySelector(".nested")?.classList.remove("active");
129
+ this.template
130
+ .querySelector(".header-caret")
131
+ ?.classList.remove("caret-down");
132
+ this.dispatchEvent(
133
+ new CustomEvent("change", {
134
+ detail: { name: this.name, value, checked: true }
135
+ })
136
+ );
137
+ }
138
+
96
139
  private onChange(e: any) {
97
140
  const { checked, value } = e.detail;
98
141
  this.dispatchEvent(
@@ -1,3 +1,5 @@
1
+ @import "dxHelpers/text";
2
+
1
3
  :host {
2
4
  display: block;
3
5
  font-family: var(--dx-g-font-sans);
@@ -42,56 +44,68 @@
42
44
  margin-top: var(--dx-g-spacing-md);
43
45
  }
44
46
 
45
- /* Body + toolbar ------------------------------------------------------- */
47
+ /* Body --------------------------------------------------------------- */
46
48
  .dx-search-body {
47
49
  margin: 0 auto;
48
50
  max-width: 1200px;
49
51
  padding: var(--dx-g-spacing-xl);
50
52
  }
51
53
 
52
- .dx-search-toolbar {
54
+ .dx-search-layout {
53
55
  display: flex;
54
- flex-wrap: wrap;
55
- align-items: flex-end;
56
- justify-content: space-between;
56
+ align-items: flex-start;
57
+ gap: var(--dx-g-spacing-2xl);
58
+ }
59
+
60
+ .dx-search-filters {
61
+ flex: 0 0 280px;
62
+ display: flex;
63
+ flex-direction: column;
57
64
  gap: var(--dx-g-spacing-md);
58
- margin-bottom: var(--dx-g-spacing-lg);
59
65
  }
60
66
 
61
- .dx-facet-row {
67
+ .dx-filter-header {
62
68
  display: flex;
63
- flex-wrap: wrap;
64
- gap: var(--dx-g-spacing-sm);
69
+ justify-content: space-between;
70
+ align-items: baseline;
65
71
  }
66
72
 
67
- .dx-facet-chip {
68
- background-color: white;
69
- color: var(--dx-g-blue-vibrant-20);
70
- border: 1px solid var(--dx-g-blue-vibrant-50);
71
- border-radius: 500px;
72
- padding: 6px 14px;
73
+ .dx-filter-title {
74
+ margin: 0;
75
+ color: var(--dx-g-text-heading-color);
76
+ font-family: var(--dx-g-font-display);
77
+ font-size: var(--dx-g-text-lg);
78
+ }
79
+
80
+ .dx-filter-clear {
81
+ color: var(--dx-g-blue-vibrant-50);
82
+ font-family: var(--dx-g-font-display);
73
83
  font-size: var(--dx-g-text-sm);
74
- cursor: pointer;
75
84
  }
76
85
 
77
- .dx-facet-chip:hover {
78
- background-color: var(--dx-g-blue-vibrant-95);
86
+ .dx-filter-clear:disabled {
87
+ opacity: 0.4;
88
+ cursor: default;
79
89
  }
80
90
 
81
- .dx-facet-chip_active {
82
- background-color: var(--dx-g-blue-vibrant-50);
83
- color: white;
84
- font-weight: var(--dx-g-font-demi);
91
+ .dx-search-range {
92
+ margin: 0 0 var(--dx-g-spacing-lg);
85
93
  }
86
94
 
87
- .dx-facet-chip-count {
88
- margin-left: 6px;
89
- opacity: 0.75;
95
+ .dx-search-main {
96
+ flex: 1 1 0;
97
+ min-width: 0;
90
98
  }
91
99
 
92
- .dx-search-controls {
93
- display: flex;
94
- gap: var(--dx-g-spacing-md);
100
+ @media (max-width: 768px) {
101
+ .dx-search-layout {
102
+ flex-direction: column;
103
+ }
104
+
105
+ .dx-search-filters {
106
+ flex-basis: auto;
107
+ width: 100%;
108
+ }
95
109
  }
96
110
 
97
111
  /* Status states -------------------------------------------------------- */
@@ -113,49 +127,30 @@
113
127
  }
114
128
 
115
129
  .dx-result {
116
- background: white;
117
- border-radius: 16px;
118
- border: 1px solid rgb(24 24 24 / 4%);
119
- box-shadow: 0 0 2px 0 rgb(24 24 24 / 8%), 0 2px 4px 1px rgb(24 24 24 / 16%);
120
- padding: 20px;
121
- margin-bottom: var(--dx-g-spacing-lg);
122
- }
123
-
124
- .dx-result-info {
125
130
  display: flex;
126
- gap: 12px;
127
- margin-bottom: var(--dx-g-spacing-smd);
131
+ flex-direction: column;
132
+ align-items: flex-start;
133
+ gap: var(--dx-g-spacing-smd);
134
+ border-bottom: 1px solid var(--sds-g-gray-4);
135
+ padding-bottom: var(--dx-g-spacing-xl);
136
+ margin-bottom: var(--dx-g-spacing-xl);
128
137
  }
129
138
 
130
- .dx-badge {
131
- display: inline-flex;
132
- align-items: center;
133
- border-radius: 4px;
134
- padding: 2px 8px;
135
- font-size: var(--dx-g-text-xs);
136
- background-color: var(--dx-g-blue-vibrant-95);
137
- color: var(--dx-g-blue-vibrant-20);
138
- }
139
-
140
- .dx-result-title {
141
- color: var(--dx-g-blue-vibrant-20);
142
- display: block;
143
- font-family: var(--dx-g-font-display);
144
- font-size: var(--dx-g-text-lg);
145
- margin: 0 0 var(--dx-g-spacing-sm);
139
+ .dx-result-title:hover {
140
+ --dx-c-card-title-color: var(--dx-g-blue-vibrant-40);
146
141
  }
147
142
 
148
143
  .dx-result-date {
149
- color: #555;
150
- font-size: var(--dx-g-text-xs);
151
- margin: 0 0 var(--dx-g-spacing-xs);
144
+ margin: 0;
145
+ text-align: start;
152
146
  }
153
147
 
154
148
  .dx-result-excerpt {
155
- color: var(--dx-g-gray-10);
156
- font-size: 14px;
157
- line-height: 20px;
158
149
  margin: 0;
150
+ display: box;
151
+ -webkit-line-clamp: 3;
152
+ -webkit-box-orient: vertical;
153
+ overflow: hidden;
159
154
  }
160
155
 
161
156
  /* Pagination ----------------------------------------------------------- */
@@ -12,133 +12,154 @@
12
12
  </div>
13
13
 
14
14
  <div class="dx-search-body">
15
- <!-- Toolbar: content-type facets + sort + locale selector -->
16
- <div class="dx-search-toolbar">
17
- <div
18
- if:true={hasFacets}
19
- class="dx-facet-row"
20
- role="group"
21
- aria-label="Filter by content type"
22
- >
23
- <template for:each={facetChips} for:item="chip">
15
+ <div class="dx-search-layout">
16
+ <!-- Sidebar: content-type filter + language (like content archive) -->
17
+ <aside class="dx-search-filters" aria-label="Search filters">
18
+ <div class="dx-filter-header">
19
+ <p class="dx-filter-title">Filters</p>
24
20
  <button
25
- key={chip.value}
26
21
  type="button"
27
- class={chip.cssClass}
28
- data-value={chip.value}
29
- aria-pressed={chip.active}
30
- onclick={handleFacetClick}
22
+ class="dx-filter-clear"
23
+ disabled={noActiveFilters}
24
+ onclick={handleClearFilters}
31
25
  >
32
- {chip.label}
33
- <span class="dx-facet-chip-count">
34
- {chip.count}
35
- </span>
26
+ Clear all
36
27
  </button>
37
- </template>
38
- </div>
39
-
40
- <div class="dx-search-controls">
41
- <div if:true={showLocaleSelector} class="dx-search-locale">
42
- <dx-select
43
- label="Language"
44
- options={localeSelectOptions}
45
- value={localeValue}
46
- onchange={handleLocaleChange}
47
- ></dx-select>
48
28
  </div>
49
- <div class="dx-search-sort">
50
- <dx-select
51
- label="Sort by"
52
- options={sortOptions}
53
- value={sortValue}
54
- onchange={handleSortChange}
55
- ></dx-select>
56
- </div>
57
- </div>
58
- </div>
29
+ <dx-filter-menu
30
+ if:true={hasFacets}
31
+ class="dx-filter-type"
32
+ name="contentType"
33
+ header="Content Type"
34
+ single-select
35
+ options={typeFilterOptions}
36
+ value={selectedTypeValue}
37
+ onchange={handleTypeFilterChange}
38
+ ></dx-filter-menu>
39
+ <dx-filter-menu
40
+ if:true={showLocaleSelector}
41
+ class="dx-filter-locale"
42
+ name="language"
43
+ header="Language"
44
+ single-select
45
+ options={localeFilterOptions}
46
+ value={localeValue}
47
+ onchange={handleLocaleChange}
48
+ ></dx-filter-menu>
49
+ </aside>
59
50
 
60
- <!-- Loading -->
61
- <template if:true={isLoading}>
62
- <div class="dx-search-status" aria-live="polite">
63
- <dx-spinner size="medium" variant="base"></dx-spinner>
64
- </div>
65
- </template>
51
+ <div class="dx-search-main">
52
+ <p
53
+ if:true={showRange}
54
+ class="dx-search-range dx-text-body-2"
55
+ aria-live="polite"
56
+ >
57
+ Showing {rangeStart} - {rangeEnd} of {total}
58
+ </p>
59
+ <!-- Loading -->
60
+ <template if:true={isLoading}>
61
+ <div class="dx-search-status" aria-live="polite">
62
+ <dx-spinner
63
+ size="medium"
64
+ variant="base"
65
+ ></dx-spinner>
66
+ </div>
67
+ </template>
66
68
 
67
- <!-- Idle prompt (no query yet) -->
68
- <template if:true={isIdle}>
69
- <div class="dx-search-status" aria-live="polite">
70
- <dx-empty-state
71
- header="Search the developer site"
72
- subtitle="Enter a search term to get started."
73
- variant="alt"
74
- ></dx-empty-state>
75
- </div>
76
- </template>
69
+ <!-- Idle prompt (no query yet) -->
70
+ <template if:true={isIdle}>
71
+ <div class="dx-search-status" aria-live="polite">
72
+ <dx-empty-state
73
+ header="Search the developer site"
74
+ subtitle="Enter a search term to get started."
75
+ variant="alt"
76
+ ></dx-empty-state>
77
+ </div>
78
+ </template>
77
79
 
78
- <!-- Empty (query ran, no results) -->
79
- <template if:true={isEmpty}>
80
- <div class="dx-search-status" aria-live="polite">
81
- <dx-empty-state
82
- header="No results found"
83
- subtitle="Try different keywords or check your spelling."
84
- variant="alt"
85
- ></dx-empty-state>
86
- </div>
87
- </template>
80
+ <!-- Empty (query ran, no results) -->
81
+ <template if:true={isEmpty}>
82
+ <div class="dx-search-status" aria-live="polite">
83
+ <dx-empty-state
84
+ header="No results found"
85
+ subtitle="Try different keywords or check your spelling."
86
+ variant="alt"
87
+ ></dx-empty-state>
88
+ </div>
89
+ </template>
88
90
 
89
- <!-- Error -->
90
- <template if:true={isError}>
91
- <div class="dx-search-status dx-search-error" role="alert">
92
- <p>{errorMessage}</p>
93
- </div>
94
- </template>
91
+ <!-- Error -->
92
+ <template if:true={isError}>
93
+ <div
94
+ class="dx-search-status dx-search-error"
95
+ role="alert"
96
+ >
97
+ <p>{errorMessage}</p>
98
+ </div>
99
+ </template>
95
100
 
96
- <!-- Results -->
97
- <template if:true={hasResults}>
98
- <ul class="dx-result-list">
99
- <template for:each={displayResults} for:item="result">
100
- <li key={result.key} class="dx-result">
101
- <div
102
- if:true={result.hasBadge}
103
- class="dx-result-info"
101
+ <!-- Results -->
102
+ <template if:true={hasResults}>
103
+ <ul class="dx-result-list">
104
+ <template
105
+ for:each={displayResults}
106
+ for:item="result"
104
107
  >
105
- <span class="dx-badge">
106
- {result.contentTypeLabel}
107
- </span>
108
- </div>
109
- <a href={result.href} class="dx-result-title">
110
- {result.title}
111
- </a>
112
- <p if:true={result.hasDate} class="dx-result-date">
113
- {result.displayDate}
114
- </p>
115
- <!-- Highlighted snippet (P4.5): innerHTML set in
108
+ <li key={result.key} class="dx-result">
109
+ <dx-type-badge
110
+ if:true={result.hasBadge}
111
+ class="dx-result-badge"
112
+ variant={result.contentType}
113
+ value={result.contentTypeLabel}
114
+ size="small"
115
+ ></dx-type-badge>
116
+ <dx-card-title
117
+ class="dx-result-title"
118
+ href={result.href}
119
+ header={result.title}
120
+ ></dx-card-title>
121
+ <p
122
+ if:true={result.hasDate}
123
+ class="dx-result-date dx-text-label-1-dark"
124
+ >
125
+ {result.displayDate}
126
+ </p>
127
+ <!-- Highlighted snippet (P4.5): innerHTML set in
116
128
  renderedCallback, keyed by data-snippet-key. -->
117
- <template lwc:if={result.hasSnippetHtml}>
118
- <p
119
- class="dx-result-excerpt"
120
- data-snippet-key={result.key}
121
- lwc:dom="manual"
122
- ></p>
129
+ <template lwc:if={result.hasSnippetHtml}>
130
+ <p
131
+ class="dx-result-excerpt dx-text-body-2"
132
+ data-snippet-key={result.key}
133
+ lwc:dom="manual"
134
+ ></p>
135
+ </template>
136
+ <template
137
+ lwc:elseif={result.hasDescription}
138
+ >
139
+ <p
140
+ class="dx-result-excerpt dx-text-body-2"
141
+ >
142
+ {result.description}
143
+ </p>
144
+ </template>
145
+ </li>
123
146
  </template>
124
- <template lwc:elseif={result.hasDescription}>
125
- <p class="dx-result-excerpt">
126
- {result.description}
127
- </p>
128
- </template>
129
- </li>
130
- </template>
131
- </ul>
147
+ </ul>
132
148
 
133
- <div if:true={showPagination} class="dx-pagination-container">
134
- <dx-pagination
135
- current-page={currentPage}
136
- total-pages={totalPages}
137
- pages-to-show="5"
138
- onpagechange={handlePageChange}
139
- ></dx-pagination>
149
+ <div
150
+ if:true={showPagination}
151
+ class="dx-pagination-container"
152
+ >
153
+ <dx-pagination
154
+ current-page={currentPage}
155
+ total-pages={totalPages}
156
+ pages-to-show="5"
157
+ onpagechange={handlePageChange}
158
+ ></dx-pagination>
159
+ </div>
160
+ </template>
140
161
  </div>
141
- </template>
162
+ </div>
142
163
  </div>
143
164
  </div>
144
165
  </template>