diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts
index 718aac02da7..a6ee8543333 100644
--- a/packages/calcite-components/src/components.d.ts
+++ b/packages/calcite-components/src/components.d.ts
@@ -395,6 +395,10 @@ export namespace Components {
* When `true`, the component is expanded.
*/
"expanded": boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements": FlipPlacement[];
/**
* Accessible name for the component.
*/
@@ -420,6 +424,10 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement": LogicalPlacement;
/**
* Specifies the size of the `calcite-action-menu`.
*/
@@ -626,6 +634,10 @@ export namespace Components {
* When `true`, displays a drag handle in the header.
*/
"dragHandle": boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements": FlipPlacement[];
/**
* The component header text.
*/
@@ -666,6 +678,10 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement": LogicalPlacement;
/**
* Sets focus on the component's first tabbable element.
*/
@@ -3858,6 +3874,10 @@ export namespace Components {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled": boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements": FlipPlacement[];
/**
* The component header text.
*/
@@ -3886,6 +3906,10 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement": LogicalPlacement;
/**
* Specifies the size of the component.
*/
@@ -8321,6 +8345,10 @@ declare namespace LocalJSX {
* When `true`, the component is expanded.
*/
"expanded"?: boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements"?: FlipPlacement[];
/**
* Accessible name for the component.
*/
@@ -8346,6 +8374,10 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement"?: LogicalPlacement;
/**
* Specifies the size of the `calcite-action-menu`.
*/
@@ -8559,6 +8591,10 @@ declare namespace LocalJSX {
* When `true`, displays a drag handle in the header.
*/
"dragHandle"?: boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements"?: FlipPlacement[];
/**
* The component header text.
*/
@@ -8620,6 +8656,10 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement"?: LogicalPlacement;
/**
* Displays a status-related indicator icon.
* @deprecated Use `icon-start` instead.
@@ -11980,6 +12020,10 @@ declare namespace LocalJSX {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled"?: boolean;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ "flipPlacements"?: FlipPlacement[];
/**
* The component header text.
*/
@@ -12020,6 +12064,10 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ "placement"?: LogicalPlacement;
/**
* Specifies the size of the component.
*/
diff --git a/packages/calcite-components/src/components/action-group/action-group.e2e.ts b/packages/calcite-components/src/components/action-group/action-group.e2e.ts
index 288761da844..c2ba175fd86 100755
--- a/packages/calcite-components/src/components/action-group/action-group.e2e.ts
+++ b/packages/calcite-components/src/components/action-group/action-group.e2e.ts
@@ -1,5 +1,16 @@
import { newE2EPage } from "@stencil/core/testing";
-import { accessible, defaults, focusable, hidden, renders, slots, t9n, themed } from "../../tests/commonTests";
+import {
+ accessible,
+ defaults,
+ focusable,
+ handlesActionMenuPlacements,
+ hidden,
+ reflects,
+ renders,
+ slots,
+ t9n,
+ themed,
+} from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { CSS, SLOTS } from "./resources";
@@ -19,6 +30,23 @@ describe("calcite-action-group", () => {
propertyName: "overlayPositioning",
defaultValue: "absolute",
},
+ {
+ propertyName: "menuPlacement",
+ defaultValue: undefined,
+ },
+ {
+ propertyName: "menuFlipPlacements",
+ defaultValue: undefined,
+ },
+ ]);
+ });
+
+ describe("reflects", () => {
+ reflects("calcite-action-group", [
+ {
+ propertyName: "menuPlacement",
+ value: "bottom",
+ },
]);
});
@@ -42,6 +70,15 @@ describe("calcite-action-group", () => {
slots("calcite-action-group", SLOTS);
});
+ describe("handles action-menu placement and flipPlacements", () => {
+ handlesActionMenuPlacements(html`
+
+
+
+
+ `);
+ });
+
it("should honor scale of expand icon", async () => {
const page = await newE2EPage({ html: actionGroupHTML });
const menu = await page.find(`calcite-action-group >>> calcite-action-menu`);
diff --git a/packages/calcite-components/src/components/action-group/action-group.tsx b/packages/calcite-components/src/components/action-group/action-group.tsx
index 4d9f309e002..df224f634e9 100755
--- a/packages/calcite-components/src/components/action-group/action-group.tsx
+++ b/packages/calcite-components/src/components/action-group/action-group.tsx
@@ -21,7 +21,7 @@ import {
} from "../../utils/t9n";
import { SLOTS as ACTION_MENU_SLOTS } from "../action-menu/resources";
import { Layout, Scale } from "../interfaces";
-import { OverlayPositioning } from "../../utils/floating-ui";
+import { FlipPlacement, LogicalPlacement, OverlayPositioning } from "../../utils/floating-ui";
import { slotChangeHasAssignedElement } from "../../utils/dom";
import { Columns } from "./interfaces";
import { ActionGroupMessages } from "./assets/action-group/t9n";
@@ -95,6 +95,16 @@ export class ActionGroup
*/
@Prop({ reflect: true }) scale: Scale;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ @Prop() menuFlipPlacements: FlipPlacement[];
+
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ @Prop({ reflect: true }) menuPlacement: LogicalPlacement;
+
/**
* Made into a prop for testing purposes only
*
@@ -178,19 +188,30 @@ export class ActionGroup
// --------------------------------------------------------------------------
renderMenu(): VNode {
- const { expanded, menuOpen, scale, layout, messages, overlayPositioning, hasMenuActions } =
- this;
+ const {
+ expanded,
+ menuOpen,
+ scale,
+ layout,
+ messages,
+ overlayPositioning,
+ hasMenuActions,
+ menuFlipPlacements,
+ menuPlacement,
+ } = this;
return (
{
@@ -43,6 +45,14 @@ describe("calcite-block", () => {
propertyName: "overlayPositioning",
defaultValue: "absolute",
},
+ {
+ propertyName: "menuPlacement",
+ defaultValue: defaultEndMenuPlacement,
+ },
+ {
+ propertyName: "menuFlipPlacements",
+ defaultValue: undefined,
+ },
]);
});
@@ -64,6 +74,10 @@ describe("calcite-block", () => {
propertyName: "overlayPositioning",
value: "fixed",
},
+ {
+ propertyName: "menuPlacement",
+ value: "bottom",
+ },
]);
});
@@ -133,6 +147,15 @@ describe("calcite-block", () => {
);
});
+ describe("handles action-menu placement and flipPlacements", () => {
+ handlesActionMenuPlacements(html`
+
+
+ content
+
+ `);
+ });
+
it("has a loading state", async () => {
const page = await newE2EPage({
html: `
diff --git a/packages/calcite-components/src/components/block/block.stories.ts b/packages/calcite-components/src/components/block/block.stories.ts
index 3798bcedef2..14144027057 100644
--- a/packages/calcite-components/src/components/block/block.stories.ts
+++ b/packages/calcite-components/src/components/block/block.stories.ts
@@ -3,11 +3,15 @@ import { boolean } from "../../../.storybook/utils";
import { placeholderImage } from "../../../.storybook/placeholder-image";
import { html } from "../../../support/formatting";
import { ATTRIBUTES } from "../../../.storybook/resources";
+import { defaultEndMenuPlacement, placements } from "../../utils/floating-ui";
import { Block } from "./block";
const { toggleDisplay } = ATTRIBUTES;
interface BlockStoryArgs
- extends Pick,
+ extends Pick<
+ Block,
+ "heading" | "description" | "open" | "collapsible" | "loading" | "disabled" | "headingLevel" | "menuPlacement"
+ >,
Pick {
text: string;
sectionOpen: BlockSection["open"];
@@ -16,6 +20,7 @@ interface BlockStoryArgs
export default {
title: "Components/Block",
args: {
+ menuPlacement: defaultEndMenuPlacement,
heading: "Heading",
description: "description",
open: true,
@@ -28,6 +33,10 @@ export default {
toggleDisplay: toggleDisplay.defaultValue,
},
argTypes: {
+ menuPlacement: {
+ options: placements,
+ control: { type: "select" },
+ },
headingLevel: {
control: { type: "number", min: 1, max: 6, step: 1 },
},
@@ -42,6 +51,7 @@ export const simple = (args: BlockStoryArgs): string => html`
diff --git a/packages/calcite-components/src/components/panel/panel.e2e.ts b/packages/calcite-components/src/components/panel/panel.e2e.ts
index e13772267a4..7900f2fb432 100644
--- a/packages/calcite-components/src/components/panel/panel.e2e.ts
+++ b/packages/calcite-components/src/components/panel/panel.e2e.ts
@@ -12,8 +12,10 @@ import {
slots,
t9n,
themed,
+ handlesActionMenuPlacements,
} from "../../tests/commonTests";
import { GlobalTestProps } from "../../tests/utils";
+import { defaultEndMenuPlacement } from "../../utils/floating-ui";
import { CSS, IDS, SLOTS } from "./resources";
type TestWindow = GlobalTestProps<{
@@ -88,6 +90,14 @@ describe("calcite-panel", () => {
hidden("calcite-panel");
});
+ describe("handles action-menu placement and flipPlacements", () => {
+ handlesActionMenuPlacements(html`
+
+
+
+ `);
+ });
+
describe("defaults", () => {
defaults("calcite-panel", [
{
@@ -122,6 +132,14 @@ describe("calcite-panel", () => {
propertyName: "scale",
defaultValue: "m",
},
+ {
+ propertyName: "menuPlacement",
+ defaultValue: defaultEndMenuPlacement,
+ },
+ {
+ propertyName: "menuFlipPlacements",
+ defaultValue: undefined,
+ },
]);
});
@@ -143,6 +161,10 @@ describe("calcite-panel", () => {
propertyName: "overlayPositioning",
value: "fixed",
},
+ {
+ propertyName: "menuPlacement",
+ value: "bottom",
+ },
]);
});
diff --git a/packages/calcite-components/src/components/panel/panel.stories.ts b/packages/calcite-components/src/components/panel/panel.stories.ts
index d6a48a5ecc3..9f318bbcd71 100644
--- a/packages/calcite-components/src/components/panel/panel.stories.ts
+++ b/packages/calcite-components/src/components/panel/panel.stories.ts
@@ -1,6 +1,7 @@
import { boolean, modesDarkDefault } from "../../../.storybook/utils";
import { html } from "../../../support/formatting";
import { ATTRIBUTES } from "../../../.storybook/resources";
+import { defaultEndMenuPlacement, placements } from "../../utils/floating-ui";
import { Panel } from "./panel";
import { SLOTS } from "./resources";
const { collapseDirection, scale } = ATTRIBUTES;
@@ -8,7 +9,15 @@ const { collapseDirection, scale } = ATTRIBUTES;
interface PanelStoryArgs
extends Pick<
Panel,
- "closed" | "disabled" | "closable" | "collapsed" | "collapsible" | "collapseDirection" | "loading" | "scale"
+ | "closed"
+ | "disabled"
+ | "closable"
+ | "collapsed"
+ | "collapsible"
+ | "collapseDirection"
+ | "loading"
+ | "scale"
+ | "menuPlacement"
> {
heightScale: string;
}
@@ -16,6 +25,7 @@ interface PanelStoryArgs
export default {
title: "Components/Panel",
args: {
+ menuPlacement: defaultEndMenuPlacement,
closed: false,
disabled: false,
closable: false,
@@ -27,6 +37,10 @@ export default {
loading: false,
},
argTypes: {
+ menuPlacement: {
+ options: placements,
+ control: { type: "select" },
+ },
collapseDirection: {
options: collapseDirection.values,
control: { type: "select" },
@@ -91,6 +105,7 @@ export const simple = (args: PanelStoryArgs): string => html`
heightScale="${args.heightScale}"
scale="${args.scale}"
${boolean("loading", args.loading)}
+ menu-placement="${args.menuPlacement}"
heading="Heading"
description="A great panel description"
>
diff --git a/packages/calcite-components/src/components/panel/panel.tsx b/packages/calcite-components/src/components/panel/panel.tsx
index 47245776ec7..5219f432203 100644
--- a/packages/calcite-components/src/components/panel/panel.tsx
+++ b/packages/calcite-components/src/components/panel/panel.tsx
@@ -39,7 +39,12 @@ import {
T9nComponent,
updateMessages,
} from "../../utils/t9n";
-import { OverlayPositioning } from "../../utils/floating-ui";
+import {
+ defaultEndMenuPlacement,
+ FlipPlacement,
+ LogicalPlacement,
+ OverlayPositioning,
+} from "../../utils/floating-ui";
import { CollapseDirection } from "../interfaces";
import { Scale } from "../interfaces";
import { PanelMessages } from "./assets/panel/t9n";
@@ -130,11 +135,21 @@ export class Panel
/** A description for the component. */
@Prop() description: string;
+ /**
+ * Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
+ */
+ @Prop() menuFlipPlacements: FlipPlacement[];
+
/**
* When `true`, the action menu items in the `header-menu-actions` slot are open.
*/
@Prop({ reflect: true }) menuOpen = false;
+ /**
+ * Determines where the action menu will be positioned.
+ */
+ @Prop({ reflect: true }) menuPlacement: LogicalPlacement = defaultEndMenuPlacement;
+
/**
* Use this property to override individual strings used by the component.
*/
@@ -548,17 +563,17 @@ export class Panel
}
renderMenu(): VNode {
- const { hasMenuItems, messages, menuOpen } = this;
+ const { hasMenuItems, messages, menuOpen, menuFlipPlacements, menuPlacement } = this;
return (
{
+ * handlesActionMenuPlacements(html`
+ *
+ *
+ *
+ * `);
+ * });
+ *
+ * @param componentTagOrHTML - The component tag or HTML markup to test against.
+ */
+export async function handlesActionMenuPlacements(componentTagOrHTML: TagOrHTML): Promise {
+ it("handles placement and flipPlacements", async () => {
+ const page = await simplePageSetup(componentTagOrHTML);
+ const tag = getTag(componentTagOrHTML);
+
+ await page.waitForChanges();
+
+ const flipPlacements = ["top", "bottom"];
+
+ const component = await page.find(tag);
+ component.setProperty("menuFlipPlacements", flipPlacements);
+ component.setProperty("menuPlacement", "top");
+ await page.waitForChanges();
+
+ const actionMenu = await page.find(`${tag} >>> calcite-action-menu`);
+
+ expect(await actionMenu.getProperty("placement")).toBe("top");
+ expect(await actionMenu.getProperty("flipPlacements")).toEqual(flipPlacements);
+ });
+}
diff --git a/packages/calcite-components/src/tests/commonTests/index.ts b/packages/calcite-components/src/tests/commonTests/index.ts
index f4e7e12baae..5842da83559 100644
--- a/packages/calcite-components/src/tests/commonTests/index.ts
+++ b/packages/calcite-components/src/tests/commonTests/index.ts
@@ -5,7 +5,7 @@ export { reflects } from "./reflects";
export { renders } from "./renders";
export { disabled } from "./disabled";
export { hidden } from "./hidden";
-export { floatingUIOwner, delegatesToFloatingUiOwningComponent } from "./floatingUI";
+export { floatingUIOwner, delegatesToFloatingUiOwningComponent, handlesActionMenuPlacements } from "./floatingUI";
export { focusable } from "./focusable";
export { formAssociated } from "./formAssociated";
export { slots } from "./slots";
diff --git a/packages/calcite-components/src/utils/floating-ui.ts b/packages/calcite-components/src/utils/floating-ui.ts
index 95b07ad042f..426dac7d1a8 100644
--- a/packages/calcite-components/src/utils/floating-ui.ts
+++ b/packages/calcite-components/src/utils/floating-ui.ts
@@ -271,6 +271,7 @@ export type MenuPlacement = Extract<
>;
export const defaultMenuPlacement: MenuPlacement = "bottom-start";
+export const defaultEndMenuPlacement: MenuPlacement = "bottom-end";
export interface FloatingUIComponent {
/**