Skip to content

Commit 5ba3112

Browse files
authored
feat(action-group, block, panel): add menuPlacement and menuFlipPlacements properties (#10249)
**Related Issue:** #7516 ## Summary - add `menuFlipPlacements` property - add `menuPlacement` property - add tests - update stories
1 parent 9a66601 commit 5ba3112

File tree

12 files changed

+269
-15
lines changed

12 files changed

+269
-15
lines changed

packages/calcite-components/src/components.d.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ export namespace Components {
393393
* When `true`, the component is expanded.
394394
*/
395395
"expanded": boolean;
396+
/**
397+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
398+
*/
399+
"flipPlacements": FlipPlacement[];
396400
/**
397401
* Accessible name for the component.
398402
*/
@@ -418,6 +422,10 @@ export namespace Components {
418422
* 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"`.
419423
*/
420424
"overlayPositioning": OverlayPositioning;
425+
/**
426+
* Determines where the action menu will be positioned.
427+
*/
428+
"placement": LogicalPlacement;
421429
/**
422430
* Specifies the size of the `calcite-action-menu`.
423431
*/
@@ -624,6 +632,10 @@ export namespace Components {
624632
* When `true`, displays a drag handle in the header.
625633
*/
626634
"dragHandle": boolean;
635+
/**
636+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
637+
*/
638+
"flipPlacements": FlipPlacement[];
627639
/**
628640
* The component header text.
629641
*/
@@ -664,6 +676,10 @@ export namespace Components {
664676
* 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"`.
665677
*/
666678
"overlayPositioning": OverlayPositioning;
679+
/**
680+
* Determines where the action menu will be positioned.
681+
*/
682+
"placement": LogicalPlacement;
667683
/**
668684
* Sets focus on the component's first tabbable element.
669685
*/
@@ -3864,6 +3880,10 @@ export namespace Components {
38643880
* When `true`, interaction is prevented and the component is displayed with lower opacity.
38653881
*/
38663882
"disabled": boolean;
3883+
/**
3884+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
3885+
*/
3886+
"flipPlacements": FlipPlacement[];
38673887
/**
38683888
* The component header text.
38693889
*/
@@ -3892,6 +3912,10 @@ export namespace Components {
38923912
* 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"`.
38933913
*/
38943914
"overlayPositioning": OverlayPositioning;
3915+
/**
3916+
* Determines where the action menu will be positioned.
3917+
*/
3918+
"placement": LogicalPlacement;
38953919
/**
38963920
* Specifies the size of the component.
38973921
*/
@@ -8325,6 +8349,10 @@ declare namespace LocalJSX {
83258349
* When `true`, the component is expanded.
83268350
*/
83278351
"expanded"?: boolean;
8352+
/**
8353+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
8354+
*/
8355+
"flipPlacements"?: FlipPlacement[];
83288356
/**
83298357
* Accessible name for the component.
83308358
*/
@@ -8350,6 +8378,10 @@ declare namespace LocalJSX {
83508378
* 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"`.
83518379
*/
83528380
"overlayPositioning"?: OverlayPositioning;
8381+
/**
8382+
* Determines where the action menu will be positioned.
8383+
*/
8384+
"placement"?: LogicalPlacement;
83538385
/**
83548386
* Specifies the size of the `calcite-action-menu`.
83558387
*/
@@ -8563,6 +8595,10 @@ declare namespace LocalJSX {
85638595
* When `true`, displays a drag handle in the header.
85648596
*/
85658597
"dragHandle"?: boolean;
8598+
/**
8599+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
8600+
*/
8601+
"flipPlacements"?: FlipPlacement[];
85668602
/**
85678603
* The component header text.
85688604
*/
@@ -8624,6 +8660,10 @@ declare namespace LocalJSX {
86248660
* 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"`.
86258661
*/
86268662
"overlayPositioning"?: OverlayPositioning;
8663+
/**
8664+
* Determines where the action menu will be positioned.
8665+
*/
8666+
"placement"?: LogicalPlacement;
86278667
/**
86288668
* Displays a status-related indicator icon.
86298669
* @deprecated Use `icon-start` instead.
@@ -11992,6 +12032,10 @@ declare namespace LocalJSX {
1199212032
* When `true`, interaction is prevented and the component is displayed with lower opacity.
1199312033
*/
1199412034
"disabled"?: boolean;
12035+
/**
12036+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
12037+
*/
12038+
"flipPlacements"?: FlipPlacement[];
1199512039
/**
1199612040
* The component header text.
1199712041
*/
@@ -12032,6 +12076,10 @@ declare namespace LocalJSX {
1203212076
* 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"`.
1203312077
*/
1203412078
"overlayPositioning"?: OverlayPositioning;
12079+
/**
12080+
* Determines where the action menu will be positioned.
12081+
*/
12082+
"placement"?: LogicalPlacement;
1203512083
/**
1203612084
* Specifies the size of the component.
1203712085
*/

packages/calcite-components/src/components/action-group/action-group.e2e.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { newE2EPage } from "@stencil/core/testing";
2-
import { accessible, defaults, focusable, hidden, renders, slots, t9n, themed } from "../../tests/commonTests";
2+
import {
3+
accessible,
4+
defaults,
5+
focusable,
6+
handlesActionMenuPlacements,
7+
hidden,
8+
reflects,
9+
renders,
10+
slots,
11+
t9n,
12+
themed,
13+
} from "../../tests/commonTests";
314
import { html } from "../../../support/formatting";
415
import { CSS, SLOTS } from "./resources";
516

@@ -19,6 +30,23 @@ describe("calcite-action-group", () => {
1930
propertyName: "overlayPositioning",
2031
defaultValue: "absolute",
2132
},
33+
{
34+
propertyName: "menuPlacement",
35+
defaultValue: undefined,
36+
},
37+
{
38+
propertyName: "menuFlipPlacements",
39+
defaultValue: undefined,
40+
},
41+
]);
42+
});
43+
44+
describe("reflects", () => {
45+
reflects("calcite-action-group", [
46+
{
47+
propertyName: "menuPlacement",
48+
value: "bottom",
49+
},
2250
]);
2351
});
2452

@@ -42,6 +70,15 @@ describe("calcite-action-group", () => {
4270
slots("calcite-action-group", SLOTS);
4371
});
4472

73+
describe("handles action-menu placement and flipPlacements", () => {
74+
handlesActionMenuPlacements(html`
75+
<calcite-action-group scale="l" overlay-positioning="fixed">
76+
<calcite-action id="plus" slot="${SLOTS.menuActions}" text="Add" icon="plus"></calcite-action>
77+
<calcite-action id="banana" slot="${SLOTS.menuActions}" text="Banana" icon="banana"></calcite-action>
78+
</calcite-action-group>
79+
`);
80+
});
81+
4582
it("should honor scale of expand icon", async () => {
4683
const page = await newE2EPage({ html: actionGroupHTML });
4784
const menu = await page.find(`calcite-action-group >>> calcite-action-menu`);

packages/calcite-components/src/components/action-group/action-group.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "../../utils/t9n";
2222
import { SLOTS as ACTION_MENU_SLOTS } from "../action-menu/resources";
2323
import { Layout, Scale } from "../interfaces";
24-
import { OverlayPositioning } from "../../utils/floating-ui";
24+
import { FlipPlacement, LogicalPlacement, OverlayPositioning } from "../../utils/floating-ui";
2525
import { slotChangeHasAssignedElement } from "../../utils/dom";
2626
import { Columns } from "./interfaces";
2727
import { ActionGroupMessages } from "./assets/action-group/t9n";
@@ -95,6 +95,16 @@ export class ActionGroup
9595
*/
9696
@Prop({ reflect: true }) scale: Scale;
9797

98+
/**
99+
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
100+
*/
101+
@Prop() menuFlipPlacements: FlipPlacement[];
102+
103+
/**
104+
* Determines where the action menu will be positioned.
105+
*/
106+
@Prop({ reflect: true }) menuPlacement: LogicalPlacement;
107+
98108
/**
99109
* Made into a prop for testing purposes only
100110
*
@@ -178,19 +188,30 @@ export class ActionGroup
178188
// --------------------------------------------------------------------------
179189

180190
renderMenu(): VNode {
181-
const { expanded, menuOpen, scale, layout, messages, overlayPositioning, hasMenuActions } =
182-
this;
191+
const {
192+
expanded,
193+
menuOpen,
194+
scale,
195+
layout,
196+
messages,
197+
overlayPositioning,
198+
hasMenuActions,
199+
menuFlipPlacements,
200+
menuPlacement,
201+
} = this;
183202

184203
return (
185204
<calcite-action-menu
186205
expanded={expanded}
187-
flipPlacements={["left", "right"]}
206+
flipPlacements={
207+
menuFlipPlacements ?? (layout === "horizontal" ? ["top", "bottom"] : ["left", "right"])
208+
}
188209
hidden={!hasMenuActions}
189210
label={messages.more}
190211
onCalciteActionMenuOpen={this.setMenuOpen}
191212
open={menuOpen}
192213
overlayPositioning={overlayPositioning}
193-
placement={layout === "horizontal" ? "bottom-start" : "leading-start"}
214+
placement={menuPlacement ?? (layout === "horizontal" ? "bottom-start" : "leading-start")}
194215
scale={scale}
195216
>
196217
<calcite-action

packages/calcite-components/src/components/block/block.e2e.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
delegatesToFloatingUiOwningComponent,
66
disabled,
77
focusable,
8+
handlesActionMenuPlacements,
89
hidden,
910
reflects,
1011
renders,
@@ -14,6 +15,7 @@ import {
1415
import { html } from "../../../support/formatting";
1516
import { openClose } from "../../tests/commonTests";
1617
import { skipAnimations } from "../../tests/utils";
18+
import { defaultEndMenuPlacement } from "../../utils/floating-ui";
1719
import { CSS, SLOTS } from "./resources";
1820

1921
describe("calcite-block", () => {
@@ -43,6 +45,14 @@ describe("calcite-block", () => {
4345
propertyName: "overlayPositioning",
4446
defaultValue: "absolute",
4547
},
48+
{
49+
propertyName: "menuPlacement",
50+
defaultValue: defaultEndMenuPlacement,
51+
},
52+
{
53+
propertyName: "menuFlipPlacements",
54+
defaultValue: undefined,
55+
},
4656
]);
4757
});
4858

@@ -64,6 +74,10 @@ describe("calcite-block", () => {
6474
propertyName: "overlayPositioning",
6575
value: "fixed",
6676
},
77+
{
78+
propertyName: "menuPlacement",
79+
value: "bottom",
80+
},
6781
]);
6882
});
6983

@@ -133,6 +147,15 @@ describe("calcite-block", () => {
133147
);
134148
});
135149

150+
describe("handles action-menu placement and flipPlacements", () => {
151+
handlesActionMenuPlacements(html`
152+
<calcite-block heading="heading" description="description">
153+
<calcite-action text="test" icon="banana" slot="${SLOTS.headerMenuActions}"></calcite-action>
154+
<div class="content">content</div>
155+
</calcite-block>
156+
`);
157+
});
158+
136159
it("has a loading state", async () => {
137160
const page = await newE2EPage({
138161
html: `

packages/calcite-components/src/components/block/block.stories.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import { boolean } from "../../../.storybook/utils";
33
import { placeholderImage } from "../../../.storybook/placeholder-image";
44
import { html } from "../../../support/formatting";
55
import { ATTRIBUTES } from "../../../.storybook/resources";
6+
import { defaultEndMenuPlacement, placements } from "../../utils/floating-ui";
67
import { Block } from "./block";
78
const { toggleDisplay } = ATTRIBUTES;
89

910
interface BlockStoryArgs
10-
extends Pick<Block, "heading" | "description" | "open" | "collapsible" | "loading" | "disabled" | "headingLevel">,
11+
extends Pick<
12+
Block,
13+
"heading" | "description" | "open" | "collapsible" | "loading" | "disabled" | "headingLevel" | "menuPlacement"
14+
>,
1115
Pick<BlockSection, "toggleDisplay"> {
1216
text: string;
1317
sectionOpen: BlockSection["open"];
@@ -16,6 +20,7 @@ interface BlockStoryArgs
1620
export default {
1721
title: "Components/Block",
1822
args: {
23+
menuPlacement: defaultEndMenuPlacement,
1924
heading: "Heading",
2025
description: "description",
2126
open: true,
@@ -28,6 +33,10 @@ export default {
2833
toggleDisplay: toggleDisplay.defaultValue,
2934
},
3035
argTypes: {
36+
menuPlacement: {
37+
options: placements,
38+
control: { type: "select" },
39+
},
3140
headingLevel: {
3241
control: { type: "number", min: 1, max: 6, step: 1 },
3342
},
@@ -42,6 +51,7 @@ export const simple = (args: BlockStoryArgs): string => html`
4251
<calcite-block
4352
heading="${args.heading}"
4453
description="${args.description}"
54+
menu-placement="${args.menuPlacement}"
4555
${boolean("open", args.open)}
4656
${boolean("collapsible", args.collapsible)}
4757
${boolean("loading", args.loading)}

0 commit comments

Comments
 (0)