Skip to content

Commit 57645fa

Browse files
committed
Restored contextualmenu
1 parent 16eba2e commit 57645fa

File tree

10 files changed

+67
-231
lines changed

10 files changed

+67
-231
lines changed

packages/lib/src/contextual-menu/ContextualMenu.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const groupItems = [
4242
icon: "bookmark",
4343
badge: <DxcBadge color="primary" label="Experimental" />,
4444
},
45-
{ label: "Selected Item 3", selected: true },
45+
{ label: "Selected Item 3", selectedByDefault: true },
4646
],
4747
},
4848
],
@@ -102,7 +102,7 @@ const sectionsWithScroll = [
102102
{ label: "Approved locations" },
103103
{ label: "Approved locations" },
104104
{ label: "Approved locations" },
105-
{ label: "Approved locations", selected: true },
105+
{ label: "Approved locations", selectedByDefault: true },
106106
],
107107
},
108108
];

packages/lib/src/contextual-menu/ContextualMenu.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ describe("Contextual menu component tests", () => {
4141
expect(actions[0]?.getAttribute("aria-pressed")).toBeTruthy();
4242
expect(getByRole("menu")).toBeTruthy();
4343
});
44-
test("Single — An item can appear as selected by default by using the attribute selected", () => {
44+
test("Single — An item can appear as selected by default by using the attribute selectedByDefault", () => {
4545
const test = [
4646
{
4747
label: "Tested item",
48-
selected: true,
48+
selectedByDefault: true,
4949
},
5050
];
5151
const { getByRole } = render(<DxcContextualMenu items={test} />);
@@ -92,7 +92,7 @@ describe("Contextual menu component tests", () => {
9292
const test = [
9393
{
9494
label: "Grouped item",
95-
items: [{ label: "Tested item", selected: true }],
95+
items: [{ label: "Tested item", selectedByDefault: true }],
9696
},
9797
];
9898
const { getByText, getAllByRole } = render(<DxcContextualMenu items={test} />);

packages/lib/src/contextual-menu/ContextualMenu.tsx

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,49 +8,28 @@ import scrollbarStyles from "../styles/scroll";
88
import { addIdToItems, isSection } from "./utils";
99
import SubMenu from "./SubMenu";
1010

11-
const ContextualMenuContainer = styled.div<{ displayBorder: boolean }>`
11+
const ContextualMenu = styled.div`
1212
box-sizing: border-box;
1313
margin: 0;
14+
border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-lighter);
15+
border-radius: var(--border-radius-s);
16+
padding: var(--spacing-padding-m) var(--spacing-padding-xs);
1417
display: grid;
1518
gap: var(--spacing-gap-xs);
16-
/* min-width: 248px; */
19+
min-width: 248px;
1720
max-height: 100%;
1821
background-color: var(--color-bg-neutral-lightest);
1922
overflow-y: auto;
2023
overflow-x: hidden;
21-
${scrollbarStyles};
22-
${({ displayBorder }) =>
23-
displayBorder &&
24-
`
25-
border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-lighter);
26-
border-radius: var(--border-radius-s);
27-
padding: var(--spacing-padding-m) var(--spacing-padding-xs);
28-
`}
24+
${scrollbarStyles}
2925
`;
3026

31-
export default function DxcContextualMenu({
32-
items,
33-
displayBorder = true,
34-
displayGroupLines = false,
35-
displayControlsAfter = false,
36-
responsiveView = false,
37-
allowNavigation = false,
38-
}: ContextualMenuPropsType) {
27+
export default function DxcContextualMenu({ items }: ContextualMenuPropsType) {
3928
const [firstUpdate, setFirstUpdate] = useState(true);
4029
const [selectedItemId, setSelectedItemId] = useState(-1);
4130
const contextualMenuRef = useRef<HTMLDivElement | null>(null);
4231
const itemsWithId = useMemo(() => addIdToItems(items), [items]);
43-
const contextValue = useMemo(
44-
() => ({
45-
selectedItemId,
46-
setSelectedItemId,
47-
displayGroupLines,
48-
displayControlsAfter,
49-
responsiveView,
50-
allowNavigation,
51-
}),
52-
[selectedItemId, setSelectedItemId, displayGroupLines, displayControlsAfter, responsiveView, allowNavigation]
53-
);
32+
const contextValue = useMemo(() => ({ selectedItemId, setSelectedItemId }), [selectedItemId, setSelectedItemId]);
5433

5534
useLayoutEffect(() => {
5635
if (selectedItemId !== -1 && firstUpdate) {
@@ -66,7 +45,7 @@ export default function DxcContextualMenu({
6645
}, [firstUpdate, selectedItemId]);
6746

6847
return (
69-
<ContextualMenuContainer displayBorder={displayBorder} ref={contextualMenuRef}>
48+
<ContextualMenu ref={contextualMenuRef}>
7049
<ContextualMenuContext.Provider value={contextValue}>
7150
{itemsWithId[0] && isSection(itemsWithId[0]) ? (
7251
(itemsWithId as SectionWithId[]).map((item, index) => (
@@ -80,6 +59,6 @@ export default function DxcContextualMenu({
8059
</SubMenu>
8160
)}
8261
</ContextualMenuContext.Provider>
83-
</ContextualMenuContainer>
62+
</ContextualMenu>
8463
);
8564
}

packages/lib/src/contextual-menu/GroupItem.tsx

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,64 +6,14 @@ import MenuItem from "./MenuItem";
66
import { GroupItemProps } from "./types";
77
import ContextualMenuContext from "./ContextualMenuContext";
88
import { isGroupSelected } from "./utils";
9-
import * as Popover from "@radix-ui/react-popover";
109

1110
const GroupItem = ({ items, ...props }: GroupItemProps) => {
1211
const groupMenuId = `group-menu-${useId()}`;
13-
const { selectedItemId, responsiveView } = useContext(ContextualMenuContext) ?? {};
12+
const { selectedItemId } = useContext(ContextualMenuContext) ?? {};
1413
const groupSelected = useMemo(() => isGroupSelected(items, selectedItemId), [items, selectedItemId]);
1514
const [isOpen, setIsOpen] = useState(groupSelected && selectedItemId === -1);
1615

17-
const contextualMenuId = `sidenav-${useId()}`;
18-
19-
const contextValue = useContext(ContextualMenuContext) ?? {};
20-
21-
return responsiveView ? (
22-
<>
23-
<Popover.Root open={isOpen}>
24-
<Popover.Trigger
25-
aria-controls={undefined}
26-
aria-expanded={undefined}
27-
aria-haspopup={undefined}
28-
asChild
29-
type={undefined}
30-
>
31-
<ItemAction
32-
aria-controls={isOpen ? groupMenuId : undefined}
33-
aria-expanded={isOpen ? true : undefined}
34-
aria-pressed={groupSelected && !isOpen}
35-
collapseIcon={isOpen ? <DxcIcon icon="filled_expand_less" /> : <DxcIcon icon="filled_expand_more" />}
36-
onClick={() => setIsOpen((isCurrentlyOpen) => !isCurrentlyOpen)}
37-
selected={groupSelected && !isOpen}
38-
{...props}
39-
/>
40-
</Popover.Trigger>
41-
<Popover.Portal container={document.getElementById(`${contextualMenuId}-portal`)}>
42-
<ContextualMenuContext.Provider value={{ ...contextValue, displayGroupLines: false, responsiveView: false }}>
43-
<Popover.Content
44-
aria-label="Group details"
45-
onCloseAutoFocus={(event) => {
46-
event.preventDefault();
47-
}}
48-
onOpenAutoFocus={(event) => {
49-
event.preventDefault();
50-
}}
51-
align="start"
52-
side="right"
53-
style={{ zIndex: "var(--z-contextualmenu)" }}
54-
>
55-
<SubMenu id={groupMenuId} depthLevel={props.depthLevel}>
56-
{items.map((item, index) => (
57-
<MenuItem item={item} depthLevel={props.depthLevel + 1} key={`${item.label}-${index}`} />
58-
))}
59-
</SubMenu>
60-
</Popover.Content>
61-
</ContextualMenuContext.Provider>
62-
</Popover.Portal>
63-
</Popover.Root>
64-
<div id={`${contextualMenuId}-portal`} style={{ position: "absolute" }} />
65-
</>
66-
) : (
16+
return (
6717
<>
6818
<ItemAction
6919
aria-controls={isOpen ? groupMenuId : undefined}
@@ -75,7 +25,7 @@ const GroupItem = ({ items, ...props }: GroupItemProps) => {
7525
{...props}
7626
/>
7727
{isOpen && (
78-
<SubMenu id={groupMenuId} depthLevel={props.depthLevel}>
28+
<SubMenu id={groupMenuId}>
7929
{items.map((item, index) => (
8030
<MenuItem item={item} depthLevel={props.depthLevel + 1} key={`${item.label}-${index}`} />
8131
))}

packages/lib/src/contextual-menu/ItemAction.tsx

Lines changed: 27 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
1-
import { cloneElement, forwardRef, memo, MouseEvent, useContext, useState } from "react";
1+
import { cloneElement, memo, MouseEvent, useState } from "react";
22
import styled from "@emotion/styled";
33
import { ItemActionProps } from "./types";
44
import DxcIcon from "../icon/Icon";
55
import { TooltipWrapper } from "../tooltip/Tooltip";
6-
import ContextualMenuContext from "./ContextualMenuContext";
76

87
const Action = styled.button<{
98
depthLevel: ItemActionProps["depthLevel"];
109
selected: ItemActionProps["selected"];
11-
displayGroupLines: boolean;
12-
responsiveView?: boolean;
1310
}>`
1411
box-sizing: content-box;
1512
border: none;
1613
border-radius: var(--border-radius-s);
17-
${({ displayGroupLines, depthLevel, responsiveView }) => `
18-
${!responsiveView ? `padding: var(--spacing-padding-xxs) var(--spacing-padding-xxs) var(--spacing-padding-xxs) calc(var(--spacing-padding-xs) + ${!displayGroupLines ? depthLevel : 0} * var(--spacing-padding-l))` : "padding: var(--spacing-padding-xxs) var(--spacing-padding-none)"};
19-
${displayGroupLines && depthLevel > 0 ? "margin-left: var(--spacing-padding-xs);" : ""}
20-
`}
14+
padding: var(--spacing-padding-xxs) var(--spacing-padding-xxs) var(--spacing-padding-xxs)
15+
${({ depthLevel }) => `calc(var(--spacing-padding-xs) + ${depthLevel} * var(--spacing-padding-l))`};
2116
display: flex;
2217
align-items: center;
2318
gap: var(--spacing-gap-m);
24-
justify-content: ${({ responsiveView }) => (responsiveView ? "center" : "space-between")};
19+
justify-content: space-between;
2520
background-color: ${({ selected }) => (selected ? "var(--color-bg-primary-lighter)" : "transparent")};
2621
height: var(--height-s);
2722
cursor: pointer;
2823
overflow: hidden;
29-
text-decoration: none;
3024
3125
&:hover {
3226
background-color: ${({ selected }) =>
@@ -69,64 +63,31 @@ const Text = styled.span<{ selected: ItemActionProps["selected"] }>`
6963
overflow: hidden;
7064
`;
7165

72-
const Control = styled.span`
73-
display: flex;
74-
align-items: center;
75-
padding: var(--spacing-padding-none);
76-
justify-content: flex-end;
77-
align-items: center;
78-
gap: var(--spacing-gap-s);
79-
`;
80-
81-
const ItemAction = memo(
82-
forwardRef<HTMLButtonElement, ItemActionProps>(
83-
({ badge, collapseIcon, depthLevel, icon, label, href, ...props }, ref) => {
84-
const [hasTooltip, setHasTooltip] = useState(false);
85-
const modifiedBadge = badge && cloneElement(badge, { size: "small" });
86-
const { displayControlsAfter, responsiveView, displayGroupLines, allowNavigation } =
87-
useContext(ContextualMenuContext) ?? {};
66+
const ItemAction = memo(({ badge, collapseIcon, depthLevel, icon, label, ...props }: ItemActionProps) => {
67+
const [hasTooltip, setHasTooltip] = useState(false);
68+
const modifiedBadge = badge && cloneElement(badge, { size: "small" });
8869

89-
return (
90-
<TooltipWrapper condition={hasTooltip} label={label}>
91-
<Action
92-
as={allowNavigation && href ? "a" : "button"}
93-
role={allowNavigation && href ? "link" : "button"}
94-
ref={ref}
95-
depthLevel={depthLevel}
96-
displayGroupLines={!!displayGroupLines}
97-
responsiveView={responsiveView}
98-
{...(allowNavigation && href && { href })}
99-
{...props}
70+
return (
71+
<TooltipWrapper condition={hasTooltip} label={label}>
72+
<Action depthLevel={depthLevel} {...props}>
73+
<Label>
74+
{collapseIcon && <Icon>{collapseIcon}</Icon>}
75+
{icon && depthLevel === 0 && <Icon>{typeof icon === "string" ? <DxcIcon icon={icon} /> : icon}</Icon>}
76+
<Text
77+
selected={props.selected}
78+
onMouseEnter={(event: MouseEvent<HTMLSpanElement>) => {
79+
const text = event.currentTarget;
80+
setHasTooltip(text.scrollWidth > text.clientWidth);
81+
}}
10082
>
101-
<Label>
102-
{!displayControlsAfter && <Control>{collapseIcon && <Icon>{collapseIcon}</Icon>}</Control>}
103-
<TooltipWrapper condition={responsiveView} label={label}>
104-
<Icon>{typeof icon === "string" ? <DxcIcon icon={icon} /> : icon}</Icon>
105-
</TooltipWrapper>
106-
{!responsiveView && (
107-
<Text
108-
selected={props.selected}
109-
onMouseEnter={(event: MouseEvent<HTMLSpanElement>) => {
110-
const text = event.currentTarget;
111-
setHasTooltip(text.scrollWidth > text.clientWidth);
112-
}}
113-
>
114-
{label}
115-
</Text>
116-
)}
117-
</Label>
118-
{!responsiveView && (
119-
<Control>
120-
{modifiedBadge}
121-
{displayControlsAfter && collapseIcon && <Icon>{collapseIcon}</Icon>}
122-
</Control>
123-
)}
124-
</Action>
125-
</TooltipWrapper>
126-
);
127-
}
128-
)
129-
);
83+
{label}
84+
</Text>
85+
</Label>
86+
{modifiedBadge}
87+
</Action>
88+
</TooltipWrapper>
89+
);
90+
});
13091

13192
ItemAction.displayName = "ItemAction";
13293

packages/lib/src/contextual-menu/Section.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useContext, useId } from "react";
1+
import { useId } from "react";
22
import styled from "@emotion/styled";
33
import { DxcInset } from "..";
44
import DxcDivider from "../divider/Divider";
55
import SubMenu from "./SubMenu";
66
import MenuItem from "./MenuItem";
77
import { SectionProps } from "./types";
8-
import ContextualMenuContext from "./ContextualMenuContext";
98

109
const SectionContainer = styled.section`
1110
display: grid;
@@ -23,11 +22,11 @@ const Title = styled.h2`
2322

2423
export default function Section({ index, length, section }: SectionProps) {
2524
const id = `section-${useId()}`;
26-
const { responsiveView } = useContext(ContextualMenuContext) ?? {};
27-
return !responsiveView ? (
25+
26+
return (
2827
<SectionContainer aria-label={section.title ?? id} aria-labelledby={id}>
2928
{section.title && <Title id={id}>{section.title}</Title>}
30-
<SubMenu depthLevel={-1}>
29+
<SubMenu>
3130
{section.items.map((item, i) => (
3231
<MenuItem item={item} key={`${item.label}-${i}`} />
3332
))}
@@ -38,11 +37,5 @@ export default function Section({ index, length, section }: SectionProps) {
3837
</DxcInset>
3938
)}
4039
</SectionContainer>
41-
) : (
42-
<SubMenu depthLevel={-1}>
43-
{section.items.map((item, i) => (
44-
<MenuItem item={item} key={`${item.label}-${i}`} />
45-
))}
46-
</SubMenu>
4740
);
4841
}

packages/lib/src/contextual-menu/SingleItem.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ItemAction from "./ItemAction";
33
import { SingleItemProps } from "./types";
44
import ContextualMenuContext from "./ContextualMenuContext";
55

6-
export default function SingleItem({ id, onSelect, selected = false, ...props }: SingleItemProps) {
6+
export default function SingleItem({ id, onSelect, selectedByDefault = false, ...props }: SingleItemProps) {
77
const { selectedItemId, setSelectedItemId } = useContext(ContextualMenuContext) ?? {};
88

99
const handleClick = () => {
@@ -12,16 +12,18 @@ export default function SingleItem({ id, onSelect, selected = false, ...props }:
1212
};
1313

1414
useEffect(() => {
15-
if (selectedItemId === -1 && selected) {
15+
if (selectedItemId === -1 && selectedByDefault) {
1616
setSelectedItemId?.(id);
1717
}
18-
}, [selectedItemId, selected, id]);
18+
}, [selectedItemId, selectedByDefault, id]);
1919

2020
return (
2121
<ItemAction
22-
aria-pressed={selectedItemId === -1 ? selected : selectedItemId === id}
22+
aria-pressed={selectedItemId === -1 ? selectedByDefault : selectedItemId === id}
2323
onClick={handleClick}
24-
selected={selectedItemId != null && (selectedItemId === -1 ? (selected ?? false) : selectedItemId === id)}
24+
selected={
25+
selectedItemId != null && (selectedItemId === -1 ? (selectedByDefault ?? false) : selectedItemId === id)
26+
}
2527
{...props}
2628
/>
2729
);

0 commit comments

Comments
 (0)