Skip to content

Commit

Permalink
feat(menu-item): add component tokens (#10654)
Browse files Browse the repository at this point in the history
**Related Issue:** #7180 

## Summary

Add the following component-scoped CSS Variables to calcite-menu-item:

`--calcite-menu-item-accent-color`: Specifies the border color of the
component when `active`.
`--calcite-menu-item-accent-color-hover`: Specifies the border color of
the component when hovered.
` --calcite-menu-background-color`: Specifies the background color of
the component.
`--calcite-menu-item-sub-menu-border-color`: Specifies the border color
of sub-menu.
` --calcite-menu-item-sub-menu-corner-radius`: Specifies the border
radius of sub-menu.
`--calcite-menu-text-color`: Specifies the text color of the component.
  • Loading branch information
anveshmekala authored Jan 8, 2025
1 parent 732933a commit 9dc1262
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export interface MenuItemCustomEvent {
children?: MenuItem["el"][];
isSubmenuOpen?: boolean;
}

export type Layout = "horizontal" | "vertical";
175 changes: 174 additions & 1 deletion packages/calcite-components/src/components/menu-item/menu-item.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { newE2EPage } from "@arcgis/lumina-compiler/puppeteerTesting";
import { describe, expect, it } from "vitest";
import { html } from "../../../support/formatting";
import { accessible, focusable, hidden, reflects, renders, t9n } from "../../tests/commonTests";
import { accessible, focusable, hidden, reflects, renders, t9n, themed } from "../../tests/commonTests";
import { getFocusedElementProp } from "../../tests/utils";
import { ComponentTestTokens } from "../../tests/commonTests/themed";
import { CSS } from "../../../src/components/menu-item/resources";
import { Layout } from "./interfaces";

describe("calcite-menu-item", () => {
describe("renders", () => {
Expand Down Expand Up @@ -116,4 +119,174 @@ describe("calcite-menu-item", () => {
expect(await getFocusedElementProp(page, "id")).toBe("Nature");
expect(eventSpy).toHaveReceivedEventTimes(2);
});

describe("theme", () => {
const menuWithSlottedSubmenuHTML = (layout: Layout): string => html`
<calcite-menu layout="${layout}">
<calcite-menu-item text="calcite-navigation" href="#calcite-menu">
<calcite-menu-item slot="submenu-item" text="Slots"></calcite-menu-item>
<calcite-menu-item slot="submenu-item" text="Css vars"></calcite-menu-item>
</calcite-menu-item>
</calcite-menu>
`;
describe("slotted submenu", () => {
const tokens = (layout: Layout): ComponentTestTokens => {
return {
"--calcite-menu-background-color": [{
selector: "calcite-menu-item",
shadowSelector: `calcite-action`,
targetProp: "--calcite-action-background-color-press",
state: { press: { attribute: "class", value: "dropdown-action" } },
}, {
selector: "calcite-menu-item",
shadowSelector: `calcite-action`,
targetProp: "--calcite-action-background-color",
}],
"--calcite-menu-text-color": {
selector: "calcite-menu-item",
shadowSelector: `calcite-action`,
targetProp: "--calcite-action-text-color",
},
"--calcite-menu-item-sub-menu-corner-radius": {
selector: "calcite-menu-item",
shadowSelector: `.${CSS.dropdownMenuItems}`,
targetProp: "borderRadius",
},
"--calcite-menu-item-sub-menu-border-color": {
selector: "calcite-menu-item",
shadowSelector: `.${CSS.dropdownMenuItems}`,
targetProp: layout === "horizontal" ? "borderColor" : "borderBlockColor",
}
};
}

describe("horizontal layout", () => {
themed(menuWithSlottedSubmenuHTML("horizontal"), tokens("horizontal"));
});

describe("vertical layout", () => {
themed(menuWithSlottedSubmenuHTML("vertical"), tokens("vertical"));
});
});

describe("default", () => {
const menuHTML = (layout: Layout): string => html`
<calcite-menu layout="${layout}">
<calcite-menu-item text="Ideas"> </calcite-menu-item>
</calcite-menu>
`;
const tokens: ComponentTestTokens = {
"--calcite-menu-text-color": [
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: "color",
},
{
selector: "calcite-menu-item",
shadowSelector: ` .${CSS.content} `,
targetProp: "color",
state: { press: { attribute: "role", value: `menuitem` } },
}
],
"--calcite-menu-background-color": [
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: "backgroundColor",
},
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: "backgroundColor",
state: { press: { attribute: "role", value: `menuitem` } },
}],
}

describe("horizontal layout", () => {
themed(menuHTML("horizontal"), {
...tokens, "--calcite-menu-item-accent-color": {
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: "borderBlockEndColor",
state: "hover",
},
});
})

describe("vertical layout", () => {
themed(menuHTML("vertical"), tokens);
});
})

describe("active", () => {
const activeMenuItemHTML = (layout: Layout): string => html`
<calcite-menu layout="${layout}">
<calcite-menu-item text="Ideas" active> </calcite-menu-item>
</calcite-menu>
`;
const tokens = (layout: Layout): ComponentTestTokens => {
const targetBorderProp = layout === "horizontal" ? "borderBlockEndColor" : "borderInlineEndColor";
return {
"--calcite-menu-item-accent-color": [{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: targetBorderProp
},
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.content}`,
targetProp: targetBorderProp,
state: "hover",
},]
}
};
describe("horizontal layout", () => {
themed(activeMenuItemHTML("horizontal"), tokens("horizontal"));
})

describe("vertical layout", () => {
themed(activeMenuItemHTML("vertical"), tokens("vertical"));
})
});

describe("icons", () => {
const iconMenuItemHTML: string = html` <calcite-menu>
<calcite-menu-item text="Ideas" breadcrumb icon-start="layers" icon-end="layers">
<calcite-menu-item
href="#calcite-navigation-css-vars"
icon-start="multiple-variables"
slot="submenu-item"
text="Css vars"
></calcite-menu-item>
</calcite-menu-item>
</calcite-menu>`;

const tokens: ComponentTestTokens = {
"--calcite-menu-text-color": [
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.iconStart}`,
targetProp: "color",
},
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.iconEnd}`,
targetProp: "color",
},
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.iconBreadcrumb}`,
targetProp: "color",
},
{
selector: "calcite-menu-item",
shadowSelector: `.${CSS.iconDropdown}`,
targetProp: "color",
},
],
};
themed(iconMenuItemHTML, tokens);
});
});
});
85 changes: 62 additions & 23 deletions packages/calcite-components/src/components/menu-item/menu-item.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* CSS Custom Properties
*
* These properties can be overridden using the component's tag as selector.
*
* @prop --calcite-menu-item-accent-color: Specifies the border color of the component when `active`.
* @prop --calcite-menu-background-color: Specifies the background color of the component.
* @prop --calcite-menu-item-sub-menu-border-color: Specifies the border color of sub-menu.
* @prop --calcite-menu-item-sub-menu-corner-radius: Specifies the border radius of sub-menu.
* @prop --calcite-menu-text-color: Specifies the text color of the component.
*/

:host {
@apply flex
items-center
Expand Down Expand Up @@ -32,46 +44,66 @@
cursor-pointer
outline-none
text-0
text-color-2
box-border
bg-foreground-1
px-4
h-full
w-full;
text-decoration: none;
border-block-end: theme("spacing[0.5]") solid transparent;
padding-block-start: theme("spacing[0.5]");
border-block-end: theme("spacing[0.5]") solid var(--calcite-color-transparent);

background-color: var(
--calcite-menu-background-color,
var(--calcite-internal-menu-background-color, var(--calcite-color-foreground-1))
);
color: var(--calcite-menu-text-color, var(--calcite-internal-menu-text-color, var(--calcite-color-text-2)));

&:hover {
@apply text-color-2 border-b-color-2;
border-block-end-color: var(--calcite-menu-item-accent-color, var(--calcite-color-border-2));
}

&:focus {
@apply text-color-2 focus-inset border-b-4;
@apply focus-inset border-b-4;
padding-block-start: theme("spacing.1");
border-block-end-width: theme("spacing.1");
}

&:active {
@apply bg-foreground-3 text-color-1;
--calcite-internal-menu-background-color: var(--calcite-color-foreground-3);
--calcite-internal-menu-text-color: var(--calcite-color-text-1);
}

& span {
display: inline-flex;
}

&.layout--vertical {
@apply flex w-full justify-start;
padding-block: 1rem;
border-block-end: 0;
border-inline-end: theme("spacing.1") solid transparent;
border-inline-end: theme("spacing.1") solid var(--calcite-color-transparent);
}
}

:host([active]) .content {
@apply text-color-1;
border-color: var(--calcite-color-brand);
:host([layout="vertical"]) .content {
@apply px-3;
}

:host([active]) {
.content {
--calcite-internal-menu-text-color: var(--calcite-color-text-1);
border-color: var(--calcite-menu-item-accent-color, var(--calcite-color-brand));
}
.icon {
--calcite-icon-color: var(--calcite-color-brand);
--calcite-internal-menu-item-icon-color: var(--calcite-color-brand);
}
}
:host([layout="vertical"]) .content {
@apply px-3;

.icon {
color: var(
--calcite-menu-text-color,
var(--calcite-icon-color, var(--calcite-internal-menu-item-icon-color, var(--calcite-color-text-3)))
);
}

.icon--start {
Expand All @@ -88,7 +120,6 @@

.icon--dropdown {
@apply ms-auto me-0 ps-2 relative;
--calcite-icon-color: var(--calcite-color-text-3);
}

:host([layout="vertical"]) .icon--end ~ .icon--dropdown {
Expand All @@ -108,7 +139,6 @@

.icon--breadcrumb {
@apply ps-2 me-0;
--calcite-icon-color: var(--calcite-color-text-3);
}

:host([layout="vertical"]) .icon--breadcrumb {
Expand All @@ -130,32 +160,41 @@
calcite-action {
@apply relative h-auto;
border-inline-start: 1px solid var(--calcite-color-foreground-1);
--calcite-action-background-color: var(--calcite-menu-background-color);
--calcite-action-text-color: var(--calcite-menu-text-color);

&::after {
@apply block w-px absolute -start-px;
content: "";
inset-block: theme("spacing.3");
background-color: var(--calcite-color-border-3);
}

&:hover::after {
@apply h-full;
inset-block: 0;
}

&:active {
--calcite-action-background-color-press: var(--calcite-menu-background-color);
}
}

// extends the broder block of calcite action when hovered on content
.content:focus ~ calcite-action,
.content:hover ~ calcite-action {
@apply text-color-1;
border-inline-start: 1px solid var(--calcite-color-border-3);
}
--calcite-action-text-color: var(--calcite-menu-text-color, var(--calcite-color-text-1));

.container:hover .dropdown-action {
@apply bg-foreground-2;
&::after {
@apply h-full;
inset-block: 0;
}
}

.dropdown-menu-items {
@apply absolute h-auto flex-col hidden overflow-visible min-w-full;
border: 1px solid var(--calcite-color-border-3);
background: var(--calcite-color-foreground-1);
border: 1px solid var(--calcite-menu-item-sub-menu-border-color, var(--calcite-color-border-3));
border-radius: var(--calcite-menu-item-sub-menu-corner-radius, var(--calcite-corner-radius));
inset-block-start: 100%;
z-index: theme("zIndex.dropdown");
&.open {
Expand All @@ -173,7 +212,7 @@ calcite-action {
}

.dropdown--vertical.dropdown-menu-items {
@apply relative rounded-none;
@apply relative;
box-shadow: none;
inset-block-start: 0;
transform: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
state,
JsxNode,
} from "@arcgis/lumina";
import { FlipContext } from "../interfaces";
import { FlipContext, Layout } from "../interfaces";
import { Direction, getElementDir, slotChangeGetAssignedElements } from "../../utils/dom";
import {
componentFocusable,
Expand All @@ -33,8 +33,6 @@ declare global {
}
}

type Layout = "horizontal" | "vertical";

/** @slot submenu-item - A slot for adding `calcite-menu-item`s in a submenu. */
export class MenuItem extends LitElement implements LoadableComponent {
// #region Static Members
Expand Down
Loading

0 comments on commit 9dc1262

Please sign in to comment.