Skip to content

Commit 2c7db3b

Browse files
authored
Merge pull request #1985 from dxc-technology/gomezivann/contextual-menu-themes
Contextual menu tokens
2 parents 65465a8 + 0614767 commit 2c7db3b

File tree

9 files changed

+465
-90
lines changed

9 files changed

+465
-90
lines changed

lib/src/HalstackContext.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ const parseTheme = (theme: DeepPartial<OpinionatedTheme>): AdvancedTheme => {
109109
chipTokens.hoverIconColor = subLightness(10, theme?.chip?.iconColor) ?? chipTokens.hoverIconColor;
110110
chipTokens.activeIconColor = subLightness(30, theme?.chip?.iconColor) ?? chipTokens.activeIconColor;
111111

112+
const contextualMenu = componentTokensCopy.contextualMenu;
113+
contextualMenu.selectedMenuItemBackgroundColor =
114+
theme?.contextualMenu?.accentColor ?? contextualMenu.selectedMenuItemBackgroundColor;
115+
contextualMenu.hoverSelectedMenuItemBackgroundColor =
116+
subLightness(5, theme?.contextualMenu?.accentColor) ?? contextualMenu.hoverSelectedMenuItemBackgroundColor;
117+
contextualMenu.activeSelectedMenuItemBackgroundColor =
118+
subLightness(5, theme?.contextualMenu?.accentColor) ?? contextualMenu.activeSelectedMenuItemBackgroundColor;
119+
contextualMenu.backgroundColor = theme?.contextualMenu?.baseColor ?? contextualMenu.backgroundColor;
120+
contextualMenu.hoverMenuItemBackgroundColor =
121+
subLightness(5, theme?.contextualMenu?.baseColor) ?? contextualMenu.hoverMenuItemBackgroundColor;
122+
contextualMenu.activeMenuItemBackgroundColor =
123+
subLightness(5, theme?.contextualMenu?.baseColor) ?? contextualMenu.activeMenuItemBackgroundColor;
124+
contextualMenu.menuItemFontColor = theme?.contextualMenu?.fontColor ?? contextualMenu.menuItemFontColor;
125+
contextualMenu.sectionTitleFontColor = theme?.contextualMenu?.fontColor ?? contextualMenu.sectionTitleFontColor;
126+
contextualMenu.iconColor = theme?.contextualMenu?.iconColor ?? contextualMenu.iconColor;
127+
112128
const dateTokens = componentTokensCopy.dateInput;
113129
dateTokens.pickerSelectedBackgroundColor = theme?.dateInput?.baseColor ?? dateTokens.pickerSelectedBackgroundColor;
114130
dateTokens.pickerSelectedFontColor = theme?.dateInput?.selectedFontColor ?? dateTokens.pickerSelectedFontColor;
@@ -379,7 +395,7 @@ type HalstackProviderPropsType = {
379395
const HalstackProvider = ({ theme, advancedTheme, labels, children }: HalstackProviderPropsType): JSX.Element => {
380396
const parsedTheme = useMemo(
381397
() => (theme ? parseTheme(theme) : advancedTheme ? parseAdvancedTheme(advancedTheme) : componentTokens),
382-
[theme, advancedTheme]
398+
[theme, advancedTheme],
383399
);
384400
const parsedLabels = useMemo(() => (labels ? parseLabels(labels) : defaultTranslatedComponentLabels), [labels]);
385401

lib/src/common/coreTokens.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ const CoreTokens = {
139139
// Typography
140140
type_sans: "Open Sans, sans-serif",
141141

142-
type_scale_root: "16px",
143142
type_scale_08: "3.75rem",
144143
type_scale_07: "2.5rem",
145144
type_scale_06: "2rem",

lib/src/common/variables.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,29 @@ export const componentTokens = {
219219
focusBorderThickness: CoreTokens.border_width_2,
220220
focusBorderRadius: CoreTokens.border_radius_medium,
221221
},
222+
contextualMenu: {
223+
fontFamily: CoreTokens.type_sans,
224+
backgroundColor: CoreTokens.color_white,
225+
borderColor: CoreTokens.color_grey_200,
226+
menuItemFontColor: CoreTokens.color_grey_900,
227+
menuItemFontSize: CoreTokens.type_scale_02,
228+
menuItemFontStyle: CoreTokens.type_normal,
229+
menuItemFontWeight: CoreTokens.type_regular,
230+
menuItemLineHeight: "24px",
231+
hoverMenuItemBackgroundColor: CoreTokens.color_grey_100,
232+
activeMenuItemBackgroundColor: CoreTokens.color_grey_100,
233+
selectedMenuItemBackgroundColor: CoreTokens.color_purple_100,
234+
hoverSelectedMenuItemBackgroundColor: CoreTokens.color_purple_200,
235+
activeSelectedMenuItemBackgroundColor: CoreTokens.color_purple_200,
236+
selectedMenuItemFontWeight: CoreTokens.type_semibold,
237+
sectionTitleFontColor: CoreTokens.color_grey_900,
238+
sectionTitleFontSize: CoreTokens.type_scale_03,
239+
sectionTitleFontStyle: CoreTokens.type_normal,
240+
sectionTitleFontWeight: CoreTokens.type_semibold,
241+
sectionTitleLineHeight: "24px",
242+
iconColor: CoreTokens.color_grey_900,
243+
iconSize: "16px",
244+
},
222245
dateInput: {
223246
pickerBackgroundColor: CoreTokens.color_white,
224247
pickerFontColor: CoreTokens.color_black,
@@ -295,7 +318,7 @@ export const componentTokens = {
295318
hoverOptionBackgroundColor: CoreTokens.color_grey_100,
296319
activeOptionBackgroundColor: CoreTokens.color_grey_300,
297320
optionFontFamily: CoreTokens.type_sans,
298-
optionFontSize: CoreTokens.type_scale_root,
321+
optionFontSize: CoreTokens.type_scale_03,
299322
optionFontStyle: CoreTokens.type_normal,
300323
optionFontWeight: CoreTokens.type_regular,
301324
optionFontColor: CoreTokens.color_black,
@@ -824,7 +847,7 @@ export const componentTokens = {
824847
disabledLabelFontColor: CoreTokens.color_grey_500,
825848
disabledLabelFontStyle: CoreTokens.type_normal,
826849
labelFontFamily: CoreTokens.type_sans,
827-
labelFontSize: CoreTokens.type_scale_root,
850+
labelFontSize: CoreTokens.type_scale_03,
828851
labelFontStyle: CoreTokens.type_normal,
829852
labelFontWeight: CoreTokens.type_regular,
830853
labelFontColor: CoreTokens.color_black,
@@ -1176,6 +1199,12 @@ export type OpinionatedTheme = {
11761199
fontColor: string;
11771200
iconColor: string;
11781201
};
1202+
contextualMenu: {
1203+
accentColor: string;
1204+
baseColor: string;
1205+
fontColor: string;
1206+
iconColor: string;
1207+
},
11791208
dateInput: {
11801209
baseColor: string;
11811210
selectedFontColor: string;

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

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import ExampleContainer from "../../.storybook/components/ExampleContainer";
77
import DxcBadge from "../badge/Badge";
88
import { disabledRules } from "../../test/accessibility/rules/specific/contextual-menu/disabledRules";
99
import preview from "../../.storybook/preview";
10+
import { ThemeProvider } from "styled-components";
11+
import useTheme from "../useTheme";
1012

1113
export default {
1214
title: "Contextual Menu",
@@ -76,7 +78,7 @@ const itemsWithIcon = [
7678
{
7779
label: "Item 1",
7880
icon: (
79-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
81+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="currentColor">
8082
<path d="M200-120v-640q0-33 23.5-56.5T280-840h400q33 0 56.5 23.5T760-760v640L480-240 200-120Zm80-122 200-86 200 86v-518H280v518Zm0-518h400-400Z" />
8183
</svg>
8284
),
@@ -123,7 +125,7 @@ const itemsWithTruncatedText = [
123125
label: "Item with a very long label that should be truncated",
124126
badge: <DxcBadge color="green" label="New" />,
125127
icon: (
126-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
128+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="currentColor">
127129
<path d="M200-120v-640q0-33 23.5-56.5T280-840h400q33 0 56.5 23.5T760-760v640L480-240 200-120Zm80-122 200-86 200 86v-518H280v518Zm0-518h400-400Z" />
128130
</svg>
129131
),
@@ -185,39 +187,45 @@ export const Chromatic = () => (
185187
</>
186188
);
187189

188-
export const SingleItemStates = () => (
189-
<DxcContainer width="300px">
190-
<ContextualMenuContext.Provider value={{ selectedItemId: -1, setSelectedItemId: () => {} }}>
191-
<Title title="Default" theme="light" level={3} />
192-
<ExampleContainer>
193-
<SingleItem {...items[0]} id={0} depthLevel={0} />
194-
</ExampleContainer>
195-
<Title title="Focus" theme="light" level={3} />
196-
<ExampleContainer pseudoState="pseudo-focus">
197-
<SingleItem {...items[0]} id={0} depthLevel={0} />
198-
</ExampleContainer>
199-
<Title title="Hover" theme="light" level={3} />
200-
<ExampleContainer pseudoState="pseudo-hover">
201-
<SingleItem {...items[0]} id={0} depthLevel={0} />
202-
</ExampleContainer>
203-
<Title title="Active" theme="light" level={3} />
204-
<ExampleContainer pseudoState="pseudo-active">
205-
<SingleItem {...items[0]} id={0} depthLevel={0} />
206-
</ExampleContainer>
207-
</ContextualMenuContext.Provider>
208-
<ContextualMenuContext.Provider value={{ selectedItemId: 0, setSelectedItemId: () => {} }}>
209-
<Title title="Selected" theme="light" level={3} />
210-
<ExampleContainer>
211-
<SingleItem {...items[0]} id={0} depthLevel={0} />
212-
</ExampleContainer>
213-
<Title title="Selected hover" theme="light" level={3} />
214-
<ExampleContainer pseudoState="pseudo-hover">
215-
<SingleItem {...items[0]} id={0} depthLevel={0} />
216-
</ExampleContainer>
217-
<Title title="Selected active" theme="light" level={3} />
218-
<ExampleContainer pseudoState="pseudo-active">
219-
<SingleItem {...items[0]} id={0} depthLevel={0} />
220-
</ExampleContainer>
221-
</ContextualMenuContext.Provider>
222-
</DxcContainer>
223-
);
190+
export const SingleItemStates = () => {
191+
const colorsTheme = useTheme();
192+
193+
return (
194+
<ThemeProvider theme={colorsTheme.contextualMenu}>
195+
<DxcContainer width="300px">
196+
<ContextualMenuContext.Provider value={{ selectedItemId: -1, setSelectedItemId: () => {} }}>
197+
<Title title="Default" theme="light" level={3} />
198+
<ExampleContainer>
199+
<SingleItem {...items[0]} id={0} depthLevel={0} />
200+
</ExampleContainer>
201+
<Title title="Focus" theme="light" level={3} />
202+
<ExampleContainer pseudoState="pseudo-focus">
203+
<SingleItem {...items[0]} id={0} depthLevel={0} />
204+
</ExampleContainer>
205+
<Title title="Hover" theme="light" level={3} />
206+
<ExampleContainer pseudoState="pseudo-hover">
207+
<SingleItem {...items[0]} id={0} depthLevel={0} />
208+
</ExampleContainer>
209+
<Title title="Active" theme="light" level={3} />
210+
<ExampleContainer pseudoState="pseudo-active">
211+
<SingleItem {...items[0]} id={0} depthLevel={0} />
212+
</ExampleContainer>
213+
</ContextualMenuContext.Provider>
214+
<ContextualMenuContext.Provider value={{ selectedItemId: 0, setSelectedItemId: () => {} }}>
215+
<Title title="Selected" theme="light" level={3} />
216+
<ExampleContainer>
217+
<SingleItem {...items[0]} id={0} depthLevel={0} />
218+
</ExampleContainer>
219+
<Title title="Selected hover" theme="light" level={3} />
220+
<ExampleContainer pseudoState="pseudo-hover">
221+
<SingleItem {...items[0]} id={0} depthLevel={0} />
222+
</ExampleContainer>
223+
<Title title="Selected active" theme="light" level={3} />
224+
<ExampleContainer pseudoState="pseudo-active">
225+
<SingleItem {...items[0]} id={0} depthLevel={0} />
226+
</ExampleContainer>
227+
</ContextualMenuContext.Provider>
228+
</DxcContainer>
229+
</ThemeProvider>
230+
);
231+
};

lib/src/contextual-menu/ContextualMenu.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { Fragment, createContext, useLayoutEffect, useMemo, useRef, useState } from "react";
2-
import styled from "styled-components";
2+
import styled, { ThemeProvider } from "styled-components";
33
import CoreTokens from "../common/coreTokens";
44
import ContextualMenuPropsType, {
55
ContextualMenuContextProps,
@@ -13,6 +13,7 @@ import ContextualMenuPropsType, {
1313
import DxcDivider from "../divider/Divider";
1414
import DxcInset from "../inset/Inset";
1515
import MenuItem from "./MenuItem";
16+
import useTheme from "../useTheme";
1617

1718
export const ContextualMenuContext = createContext<ContextualMenuContextProps | null>(null);
1819

@@ -26,8 +27,8 @@ const addIdToItems = (items: ContextualMenuPropsType["items"]): (ItemWithId | Gr
2627
isSection(item)
2728
? { ...item, items: innerAddIdToItems(item.items) }
2829
: isGroupItem(item)
29-
? { ...item, items: innerAddIdToItems(item.items) }
30-
: { ...item, id: accId++ }
30+
? { ...item, items: innerAddIdToItems(item.items) }
31+
: { ...item, id: accId++ },
3132
);
3233
};
3334
return innerAddIdToItems(items);
@@ -37,6 +38,7 @@ const DxcContextualMenu = ({ items }: ContextualMenuPropsType) => {
3738
const [selectedItemId, setSelectedItemId] = useState(-1);
3839
const contextualMenuRef = useRef(null);
3940
const itemsWithId = useMemo(() => addIdToItems(items), [items]);
41+
const colorsTheme = useTheme();
4042

4143
const renderSection = (section: SectionWithId, currentSectionIndex: number, length: number) => (
4244
<Fragment key={`section-${currentSectionIndex}`}>
@@ -67,31 +69,33 @@ const DxcContextualMenu = ({ items }: ContextualMenuPropsType) => {
6769
}, [firstUpdate, selectedItemId]);
6870

6971
return (
70-
<ContextualMenu role="menu" ref={contextualMenuRef}>
71-
<ContextualMenuContext.Provider value={{ selectedItemId, setSelectedItemId }}>
72-
{itemsWithId.map((item: GroupItemWithId | ItemWithId | SectionWithId, index: number) =>
73-
"items" in item && !("label" in item) ? (
74-
renderSection(item, index, itemsWithId.length)
75-
) : (
76-
<MenuItem item={item} key={`${item.label}-${index}`} />
77-
)
78-
)}
79-
</ContextualMenuContext.Provider>
80-
</ContextualMenu>
72+
<ThemeProvider theme={colorsTheme.contextualMenu}>
73+
<ContextualMenu role="menu" ref={contextualMenuRef}>
74+
<ContextualMenuContext.Provider value={{ selectedItemId, setSelectedItemId }}>
75+
{itemsWithId.map((item: GroupItemWithId | ItemWithId | SectionWithId, index: number) =>
76+
"items" in item && !("label" in item) ? (
77+
renderSection(item, index, itemsWithId.length)
78+
) : (
79+
<MenuItem item={item} key={`${item.label}-${index}`} />
80+
),
81+
)}
82+
</ContextualMenuContext.Provider>
83+
</ContextualMenu>
84+
</ThemeProvider>
8185
);
8286
};
8387

8488
const ContextualMenu = styled.ul`
8589
box-sizing: border-box;
8690
margin: 0;
87-
border: 1px solid ${CoreTokens.color_grey_200};
91+
border: 1px solid ${({ theme }) => theme.borderColor};
8892
border-radius: 0.25rem;
8993
padding: ${CoreTokens.spacing_16} ${CoreTokens.spacing_8};
9094
display: grid;
9195
gap: ${CoreTokens.spacing_4};
9296
min-width: 248px;
9397
max-height: 100%;
94-
background-color: ${CoreTokens.color_white};
98+
background-color: ${({ theme }) => theme.backgroundColor};
9599
overflow-y: auto;
96100
&::-webkit-scrollbar {
97101
width: 8px;
@@ -118,11 +122,12 @@ const SectionList = styled.ul`
118122
const Title = styled.h2`
119123
margin: 0 0 ${CoreTokens.spacing_4} 0;
120124
padding: ${CoreTokens.spacing_4};
121-
color: ${CoreTokens.color_grey_900};
122-
font-family: ${CoreTokens.type_sans};
123-
font-size: ${CoreTokens.type_scale_03};
124-
font-weight: ${CoreTokens.type_semibold};
125-
line-height: 24px;
125+
color: ${({ theme }) => theme.sectionTitleFontColor};
126+
font-family: ${({ theme }) => theme.fontFamily};
127+
font-size: ${({ theme }) => theme.sectionTitleFontSize};
128+
font-style: ${({ theme }) => theme.sectionTitleFontStyle};
129+
font-weight: ${({ theme }) => theme.sectionTitleFontWeight};
130+
line-height: ${({ theme }) => theme.sectionTitleLineHeight};
126131
`;
127132

128133
export default DxcContextualMenu;

lib/src/contextual-menu/ItemAction.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ const ItemAction = ({ badge, collapseIcon, icon, label, depthLevel, ...props }:
1010
return (
1111
<Action depthLevel={depthLevel} {...props}>
1212
<Label>
13-
{collapseIcon}
13+
{collapseIcon && <Icon>{collapseIcon}</Icon>}
1414
{icon && depthLevel === 0 && <Icon>{typeof icon === "string" ? <DxcIcon icon={icon} /> : icon}</Icon>}
1515
<Text
16+
selected={props.selected}
1617
onMouseEnter={(event: React.MouseEvent<HTMLSpanElement>) => {
1718
const text = event.currentTarget;
1819
if (text.title === "" && text.scrollWidth > text.clientWidth) text.title = label;
@@ -39,24 +40,24 @@ const Action = styled.button<{ depthLevel: ItemActionProps["depthLevel"]; select
3940
align-items: center;
4041
justify-content: space-between;
4142
gap: ${CoreTokens.spacing_16};
42-
${(props) =>
43-
props.selected
44-
? `background-color: ${CoreTokens.color_purple_100}; font-weight: ${CoreTokens.type_semibold};`
43+
${({ selected, theme }) =>
44+
selected
45+
? `background-color: ${theme.selectedMenuItemBackgroundColor};`
4546
: `background-color: ${CoreTokens.color_transparent}`};
4647
cursor: pointer;
4748
overflow: hidden;
4849
4950
&:hover {
50-
${(props) =>
51-
props.selected
52-
? `background-color: ${CoreTokens.color_purple_200};`
53-
: `background-color: ${CoreTokens.color_grey_100};`};
51+
${({ selected, theme }) =>
52+
selected
53+
? `background-color: ${theme.hoverSelectedMenuItemBackgroundColor};`
54+
: `background-color: ${theme.hoverMenuItemBackgroundColor};`};
5455
}
5556
&:active {
56-
${(props) =>
57-
props.selected
58-
? `background-color: ${CoreTokens.color_purple_200};`
59-
: `background-color: ${CoreTokens.color_grey_100};`};
57+
${({ selected, theme }) =>
58+
selected
59+
? `background-color: ${theme.activeSelectedMenuItemBackgroundColor};`
60+
: `background-color: ${theme.activeMenuItemBackgroundColor};`};
6061
}
6162
&:focus {
6263
outline: 2px solid ${CoreTokens.color_blue_600};
@@ -66,11 +67,12 @@ const Action = styled.button<{ depthLevel: ItemActionProps["depthLevel"]; select
6667

6768
const Icon = styled.span`
6869
display: flex;
69-
font-size: 16px;
70+
font-size: ${({ theme }) => theme.iconSize};
71+
color: ${({ theme }) => theme.iconColor};
7072
7173
svg {
72-
height: 16px;
73-
width: 16px;
74+
height: ${({ theme }) => theme.iconSize};
75+
width: ${({ theme }) => theme.iconSize};
7476
}
7577
`;
7678

@@ -81,11 +83,13 @@ const Label = styled.span`
8183
overflow: hidden;
8284
`;
8385

84-
const Text = styled.span`
85-
color: ${CoreTokens.color_grey_900};
86-
font-family: ${CoreTokens.type_sans};
87-
font-size: ${CoreTokens.type_scale_02};
88-
line-height: 24px;
86+
const Text = styled.span<{ selected: ItemActionProps["selected"] }>`
87+
color: ${({ theme }) => theme.menuItemFontColor};
88+
font-family: ${({ theme }) => theme.fontFamily};
89+
font-size: ${({ theme }) => theme.menuItemFontSize};
90+
font-style: ${({ theme }) => theme.menuItemFontStyle};
91+
font-weight: ${({ selected, theme }) => (selected ? theme.selectedMenuItemFontWeight : theme.menuItemFontWeight)};
92+
line-height: ${({ theme }) => theme.menuItemLineHeight};
8993
text-overflow: ellipsis;
9094
white-space: nowrap;
9195
overflow: hidden;

0 commit comments

Comments
 (0)