@salesforcedevs/dx-components 0.22.0 → 0.23.0-alpha.4
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 +2 -0
- package/package.json +2 -3
- package/src/.DS_Store +0 -0
- package/src/modules/.DS_Store +0 -0
- package/src/modules/dx/.DS_Store +0 -0
- package/src/modules/dx/cardExpanded/__tests__/cardExpanded.test.ts +193 -0
- package/src/modules/dx/cardExpanded/__tests__/mockProps.ts +21 -0
- package/src/modules/dx/cardExpanded/cardExpanded.css +49 -0
- package/src/modules/dx/cardExpanded/cardExpanded.html +37 -0
- package/src/modules/dx/cardExpanded/cardExpanded.stories.ts +59 -0
- package/src/modules/dx/cardExpanded/cardExpanded.ts +45 -0
- package/src/modules/dx/{minimalCard/__tests__/minimalCard.test.ts → cardMinimal/__tests__/cardMinimal.test.ts} +3 -3
- package/src/modules/dx/{minimalCard → cardMinimal}/__tests__/mockProps.ts +0 -0
- package/src/modules/dx/{minimalCard/minimalCard.css → cardMinimal/cardMinimal.css} +3 -4
- package/src/modules/dx/{minimalCard/minimalCard.html → cardMinimal/cardMinimal.html} +1 -1
- package/src/modules/dx/{minimalCard/minimalCard.stories.ts → cardMinimal/cardMinimal.stories.ts} +6 -6
- package/src/modules/dx/cardMinimal/cardMinimal.ts +3 -0
- package/src/modules/dx/imageAndLabel/imageAndLabel.html +14 -2
- package/src/modules/dx/sidebar/sidebar.ts +1 -0
- package/src/modules/dx/sidebarSearch/sidebarSearch.html +2 -2
- package/src/modules/dx/sidebarSearch/sidebarSearch.ts +5 -1
- package/src/modules/dx/topics/__tests__/mockProps.ts +3 -0
- package/src/modules/dx/topics/__tests__/topics.test.ts +31 -0
- package/src/modules/dx/topics/topics.css +25 -0
- package/src/modules/dx/topics/topics.html +12 -0
- package/src/modules/dx/topics/topics.ts +25 -0
- package/src/modules/helpers/text/text.css +7 -0
- package/src/modules/{dx/minimalCard/minimalCard.ts → utils/archiveCard/archiveCard.ts} +9 -9
- package/src/modules/utils/normalizers/normalizers.ts +2 -2
- package/LICENSE +0 -12
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/dx-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0-alpha.4",
|
|
4
4
|
"description": "DX Lightning web components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -28,6 +28,5 @@
|
|
|
28
28
|
"@types/classnames": "^2.2.10",
|
|
29
29
|
"@types/debounce": "^1.2.0",
|
|
30
30
|
"@types/lodash.get": "^4.4.6"
|
|
31
|
-
}
|
|
32
|
-
"gitHead": "4a6bdaf5c490103e5620c3199b24528b4d3d26f0"
|
|
31
|
+
}
|
|
33
32
|
}
|
package/src/.DS_Store
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import CardTitle from "dx/cardTitle";
|
|
2
|
+
import CardExpanded from "dx/cardExpanded";
|
|
3
|
+
import FormattedDateTime from "dx/formattedDateTime";
|
|
4
|
+
import ImageAndLabel from "dx/imageAndLabel";
|
|
5
|
+
import Topics from "dx/topics";
|
|
6
|
+
import TypeBadge from "dx/typeBadge";
|
|
7
|
+
import { createMediaMock } from "utils/jest";
|
|
8
|
+
import { createRenderComponent } from "utils/tests";
|
|
9
|
+
import mockProps, { podcastMock } from "./mockProps";
|
|
10
|
+
|
|
11
|
+
const MOBILE_QUERY = "(max-width: 640px)";
|
|
12
|
+
const TAG = "dx-card-expanded";
|
|
13
|
+
const render = createRenderComponent(TAG, CardExpanded);
|
|
14
|
+
|
|
15
|
+
describe(TAG, () => {
|
|
16
|
+
describe("render", () => {
|
|
17
|
+
it("renders blog content type", () => {
|
|
18
|
+
const component = render(mockProps);
|
|
19
|
+
|
|
20
|
+
const container: HTMLElement =
|
|
21
|
+
component.shadowRoot.querySelector(".card-container");
|
|
22
|
+
expect(container).not.toBeNull();
|
|
23
|
+
expect(container.classList).toHaveLength(1);
|
|
24
|
+
|
|
25
|
+
const typeBadge: TypeBadge =
|
|
26
|
+
component.shadowRoot.querySelector("dx-type-badge");
|
|
27
|
+
expect(typeBadge).not.toBeNull();
|
|
28
|
+
expect(typeBadge.color).toBe("teal");
|
|
29
|
+
expect(typeBadge.value).toBe("Blog");
|
|
30
|
+
expect(typeBadge.size).toBe("default");
|
|
31
|
+
|
|
32
|
+
const title: CardTitle =
|
|
33
|
+
component.shadowRoot.querySelector("dx-card-title");
|
|
34
|
+
expect(title).not.toBeNull();
|
|
35
|
+
expect(title.href).toBe(mockProps.href);
|
|
36
|
+
expect(title.target).toBe("_self");
|
|
37
|
+
|
|
38
|
+
const imageAndLabel: ImageAndLabel =
|
|
39
|
+
component.shadowRoot.querySelector("dx-image-and-label");
|
|
40
|
+
expect(imageAndLabel).not.toBeNull();
|
|
41
|
+
expect(imageAndLabel.href).toBe(mockProps.authorHref);
|
|
42
|
+
expect(imageAndLabel.imgSrc).toBe(mockProps.authorImgSrc);
|
|
43
|
+
expect(imageAndLabel.label).toBe(mockProps.authorLabel);
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
component.shadowRoot.querySelector(".card-dot-separator")
|
|
47
|
+
).not.toBeNull();
|
|
48
|
+
|
|
49
|
+
const formatedDate: FormattedDateTime =
|
|
50
|
+
component.shadowRoot.querySelector("dx-formatted-date-time");
|
|
51
|
+
expect(formatedDate).not.toBeNull();
|
|
52
|
+
expect(formatedDate.value).toBe("February 20, 2021");
|
|
53
|
+
|
|
54
|
+
const body: HTMLSpanElement = component.shadowRoot.querySelector(
|
|
55
|
+
".card-container > span"
|
|
56
|
+
);
|
|
57
|
+
expect(body).not.toBeNull();
|
|
58
|
+
expect(body.textContent).toBe(mockProps.body);
|
|
59
|
+
|
|
60
|
+
const topics: Topics =
|
|
61
|
+
component.shadowRoot.querySelector("dx-topics");
|
|
62
|
+
expect(topics).not.toBeNull();
|
|
63
|
+
expect(topics.topics).toEqual(mockProps.topics);
|
|
64
|
+
|
|
65
|
+
expect(window.matchMedia).toBeCalledTimes(1);
|
|
66
|
+
expect(window.matchMedia).toBeCalledWith(MOBILE_QUERY);
|
|
67
|
+
|
|
68
|
+
return expect(component).toBeAccessible();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders with no props", () => {
|
|
72
|
+
const component = render();
|
|
73
|
+
|
|
74
|
+
const container: HTMLElement =
|
|
75
|
+
component.shadowRoot.querySelector(".card-container");
|
|
76
|
+
expect(container).not.toBeNull();
|
|
77
|
+
expect(container.classList).toHaveLength(1);
|
|
78
|
+
|
|
79
|
+
const typeBadge: TypeBadge =
|
|
80
|
+
component.shadowRoot.querySelector("dx-type-badge");
|
|
81
|
+
expect(typeBadge).not.toBeNull();
|
|
82
|
+
expect(typeBadge.color).toBe("teal");
|
|
83
|
+
expect(typeBadge.value).toBe("Blog");
|
|
84
|
+
expect(typeBadge.size).toBe("default");
|
|
85
|
+
|
|
86
|
+
expect(
|
|
87
|
+
component.shadowRoot.querySelector("dx-card-title")
|
|
88
|
+
).toBeNull();
|
|
89
|
+
expect(
|
|
90
|
+
component.shadowRoot.querySelector("dx-image-and-label")
|
|
91
|
+
).toBeNull();
|
|
92
|
+
expect(
|
|
93
|
+
component.shadowRoot.querySelector(".card-dot-separator")
|
|
94
|
+
).toBeNull();
|
|
95
|
+
expect(
|
|
96
|
+
component.shadowRoot.querySelector("dx-formatted-date-time")
|
|
97
|
+
).toBeNull();
|
|
98
|
+
expect(
|
|
99
|
+
component.shadowRoot.querySelector("span:last-of-type")
|
|
100
|
+
).toBeNull();
|
|
101
|
+
expect(component.shadowRoot.querySelector("dx-topics")).toBeNull();
|
|
102
|
+
|
|
103
|
+
expect(window.matchMedia).toBeCalledTimes(1);
|
|
104
|
+
expect(window.matchMedia).toBeCalledWith(MOBILE_QUERY);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("renders Podcast content type", () => {
|
|
108
|
+
const component = render(podcastMock);
|
|
109
|
+
|
|
110
|
+
const container: HTMLElement =
|
|
111
|
+
component.shadowRoot.querySelector(".card-container");
|
|
112
|
+
expect(container).not.toBeNull();
|
|
113
|
+
expect(container.classList).toHaveLength(1);
|
|
114
|
+
|
|
115
|
+
const typeBadge: TypeBadge =
|
|
116
|
+
component.shadowRoot.querySelector("dx-type-badge");
|
|
117
|
+
expect(typeBadge).not.toBeNull();
|
|
118
|
+
expect(typeBadge.color).toBe("blue");
|
|
119
|
+
expect(typeBadge.value).toBe("Podcast");
|
|
120
|
+
expect(typeBadge.size).toBe("default");
|
|
121
|
+
|
|
122
|
+
const title: CardTitle =
|
|
123
|
+
component.shadowRoot.querySelector("dx-card-title");
|
|
124
|
+
expect(title).not.toBeNull();
|
|
125
|
+
expect(title.href).toBe(podcastMock.href);
|
|
126
|
+
expect(title.target).toBe("_self");
|
|
127
|
+
|
|
128
|
+
expect(
|
|
129
|
+
component.shadowRoot.querySelector("dx-image-and-label")
|
|
130
|
+
).toBeNull();
|
|
131
|
+
|
|
132
|
+
const lengthSpan: HTMLSpanElement =
|
|
133
|
+
component.shadowRoot.querySelector(".card-extra-info > span");
|
|
134
|
+
expect(lengthSpan).not.toBeNull();
|
|
135
|
+
expect(lengthSpan.textContent).toBe(podcastMock.length);
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
component.shadowRoot.querySelector(".card-dot-separator")
|
|
139
|
+
).not.toBeNull();
|
|
140
|
+
|
|
141
|
+
const formatedDate: FormattedDateTime =
|
|
142
|
+
component.shadowRoot.querySelector("dx-formatted-date-time");
|
|
143
|
+
expect(formatedDate).not.toBeNull();
|
|
144
|
+
expect(formatedDate.value).toBe("February 20, 2021");
|
|
145
|
+
|
|
146
|
+
const body: HTMLSpanElement = component.shadowRoot.querySelector(
|
|
147
|
+
".card-container > span"
|
|
148
|
+
);
|
|
149
|
+
expect(body).not.toBeNull();
|
|
150
|
+
expect(body.textContent).toBe(podcastMock.body);
|
|
151
|
+
|
|
152
|
+
const topics: Topics =
|
|
153
|
+
component.shadowRoot.querySelector("dx-topics");
|
|
154
|
+
expect(topics).not.toBeNull();
|
|
155
|
+
expect(topics.topics).toEqual(podcastMock.topics);
|
|
156
|
+
|
|
157
|
+
expect(window.matchMedia).toBeCalledTimes(1);
|
|
158
|
+
expect(window.matchMedia).toBeCalledWith(MOBILE_QUERY);
|
|
159
|
+
|
|
160
|
+
return expect(component).toBeAccessible();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("mobile render", () => {
|
|
165
|
+
beforeAll(() => {
|
|
166
|
+
window.matchMedia = createMediaMock(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("renders blog content type as mobile", () => {
|
|
170
|
+
const component = render(mockProps);
|
|
171
|
+
|
|
172
|
+
const container: HTMLElement =
|
|
173
|
+
component.shadowRoot.querySelector(".card-container");
|
|
174
|
+
expect(container).not.toBeNull();
|
|
175
|
+
expect(container.classList).toHaveLength(2);
|
|
176
|
+
expect(container.classList).toContain("card-mobile");
|
|
177
|
+
|
|
178
|
+
const typeBadge: TypeBadge =
|
|
179
|
+
component.shadowRoot.querySelector("dx-type-badge");
|
|
180
|
+
expect(typeBadge).not.toBeNull();
|
|
181
|
+
expect(typeBadge.size).toBe("small");
|
|
182
|
+
|
|
183
|
+
const imageAndLabel: ImageAndLabel =
|
|
184
|
+
component.shadowRoot.querySelector("dx-image-and-label");
|
|
185
|
+
expect(imageAndLabel).not.toBeNull();
|
|
186
|
+
expect(imageAndLabel.href).toBe(mockProps.authorHref);
|
|
187
|
+
expect(imageAndLabel.imgSrc).toBe("");
|
|
188
|
+
expect(imageAndLabel.label).toBe(mockProps.authorLabel);
|
|
189
|
+
|
|
190
|
+
return expect(component).toBeAccessible();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
authorHref: "/",
|
|
3
|
+
authorImgSrc: "https://placekitten.com/300/300",
|
|
4
|
+
authorLabel: "Author Name",
|
|
5
|
+
contentType: "Blog",
|
|
6
|
+
href: "/awesome-blog-post",
|
|
7
|
+
date: "02/20/2021",
|
|
8
|
+
title: "Lightning Web Components for your Inbox",
|
|
9
|
+
body: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptate nulla inventore, quis enim dignissimos culpa assumenda non sed corporis similique praesentium ex quasi, cumque quia sequi rerum ab asperiores fugiat!",
|
|
10
|
+
topics: ["mobile", "performance"]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const podcastMock = {
|
|
14
|
+
body: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptate nulla inventore, quis enim dignissimos culpa assumenda non sed corporis similique praesentium ex quasi, cumque quia sequi rerum ab asperiores fugiat!",
|
|
15
|
+
contentType: "Podcast",
|
|
16
|
+
date: "02/20/2021",
|
|
17
|
+
href: "/awesome-blog-post",
|
|
18
|
+
length: "40 min",
|
|
19
|
+
title: "Lightning Web Components for your Inbox",
|
|
20
|
+
topics: ["mobile", "performance"]
|
|
21
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@import "helpers/text";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dx-card-title:hover {
|
|
8
|
+
--dx-c-card-title-color: var(--dx-g-blue-vibrant-40);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dx-formatted-date-time {
|
|
12
|
+
/* Push down a little to align with dx-image-and-label */
|
|
13
|
+
margin-top: 3px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.card-body {
|
|
17
|
+
--dx-c-max-lines: var(--dx-c-body-max-lines, 3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.card-extra-info {
|
|
21
|
+
gap: var(--dx-g-spacing-sm);
|
|
22
|
+
align-items: center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.card-container {
|
|
26
|
+
border-bottom: 1px solid var(--sds-g-gray-4);
|
|
27
|
+
padding-bottom: var(--dx-g-spacing-xl);
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: var(--dx-g-spacing-smd);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.card-container,
|
|
33
|
+
.card-extra-info {
|
|
34
|
+
display: flex;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.card-dot-separator {
|
|
38
|
+
--size: 5px;
|
|
39
|
+
|
|
40
|
+
height: var(--size);
|
|
41
|
+
width: var(--size);
|
|
42
|
+
background-color: var(--dx-g-text-label-color);
|
|
43
|
+
border-radius: 100%;
|
|
44
|
+
margin-bottom: 3px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.card-mobile .card-body {
|
|
48
|
+
--dx-c-max-lines: var(--dx-c-body-max-lines, 2);
|
|
49
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class={containerClass}>
|
|
3
|
+
<dx-type-badge
|
|
4
|
+
color={badgeColor}
|
|
5
|
+
type="content-type"
|
|
6
|
+
value={contentType}
|
|
7
|
+
size={badgeSize}
|
|
8
|
+
></dx-type-badge>
|
|
9
|
+
<dx-card-title
|
|
10
|
+
if:true={title}
|
|
11
|
+
href={href}
|
|
12
|
+
target={target}
|
|
13
|
+
title={title}
|
|
14
|
+
></dx-card-title>
|
|
15
|
+
<div class="card-extra-info dx-text-label-1-dark">
|
|
16
|
+
<dx-image-and-label
|
|
17
|
+
if:true={renderAuthor}
|
|
18
|
+
href={authorHref}
|
|
19
|
+
img-src={authorImage}
|
|
20
|
+
label={authorLabel}
|
|
21
|
+
></dx-image-and-label>
|
|
22
|
+
<span if:true={renderLength}>{length}</span>
|
|
23
|
+
<span if:true={renderDot} class="card-dot-separator"></span>
|
|
24
|
+
<dx-formatted-date-time
|
|
25
|
+
if:true={date}
|
|
26
|
+
day="2-digit"
|
|
27
|
+
month="long"
|
|
28
|
+
year="numeric"
|
|
29
|
+
value={date}
|
|
30
|
+
></dx-formatted-date-time>
|
|
31
|
+
</div>
|
|
32
|
+
<span if:true={body} class="card-body dx-text-body-2 dx-text-truncate">
|
|
33
|
+
{body}
|
|
34
|
+
</span>
|
|
35
|
+
<dx-topics if:true={hasTopics} topics={topics}></dx-topics>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { html } from "lit-html";
|
|
2
|
+
import mockProps, { podcastMock } from "./__tests__/mockProps";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "dx/dx-card-expanded",
|
|
6
|
+
component: "dx-card-expanded",
|
|
7
|
+
argTypes: {
|
|
8
|
+
contentType: {
|
|
9
|
+
control: {
|
|
10
|
+
options: ["Blog", "Podcast"],
|
|
11
|
+
type: "select"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const style = html`
|
|
18
|
+
<style>
|
|
19
|
+
dx-card-expanded {
|
|
20
|
+
width: clamp(400px, 80%, 800px);
|
|
21
|
+
}
|
|
22
|
+
</style>
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
type CardExpandedProps = {
|
|
26
|
+
authorHref?: string;
|
|
27
|
+
authorImgSrc?: string;
|
|
28
|
+
authorLabel?: string;
|
|
29
|
+
body: string;
|
|
30
|
+
contentType: "Blog" | "Podcast";
|
|
31
|
+
date: string;
|
|
32
|
+
href: string;
|
|
33
|
+
title: string;
|
|
34
|
+
length?: string;
|
|
35
|
+
topics: Array<string>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const Template = (args: CardExpandedProps) => html`
|
|
39
|
+
${style}
|
|
40
|
+
<dx-card-expanded
|
|
41
|
+
author-href="${args.authorHref || ""}"
|
|
42
|
+
author-img-src="${args.authorImgSrc || ""}"
|
|
43
|
+
author-label="${args.authorLabel || ""}"
|
|
44
|
+
body="${args.body}"
|
|
45
|
+
content-type=${args.contentType || "Blog"}
|
|
46
|
+
date="${args.date}"
|
|
47
|
+
href="${args.href}"
|
|
48
|
+
length="${args.length || ""}"
|
|
49
|
+
title="${args.title}"
|
|
50
|
+
topics=${JSON.stringify(args.topics)}
|
|
51
|
+
></dx-card>-expanded
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
export const BlogPost = (args: CardExpandedProps) => Template(args);
|
|
55
|
+
|
|
56
|
+
BlogPost.args = mockProps;
|
|
57
|
+
|
|
58
|
+
export const PodcastPost = (args: CardExpandedProps) => Template(args);
|
|
59
|
+
PodcastPost.args = podcastMock;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { api } from "lwc";
|
|
2
|
+
import { ArchiveCard } from "utils/archiveCard";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
|
|
5
|
+
export default class ExpandedCard extends ArchiveCard {
|
|
6
|
+
@api body!: string;
|
|
7
|
+
@api date: string | null = null;
|
|
8
|
+
@api topics: Array<string> = [];
|
|
9
|
+
@api authorLabel?: string | null = null;
|
|
10
|
+
@api authorHref?: string | null = null;
|
|
11
|
+
@api authorImgSrc?: string | null = null;
|
|
12
|
+
@api length?: string;
|
|
13
|
+
|
|
14
|
+
private get isBlog() {
|
|
15
|
+
return this.contentType === "Blog";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private get hasAuthor(): boolean {
|
|
19
|
+
return !!(this.authorImgSrc && this.authorLabel);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private get renderAuthor(): boolean {
|
|
23
|
+
return this.isBlog && this.hasAuthor;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private get renderLength(): boolean {
|
|
27
|
+
return !this.isBlog && !!this.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private get renderDot(): boolean {
|
|
31
|
+
return !!((this.renderAuthor || this.renderLength) && this.date);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private get hasTopics(): boolean {
|
|
35
|
+
return !!this.topics?.length;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private get authorImage(): string {
|
|
39
|
+
return !this.mobile && this.authorImgSrc ? this.authorImgSrc : "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private get containerClass(): string {
|
|
43
|
+
return cx("card-container", this.mobile && "card-mobile");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CardMinimal from "dx/cardMinimal";
|
|
2
2
|
import TypeBadge from "dx/typeBadge";
|
|
3
3
|
import { createRenderComponent } from "utils/tests";
|
|
4
4
|
import { createMediaMock } from "utils/jest";
|
|
5
5
|
import { blogPostMock, podcastEpisodeMock } from "./mockProps";
|
|
6
6
|
|
|
7
7
|
const MOBILE_QUERY = "(max-width: 640px)";
|
|
8
|
-
const TAG = "dx-minimal
|
|
9
|
-
const render = createRenderComponent(TAG,
|
|
8
|
+
const TAG = "dx-card-minimal";
|
|
9
|
+
const render = createRenderComponent(TAG, CardMinimal);
|
|
10
10
|
|
|
11
11
|
describe(TAG, () => {
|
|
12
12
|
describe("default render", () => {
|
|
File without changes
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
@import "helpers/reset";
|
|
2
|
+
@import "helpers/text";
|
|
2
3
|
|
|
3
4
|
:host {
|
|
4
5
|
display: block;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
a {
|
|
9
|
+
--dx-c-max-lines: var(--dx-c-title-max-lines, 3);
|
|
10
|
+
|
|
8
11
|
color: var(--dx-g-blue-vibrant-50);
|
|
9
|
-
display: -webkit-box;
|
|
10
12
|
flex-grow: 1;
|
|
11
13
|
font-family: var(--dx-g-font-sans);
|
|
12
14
|
font-size: var(--dx-g-text-sm);
|
|
13
15
|
font-weight: var(--dx-g-font-demi);
|
|
14
|
-
overflow: hidden;
|
|
15
|
-
-webkit-line-clamp: var(--dx-c-title-max-lines, 3);
|
|
16
|
-
-webkit-box-orient: vertical;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
a:hover {
|
package/src/modules/dx/{minimalCard/minimalCard.stories.ts → cardMinimal/cardMinimal.stories.ts}
RENAMED
|
@@ -2,15 +2,15 @@ import { html } from "lit-html";
|
|
|
2
2
|
import { blogPostMock, podcastEpisodeMock } from "./__tests__/mockProps";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
|
-
title: "dx/dx-minimal
|
|
6
|
-
component: "dx-minimal
|
|
5
|
+
title: "dx/dx-card-minimal",
|
|
6
|
+
component: "dx-card-minimal"
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export const BlogPost = (args: { title: string; href: string }) => html`
|
|
10
|
-
<dx-minimal
|
|
10
|
+
<dx-card-minimal
|
|
11
11
|
title="${args.title}"
|
|
12
12
|
href="${args.href}"
|
|
13
|
-
></dx-minimal
|
|
13
|
+
></dx-card-minimal>
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
16
|
BlogPost.args = {
|
|
@@ -19,11 +19,11 @@ BlogPost.args = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const PodcastEpisode = (args: { title: string; href: string }) => html`
|
|
22
|
-
<dx-minimal
|
|
22
|
+
<dx-card-minimal
|
|
23
23
|
title="${args.title}"
|
|
24
24
|
href="${args.href}"
|
|
25
25
|
content-type="${podcastEpisodeMock.contentType}"
|
|
26
|
-
></dx-minimal
|
|
26
|
+
></dx-card-minimal>
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
29
|
PodcastEpisode.args = {
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div if:false={href} class="container">
|
|
3
|
-
<img
|
|
3
|
+
<img
|
|
4
|
+
if:true={imgSrc}
|
|
5
|
+
class={imgClass}
|
|
6
|
+
src={imgSrc}
|
|
7
|
+
alt={imgAlt}
|
|
8
|
+
part="image"
|
|
9
|
+
/>
|
|
4
10
|
<span class="dx-text-label-1-dark" part="label">{label}</span>
|
|
5
11
|
</div>
|
|
6
12
|
<a if:true={href} href={href} class="container">
|
|
7
|
-
<img
|
|
13
|
+
<img
|
|
14
|
+
if:true={imgSrc}
|
|
15
|
+
class={imgClass}
|
|
16
|
+
src={imgSrc}
|
|
17
|
+
alt={imgAlt}
|
|
18
|
+
part="image"
|
|
19
|
+
/>
|
|
8
20
|
<span class="dx-text-label-1-dark" part="label">{label}</span>
|
|
9
21
|
</a>
|
|
10
22
|
</template>
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<dx-input
|
|
11
11
|
class="search-box"
|
|
12
12
|
type="search"
|
|
13
|
-
placeholder="Search
|
|
13
|
+
placeholder="Search"
|
|
14
14
|
role="search"
|
|
15
|
-
aria-label="
|
|
15
|
+
aria-label="Search in this guide"
|
|
16
16
|
icon-symbol="search"
|
|
17
17
|
size="small"
|
|
18
18
|
value={value}
|
|
@@ -44,6 +44,7 @@ export default class SidebarSearch extends LightningElement {
|
|
|
44
44
|
set coveoAdvancedQueryConfig(value) {
|
|
45
45
|
const jsonValue = toJson(value);
|
|
46
46
|
const hasChanged =
|
|
47
|
+
this.engine &&
|
|
47
48
|
this._coveoAdvancedQueryConfig &&
|
|
48
49
|
Object.entries(jsonValue).some(
|
|
49
50
|
([key, val]: [string, any]) =>
|
|
@@ -51,9 +52,12 @@ export default class SidebarSearch extends LightningElement {
|
|
|
51
52
|
);
|
|
52
53
|
this._coveoAdvancedQueryConfig = jsonValue;
|
|
53
54
|
|
|
55
|
+
if (hasChanged && this.engine && this.value !== "") {
|
|
56
|
+
this.dispatchOnLoading(true);
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
if (hasChanged && this.engine) {
|
|
55
60
|
// set advanced filters needs access to the latest coveoAdvancedQueryConfig
|
|
56
|
-
this.dispatchOnLoading(true);
|
|
57
61
|
this.setAdvancedFilters(this.engine);
|
|
58
62
|
this.submitSearch(true);
|
|
59
63
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Topics from "dx/topics";
|
|
2
|
+
import { createRenderComponent } from "utils/tests";
|
|
3
|
+
import mockProps from "./mockProps";
|
|
4
|
+
|
|
5
|
+
const TAG = "dx-topics";
|
|
6
|
+
const render = createRenderComponent(TAG, Topics);
|
|
7
|
+
describe(TAG, () => {
|
|
8
|
+
it("renders", () => {
|
|
9
|
+
const component = render(mockProps);
|
|
10
|
+
expect(component.topics).toEqual(mockProps.topics);
|
|
11
|
+
|
|
12
|
+
const spans: Array<HTMLElement> =
|
|
13
|
+
component.shadowRoot.querySelectorAll("span");
|
|
14
|
+
expect(spans).toHaveLength(3);
|
|
15
|
+
|
|
16
|
+
const [expectedFirst, expectedSecond] = mockProps.topics;
|
|
17
|
+
const [first, dot, second] = spans;
|
|
18
|
+
expect(first.textContent).toBe(expectedFirst);
|
|
19
|
+
expect(second.textContent).toBe(expectedSecond);
|
|
20
|
+
expect(dot.classList).toContain("dot-separator");
|
|
21
|
+
|
|
22
|
+
return expect(component).toBeAccessible();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("doesn't render any span if list is empty", () => {
|
|
26
|
+
const component = render();
|
|
27
|
+
const spans: Array<HTMLElement> =
|
|
28
|
+
component.shadowRoot.querySelectorAll("span");
|
|
29
|
+
expect(spans).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
align-items: center;
|
|
7
|
+
color: var(--dx-g-blue-vibrant-20);
|
|
8
|
+
font-size: var(--dx-g-text-xs);
|
|
9
|
+
font-family: var(--dx-g-font-display);
|
|
10
|
+
font-weight: var(--dx-g-font-demi);
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: var(--dx-g-spacing-sm);
|
|
13
|
+
letter-spacing: 1px;
|
|
14
|
+
text-transform: uppercase;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dot-separator {
|
|
18
|
+
--size: 4px;
|
|
19
|
+
|
|
20
|
+
height: var(--size);
|
|
21
|
+
width: var(--size);
|
|
22
|
+
background-color: var(--dx-g-blue-vibrant-20);
|
|
23
|
+
border-radius: 100%;
|
|
24
|
+
margin-bottom: 3px;
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="container dx-text-label-1-dark">
|
|
3
|
+
<template for:each={normalizedTopics} for:item="topic">
|
|
4
|
+
<span key={topic.id}>{topic.label}</span>
|
|
5
|
+
<span
|
|
6
|
+
key={topic.id}
|
|
7
|
+
if:false={topic.isLast}
|
|
8
|
+
class="dot-separator"
|
|
9
|
+
></span>
|
|
10
|
+
</template>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LightningElement, api } from "lwc";
|
|
2
|
+
import { normalizeToArray } from "utils/normalizers";
|
|
3
|
+
|
|
4
|
+
export default class Topics extends LightningElement {
|
|
5
|
+
private _topics: Array<string> = [];
|
|
6
|
+
|
|
7
|
+
@api
|
|
8
|
+
get topics() {
|
|
9
|
+
return this._topics;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set topics(value) {
|
|
13
|
+
this._topics = normalizeToArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get normalizedTopics() {
|
|
17
|
+
const { length } = this._topics;
|
|
18
|
+
|
|
19
|
+
return this._topics?.map((topic, index) => ({
|
|
20
|
+
id: topic,
|
|
21
|
+
label: topic,
|
|
22
|
+
isLast: index === length - 1
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { LightningElement, api } from "lwc";
|
|
2
|
-
import {
|
|
2
|
+
import { LinkTarget, Color } from "typings/custom";
|
|
3
3
|
|
|
4
4
|
const MOBILE_QUERY = "(max-width: 640px)";
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export abstract class ArchiveCard extends LightningElement {
|
|
7
7
|
@api contentType: "Blog" | "Podcast" = "Blog";
|
|
8
8
|
@api href!: string;
|
|
9
|
-
@api target: LinkTarget = "_self";
|
|
10
9
|
@api title!: string;
|
|
10
|
+
@api target: LinkTarget = "_self";
|
|
11
11
|
|
|
12
12
|
private matchMedia!: MediaQueryList;
|
|
13
|
-
|
|
13
|
+
protected mobile = false;
|
|
14
14
|
|
|
15
|
-
get badgeColor(): Color {
|
|
15
|
+
protected get badgeColor(): Color {
|
|
16
16
|
return this.contentType === "Blog" ? "teal" : "blue";
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
get badgeSize(): string {
|
|
20
|
-
return (this.mobile && "small") || "default";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
connectedCallback(): void {
|
|
24
20
|
this.matchMedia = window.matchMedia(MOBILE_QUERY);
|
|
25
21
|
this.onMediaChange(this.matchMedia);
|
|
@@ -32,4 +28,8 @@ export default class MinimalCard extends LightningElement {
|
|
|
32
28
|
|
|
33
29
|
private onMediaChange = (e: MediaQueryListEvent | MediaQueryList) =>
|
|
34
30
|
(this.mobile = e.matches);
|
|
31
|
+
|
|
32
|
+
protected get badgeSize(): string {
|
|
33
|
+
return (this.mobile && "small") || "default";
|
|
34
|
+
}
|
|
35
35
|
}
|
|
@@ -6,7 +6,7 @@ export const toJson = (value: string | object | null) =>
|
|
|
6
6
|
export const normalizeToArray = (
|
|
7
7
|
value: string[] | string | null,
|
|
8
8
|
splitKey: string = ","
|
|
9
|
-
) => {
|
|
9
|
+
): Array<string> => {
|
|
10
10
|
if (!value) {
|
|
11
11
|
return [];
|
|
12
12
|
}
|
|
@@ -19,7 +19,7 @@ export const normalizeToArray = (
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
return Array.isArray(value) ? value : [value]
|
|
22
|
+
return (Array.isArray(value) ? value : [value]) as Array<string>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
type WordpressPodcastEpisode = {
|
package/LICENSE
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2020, Salesforce.com, Inc.
|
|
2
|
-
All rights reserved.
|
|
3
|
-
|
|
4
|
-
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
-
|
|
6
|
-
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
-
|
|
8
|
-
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
-
|
|
10
|
-
* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
11
|
-
|
|
12
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|