-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(menu, menu-item): Adds menu & menu-item components. #6901
Changes from 22 commits
4a60b88
29a9822
cdb6238
15392ce
a30cd27
a8ba656
47f6e44
cb9c37f
d972bdb
5f21d0b
4a72f5f
de88018
96298be
e8a25a8
d153f37
b09d8aa
9ef8b37
fc38b1f
8725ed3
71a58e6
eed1631
d2bfa3d
bf4a06a
7dbfc7e
e0f8c5f
7bf6557
a98d8f0
a8bd6d9
a4a7f94
f44308c
9353872
4d09582
5167d55
3d09c64
56d9d26
43f9076
9dcafc1
cff5c89
aed03cf
5fe121d
142e4fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```html | ||
<calcite-menu> <calcite-menu-item id="Nature" text="Nature"> </calcite-menu-item></calcite-menu> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Nested SubMenu with href. | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```html | ||
<calcite-menu> | ||
<calcite-menu-item id="Nature" text="Nature" href="#"> | ||
<calcite-menu-item id="Mountains" text="Mountains" slot="sub-menu-item"> </calcite-menu-item> | ||
</calcite-menu-item> | ||
</calcite-menu> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface MenuItemCustomEvent { | ||
event: KeyboardEvent; | ||
children?: HTMLCalciteMenuItemElement[]; | ||
isSubMenuOpen?: boolean; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { newE2EPage } from "@stencil/core/testing"; | ||
import { html } from "../../../support/formatting"; | ||
import { accessible, focusable, hidden, reflects, renders } from "../../tests/commonTests"; | ||
|
||
describe("calcite-menu-item", () => { | ||
describe("renders", () => { | ||
renders("calcite-menu-item", { display: "flex" }); | ||
}); | ||
|
||
it("reflects", async () => | ||
reflects("calcite-menu-item", [ | ||
{ | ||
propertyName: "active", | ||
value: "true" | ||
}, | ||
{ | ||
propertyName: "iconStart", | ||
value: "layers" | ||
}, | ||
{ | ||
propertyName: "iconEnd", | ||
value: "layers" | ||
}, | ||
{ | ||
propertyName: "href", | ||
value: "www.esri.com" | ||
}, | ||
{ | ||
propertyName: "target", | ||
value: "_blank" | ||
}, | ||
{ | ||
propertyName: "text", | ||
value: "Calcite" | ||
} | ||
])); | ||
|
||
it("honors hidden attribute", async () => hidden("calcite-menu-item")); | ||
|
||
describe("accessible", () => { | ||
accessible(html`<calcite-menu> <calcite-menu-item text="calcite"> </calcite-menu-item> </calcite-menu>`); | ||
}); | ||
|
||
it("is focusable", () => focusable("calcite-menu-item")); | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
//todo : debug why calcite-menu-item requires calite-menu as parent for the click to emit event in test. | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is being caused by the click-handling element (anchor) not stretching along with its host and therefore not being in the center when the click happens ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense. we can mark |
||
it("should emit calciteMenuItemSelect event on user click", async () => { | ||
const page = await newE2EPage(); | ||
await page.setContent( | ||
html`<calcite-menu> | ||
<calcite-menu-item id="Nature" text="Nature" href="#nature"> </calcite-menu-item> | ||
</calcite-menu>` | ||
); | ||
|
||
const menuItem = await page.find("calcite-menu-item"); | ||
const eventSpy = await menuItem.spyOnEvent("calciteMenuItemSelect"); | ||
|
||
await menuItem.click(); | ||
await page.waitForChanges(); | ||
expect(await page.evaluate(() => document.activeElement.id)).toBe("Nature"); | ||
expect(eventSpy).toHaveReceivedEventTimes(1); | ||
}); | ||
|
||
it("should emit calciteMenuItemSelect event when user select the text area of the component using Enter or Space key", async () => { | ||
const page = await newE2EPage(); | ||
await page.setContent(html` | ||
<calcite-menu> | ||
<calcite-menu-item id="Nature" text="Nature" href="#nature"> | ||
<calcite-menu-item id="Mountains" text="Mountains" slot="sub-menu-item"> </calcite-menu-item> | ||
<calcite-menu-item id="Rivers" text="Rivers" slot="sub-menu-item"> </calcite-menu-item> | ||
</calcite-menu-item> | ||
</calcite-menu> | ||
`); | ||
|
||
const element = await page.find("calcite-menu-item"); | ||
const eventSpy = await element.spyOnEvent("calciteMenuItemSelect"); | ||
|
||
await page.keyboard.press("Tab"); | ||
await page.waitForChanges(); | ||
expect(await page.evaluate(() => document.activeElement.id)).toBe("Nature"); | ||
expect(eventSpy).not.toHaveReceivedEvent(); | ||
|
||
await page.keyboard.press("Enter"); | ||
await page.waitForChanges(); | ||
expect(eventSpy).toHaveReceivedEventTimes(1); | ||
|
||
await page.keyboard.press("Space"); | ||
await page.waitForChanges(); | ||
expect(eventSpy).toHaveReceivedEventTimes(2); | ||
|
||
await page.keyboard.press("Tab"); | ||
await page.waitForChanges(); | ||
expect(await page.evaluate(() => document.activeElement.id)).toBe("Nature"); | ||
expect(eventSpy).toHaveReceivedEventTimes(2); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
:host { | ||
@apply flex | ||
items-center | ||
relative | ||
h-full | ||
box-border; | ||
} | ||
|
||
:host(:not([layout="vertical"])) { | ||
flex-shrink: 0; | ||
& .container, | ||
& .item-content, | ||
& a { | ||
min-block-size: theme("spacing.12"); | ||
} | ||
} | ||
|
||
:host([layout="vertical"]) { | ||
@apply w-full; | ||
} | ||
|
||
.container, | ||
.item-content { | ||
display: flex; | ||
flex-direction: row; | ||
block-size: 100%; | ||
inline-size: 100%; | ||
align-items: stretch; | ||
} | ||
|
||
a { | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@apply flex | ||
h-full | ||
items-center | ||
relative | ||
justify-center | ||
cursor-pointer | ||
px-4 | ||
outline-none | ||
text-0 | ||
text-color-2 box-border bg-foreground-1; | ||
text-decoration: none; | ||
border-block-end: theme("spacing[0.5]") solid transparent; | ||
padding-block-start: theme("spacing[0.5]"); | ||
&:hover { | ||
@apply bg-foreground-2 text-color-2; | ||
} | ||
&:focus { | ||
@apply bg-foreground-2 text-color-2 focus-inset; | ||
} | ||
&:active { | ||
@apply bg-foreground-3 text-color-1; | ||
} | ||
& span { | ||
display: inline-flex; | ||
} | ||
&.layout--vertical { | ||
@apply py-4 w-full; | ||
display: flex; | ||
justify-content: flex-start; | ||
border-block-end: 0; | ||
border-inline-end: theme("spacing.1") solid transparent; | ||
} | ||
} | ||
|
||
:host([active]) a { | ||
@apply text-color-1; | ||
border-color: var(--calcite-ui-brand); | ||
.icon { | ||
--calcite-ui-icon-color: var(--calcite-ui-brand); | ||
} | ||
} | ||
|
||
.icon--start { | ||
@apply mr-3; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
.icon--end { | ||
@apply ml-3; | ||
} | ||
|
||
.icon--dropdown { | ||
@apply pl-2 mr-0 ml-auto; | ||
--calcite-ui-icon-color: var(--calcite-ui-text-3); | ||
position: relative; | ||
} | ||
|
||
:host([layout="vertical"]) .icon--dropdown { | ||
inset-inline-start: theme("spacing.1"); | ||
} | ||
|
||
.icon--breadcrumb { | ||
@apply pl-2 mr-0; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
--calcite-ui-icon-color: var(--calcite-ui-text-3); | ||
} | ||
|
||
:host([breadcrumb]) a { | ||
@apply pr-3; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
calcite-action { | ||
block-size: auto; | ||
position: relative; | ||
border-inline-start: 1px solid var(--calcite-ui-foreground-1); | ||
&:after { | ||
display: block; | ||
inline-size: 1px; | ||
content: ""; | ||
inset-block: theme("spacing.3"); | ||
background-color: var(--calcite-ui-border-3); | ||
position: absolute; | ||
inset-inline-start: -1px; | ||
z-index: 9; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
&:hover:after { | ||
block-size: 100%; | ||
inset-block: 0; | ||
} | ||
} | ||
|
||
a:focus ~ calcite-action, | ||
a:hover ~ calcite-action { | ||
@apply text-color-1; | ||
border-inline-start: 1px solid var(--calcite-ui-border-3); | ||
} | ||
|
||
.container:hover .dropdown-action { | ||
@apply bg-foreground-2; | ||
} | ||
|
||
.dropdown-menu-items { | ||
border: 1px solid var(--calcite-ui-border-3); | ||
position: absolute; | ||
background: var(--calcite-ui-foreground-1); | ||
z-index: 9; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
block-size: auto; | ||
flex-direction: column; | ||
min-inline-size: 100%; | ||
display: none; | ||
transition: all var(--calcite-internal-animation-timing-slow) ease-in-out; | ||
overflow: visible; | ||
inset-block-start: 100%; | ||
|
||
&.open { | ||
display: block; | ||
} | ||
&.nested { | ||
z-index: 9; | ||
anveshmekala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
inset-block-start: -1px; | ||
position: absolute; | ||
transform: translateX(calc(100% - 2px)); | ||
} | ||
} | ||
|
||
.parent--vertical { | ||
flex-direction: column; | ||
} | ||
|
||
.dropdown--vertical.dropdown-menu-items { | ||
position: relative; | ||
border-radius: 0; | ||
box-shadow: none; | ||
inset-block-start: 0; | ||
transform: none; | ||
&:last-of-type { | ||
border-inline: 0; | ||
} | ||
} | ||
|
||
:host([slot="sub-menu-item"]) .parent--vertical { | ||
padding-inline-start: theme("spacing.7"); | ||
} | ||
|
||
.dropdown-menu-items.is-rtl.nested { | ||
transform: translateX(calc(-100% + 2px)); | ||
} | ||
|
||
.dropdown--vertical.dropdown-menu-items.is-rtl.nested { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should revisit this later to reduce specificity. This also bypasses the way we've been applying our BEM-like™ classes. |
||
transform: none; | ||
} | ||
|
||
.dropdown-action { | ||
z-index: 0; | ||
} | ||
|
||
.hover-href-icon { | ||
opacity: 0; | ||
padding-inline-start: 2rem; | ||
margin-inline-start: auto; | ||
transition: opacity var(--calcite-internal-animation-timing-medium) ease-in-out, | ||
inset-inline-end var(--calcite-internal-animation-timing-slow) ease-in-out; | ||
inset-inline-end: 0.25rem; | ||
position: relative; | ||
} | ||
|
||
a:focus .hover-href-icon, | ||
a:hover .hover-href-icon { | ||
opacity: 1; | ||
inset-inline-end: -0.25rem; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { boolean, iconNames, storyFilters } from "../../../.storybook/helpers"; | ||
import readme from "./readme.md"; | ||
import { html } from "../../../support/formatting"; | ||
import { select, text } from "@storybook/addon-knobs"; | ||
|
||
export default { | ||
title: "Components/Menu Item", | ||
parameters: { | ||
notes: readme | ||
}, | ||
...storyFilters() | ||
}; | ||
|
||
export const simple = (): string => html` <calcite-menu> | ||
<calcite-menu-item | ||
text="${text("text", "My nav item")}" | ||
src="${text("src", "")}" | ||
href="${text("href", "")}" | ||
rel="${text("rel", "")}" | ||
target="${text("target", "")}" | ||
label="${text("label", "")}" | ||
${boolean("active", false)} | ||
${boolean("breadcrumb", false)} | ||
${boolean("text-enabled", true)} | ||
/> | ||
</calcite-menu>`; | ||
|
||
export const iconStart = (): string => html` <calcite-menu> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason for keeping this is to verify how the |
||
<calcite-menu-item | ||
text="${text("text", "My nav item")}" | ||
src="${text("src", "")}" | ||
href="${text("href", "")}" | ||
rel="${text("rel", "")}" | ||
target="${text("target", "")}" | ||
label="${text("label", "")}" | ||
icon-start="${select("icon-start", iconNames, iconNames[0])}" | ||
${boolean("active", false)} | ||
${boolean("breadcrumb", false)} | ||
${boolean("text-enabled", true)} | ||
/> | ||
</calcite-menu>`; | ||
|
||
export const iconEnd = (): string => html` <calcite-menu> | ||
<calcite-menu-item | ||
text="${text("text", "My nav item")}" | ||
src="${text("src", "")}" | ||
href="${text("href", "")}" | ||
rel="${text("rel", "")}" | ||
target="${text("target", "")}" | ||
label="${text("label", "")}" | ||
icon-end="${select("icon-end", iconNames, iconNames[0])}" | ||
${boolean("active", false)} | ||
${boolean("breadcrumb", false)} | ||
${boolean("text-enabled", true)} | ||
/> | ||
</calcite-menu>`; | ||
|
||
export const iconsBoth = (): string => html` <calcite-menu> | ||
<calcite-menu-item | ||
text="${text("text", "My nav item")}" | ||
src="${text("src", "")}" | ||
href="${text("href", "")}" | ||
rel="${text("rel", "")}" | ||
target="${text("target", "")}" | ||
label="${text("label", "")}" | ||
icon-end="${select("icon-end", iconNames, "")}" | ||
icon-start="${select("icon-start", iconNames, "")}" | ||
${boolean("active", false)} | ||
${boolean("breadcrumb", false)} | ||
${boolean("text-enabled", true)} | ||
/> | ||
</calcite-menu>`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: maybe we should take out the
id
attributes from the usage files