@salesforcedevs/dx-components 0.15.1 → 0.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -29,5 +29,5 @@
29
29
  "@types/debounce": "^1.2.0",
30
30
  "@types/lodash.get": "^4.4.6"
31
31
  },
32
- "gitHead": "2f4882a3e2e0b3693ff8d16612d54953be57a522"
32
+ "gitHead": "7363f31ed0a0565c41611cb1e41fff7ea6c43c06"
33
33
  }
@@ -11,9 +11,7 @@ describe(TAG, () => {
11
11
  const element = render({ navItems });
12
12
 
13
13
  const items = element.shadowRoot.querySelectorAll(".nav-list-item");
14
- const dropdownItems = element.shadowRoot.querySelectorAll(
15
- "dx-header-nav-option"
16
- );
14
+ const dropdownItems = element.shadowRoot.querySelectorAll("dx-tab");
17
15
 
18
16
  // items with more options render something different on mobile and desktop whose display is toggled by `display: none;`
19
17
  const expectedItemLength =
@@ -34,7 +32,7 @@ describe(TAG, () => {
34
32
  const mockOpenNav = jest.fn();
35
33
  element.addEventListener("requestopennavmenu", mockOpenNav);
36
34
  const navOption: HTMLElement = element.shadowRoot.querySelector(
37
- "dx-header-nav-option.nav-list-item-nav-menu"
35
+ "dx-tab.nav-list-item-nav-menu"
38
36
  );
39
37
  expect(navOption).not.toBeNull();
40
38
  navOption.click();
@@ -3,13 +3,13 @@
3
3
  <ul class="nav-list">
4
4
  <template for:each={navItems} for:item="navItem">
5
5
  <li key={navItem.id}>
6
- <dx-header-nav-option
6
+ <dx-tab
7
7
  if:false={navItem.options}
8
8
  is-active={navItem.isActive}
9
9
  label={navItem.label}
10
10
  href={navItem.link.href}
11
11
  variant={variant}
12
- ></dx-header-nav-option>
12
+ ></dx-tab>
13
13
 
14
14
  <!-- DESKTOP "MORE CONTENT" NAV ITEM -->
15
15
  <dx-dropdown
@@ -23,21 +23,23 @@
23
23
  value={pathname}
24
24
  value-path="link.href"
25
25
  >
26
- <dx-header-nav-option
26
+ <dx-tab
27
27
  label={navItem.label}
28
28
  variant={variant}
29
- ></dx-header-nav-option>
29
+ use-icon
30
+ ></dx-tab>
30
31
  </dx-dropdown>
31
32
 
32
33
  <!-- MOBILE "MORE CONTENT" NAV ITEM -->
33
- <dx-header-nav-option
34
+ <dx-tab
34
35
  if:true={navItem.options}
35
36
  class="nav-list-item-nav-menu"
36
37
  data-id={navItem.id}
37
38
  label={navItem.label}
39
+ use-icon
38
40
  variant={variant}
39
41
  onclick={requestOpenNavMenu}
40
- ></dx-header-nav-option>
42
+ ></dx-tab>
41
43
  </li>
42
44
  </template>
43
45
  </ul>
@@ -1,8 +1,8 @@
1
1
  import { LightningElement, api } from "lwc";
2
- import { HeaderNavVariant, Option } from "typings/custom";
2
+ import { TabVariant, Option } from "typings/custom";
3
3
  export default class HeaderNav extends LightningElement {
4
4
  @api pathname: string | null = null;
5
- @api variant: HeaderNavVariant = "default";
5
+ @api variant: TabVariant = "default";
6
6
  @api ariaLabel: string = "Navigation Bar";
7
7
 
8
8
  @api
@@ -1,9 +1,12 @@
1
- import HeaderNavOption from "dx/headerNavOption";
1
+ import Tab from "dx/tab";
2
2
  import Icon from "dx/icon";
3
+ import { track } from "dx/instrumentation";
3
4
  import { createRenderComponent } from "utils/tests";
4
5
 
5
- const TAG = "dx-header-nav-option";
6
- const render = createRenderComponent(TAG, HeaderNavOption);
6
+ jest.mock("dx/instrumentation");
7
+
8
+ const TAG = "dx-tab";
9
+ const render = createRenderComponent(TAG, Tab);
7
10
 
8
11
  const PROPS = {
9
12
  label: "documentation",
@@ -14,9 +17,8 @@ describe(TAG, () => {
14
17
  it("renders a single link with a label", () => {
15
18
  const element = render(PROPS);
16
19
 
17
- const links: Array<HTMLAnchorElement> = element.shadowRoot.querySelectorAll(
18
- "a"
19
- );
20
+ const links: Array<HTMLAnchorElement> =
21
+ element.shadowRoot.querySelectorAll("a");
20
22
 
21
23
  expect(links.length).toEqual(1);
22
24
  const [singleLink] = links;
@@ -53,9 +55,8 @@ describe(TAG, () => {
53
55
  const label = "test button";
54
56
  const element = render({ label });
55
57
 
56
- const buttons: Array<HTMLButtonElement> = element.shadowRoot.querySelectorAll(
57
- "button"
58
- );
58
+ const buttons: Array<HTMLButtonElement> =
59
+ element.shadowRoot.querySelectorAll("button");
59
60
 
60
61
  expect(buttons).toHaveLength(1);
61
62
  const [singleButton] = buttons;
@@ -66,6 +67,23 @@ describe(TAG, () => {
66
67
 
67
68
  const span: HTMLElement = singleButton.querySelector("span")!;
68
69
  expect(span.textContent).toBe(label);
70
+
71
+ return expect(element).toBeAccessible();
72
+ });
73
+
74
+ it("renders a button with an icon if useIcon is passed as true", () => {
75
+ const label = "test button";
76
+ const element = render({ label, useIcon: true });
77
+
78
+ const buttons: Array<HTMLButtonElement> =
79
+ element.shadowRoot.querySelectorAll("button");
80
+
81
+ expect(buttons).toHaveLength(1);
82
+ const [singleButton] = buttons;
83
+
84
+ const span: HTMLElement = singleButton.querySelector("span")!;
85
+ expect(span.textContent).toBe(label);
86
+
69
87
  const icon: Element & Icon = singleButton.querySelector("dx-icon")!;
70
88
  expect(icon).not.toBeNull();
71
89
  expect(icon.size).toBe("small");
@@ -75,11 +93,10 @@ describe(TAG, () => {
75
93
 
76
94
  it("renders a button with label only if variant is small", () => {
77
95
  const label = "test button";
78
- const element = render({ label, variant: "small" });
96
+ const element = render({ label, variant: "small", useIcon: "true" });
79
97
 
80
- const button: HTMLButtonElement = element.shadowRoot.querySelector(
81
- "button"
82
- );
98
+ const button: HTMLButtonElement =
99
+ element.shadowRoot.querySelector("button");
83
100
 
84
101
  expect(button.classList).toHaveLength(2);
85
102
  expect(button.classList).toContain("nav-list-item");
@@ -95,19 +112,18 @@ describe(TAG, () => {
95
112
  return expect(element).toBeAccessible();
96
113
  });
97
114
 
98
- it("fires sendGtm function when button is clicked", () => {
99
- const sendGtmMock = jest.fn();
100
- const element = render({
101
- ...PROPS,
102
- onclick: sendGtmMock
103
- });
104
-
105
- const link = element.shadowRoot.querySelector("a");
115
+ it("calls track when the link is clicked", () => {
116
+ const element = render(PROPS);
106
117
 
118
+ const link: HTMLElement = element.shadowRoot.querySelector("a");
119
+ expect(link).not.toBeNull();
107
120
  link.click();
108
121
 
109
- expect(sendGtmMock).toHaveBeenCalled();
110
-
111
- return expect(element).toBeAccessible();
122
+ expect(track).toHaveBeenCalledTimes(1);
123
+ expect(track).toBeCalledWith(link, "custEv_topNavLinkClick", {
124
+ navHeading: PROPS.label,
125
+ clickText: PROPS.href,
126
+ pageLocation: window.location.pathname
127
+ });
112
128
  });
113
129
  });
@@ -5,7 +5,10 @@ a {
5
5
  }
6
6
 
7
7
  .nav-list-item {
8
- --horizontal-spacing: var(--dx-g-spacing-md);
8
+ --horizontal-spacing: var(
9
+ --dx-c-tab-horizontal-spacing,
10
+ var(--dx-g-spacing-md)
11
+ );
9
12
 
10
13
  position: relative;
11
14
  display: flex;
@@ -50,12 +53,15 @@ button.nav-list-item_small > dx-icon {
50
53
  .nav-list-item_default:hover,
51
54
  .nav-list-item_default:active,
52
55
  :host([aria-expanded="true"]) button.nav-list-item_default {
53
- background: var(--dx-g-brand-current-button-color-background-inactive);
56
+ background: var(
57
+ --dx-g-brand-current-button-color-background-inactive,
58
+ var(--dx-g-brand-default-button-color-background-inactive)
59
+ );
54
60
  }
55
61
 
56
- button.nav-list-item:hover > *:last-child,
57
- button.nav-list-item:active > *:last-child,
58
- :host([aria-expanded="true"]) button.nav-list-item > *:last-child {
62
+ button.nav-list-item:hover > dx-icon,
63
+ button.nav-list-item:active > dx-icon,
64
+ :host([aria-expanded="true"]) button.nav-list-item > dx-icon {
59
65
  transform: rotateX(180deg);
60
66
  }
61
67
 
@@ -74,18 +80,27 @@ button.nav-list-item:active > *:last-child,
74
80
  }
75
81
 
76
82
  .nav-list-item_default.state-active {
77
- background: var(--dx-g-brand-current-button-color-background-active);
78
- color: var(--dx-g-brand-current-color);
83
+ background: var(
84
+ --dx-g-brand-current-button-color-background-active,
85
+ var(--dx-g-brand-default-button-color-background-active)
86
+ );
87
+ color: var(--dx-g-brand-current-color, var(--dx-g-brand-default-color));
79
88
  }
80
89
 
81
90
  @media (max-width: 1160px) {
82
91
  .nav-list-item_small {
83
- --horizontal-spacing: var(--dx-g-spacing-smd);
92
+ --horizontal-spacing: var(
93
+ --dx-c-tab-horizontal-spacing,
94
+ var(--dx-g-spacing-smd)
95
+ );
84
96
  }
85
97
  }
86
98
 
87
99
  @media (max-width: 1090px) {
88
100
  .nav-list-item_small {
89
- --horizontal-spacing: var(--dx-g-spacing-sm);
101
+ --horizontal-spacing: var(
102
+ --dx-c-tab-horizontal-spacing,
103
+ var(--dx-g-spacing-sm)
104
+ );
90
105
  }
91
106
  }
@@ -4,6 +4,10 @@
4
4
  </a>
5
5
  <button if:false={href} class={className}>
6
6
  <span>{label}</span>
7
- <dx-icon symbol="chevrondown" size={iconSize}></dx-icon>
7
+ <dx-icon
8
+ if:true={useIcon}
9
+ symbol="chevrondown"
10
+ size={iconSize}
11
+ ></dx-icon>
8
12
  </button>
9
13
  </template>
@@ -0,0 +1,57 @@
1
+ import { html } from "lit-html";
2
+ import { TabVariant } from "typings/custom";
3
+
4
+ type StoryArgs = {
5
+ isActive: boolean;
6
+ label: string;
7
+ useIcon: boolean;
8
+ variant: TabVariant;
9
+ };
10
+ const VARIANT_OPTIONS: Array<TabVariant> = ["default", "small"];
11
+
12
+ export default {
13
+ title: "dx/dx-tab",
14
+ component: "dx-tab",
15
+ argTypes: {
16
+ variant: {
17
+ control: {
18
+ type: "select",
19
+ options: VARIANT_OPTIONS
20
+ }
21
+ }
22
+ }
23
+ };
24
+
25
+ const Template = ({ variant, label, isActive, useIcon }: StoryArgs) => html`
26
+ <dx-tab
27
+ label="${label}"
28
+ is-active="${isActive}"
29
+ variant="${variant}"
30
+ use-icon="${useIcon}"
31
+ ></dx-tab>
32
+ `;
33
+
34
+ export const Base = (args: StoryArgs) => Template(args);
35
+ Base.args = {
36
+ isActive: false,
37
+ useIcon: false,
38
+ label: "Banana",
39
+ variant: VARIANT_OPTIONS[0]
40
+ };
41
+
42
+ export const TabGroup = ({ variant }: { variant: TabVariant }) => html`
43
+ <style>
44
+ .container {
45
+ display: flex;
46
+ --dx-c-tab-horizontal-spacing: var(--dx-g-spacing-xs);
47
+ }
48
+ </style>
49
+ <nav class="container">
50
+ <dx-tab label="Banana" variant="${variant}"></dx-tab>
51
+ <dx-tab label="Apple" variant="${variant}"></dx-tab>
52
+ </nav>
53
+ `;
54
+
55
+ TabGroup.args = {
56
+ variant: VARIANT_OPTIONS[0]
57
+ };
@@ -1,19 +1,40 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import { track } from "dx/instrumentation";
3
3
  import cx from "classnames";
4
- import { HeaderNavVariant } from "typings/custom";
4
+ import { TabVariant } from "typings/custom";
5
+ import { normalizeBoolean } from "utils/normalizers";
5
6
 
6
- export default class HeaderNavOption extends LightningElement {
7
- @api isActive: boolean = false;
7
+ export default class Tab extends LightningElement {
8
8
  @api label: string | null = null;
9
9
  @api href: string | null = null;
10
- @api variant: HeaderNavVariant = "default";
10
+ @api variant: TabVariant = "default";
11
+
12
+ @api
13
+ get isActive() {
14
+ return this._isActive;
15
+ }
16
+
17
+ set isActive(value) {
18
+ this._isActive = normalizeBoolean(value);
19
+ }
20
+
21
+ @api
22
+ get useIcon() {
23
+ return this._useIcon;
24
+ }
25
+
26
+ set useIcon(value) {
27
+ this._useIcon = normalizeBoolean(value);
28
+ }
29
+
30
+ _useIcon = false;
31
+ _isActive = false;
11
32
 
12
33
  private sendGtm(e: PointerEvent) {
13
34
  track(e.currentTarget!, "custEv_topNavLinkClick", {
14
35
  navHeading: this.label || undefined,
15
36
  clickText: this.href || undefined,
16
- pageLocation: window.location.pathname || undefined
37
+ pageLocation: window.location.pathname
17
38
  });
18
39
  }
19
40
 
@@ -86,3 +86,6 @@ export const normalizePost = ({
86
86
  id,
87
87
  title
88
88
  });
89
+
90
+ export const normalizeBoolean = (value: string | boolean | number): boolean =>
91
+ typeof value === "string" ? value === "true" : !!value;