Skip to content

Commit 8339feb

Browse files
LuisValgoiMarcusNotheis
authored andcommitted
Merge pull request #77 from LuisValgoi/feat-theme-switch
[Issue-58] Change Colors / Theme Programmatically
1 parent bc2db14 commit 8339feb

File tree

11 files changed

+261
-11
lines changed

11 files changed

+261
-11
lines changed

packages/ui5-webcomponents-react-seed/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@
8282
"test:coverage": "npm run test -- --watchAll=false --coverage",
8383
"lint": "eslint --quiet .",
8484
"lint:fix": "eslint . --fix",
85-
"prettier": "./node_modules/.bin/prettier --config .prettierrc --check 'src/**/*.js'",
86-
"prettier:fix": "./node_modules/.bin/prettier --config .prettierrc --write 'src/**/*.js'",
85+
"prettier": "./node_modules/.bin/prettier --config .prettierrc --check \"src/**/*.js\"",
86+
"prettier:fix": "./node_modules/.bin/prettier --config .prettierrc --write \"src/**/*.js\"",
8787
"eject": "react-scripts eject",
8888
"push:pre": "npm-run-all --parallel test:ci lint prettier",
8989
"publish:clean": "rm -rf ./template/src ./template/server ./template/.vscode ./template/public && rm -f ./template/.editorconfig ./template/.env.development ./template/.env.production ./template/.eslintignore ./template/.eslintrc.js ./template/.prettierrc ./template/commitlint.config.js ./template/jest.config.json ./template/PULL_REQUEST_TEMPLATE.md ./template/README.md ./template/webpack.config.js",

packages/ui5-webcomponents-react-seed/public/index.html

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@
1818
"rtl": false
1919
}
2020
</script>
21-
<style>
22-
* {
23-
--sapBackgroundColor: white;
24-
--sapLinkColor: #0077ff;
25-
}
26-
</style>
2721
</head>
2822

2923
<body>

packages/ui5-webcomponents-react-seed/src/App.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ code {
1717

1818
#root {
1919
width: 100%;
20-
background-color: 'var(--sapBackgroundColor)';
2120
height: 100%;
2221
display: flex;
2322
flex-direction: column;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React, { useEffect } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { spacing } from '@ui5/webcomponents-react-base';
4+
import { Icon } from '@ui5/webcomponents-react/lib/Icon';
5+
import { Button } from '@ui5/webcomponents-react/lib/Button';
6+
import { ButtonDesign } from '@ui5/webcomponents-react/lib/ButtonDesign';
7+
import { Dialog } from '@ui5/webcomponents-react/lib/Dialog';
8+
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
9+
import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
10+
import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems';
11+
import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent';
12+
import { Text } from '@ui5/webcomponents-react/lib/Text';
13+
14+
const KEYBOARD_KEYS = {
15+
ESCAPE: 27,
16+
};
17+
18+
const style = {
19+
warning: {
20+
width: '1.5rem',
21+
height: '1.5rem',
22+
color: '#feb60a',
23+
},
24+
error: {
25+
width: '1.5rem',
26+
height: '1.5rem',
27+
color: '#ff5254',
28+
},
29+
information: {
30+
width: '1.5rem',
31+
height: '1.5rem',
32+
color: 'black',
33+
},
34+
text: {
35+
lineHeight: '20px',
36+
},
37+
};
38+
39+
const _getHeaderIcon = (type) => {
40+
switch (type) {
41+
case Type.Warning:
42+
return _getHeaderWarningIcon();
43+
case Type.Error:
44+
return _getHeaderErrorIcon();
45+
default:
46+
return _getHeaderInfoIcon();
47+
}
48+
};
49+
50+
const _getHeaderWarningIcon = () => {
51+
return <Icon name="message-warning" style={style.warning} />;
52+
};
53+
54+
const _getHeaderErrorIcon = () => {
55+
return <Icon name="message-error" style={style.error} />;
56+
};
57+
58+
const _getHeaderInfoIcon = () => {
59+
return <Icon name="message-information" style={style.information} />;
60+
};
61+
62+
const _handleAvoidEscapeClosing = (avoidEscapeClose) => {
63+
document.addEventListener(
64+
'keydown',
65+
(e) => {
66+
if (e.keyCode === KEYBOARD_KEYS.ESCAPE && avoidEscapeClose) {
67+
e.stopPropagation();
68+
}
69+
},
70+
true,
71+
);
72+
};
73+
74+
const InformationDialog = ({ dialogRef, avoidEscapeClose, headerText, innerText, closeButtonText, children, onClose, type }) => {
75+
const { t } = useTranslation();
76+
77+
useEffect(() => {
78+
_handleAvoidEscapeClosing(avoidEscapeClose);
79+
});
80+
81+
const _onClose = () => {
82+
onClose && onClose();
83+
if (dialogRef.current) {
84+
dialogRef.current.close();
85+
}
86+
};
87+
88+
const _getFooter = () => {
89+
return (
90+
<FlexBox alignItems={FlexBoxAlignItems.Center} direction={FlexBoxDirection.RowReverse} style={spacing.sapUiTinyMargin}>
91+
<Button design={ButtonDesign.Transparent} onClick={_onClose}>
92+
{closeButtonText ? closeButtonText : t('app.generics.close')}
93+
</Button>
94+
</FlexBox>
95+
);
96+
};
97+
98+
const _getHeader = () => {
99+
return (
100+
<FlexBox alignItems={FlexBoxAlignItems.Center} justifyContent={FlexBoxJustifyContent.Center} style={spacing.sapUiContentPadding}>
101+
{_getHeaderIcon(type)}
102+
<Text tooltip={headerText} wrapping style={{ ...spacing.sapUiTinyMarginBegin, ...style.text }}>
103+
{headerText}
104+
</Text>
105+
</FlexBox>
106+
);
107+
};
108+
109+
return (
110+
<Dialog ref={dialogRef} slot="header" header={_getHeader()} footer={_getFooter()} onAfterClose={_onClose} data-testid="information-dialog">
111+
<div style={{ ...spacing.sapUiContentPadding }}>
112+
<FlexBox direction={FlexBoxDirection.Column}>
113+
{innerText ? (
114+
<Text tooltip={innerText} wrapping style={{ ...spacing.sapUiTinyMarginBegin, ...style.text }}>
115+
{innerText}
116+
</Text>
117+
) : (
118+
children
119+
)}
120+
</FlexBox>
121+
</div>
122+
</Dialog>
123+
);
124+
};
125+
126+
export default InformationDialog;
127+
128+
export const Type = {
129+
Warning: 'WARNING',
130+
Error: 'ERROR',
131+
Info: 'INFO',
132+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
3+
import '@testing-library/jest-dom/extend-expect';
4+
import { render, screen } from '../../util/TestSetup';
5+
import InformationDialog, { Type } from './InformationDialog';
6+
7+
describe('InformationDialog.js Test Suite', () => {
8+
test('should render', () => {
9+
const dialog = <InformationDialog avoidEscapeClose type={Type.Warning} headerText={'Header text'} closeButtonText={'Close'} innerText={'Inner text'} />;
10+
render(dialog);
11+
const infoDialog = screen.getByTestId('information-dialog');
12+
expect(infoDialog).toBeInTheDocument();
13+
});
14+
15+
test('should render child when not inner text is passed', () => {
16+
const dialog = (
17+
<InformationDialog avoidEscapeClose type={Type.Warning} headerText={'Header text'} closeButtonText={'Close'}>
18+
<div data-testid="information-dialog-child"></div>
19+
</InformationDialog>
20+
);
21+
render(dialog);
22+
const child = screen.getByTestId('information-dialog-child');
23+
expect(child).toBeInTheDocument();
24+
});
25+
});

packages/ui5-webcomponents-react-seed/src/components/Shell/Shell.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AvatarShape } from '@ui5/webcomponents-react/lib/AvatarShape';
88
import { AvatarSize } from '@ui5/webcomponents-react/lib/AvatarSize';
99
import BrowserProvider from '../../util/browser/BrowserProvider';
1010
import PopoverListItems from '../Popover/List/PopoverListItems';
11+
import ThemeSwitch from '../ThemeSwitch/ThemeSwitch';
1112

1213
const style = {
1314
shell: {
@@ -24,6 +25,7 @@ const Shell = ({ title, ...props }) => {
2425
const { t } = useTranslation();
2526
const history = useHistory();
2627
const popoverConfigItemsRef = useRef(null);
28+
const themeSwitchRef = useRef(null);
2729
const popoverItems = [
2830
{
2931
children: t('shell.button.user.settings.item.languageSwitch'),
@@ -33,7 +35,7 @@ const Shell = ({ title, ...props }) => {
3335
{
3436
children: t('shell.button.user.settings.item.themeSwitch'),
3537
icon: 'customize',
36-
onClick: () => alert('activate theme switch dialog'),
38+
onClick: () => themeSwitchRef.current.open(),
3739
},
3840
];
3941

@@ -51,6 +53,7 @@ const Shell = ({ title, ...props }) => {
5153
/>
5254
<div data-testid="emptySpace-wrapper" style={style.emptySpace} />
5355
<PopoverListItems popoverRef={popoverConfigItemsRef} title={t('shell.button.user.settings')} items={popoverItems} />
56+
<ThemeSwitch dialogRef={themeSwitchRef} />
5457
</>
5558
);
5659
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { useEffect } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
import { Option } from '@ui5/webcomponents-react/lib/Option';
5+
import { Select } from '@ui5/webcomponents-react/lib/Select';
6+
import { setTheme } from '@ui5/webcomponents-base/dist/config/Theme.js';
7+
import InformationDialog from '../InformationDialog/InformationDialog';
8+
import Constants from '../../util/Constants';
9+
10+
const style = {
11+
select: {
12+
width: '100%',
13+
},
14+
};
15+
16+
const themeOptions = [
17+
{ value: 'sap_fiori_3', title: 'shell.button.user.settings.item.themeSwitch.option.default' },
18+
{ value: 'sap_fiori_3_dark', title: 'shell.button.user.settings.item.themeSwitch.option.dark' },
19+
{ value: 'sap_belize', title: 'shell.button.user.settings.item.themeSwitch.option.belize' },
20+
{ value: 'sap_fiori_3_hcb', title: 'shell.button.user.settings.item.themeSwitch.option.highContrastBlack' },
21+
{ value: 'sap_fiori_3_hcw', title: 'shell.button.user.settings.item.themeSwitch.option.highContrastWhite' },
22+
];
23+
24+
const ThemeSwitch = ({ dialogRef, storedTheme = localStorage.getItem(Constants.SEED.SELECTED_THEME) }) => {
25+
const { t } = useTranslation();
26+
27+
useEffect(() => {
28+
setTheme(storedTheme ? storedTheme : themeOptions[0].value);
29+
});
30+
31+
const onChange = (event) => {
32+
localStorage.setItem(Constants.SEED.SELECTED_THEME, event.detail.selectedOption.dataset.value);
33+
setTheme(event.detail.selectedOption.dataset.value);
34+
};
35+
36+
return (
37+
<InformationDialog dialogRef={dialogRef} headerText={t('shell.button.user.settings.item.themeSwitch.title')}>
38+
<Select onChange={onChange} style={style.select} data-testid="language-switch-wrapper">
39+
{themeOptions &&
40+
themeOptions.map((option) => {
41+
return (
42+
<Option key={option.value} data-value={option.value} selected={option.value === storedTheme}>
43+
{t(option.title)}
44+
</Option>
45+
);
46+
})}
47+
</Select>
48+
</InformationDialog>
49+
);
50+
};
51+
52+
export default ThemeSwitch;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
3+
import '@testing-library/jest-dom/extend-expect';
4+
import { getTheme } from '@ui5/webcomponents-base/dist/config/Theme.js';
5+
import { render, screen } from '../../util/TestSetup';
6+
7+
import ThemeSwitch from './ThemeSwitch';
8+
9+
describe('ThemeSwitch.js Test Suite', () => {
10+
test('Should render', () => {
11+
const dialog = <ThemeSwitch />;
12+
render(dialog);
13+
const infoDialog = screen.getByTestId('language-switch-wrapper');
14+
expect(infoDialog).toBeInTheDocument();
15+
});
16+
17+
test('Should load sap_fiori_3 as default theme', () => {
18+
render(<ThemeSwitch />);
19+
const currentTheme = getTheme();
20+
expect(currentTheme).toBe('sap_fiori_3');
21+
});
22+
23+
test('Should load black contrast theme', () => {
24+
const themeSet = 'sap_fiori_3_hcb';
25+
render(<ThemeSwitch storedTheme={themeSet} />);
26+
const currentTheme = getTheme();
27+
expect(currentTheme).toBe(themeSet);
28+
});
29+
30+
test('Should load belize theme', () => {
31+
const themeSet = 'sap_belize';
32+
render(<ThemeSwitch storedTheme={themeSet} />);
33+
const currentTheme = getTheme();
34+
expect(currentTheme).toBe(themeSet);
35+
});
36+
});

packages/ui5-webcomponents-react-seed/src/locales/en/translation.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"app.generics.close": "Close",
23
"helmet.title.app": "TodoList App",
34
"helmet.title.error": "Buggy Component - TodoList App",
45
"helmet.title.notfound": "NotFound - TodoList App",
@@ -7,6 +8,12 @@
78
"shell.button.user.settings": "User Settings",
89
"shell.button.user.settings.item.languageSwitch": "Language Switch",
910
"shell.button.user.settings.item.themeSwitch": "Theme Switch",
11+
"shell.button.user.settings.item.themeSwitch.option.default": "Default",
12+
"shell.button.user.settings.item.themeSwitch.option.dark": "Dark",
13+
"shell.button.user.settings.item.themeSwitch.option.highContrastBlack": "High Contrast Black",
14+
"shell.button.user.settings.item.themeSwitch.option.highContrastWhite": "High Contrast White",
15+
"shell.button.user.settings.item.themeSwitch.option.belize": "Belize",
16+
"shell.button.user.settings.item.themeSwitch.title": "Theme Switch",
1017
"page.error.text": "Ops! There was an error in loading this page",
1118
"page.error.alt": "Error",
1219
"page.notfound.text": "Hmmm, we could find this URL",

packages/ui5-webcomponents-react-seed/src/pages/Fallback/Fallback.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const style = {
1717
},
1818
reloadButton: {
1919
cursor: 'pointer',
20-
color: 'var(--sapLinkColor)',
2120
},
2221
};
2322

0 commit comments

Comments
 (0)