diff --git a/packages/calcite-components/src/components/menu-item/interfaces.ts b/packages/calcite-components/src/components/menu-item/interfaces.ts
index a25275470c7..8c862aa3064 100644
--- a/packages/calcite-components/src/components/menu-item/interfaces.ts
+++ b/packages/calcite-components/src/components/menu-item/interfaces.ts
@@ -5,3 +5,5 @@ export interface MenuItemCustomEvent {
children?: MenuItem["el"][];
isSubmenuOpen?: boolean;
}
+
+export type Layout = "horizontal" | "vertical";
diff --git a/packages/calcite-components/src/components/menu-item/menu-item.e2e.ts b/packages/calcite-components/src/components/menu-item/menu-item.e2e.ts
index 9794ef22e23..fa900686bdf 100644
--- a/packages/calcite-components/src/components/menu-item/menu-item.e2e.ts
+++ b/packages/calcite-components/src/components/menu-item/menu-item.e2e.ts
@@ -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", () => {
@@ -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`
+
+
+
+
+
+
+ `;
+ 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`
+
+
+
+ `;
+ 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`
+
+
+
+ `;
+ 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`
+
+
+
+ `;
+
+ 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);
+ });
+ });
});
diff --git a/packages/calcite-components/src/components/menu-item/menu-item.scss b/packages/calcite-components/src/components/menu-item/menu-item.scss
index 5b2602ea3c0..12fea8c2a5d 100644
--- a/packages/calcite-components/src/components/menu-item/menu-item.scss
+++ b/packages/calcite-components/src/components/menu-item/menu-item.scss
@@ -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
@@ -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 {
@@ -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 {
@@ -108,7 +139,6 @@
.icon--breadcrumb {
@apply ps-2 me-0;
- --calcite-icon-color: var(--calcite-color-text-3);
}
:host([layout="vertical"]) .icon--breadcrumb {
@@ -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 {
@@ -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;
diff --git a/packages/calcite-components/src/components/menu-item/menu-item.tsx b/packages/calcite-components/src/components/menu-item/menu-item.tsx
index 8052d1422f3..6609f266a34 100644
--- a/packages/calcite-components/src/components/menu-item/menu-item.tsx
+++ b/packages/calcite-components/src/components/menu-item/menu-item.tsx
@@ -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,
@@ -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
diff --git a/packages/calcite-components/src/custom-theme.stories.ts b/packages/calcite-components/src/custom-theme.stories.ts
index d15f02078f5..5b39ffafb7e 100644
--- a/packages/calcite-components/src/custom-theme.stories.ts
+++ b/packages/calcite-components/src/custom-theme.stories.ts
@@ -49,6 +49,7 @@ import { navigationLogoTokens, navigationLogos } from "./custom-theme/navigation
import { navigationUserTokens, navigationUsers } from "./custom-theme/navigation-user";
import { tileTokens, tile } from "./custom-theme/tile";
import { navigationTokens, navigation } from "./custom-theme/navigation";
+import { menuItem, menuItemTokens } from "./custom-theme/menu-item";
const globalTokens = {
calciteColorBrand: "#007ac2",
@@ -138,6 +139,7 @@ const kitchenSink = (args: Record, useTestValues = false) =>
${navigation} ${navigationLogos} ${navigationUsers} ${blockSection} ${block} ${rating}
+ ${menuItem}
`;
@@ -177,6 +179,7 @@ const componentTokens = {
...textAreaTokens,
...tileTokens,
...tooltipTokens,
+ ...menuItemTokens,
};
export default {
diff --git a/packages/calcite-components/src/custom-theme/menu-item.ts b/packages/calcite-components/src/custom-theme/menu-item.ts
new file mode 100644
index 00000000000..f7a5990f7fd
--- /dev/null
+++ b/packages/calcite-components/src/custom-theme/menu-item.ts
@@ -0,0 +1,45 @@
+import { html } from "../../support/formatting";
+
+export const menuItemTokens = {
+ calciteMenuItemAccentColor: "",
+ calciteMenuBackgroundColor: "",
+ calciteMenuItemSubMenuBorderColor: "",
+ calciteMenuItemSubMenuCornerRadius: "",
+ calciteMenuTextColor: "",
+};
+
+const menuHTML = (layout: string) => html`
+
+
+
+
+
+
+
+
+
+ `;
+
+export const menuItem = html` ${menuHTML("horizontal")} ${menuHTML("vertical")} `;