@wise/components-theming 0.0.0-experimental-2e39705

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2022 Wise Ltd.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # Design System Componentents - Theming
@@ -0,0 +1,44 @@
1
+ import { PropsWithChildren } from "react";
2
+ // TODO: Change 'light' with 'legacy' in the future
3
+ declare const baseThemes: readonly [
4
+ "light",
5
+ "personal"
6
+ ];
7
+ declare const extraThemes: readonly [
8
+ "forest-green",
9
+ "bright-green"
10
+ ];
11
+ declare const screenModes: readonly [
12
+ "light",
13
+ "dark"
14
+ ];
15
+ // TODO: componentThemes returned back for backward compatibility, refactor this place in the future
16
+ type ComponentTheme = (typeof baseThemes)[number];
17
+ type BaseTheme = (typeof baseThemes)[number];
18
+ type ExtraTheme = (typeof extraThemes)[number];
19
+ type ScreenMode = (typeof screenModes)[number];
20
+ type Theming = {
21
+ theme?: ComponentTheme | BaseTheme | ExtraTheme;
22
+ screenMode?: ScreenMode;
23
+ isNotRootProvider?: boolean | undefined;
24
+ };
25
+ declare const isThemeModern: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "personal" | "forest-green" | "bright-green";
26
+ declare const isExtraTheme: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "forest-green" | "bright-green";
27
+ declare const isForestGreenTheme: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "forest-green";
28
+ declare const isScreenModeDark: (theme: ComponentTheme | BaseTheme | ExtraTheme, screenMode: ScreenMode) => screenMode is "dark";
29
+ declare const getThemeClassName: (theme: ComponentTheme | BaseTheme | ExtraTheme, screenMode: ScreenMode) => string;
30
+ type ThemeProviderProps = PropsWithChildren<Theming> & {
31
+ className?: string;
32
+ };
33
+ declare const ThemeProvider: ({ theme, screenMode, isNotRootProvider, children, className }: ThemeProviderProps) => JSX.Element;
34
+ interface ThemeHookValue {
35
+ theme: ComponentTheme | BaseTheme | ExtraTheme;
36
+ screenMode: ScreenMode;
37
+ isModern: boolean;
38
+ isForestGreenTheme: boolean;
39
+ isScreenModeDark: boolean;
40
+ className: string;
41
+ }
42
+ declare const useTheme: () => ThemeHookValue;
43
+ export type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming };
44
+ export { isThemeModern, isExtraTheme, isForestGreenTheme, isScreenModeDark, getThemeClassName, ThemeProvider, useTheme };
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
5
+ var classNames = require('classnames');
6
+
7
+ // TODO: Change 'light' with 'legacy' in the future
8
+ var baseThemes = ['light', 'personal'];
9
+ var extraThemes = ['forest-green', 'bright-green'];
10
+ var screenModes = ['light', 'dark'];
11
+ var modernThemes = [baseThemes[1]].concat(extraThemes);
12
+ var DEFAULT_BASE_THEME = 'light';
13
+ var DEFAULT_SCREEN_MODE = 'light';
14
+
15
+ var isThemeModern = function isThemeModern(theme) {
16
+ return modernThemes.includes(theme);
17
+ };
18
+ var isExtraTheme = function isExtraTheme(theme) {
19
+ return extraThemes.includes(theme);
20
+ };
21
+ var isForestGreenTheme = function isForestGreenTheme(theme) {
22
+ return theme === extraThemes[0];
23
+ };
24
+ var isScreenModeDark = function isScreenModeDark(theme, screenMode) {
25
+ return isThemeModern(theme) && screenModes[1] === screenMode;
26
+ };
27
+ var getThemeClassName = function getThemeClassName(theme, screenMode) {
28
+ if (!isThemeModern(theme)) {
29
+ return "np-theme-".concat(theme);
30
+ }
31
+ var classes = "np-theme-personal";
32
+ if (isExtraTheme(theme)) {
33
+ classes += " ".concat(classes, "--").concat(theme);
34
+ } else if (screenModes[1] === screenMode) {
35
+ classes += " ".concat(classes, "--").concat(screenMode);
36
+ }
37
+ return classes;
38
+ };
39
+
40
+ var FALLBACK_VALUES = {
41
+ theme: DEFAULT_BASE_THEME,
42
+ screenMode: DEFAULT_SCREEN_MODE
43
+ };
44
+ var isNotProduction = function isNotProduction() {
45
+ try {
46
+ return ['localhost', 'dev-wi.se'].includes(window.location.hostname);
47
+ } catch (_unused) {
48
+ return false;
49
+ }
50
+ };
51
+ var useTheme = function useTheme() {
52
+ var theming = react.useContext(ThemeContext);
53
+ if (!theming && isNotProduction()) {
54
+ // eslint-disable-next-line no-console
55
+ console.warn('Call to useTheme outside a ThemeProvider');
56
+ }
57
+ var _ref = theming !== null && theming !== void 0 ? theming : FALLBACK_VALUES,
58
+ theme = _ref.theme,
59
+ contextScreenMode = _ref.screenMode;
60
+ var screenMode = theme === DEFAULT_BASE_THEME ? DEFAULT_SCREEN_MODE : contextScreenMode;
61
+ return react.useMemo(function () {
62
+ return {
63
+ theme: theme,
64
+ screenMode: screenMode,
65
+ isModern: isThemeModern(theme),
66
+ isForestGreenTheme: isForestGreenTheme(theme),
67
+ isScreenModeDark: isScreenModeDark(theme, screenMode),
68
+ className: getThemeClassName(theme, screenMode)
69
+ };
70
+ }, [theme, screenMode]);
71
+ };
72
+
73
+ var ThemedChildren = function ThemedChildren(_ref) {
74
+ var _ref$className = _ref.className,
75
+ className = _ref$className === void 0 ? undefined : _ref$className,
76
+ children = _ref.children;
77
+ var _useTheme = useTheme(),
78
+ themeClass = _useTheme.className;
79
+ return jsxRuntime.jsx("div", {
80
+ className: classNames.default(themeClass, className),
81
+ children: children
82
+ });
83
+ };
84
+
85
+ var ThemeContext = /*#__PURE__*/react.createContext(undefined);
86
+ var ThemeProvider = function ThemeProvider(_ref) {
87
+ var _ref$theme = _ref.theme,
88
+ theme = _ref$theme === void 0 ? DEFAULT_BASE_THEME : _ref$theme,
89
+ _ref$screenMode = _ref.screenMode,
90
+ screenMode = _ref$screenMode === void 0 ? DEFAULT_SCREEN_MODE : _ref$screenMode,
91
+ _ref$isNotRootProvide = _ref.isNotRootProvider,
92
+ isNotRootProvider = _ref$isNotRootProvide === void 0 ? false : _ref$isNotRootProvide,
93
+ children = _ref.children,
94
+ _ref$className = _ref.className,
95
+ className = _ref$className === void 0 ? undefined : _ref$className;
96
+ var isContextRoot = react.useContext(ThemeContext) === undefined;
97
+ // RegEx to check for `np-theme-` class name
98
+ // eslint-disable-next-line react-hooks/exhaustive-deps
99
+ var themeClass = new RegExp(/\bnp-theme-[a-z-]+\b/, 'g');
100
+ // useEffect hook used to apply the theme class to the HTML element
101
+ react.useEffect(function () {
102
+ if (!isNotRootProvider && isContextRoot) {
103
+ var _document$documentEle;
104
+ // Remove all the theme classes from the documentElement
105
+ (_document$documentEle = document.documentElement.className.match(themeClass)) === null || _document$documentEle === void 0 ? void 0 : _document$documentEle.forEach(function (item) {
106
+ document.documentElement.classList.remove(item);
107
+ });
108
+ getThemeClassName(theme, screenMode).split(' ').forEach(function (item) {
109
+ document.documentElement.classList.add(item);
110
+ });
111
+ }
112
+ }, [isNotRootProvider, isContextRoot, theme, screenMode, themeClass]);
113
+ var contextValue = react.useMemo(function () {
114
+ return {
115
+ theme: theme,
116
+ screenMode: screenMode
117
+ };
118
+ }, [screenMode, theme]);
119
+ return jsxRuntime.jsx(ThemeContext.Provider, {
120
+ value: contextValue,
121
+ children: jsxRuntime.jsx(ThemedChildren, {
122
+ className: className,
123
+ children: children
124
+ })
125
+ });
126
+ };
127
+
128
+ exports.ThemeProvider = ThemeProvider;
129
+ exports.getThemeClassName = getThemeClassName;
130
+ exports.isExtraTheme = isExtraTheme;
131
+ exports.isForestGreenTheme = isForestGreenTheme;
132
+ exports.isScreenModeDark = isScreenModeDark;
133
+ exports.isThemeModern = isThemeModern;
134
+ exports.useTheme = useTheme;
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/const.ts","../../src/helpers.ts","../../src/useTheme.ts","../../src/ThemedChildren.tsx","../../src/ThemeProvider.tsx"],"sourcesContent":["// TODO: Change 'light' with 'legacy' in the future\nexport const baseThemes = ['light', 'personal'] as const;\nexport const extraThemes = ['forest-green', 'bright-green'] as const;\nexport const screenModes = ['light', 'dark'] as const;\nexport const modernThemes = [baseThemes[1], ...extraThemes] as const;\n\n// TODO: componentThemes returned back for backward compatibility, refactor this place in the future\nexport type ComponentTheme = (typeof baseThemes)[number];\nexport type ModernTheme = (typeof modernThemes)[number];\nexport type BaseTheme = (typeof baseThemes)[number];\nexport type ExtraTheme = (typeof extraThemes)[number];\nexport type ForestGreenTheme = (typeof extraThemes)[0];\nexport type ScreenMode = (typeof screenModes)[number];\nexport type ScreenModeDark = (typeof screenModes)[1];\n\nexport const DEFAULT_BASE_THEME = 'light' as const;\nexport const DEFAULT_SCREEN_MODE = 'light' as const;\n\nexport type Theming = {\n theme?: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode?: ScreenMode;\n isNotRootProvider?: boolean | undefined;\n};\n","import type {\n ComponentTheme,\n ModernTheme,\n BaseTheme,\n ExtraTheme,\n ForestGreenTheme,\n ScreenMode,\n ScreenModeDark,\n} from './const';\nimport { extraThemes, screenModes, modernThemes } from './const';\n\nexport const isThemeModern = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n): theme is ModernTheme => modernThemes.includes(theme as ModernTheme);\n\nexport const isExtraTheme = (theme: ComponentTheme | BaseTheme | ExtraTheme): theme is ExtraTheme =>\n extraThemes.includes(theme as ExtraTheme);\n\nexport const isForestGreenTheme = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n): theme is ForestGreenTheme => theme === extraThemes[0];\n\nexport const isScreenModeDark = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n screenMode: ScreenMode,\n): screenMode is ScreenModeDark =>\n isThemeModern(theme as ModernTheme) && screenModes[1] === screenMode;\n\nexport const getThemeClassName = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n screenMode: ScreenMode,\n) => {\n if (!isThemeModern(theme)) {\n return `np-theme-${theme}`;\n }\n\n let classes = `np-theme-personal`;\n\n if (isExtraTheme(theme)) {\n classes += ` ${classes}--${theme}`;\n } else if (screenModes[1] === screenMode) {\n classes += ` ${classes}--${screenMode}`;\n }\n\n return classes;\n};\n","import { useContext, useMemo } from 'react';\n\nimport { ThemeContext } from './ThemeProvider';\nimport type { ComponentTheme, BaseTheme, ScreenMode, ExtraTheme } from './const';\nimport { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';\nimport { isThemeModern, isForestGreenTheme, isScreenModeDark, getThemeClassName } from './helpers';\n\ninterface ThemeHookValue {\n theme: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode: ScreenMode;\n isModern: boolean;\n isForestGreenTheme: boolean;\n isScreenModeDark: boolean;\n className: string;\n}\n\nconst FALLBACK_VALUES = {\n theme: DEFAULT_BASE_THEME,\n screenMode: DEFAULT_SCREEN_MODE,\n};\n\nconst isNotProduction = () => {\n try {\n return ['localhost', 'dev-wi.se'].includes(window.location.hostname);\n } catch {\n return false;\n }\n};\n\nexport const useTheme = (): ThemeHookValue => {\n const theming = useContext(ThemeContext);\n\n if (!theming && isNotProduction()) {\n // eslint-disable-next-line no-console\n console.warn('Call to useTheme outside a ThemeProvider');\n }\n\n const { theme, screenMode: contextScreenMode } = theming ?? FALLBACK_VALUES;\n\n const screenMode = theme === DEFAULT_BASE_THEME ? DEFAULT_SCREEN_MODE : contextScreenMode;\n\n return useMemo(\n () => ({\n theme,\n screenMode,\n isModern: isThemeModern(theme),\n isForestGreenTheme: isForestGreenTheme(theme),\n isScreenModeDark: isScreenModeDark(theme, screenMode),\n className: getThemeClassName(theme, screenMode),\n }),\n [theme, screenMode],\n );\n};\n","import classNames from 'classnames';\nimport { ReactNode } from 'react';\n\nimport { useTheme } from './useTheme';\n\ntype ThemedChildrenProps = { className?: string; children: ReactNode };\n\nexport const ThemedChildren = ({ className = undefined, children }: ThemedChildrenProps) => {\n const { className: themeClass } = useTheme();\n\n return <div className={classNames(themeClass, className)}>{children}</div>;\n};\n","import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react';\n\nimport { ThemedChildren } from './ThemedChildren';\nimport type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming } from './const';\nimport { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';\nimport { getThemeClassName } from './helpers';\n\nexport const ThemeContext = createContext<\n | {\n theme: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode: ScreenMode;\n }\n | undefined\n>(undefined);\n\ntype ThemeProviderProps = PropsWithChildren<Theming> & { className?: string };\n\nexport const ThemeProvider = ({\n theme = DEFAULT_BASE_THEME,\n screenMode = DEFAULT_SCREEN_MODE,\n isNotRootProvider = false,\n children,\n className = undefined,\n}: ThemeProviderProps) => {\n const isContextRoot = useContext(ThemeContext) === undefined;\n\n // RegEx to check for `np-theme-` class name\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const themeClass = new RegExp(/\\bnp-theme-[a-z-]+\\b/, 'g');\n\n // useEffect hook used to apply the theme class to the HTML element\n useEffect(() => {\n if (!isNotRootProvider && isContextRoot) {\n // Remove all the theme classes from the documentElement\n document.documentElement.className.match(themeClass)?.forEach((item) => {\n document.documentElement.classList.remove(item);\n });\n getThemeClassName(theme, screenMode)\n .split(' ')\n .forEach((item) => {\n document.documentElement.classList.add(item);\n });\n }\n }, [isNotRootProvider, isContextRoot, theme, screenMode, themeClass]);\n\n const contextValue = useMemo(() => ({ theme, screenMode }), [screenMode, theme]);\n\n return (\n <ThemeContext.Provider value={contextValue}>\n <ThemedChildren className={className}>{children}</ThemedChildren>\n </ThemeContext.Provider>\n );\n};\n"],"names":["baseThemes","extraThemes","screenModes","modernThemes","concat","DEFAULT_BASE_THEME","DEFAULT_SCREEN_MODE","isThemeModern","theme","includes","isExtraTheme","isForestGreenTheme","isScreenModeDark","screenMode","getThemeClassName","classes","FALLBACK_VALUES","isNotProduction","window","location","hostname","_unused","useTheme","theming","useContext","ThemeContext","console","warn","_ref","contextScreenMode","useMemo","isModern","className","ThemedChildren","_ref$className","undefined","children","_useTheme","themeClass","_jsx","classNames","createContext","ThemeProvider","_ref$theme","_ref$screenMode","_ref$isNotRootProvide","isNotRootProvider","isContextRoot","RegExp","useEffect","_document$documentEle","document","documentElement","match","forEach","item","classList","remove","split","add","contextValue","Provider","value"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { PropsWithChildren } from "react";
2
+ // TODO: Change 'light' with 'legacy' in the future
3
+ declare const baseThemes: readonly [
4
+ "light",
5
+ "personal"
6
+ ];
7
+ declare const extraThemes: readonly [
8
+ "forest-green",
9
+ "bright-green"
10
+ ];
11
+ declare const screenModes: readonly [
12
+ "light",
13
+ "dark"
14
+ ];
15
+ // TODO: componentThemes returned back for backward compatibility, refactor this place in the future
16
+ type ComponentTheme = (typeof baseThemes)[number];
17
+ type BaseTheme = (typeof baseThemes)[number];
18
+ type ExtraTheme = (typeof extraThemes)[number];
19
+ type ScreenMode = (typeof screenModes)[number];
20
+ type Theming = {
21
+ theme?: ComponentTheme | BaseTheme | ExtraTheme;
22
+ screenMode?: ScreenMode;
23
+ isNotRootProvider?: boolean | undefined;
24
+ };
25
+ declare const isThemeModern: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "personal" | "forest-green" | "bright-green";
26
+ declare const isExtraTheme: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "forest-green" | "bright-green";
27
+ declare const isForestGreenTheme: (theme: ComponentTheme | BaseTheme | ExtraTheme) => theme is "forest-green";
28
+ declare const isScreenModeDark: (theme: ComponentTheme | BaseTheme | ExtraTheme, screenMode: ScreenMode) => screenMode is "dark";
29
+ declare const getThemeClassName: (theme: ComponentTheme | BaseTheme | ExtraTheme, screenMode: ScreenMode) => string;
30
+ type ThemeProviderProps = PropsWithChildren<Theming> & {
31
+ className?: string;
32
+ };
33
+ declare const ThemeProvider: ({ theme, screenMode, isNotRootProvider, children, className }: ThemeProviderProps) => JSX.Element;
34
+ interface ThemeHookValue {
35
+ theme: ComponentTheme | BaseTheme | ExtraTheme;
36
+ screenMode: ScreenMode;
37
+ isModern: boolean;
38
+ isForestGreenTheme: boolean;
39
+ isScreenModeDark: boolean;
40
+ className: string;
41
+ }
42
+ declare const useTheme: () => ThemeHookValue;
43
+ export type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming };
44
+ export { isThemeModern, isExtraTheme, isForestGreenTheme, isScreenModeDark, getThemeClassName, ThemeProvider, useTheme };
@@ -0,0 +1,127 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useContext, useMemo, createContext, useEffect } from 'react';
3
+ import classNames from 'classnames';
4
+
5
+ // TODO: Change 'light' with 'legacy' in the future
6
+ var baseThemes = ['light', 'personal'];
7
+ var extraThemes = ['forest-green', 'bright-green'];
8
+ var screenModes = ['light', 'dark'];
9
+ var modernThemes = [baseThemes[1]].concat(extraThemes);
10
+ var DEFAULT_BASE_THEME = 'light';
11
+ var DEFAULT_SCREEN_MODE = 'light';
12
+
13
+ var isThemeModern = function isThemeModern(theme) {
14
+ return modernThemes.includes(theme);
15
+ };
16
+ var isExtraTheme = function isExtraTheme(theme) {
17
+ return extraThemes.includes(theme);
18
+ };
19
+ var isForestGreenTheme = function isForestGreenTheme(theme) {
20
+ return theme === extraThemes[0];
21
+ };
22
+ var isScreenModeDark = function isScreenModeDark(theme, screenMode) {
23
+ return isThemeModern(theme) && screenModes[1] === screenMode;
24
+ };
25
+ var getThemeClassName = function getThemeClassName(theme, screenMode) {
26
+ if (!isThemeModern(theme)) {
27
+ return "np-theme-".concat(theme);
28
+ }
29
+ var classes = "np-theme-personal";
30
+ if (isExtraTheme(theme)) {
31
+ classes += " ".concat(classes, "--").concat(theme);
32
+ } else if (screenModes[1] === screenMode) {
33
+ classes += " ".concat(classes, "--").concat(screenMode);
34
+ }
35
+ return classes;
36
+ };
37
+
38
+ var FALLBACK_VALUES = {
39
+ theme: DEFAULT_BASE_THEME,
40
+ screenMode: DEFAULT_SCREEN_MODE
41
+ };
42
+ var isNotProduction = function isNotProduction() {
43
+ try {
44
+ return ['localhost', 'dev-wi.se'].includes(window.location.hostname);
45
+ } catch (_unused) {
46
+ return false;
47
+ }
48
+ };
49
+ var useTheme = function useTheme() {
50
+ var theming = useContext(ThemeContext);
51
+ if (!theming && isNotProduction()) {
52
+ // eslint-disable-next-line no-console
53
+ console.warn('Call to useTheme outside a ThemeProvider');
54
+ }
55
+ var _ref = theming !== null && theming !== void 0 ? theming : FALLBACK_VALUES,
56
+ theme = _ref.theme,
57
+ contextScreenMode = _ref.screenMode;
58
+ var screenMode = theme === DEFAULT_BASE_THEME ? DEFAULT_SCREEN_MODE : contextScreenMode;
59
+ return useMemo(function () {
60
+ return {
61
+ theme: theme,
62
+ screenMode: screenMode,
63
+ isModern: isThemeModern(theme),
64
+ isForestGreenTheme: isForestGreenTheme(theme),
65
+ isScreenModeDark: isScreenModeDark(theme, screenMode),
66
+ className: getThemeClassName(theme, screenMode)
67
+ };
68
+ }, [theme, screenMode]);
69
+ };
70
+
71
+ var ThemedChildren = function ThemedChildren(_ref) {
72
+ var _ref$className = _ref.className,
73
+ className = _ref$className === void 0 ? undefined : _ref$className,
74
+ children = _ref.children;
75
+ var _useTheme = useTheme(),
76
+ themeClass = _useTheme.className;
77
+ return jsx("div", {
78
+ className: classNames(themeClass, className),
79
+ children: children
80
+ });
81
+ };
82
+
83
+ var ThemeContext = /*#__PURE__*/createContext(undefined);
84
+ var ThemeProvider = function ThemeProvider(_ref) {
85
+ var _ref$theme = _ref.theme,
86
+ theme = _ref$theme === void 0 ? DEFAULT_BASE_THEME : _ref$theme,
87
+ _ref$screenMode = _ref.screenMode,
88
+ screenMode = _ref$screenMode === void 0 ? DEFAULT_SCREEN_MODE : _ref$screenMode,
89
+ _ref$isNotRootProvide = _ref.isNotRootProvider,
90
+ isNotRootProvider = _ref$isNotRootProvide === void 0 ? false : _ref$isNotRootProvide,
91
+ children = _ref.children,
92
+ _ref$className = _ref.className,
93
+ className = _ref$className === void 0 ? undefined : _ref$className;
94
+ var isContextRoot = useContext(ThemeContext) === undefined;
95
+ // RegEx to check for `np-theme-` class name
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ var themeClass = new RegExp(/\bnp-theme-[a-z-]+\b/, 'g');
98
+ // useEffect hook used to apply the theme class to the HTML element
99
+ useEffect(function () {
100
+ if (!isNotRootProvider && isContextRoot) {
101
+ var _document$documentEle;
102
+ // Remove all the theme classes from the documentElement
103
+ (_document$documentEle = document.documentElement.className.match(themeClass)) === null || _document$documentEle === void 0 ? void 0 : _document$documentEle.forEach(function (item) {
104
+ document.documentElement.classList.remove(item);
105
+ });
106
+ getThemeClassName(theme, screenMode).split(' ').forEach(function (item) {
107
+ document.documentElement.classList.add(item);
108
+ });
109
+ }
110
+ }, [isNotRootProvider, isContextRoot, theme, screenMode, themeClass]);
111
+ var contextValue = useMemo(function () {
112
+ return {
113
+ theme: theme,
114
+ screenMode: screenMode
115
+ };
116
+ }, [screenMode, theme]);
117
+ return jsx(ThemeContext.Provider, {
118
+ value: contextValue,
119
+ children: jsx(ThemedChildren, {
120
+ className: className,
121
+ children: children
122
+ })
123
+ });
124
+ };
125
+
126
+ export { ThemeProvider, getThemeClassName, isExtraTheme, isForestGreenTheme, isScreenModeDark, isThemeModern, useTheme };
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/const.ts","../../src/helpers.ts","../../src/useTheme.ts","../../src/ThemedChildren.tsx","../../src/ThemeProvider.tsx"],"sourcesContent":["// TODO: Change 'light' with 'legacy' in the future\nexport const baseThemes = ['light', 'personal'] as const;\nexport const extraThemes = ['forest-green', 'bright-green'] as const;\nexport const screenModes = ['light', 'dark'] as const;\nexport const modernThemes = [baseThemes[1], ...extraThemes] as const;\n\n// TODO: componentThemes returned back for backward compatibility, refactor this place in the future\nexport type ComponentTheme = (typeof baseThemes)[number];\nexport type ModernTheme = (typeof modernThemes)[number];\nexport type BaseTheme = (typeof baseThemes)[number];\nexport type ExtraTheme = (typeof extraThemes)[number];\nexport type ForestGreenTheme = (typeof extraThemes)[0];\nexport type ScreenMode = (typeof screenModes)[number];\nexport type ScreenModeDark = (typeof screenModes)[1];\n\nexport const DEFAULT_BASE_THEME = 'light' as const;\nexport const DEFAULT_SCREEN_MODE = 'light' as const;\n\nexport type Theming = {\n theme?: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode?: ScreenMode;\n isNotRootProvider?: boolean | undefined;\n};\n","import type {\n ComponentTheme,\n ModernTheme,\n BaseTheme,\n ExtraTheme,\n ForestGreenTheme,\n ScreenMode,\n ScreenModeDark,\n} from './const';\nimport { extraThemes, screenModes, modernThemes } from './const';\n\nexport const isThemeModern = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n): theme is ModernTheme => modernThemes.includes(theme as ModernTheme);\n\nexport const isExtraTheme = (theme: ComponentTheme | BaseTheme | ExtraTheme): theme is ExtraTheme =>\n extraThemes.includes(theme as ExtraTheme);\n\nexport const isForestGreenTheme = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n): theme is ForestGreenTheme => theme === extraThemes[0];\n\nexport const isScreenModeDark = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n screenMode: ScreenMode,\n): screenMode is ScreenModeDark =>\n isThemeModern(theme as ModernTheme) && screenModes[1] === screenMode;\n\nexport const getThemeClassName = (\n theme: ComponentTheme | BaseTheme | ExtraTheme,\n screenMode: ScreenMode,\n) => {\n if (!isThemeModern(theme)) {\n return `np-theme-${theme}`;\n }\n\n let classes = `np-theme-personal`;\n\n if (isExtraTheme(theme)) {\n classes += ` ${classes}--${theme}`;\n } else if (screenModes[1] === screenMode) {\n classes += ` ${classes}--${screenMode}`;\n }\n\n return classes;\n};\n","import { useContext, useMemo } from 'react';\n\nimport { ThemeContext } from './ThemeProvider';\nimport type { ComponentTheme, BaseTheme, ScreenMode, ExtraTheme } from './const';\nimport { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';\nimport { isThemeModern, isForestGreenTheme, isScreenModeDark, getThemeClassName } from './helpers';\n\ninterface ThemeHookValue {\n theme: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode: ScreenMode;\n isModern: boolean;\n isForestGreenTheme: boolean;\n isScreenModeDark: boolean;\n className: string;\n}\n\nconst FALLBACK_VALUES = {\n theme: DEFAULT_BASE_THEME,\n screenMode: DEFAULT_SCREEN_MODE,\n};\n\nconst isNotProduction = () => {\n try {\n return ['localhost', 'dev-wi.se'].includes(window.location.hostname);\n } catch {\n return false;\n }\n};\n\nexport const useTheme = (): ThemeHookValue => {\n const theming = useContext(ThemeContext);\n\n if (!theming && isNotProduction()) {\n // eslint-disable-next-line no-console\n console.warn('Call to useTheme outside a ThemeProvider');\n }\n\n const { theme, screenMode: contextScreenMode } = theming ?? FALLBACK_VALUES;\n\n const screenMode = theme === DEFAULT_BASE_THEME ? DEFAULT_SCREEN_MODE : contextScreenMode;\n\n return useMemo(\n () => ({\n theme,\n screenMode,\n isModern: isThemeModern(theme),\n isForestGreenTheme: isForestGreenTheme(theme),\n isScreenModeDark: isScreenModeDark(theme, screenMode),\n className: getThemeClassName(theme, screenMode),\n }),\n [theme, screenMode],\n );\n};\n","import classNames from 'classnames';\nimport { ReactNode } from 'react';\n\nimport { useTheme } from './useTheme';\n\ntype ThemedChildrenProps = { className?: string; children: ReactNode };\n\nexport const ThemedChildren = ({ className = undefined, children }: ThemedChildrenProps) => {\n const { className: themeClass } = useTheme();\n\n return <div className={classNames(themeClass, className)}>{children}</div>;\n};\n","import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react';\n\nimport { ThemedChildren } from './ThemedChildren';\nimport type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming } from './const';\nimport { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';\nimport { getThemeClassName } from './helpers';\n\nexport const ThemeContext = createContext<\n | {\n theme: ComponentTheme | BaseTheme | ExtraTheme;\n screenMode: ScreenMode;\n }\n | undefined\n>(undefined);\n\ntype ThemeProviderProps = PropsWithChildren<Theming> & { className?: string };\n\nexport const ThemeProvider = ({\n theme = DEFAULT_BASE_THEME,\n screenMode = DEFAULT_SCREEN_MODE,\n isNotRootProvider = false,\n children,\n className = undefined,\n}: ThemeProviderProps) => {\n const isContextRoot = useContext(ThemeContext) === undefined;\n\n // RegEx to check for `np-theme-` class name\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const themeClass = new RegExp(/\\bnp-theme-[a-z-]+\\b/, 'g');\n\n // useEffect hook used to apply the theme class to the HTML element\n useEffect(() => {\n if (!isNotRootProvider && isContextRoot) {\n // Remove all the theme classes from the documentElement\n document.documentElement.className.match(themeClass)?.forEach((item) => {\n document.documentElement.classList.remove(item);\n });\n getThemeClassName(theme, screenMode)\n .split(' ')\n .forEach((item) => {\n document.documentElement.classList.add(item);\n });\n }\n }, [isNotRootProvider, isContextRoot, theme, screenMode, themeClass]);\n\n const contextValue = useMemo(() => ({ theme, screenMode }), [screenMode, theme]);\n\n return (\n <ThemeContext.Provider value={contextValue}>\n <ThemedChildren className={className}>{children}</ThemedChildren>\n </ThemeContext.Provider>\n );\n};\n"],"names":["baseThemes","extraThemes","screenModes","modernThemes","concat","DEFAULT_BASE_THEME","DEFAULT_SCREEN_MODE","isThemeModern","theme","includes","isExtraTheme","isForestGreenTheme","isScreenModeDark","screenMode","getThemeClassName","classes","FALLBACK_VALUES","isNotProduction","window","location","hostname","_unused","useTheme","theming","useContext","ThemeContext","console","warn","_ref","contextScreenMode","useMemo","isModern","className","ThemedChildren","_ref$className","undefined","children","_useTheme","themeClass","_jsx","classNames","createContext","ThemeProvider","_ref$theme","_ref$screenMode","_ref$isNotRootProvide","isNotRootProvider","isContextRoot","RegExp","useEffect","_document$documentEle","document","documentElement","match","forEach","item","classList","remove","split","add","contextValue","Provider","value"],"mappings":";;;;AAAA;AACO,IAAMA,UAAU,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAA;AACjD,IAAMC,WAAW,GAAG,CAAC,cAAc,EAAE,cAAc,CAAU,CAAA;AAC7D,IAAMC,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,CAAU,CAAA;AAC9C,IAAMC,YAAY,GAAA,CAAIH,UAAU,CAAC,CAAC,CAAC,CAAAI,CAAAA,MAAA,CAAKH,WAAW,CAAU,CAAA;AAW7D,IAAMI,kBAAkB,GAAG,OAAgB,CAAA;AAC3C,IAAMC,mBAAmB,GAAG,OAAgB;;ICLtCC,aAAa,GAAG,SAAhBA,aAAaA,CACxBC,KAA8C,EAAA;AAAA,EAAA,OACrBL,YAAY,CAACM,QAAQ,CAACD,KAAoB,CAAC,CAAA;AAAA,EAAA;IAEzDE,YAAY,GAAG,SAAfA,YAAYA,CAAIF,KAA8C,EAAA;AAAA,EAAA,OACzEP,WAAW,CAACQ,QAAQ,CAACD,KAAmB,CAAC,CAAA;AAAA,EAAA;IAE9BG,kBAAkB,GAAG,SAArBA,kBAAkBA,CAC7BH,KAA8C,EAAA;AAAA,EAAA,OAChBA,KAAK,KAAKP,WAAW,CAAC,CAAC,CAAC,CAAA;AAAA,EAAA;AAEjD,IAAMW,gBAAgB,GAAG,SAAnBA,gBAAgBA,CAC3BJ,KAA8C,EAC9CK,UAAsB,EAAA;EAAA,OAEtBN,aAAa,CAACC,KAAoB,CAAC,IAAIN,WAAW,CAAC,CAAC,CAAC,KAAKW,UAAU,CAAA;AAAA,EAAA;AAE/D,IAAMC,iBAAiB,GAAG,SAApBA,iBAAiBA,CAC5BN,KAA8C,EAC9CK,UAAsB,EACpB;AACF,EAAA,IAAI,CAACN,aAAa,CAACC,KAAK,CAAC,EAAE;IACzB,OAAAJ,WAAAA,CAAAA,MAAA,CAAmBI,KAAK,CAAA,CAAA;;AAG1B,EAAA,IAAIO,OAAO,GAAsB,mBAAA,CAAA;AAEjC,EAAA,IAAIL,YAAY,CAACF,KAAK,CAAC,EAAE;IACvBO,OAAO,IAAA,GAAA,CAAAX,MAAA,CAAQW,OAAO,QAAAX,MAAA,CAAKI,KAAK,CAAE,CAAA;AACnC,GAAA,MAAM,IAAIN,WAAW,CAAC,CAAC,CAAC,KAAKW,UAAU,EAAE;IACxCE,OAAO,IAAA,GAAA,CAAAX,MAAA,CAAQW,OAAO,QAAAX,MAAA,CAAKS,UAAU,CAAE,CAAA;;AAGzC,EAAA,OAAOE,OAAO,CAAA;AAChB;;AC7BA,IAAMC,eAAe,GAAG;AACtBR,EAAAA,KAAK,EAAEH,kBAAkB;AACzBQ,EAAAA,UAAU,EAAEP,mBAAAA;AACb,CAAA,CAAA;AAED,IAAMW,eAAe,GAAG,SAAlBA,eAAeA,GAAQ;EAC3B,IAAI;AACF,IAAA,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAACR,QAAQ,CAACS,MAAM,CAACC,QAAQ,CAACC,QAAQ,CAAC,CAAA;GACrE,CAAC,OAAAC,OAAA,EAAM;AACN,IAAA,OAAO,KAAK,CAAA;;AAEhB,CAAC,CAAA;IAEYC,QAAQ,GAAG,SAAXA,QAAQA,GAAwB;AAC3C,EAAA,IAAMC,OAAO,GAAGC,UAAU,CAACC,YAAY,CAAC,CAAA;AAExC,EAAA,IAAI,CAACF,OAAO,IAAIN,eAAe,EAAE,EAAE;AACjC;AACAS,IAAAA,OAAO,CAACC,IAAI,CAAC,0CAA0C,CAAC,CAAA;;EAG1D,IAAAC,IAAA,GAAiDL,OAAO,KAAA,IAAA,IAAPA,OAAO,KAAPA,KAAAA,CAAAA,GAAAA,OAAO,GAAIP,eAAe;IAAnER,KAAK,GAAAoB,IAAA,CAALpB,KAAK;IAAcqB,iBAAiB,GAAAD,IAAA,CAA7Bf,UAAU,CAAA;EAEzB,IAAMA,UAAU,GAAGL,KAAK,KAAKH,kBAAkB,GAAGC,mBAAmB,GAAGuB,iBAAiB,CAAA;AAEzF,EAAA,OAAOC,OAAO,CACZ,YAAA;IAAA,OAAO;AACLtB,MAAAA,KAAK,EAALA,KAAK;AACLK,MAAAA,UAAU,EAAVA,UAAU;AACVkB,MAAAA,QAAQ,EAAExB,aAAa,CAACC,KAAK,CAAC;AAC9BG,MAAAA,kBAAkB,EAAEA,kBAAkB,CAACH,KAAK,CAAC;AAC7CI,MAAAA,gBAAgB,EAAEA,gBAAgB,CAACJ,KAAK,EAAEK,UAAU,CAAC;AACrDmB,MAAAA,SAAS,EAAElB,iBAAiB,CAACN,KAAK,EAAEK,UAAU,CAAA;AAC/C,KAAA,CAAA;AAAA,GAAC,EACF,CAACL,KAAK,EAAEK,UAAU,CAAC,CACpB,CAAA;AACH;;AC7CO,IAAMoB,cAAc,GAAG,SAAjBA,cAAcA,CAAAL,IAAA,EAAgE;AAAA,EAAA,IAAAM,cAAA,GAAAN,IAAA,CAA1DI,SAAS;AAATA,IAAAA,SAAS,GAAAE,cAAA,KAAGC,KAAAA,CAAAA,GAAAA,SAAS,GAAAD,cAAA;IAAEE,QAAQ,GAAAR,IAAA,CAARQ,QAAQ,CAAA;EAC9D,IAAAC,SAAA,GAAkCf,QAAQ,EAAE;IAAzBgB,UAAU,GAAAD,SAAA,CAArBL,SAAS,CAAA;AAEjB,EAAA,OAAOO,GAAA,CAAA,KAAA,EAAA;AAAKP,IAAAA,SAAS,EAAEQ,UAAU,CAACF,UAAU,EAAEN,SAAS,CAAC;AAAAI,IAAAA,QAAA,EAAGA,QAAAA;GAAe,CAAA,CAAA;AAC5E,CAAC;;ACJM,IAAMX,YAAY,gBAAGgB,aAAa,CAMvCN,SAAS,CAAC,CAAA;IAICO,aAAa,GAAG,SAAhBA,aAAaA,CAAAd,IAAA,EAMD;AAAA,EAAA,IAAAe,UAAA,GAAAf,IAAA,CALvBpB,KAAK;AAALA,IAAAA,KAAK,GAAAmC,UAAA,KAAGtC,KAAAA,CAAAA,GAAAA,kBAAkB,GAAAsC,UAAA;IAAAC,eAAA,GAAAhB,IAAA,CAC1Bf,UAAU;AAAVA,IAAAA,UAAU,GAAA+B,eAAA,KAAGtC,KAAAA,CAAAA,GAAAA,mBAAmB,GAAAsC,eAAA;IAAAC,qBAAA,GAAAjB,IAAA,CAChCkB,iBAAiB;AAAjBA,IAAAA,iBAAiB,GAAAD,qBAAA,KAAG,KAAA,CAAA,GAAA,KAAK,GAAAA,qBAAA;IACzBT,QAAQ,GAAAR,IAAA,CAARQ,QAAQ;IAAAF,cAAA,GAAAN,IAAA,CACRI,SAAS;AAATA,IAAAA,SAAS,GAAAE,cAAA,KAAGC,KAAAA,CAAAA,GAAAA,SAAS,GAAAD,cAAA,CAAA;AAErB,EAAA,IAAMa,aAAa,GAAGvB,UAAU,CAACC,YAAY,CAAC,KAAKU,SAAS,CAAA;AAE5D;AACA;EACA,IAAMG,UAAU,GAAG,IAAIU,MAAM,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAA;AAE1D;AACAC,EAAAA,SAAS,CAAC,YAAK;AACb,IAAA,IAAI,CAACH,iBAAiB,IAAIC,aAAa,EAAE;AAAA,MAAA,IAAAG,qBAAA,CAAA;AACvC;MACA,CAAAA,qBAAA,GAAAC,QAAQ,CAACC,eAAe,CAACpB,SAAS,CAACqB,KAAK,CAACf,UAAU,CAAC,MAAA,IAAA,IAAAY,qBAAA,KAApDA,KAAAA,CAAAA,GAAAA,KAAAA,CAAAA,GAAAA,qBAAA,CAAsDI,OAAO,CAAC,UAACC,IAAI,EAAI;QACrEJ,QAAQ,CAACC,eAAe,CAACI,SAAS,CAACC,MAAM,CAACF,IAAI,CAAC,CAAA;AACjD,OAAC,CAAC,CAAA;AACFzC,MAAAA,iBAAiB,CAACN,KAAK,EAAEK,UAAU,CAAC,CACjC6C,KAAK,CAAC,GAAG,CAAC,CACVJ,OAAO,CAAC,UAACC,IAAI,EAAI;QAChBJ,QAAQ,CAACC,eAAe,CAACI,SAAS,CAACG,GAAG,CAACJ,IAAI,CAAC,CAAA;AAC9C,OAAC,CAAC,CAAA;;AAER,GAAC,EAAE,CAACT,iBAAiB,EAAEC,aAAa,EAAEvC,KAAK,EAAEK,UAAU,EAAEyB,UAAU,CAAC,CAAC,CAAA;EAErE,IAAMsB,YAAY,GAAG9B,OAAO,CAAC,YAAA;IAAA,OAAO;AAAEtB,MAAAA,KAAK,EAALA,KAAK;AAAEK,MAAAA,UAAU,EAAVA,UAAAA;KAAY,CAAA;AAAA,GAAC,EAAE,CAACA,UAAU,EAAEL,KAAK,CAAC,CAAC,CAAA;AAEhF,EAAA,OACE+B,GAAA,CAACd,YAAY,CAACoC,QAAQ,EAAA;AAACC,IAAAA,KAAK,EAAEF,YAAY;IAAAxB,QAAA,EACxCG,GAAA,CAACN,cAAc,EAAA;AAACD,MAAAA,SAAS,EAAEA,SAAS;AAAAI,MAAAA,QAAA,EAAGA,QAAAA;AAAQ,KAAA,CAAA;GACzB,CAAA,CAAA;AAE5B;;;;"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@wise/components-theming",
3
+ "version": "0.0.0-experimental-2e39705",
4
+ "description": "Provides theming support for the Wise Design system components",
5
+ "license": "Apache-2.0",
6
+ "keywords": [
7
+ "wise"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "fullname": "transferwise/neptune-web",
12
+ "url": "git+https://github.com/transferwise/neptune-web.git"
13
+ },
14
+ "dependencies": {
15
+ "classnames": "^2.3.2"
16
+ },
17
+ "devDependencies": {
18
+ "@babel/plugin-syntax-import-assertions": "7.20.0",
19
+ "@rollup/plugin-typescript": "11.0.0",
20
+ "@testing-library/jest-dom": "^5.14.1",
21
+ "@testing-library/react": "^12.0.0",
22
+ "@testing-library/react-hooks": "^8.0.0",
23
+ "@transferwise/eslint-config": "^7.4.0",
24
+ "@transferwise/test-config": "^5.0.2",
25
+ "@types/jest": "^27.5.2",
26
+ "@types/node": "^18.15.12",
27
+ "@types/react": "^17.0.59",
28
+ "@types/react-dom": "^17.0.20",
29
+ "@types/testing-library__jest-dom": "^5.14.5",
30
+ "classnames": "^2.3.2",
31
+ "jest": "^27.0.6",
32
+ "rollup": "3.2.3",
33
+ "rollup-plugin-ts": "3.0.2",
34
+ "typescript": "4.9.4"
35
+ },
36
+ "peerDependencies": {
37
+ "react": ">=16.14",
38
+ "react-dom": ">=16.14"
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "import": "./dist/es/index.js",
43
+ "require": "./dist/cjs/index.js"
44
+ }
45
+ },
46
+ "main": "./dist/cjs/index.js",
47
+ "module": "./dist/es/index.js",
48
+ "types": "./dist/es/index.d.ts",
49
+ "files": [
50
+ "dist/",
51
+ "src/"
52
+ ],
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "rollup --config --sourcemap",
58
+ "test": "jest --maxWorkers=4",
59
+ "test:watch": "jest --watch --env=jsdom",
60
+ "lint": "pnpm run lint:check",
61
+ "lint:check": "npm-run-all --parallel lint:check:*",
62
+ "lint:check:format": "prettier --check --ignore-path ../../.prettierignore .",
63
+ "lint:check:js+ts": "eslint --ext .js,.jsx,.mjs,.ts,.tsx,.mts --ignore-path ../../.gitignore .",
64
+ "lint:check:types": "tsc --noEmit --emitDeclarationOnly false",
65
+ "lint:fix": "npm-run-all --serial lint:fix:*",
66
+ "lint:fix:format": "prettier --write --ignore-path ../../.prettierignore .",
67
+ "lint:fix:js+ts": "pnpm run lint:check:js+ts --fix"
68
+ }
69
+ }
@@ -0,0 +1,44 @@
1
+ import { render, screen } from '@testing-library/react';
2
+
3
+ import { ThemeProvider } from './ThemeProvider';
4
+
5
+ import {} from './const';
6
+
7
+ describe('ThemeProvider', () => {
8
+ it('can nest theme providers', () => {
9
+ render(
10
+ <ThemeProvider theme="light">
11
+ <div>light</div>
12
+ <ThemeProvider theme="personal">
13
+ <div>personal</div>
14
+ <ThemeProvider theme="light">
15
+ <div>light</div>
16
+ </ThemeProvider>
17
+ </ThemeProvider>
18
+ </ThemeProvider>,
19
+ );
20
+
21
+ // The first provider will not have a className attached to it
22
+ expect(screen.getAllByText('light')[0]).not.toHaveClass('np-theme-light');
23
+ // eslint-disable-next-line testing-library/no-node-access
24
+ expect(screen.getByText('personal').parentElement).toHaveClass('np-theme-personal');
25
+ // eslint-disable-next-line testing-library/no-node-access
26
+ expect(screen.getAllByText('light')[1].parentElement).toHaveClass('np-theme-light');
27
+ });
28
+
29
+ it('matches snapshot', () => {
30
+ const { asFragment } = render(
31
+ <ThemeProvider theme="light">
32
+ <div>light</div>
33
+ <ThemeProvider theme="personal">
34
+ <div>personal</div>
35
+ <ThemeProvider theme="light">
36
+ <div>light</div>
37
+ </ThemeProvider>
38
+ </ThemeProvider>
39
+ </ThemeProvider>,
40
+ );
41
+
42
+ expect(asFragment()).toMatchSnapshot();
43
+ });
44
+ });
@@ -0,0 +1,53 @@
1
+ import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react';
2
+
3
+ import { ThemedChildren } from './ThemedChildren';
4
+ import type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming } from './const';
5
+ import { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';
6
+ import { getThemeClassName } from './helpers';
7
+
8
+ export const ThemeContext = createContext<
9
+ | {
10
+ theme: ComponentTheme | BaseTheme | ExtraTheme;
11
+ screenMode: ScreenMode;
12
+ }
13
+ | undefined
14
+ >(undefined);
15
+
16
+ type ThemeProviderProps = PropsWithChildren<Theming> & { className?: string };
17
+
18
+ export const ThemeProvider = ({
19
+ theme = DEFAULT_BASE_THEME,
20
+ screenMode = DEFAULT_SCREEN_MODE,
21
+ isNotRootProvider = false,
22
+ children,
23
+ className = undefined,
24
+ }: ThemeProviderProps) => {
25
+ const isContextRoot = useContext(ThemeContext) === undefined;
26
+
27
+ // RegEx to check for `np-theme-` class name
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ const themeClass = new RegExp(/\bnp-theme-[a-z-]+\b/, 'g');
30
+
31
+ // useEffect hook used to apply the theme class to the HTML element
32
+ useEffect(() => {
33
+ if (!isNotRootProvider && isContextRoot) {
34
+ // Remove all the theme classes from the documentElement
35
+ document.documentElement.className.match(themeClass)?.forEach((item) => {
36
+ document.documentElement.classList.remove(item);
37
+ });
38
+ getThemeClassName(theme, screenMode)
39
+ .split(' ')
40
+ .forEach((item) => {
41
+ document.documentElement.classList.add(item);
42
+ });
43
+ }
44
+ }, [isNotRootProvider, isContextRoot, theme, screenMode, themeClass]);
45
+
46
+ const contextValue = useMemo(() => ({ theme, screenMode }), [screenMode, theme]);
47
+
48
+ return (
49
+ <ThemeContext.Provider value={contextValue}>
50
+ <ThemedChildren className={className}>{children}</ThemedChildren>
51
+ </ThemeContext.Provider>
52
+ );
53
+ };
@@ -0,0 +1,12 @@
1
+ import classNames from 'classnames';
2
+ import { ReactNode } from 'react';
3
+
4
+ import { useTheme } from './useTheme';
5
+
6
+ type ThemedChildrenProps = { className?: string; children: ReactNode };
7
+
8
+ export const ThemedChildren = ({ className = undefined, children }: ThemedChildrenProps) => {
9
+ const { className: themeClass } = useTheme();
10
+
11
+ return <div className={classNames(themeClass, className)}>{children}</div>;
12
+ };
@@ -0,0 +1,27 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ThemeProvider matches snapshot 1`] = `
4
+ <DocumentFragment>
5
+ <div
6
+ class="np-theme-light"
7
+ >
8
+ <div>
9
+ light
10
+ </div>
11
+ <div
12
+ class="np-theme-personal"
13
+ >
14
+ <div>
15
+ personal
16
+ </div>
17
+ <div
18
+ class="np-theme-light"
19
+ >
20
+ <div>
21
+ light
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </DocumentFragment>
27
+ `;
package/src/const.ts ADDED
@@ -0,0 +1,23 @@
1
+ // TODO: Change 'light' with 'legacy' in the future
2
+ export const baseThemes = ['light', 'personal'] as const;
3
+ export const extraThemes = ['forest-green', 'bright-green'] as const;
4
+ export const screenModes = ['light', 'dark'] as const;
5
+ export const modernThemes = [baseThemes[1], ...extraThemes] as const;
6
+
7
+ // TODO: componentThemes returned back for backward compatibility, refactor this place in the future
8
+ export type ComponentTheme = (typeof baseThemes)[number];
9
+ export type ModernTheme = (typeof modernThemes)[number];
10
+ export type BaseTheme = (typeof baseThemes)[number];
11
+ export type ExtraTheme = (typeof extraThemes)[number];
12
+ export type ForestGreenTheme = (typeof extraThemes)[0];
13
+ export type ScreenMode = (typeof screenModes)[number];
14
+ export type ScreenModeDark = (typeof screenModes)[1];
15
+
16
+ export const DEFAULT_BASE_THEME = 'light' as const;
17
+ export const DEFAULT_SCREEN_MODE = 'light' as const;
18
+
19
+ export type Theming = {
20
+ theme?: ComponentTheme | BaseTheme | ExtraTheme;
21
+ screenMode?: ScreenMode;
22
+ isNotRootProvider?: boolean | undefined;
23
+ };
package/src/helpers.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type {
2
+ ComponentTheme,
3
+ ModernTheme,
4
+ BaseTheme,
5
+ ExtraTheme,
6
+ ForestGreenTheme,
7
+ ScreenMode,
8
+ ScreenModeDark,
9
+ } from './const';
10
+ import { extraThemes, screenModes, modernThemes } from './const';
11
+
12
+ export const isThemeModern = (
13
+ theme: ComponentTheme | BaseTheme | ExtraTheme,
14
+ ): theme is ModernTheme => modernThemes.includes(theme as ModernTheme);
15
+
16
+ export const isExtraTheme = (theme: ComponentTheme | BaseTheme | ExtraTheme): theme is ExtraTheme =>
17
+ extraThemes.includes(theme as ExtraTheme);
18
+
19
+ export const isForestGreenTheme = (
20
+ theme: ComponentTheme | BaseTheme | ExtraTheme,
21
+ ): theme is ForestGreenTheme => theme === extraThemes[0];
22
+
23
+ export const isScreenModeDark = (
24
+ theme: ComponentTheme | BaseTheme | ExtraTheme,
25
+ screenMode: ScreenMode,
26
+ ): screenMode is ScreenModeDark =>
27
+ isThemeModern(theme as ModernTheme) && screenModes[1] === screenMode;
28
+
29
+ export const getThemeClassName = (
30
+ theme: ComponentTheme | BaseTheme | ExtraTheme,
31
+ screenMode: ScreenMode,
32
+ ) => {
33
+ if (!isThemeModern(theme)) {
34
+ return `np-theme-${theme}`;
35
+ }
36
+
37
+ let classes = `np-theme-personal`;
38
+
39
+ if (isExtraTheme(theme)) {
40
+ classes += ` ${classes}--${theme}`;
41
+ } else if (screenModes[1] === screenMode) {
42
+ classes += ` ${classes}--${screenMode}`;
43
+ }
44
+
45
+ return classes;
46
+ };
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type { ComponentTheme, BaseTheme, ExtraTheme, ScreenMode, Theming } from './const';
2
+ export {
3
+ isThemeModern,
4
+ isExtraTheme,
5
+ isForestGreenTheme,
6
+ isScreenModeDark,
7
+ getThemeClassName,
8
+ } from './helpers';
9
+
10
+ export { ThemeProvider } from './ThemeProvider';
11
+ export { useTheme } from './useTheme';
@@ -0,0 +1,224 @@
1
+ import { renderHook } from '@testing-library/react-hooks';
2
+
3
+ import { ThemeProvider } from './ThemeProvider';
4
+ import { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';
5
+ import { useTheme } from './useTheme';
6
+
7
+ describe('useTheme', () => {
8
+ it('returns default light theme', () => {
9
+ jest.spyOn(console, 'warn').mockImplementation();
10
+
11
+ const {
12
+ result: { current },
13
+ } = renderHook(() => useTheme());
14
+
15
+ expect(current).toStrictEqual({
16
+ theme: DEFAULT_BASE_THEME,
17
+ screenMode: DEFAULT_SCREEN_MODE,
18
+ isModern: false,
19
+ isForestGreenTheme: false,
20
+ isScreenModeDark: false,
21
+ className: 'np-theme-light',
22
+ });
23
+
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ it('returns personal theme', () => {
28
+ const {
29
+ result: { current },
30
+ } = renderHook(() => useTheme(), {
31
+ wrapper: ThemeProvider,
32
+ initialProps: {
33
+ theme: 'personal' as const,
34
+ },
35
+ });
36
+
37
+ expect(current).toStrictEqual({
38
+ theme: 'personal',
39
+ screenMode: DEFAULT_SCREEN_MODE,
40
+ isModern: true,
41
+ isForestGreenTheme: false,
42
+ isScreenModeDark: false,
43
+ className: 'np-theme-personal',
44
+ });
45
+ });
46
+
47
+ it('returns forest-green theme', () => {
48
+ const {
49
+ result: { current },
50
+ } = renderHook(() => useTheme(), {
51
+ wrapper: ThemeProvider,
52
+ initialProps: {
53
+ theme: 'forest-green' as const,
54
+ },
55
+ });
56
+
57
+ expect(current).toStrictEqual({
58
+ theme: 'forest-green',
59
+ screenMode: DEFAULT_SCREEN_MODE,
60
+ isModern: true,
61
+ isForestGreenTheme: true,
62
+ isScreenModeDark: false,
63
+ className: 'np-theme-personal np-theme-personal--forest-green',
64
+ });
65
+ });
66
+
67
+ it('returns bright-green theme', () => {
68
+ const {
69
+ result: { current },
70
+ } = renderHook(() => useTheme(), {
71
+ wrapper: ThemeProvider,
72
+ initialProps: {
73
+ theme: 'bright-green' as const,
74
+ },
75
+ });
76
+
77
+ expect(current).toStrictEqual({
78
+ theme: 'bright-green',
79
+ screenMode: DEFAULT_SCREEN_MODE,
80
+ isModern: true,
81
+ isForestGreenTheme: false,
82
+ isScreenModeDark: false,
83
+ className: 'np-theme-personal np-theme-personal--bright-green',
84
+ });
85
+ });
86
+
87
+ it('returns default screen mode if used default light theme', () => {
88
+ const {
89
+ result: { current },
90
+ } = renderHook(() => useTheme(), {
91
+ wrapper: ThemeProvider,
92
+ initialProps: {
93
+ theme: DEFAULT_BASE_THEME,
94
+ screenMode: 'dark' as const,
95
+ },
96
+ });
97
+
98
+ expect(current).toStrictEqual({
99
+ theme: DEFAULT_BASE_THEME,
100
+ screenMode: DEFAULT_SCREEN_MODE,
101
+ isModern: false,
102
+ isForestGreenTheme: false,
103
+ isScreenModeDark: false,
104
+ className: 'np-theme-light',
105
+ });
106
+ });
107
+
108
+ it('returns dark screen mode', () => {
109
+ const {
110
+ result: { current },
111
+ } = renderHook(() => useTheme(), {
112
+ wrapper: ThemeProvider,
113
+ initialProps: {
114
+ theme: 'personal' as const,
115
+ screenMode: 'dark' as const,
116
+ },
117
+ });
118
+
119
+ expect(current).toStrictEqual({
120
+ theme: 'personal',
121
+ screenMode: 'dark',
122
+ isModern: true,
123
+ isForestGreenTheme: false,
124
+ isScreenModeDark: true,
125
+ className: 'np-theme-personal np-theme-personal--dark',
126
+ });
127
+ });
128
+
129
+ it('warns when used outside a theme provider on staging or localhost', () => {
130
+ jest.spyOn(console, 'warn').mockImplementation();
131
+
132
+ Object.defineProperty(window, 'location', {
133
+ value: {
134
+ hostname: 'wise.com',
135
+ },
136
+ writable: true,
137
+ });
138
+
139
+ const {
140
+ result: { current: productionValue },
141
+ } = renderHook(() => useTheme());
142
+
143
+ expect(productionValue).toStrictEqual({
144
+ theme: DEFAULT_BASE_THEME,
145
+ screenMode: DEFAULT_SCREEN_MODE,
146
+ isModern: false,
147
+ isForestGreenTheme: false,
148
+ isScreenModeDark: false,
149
+ className: 'np-theme-light',
150
+ });
151
+
152
+ // eslint-disable-next-line no-console
153
+ expect(console.warn).not.toHaveBeenCalled();
154
+
155
+ Object.defineProperty(window, 'location', {
156
+ value: {
157
+ hostname: 'dev-wi.se',
158
+ },
159
+ writable: true,
160
+ });
161
+
162
+ const {
163
+ result: { current: stagingValue },
164
+ } = renderHook(() => useTheme());
165
+
166
+ expect(stagingValue).toStrictEqual({
167
+ theme: DEFAULT_BASE_THEME,
168
+ screenMode: DEFAULT_SCREEN_MODE,
169
+ isModern: false,
170
+ isForestGreenTheme: false,
171
+ isScreenModeDark: false,
172
+ className: 'np-theme-light',
173
+ });
174
+
175
+ // eslint-disable-next-line no-console
176
+ expect(console.warn).toHaveBeenCalledTimes(1);
177
+
178
+ Object.defineProperty(window, 'location', {
179
+ value: {
180
+ hostname: 'localhost',
181
+ },
182
+ writable: true,
183
+ });
184
+
185
+ const {
186
+ result: { current: localhostValue },
187
+ } = renderHook(() => useTheme());
188
+
189
+ expect(localhostValue).toStrictEqual({
190
+ theme: DEFAULT_BASE_THEME,
191
+ screenMode: DEFAULT_SCREEN_MODE,
192
+ isModern: false,
193
+ isForestGreenTheme: false,
194
+ isScreenModeDark: false,
195
+ className: 'np-theme-light',
196
+ });
197
+
198
+ // eslint-disable-next-line no-console
199
+ expect(console.warn).toHaveBeenCalledTimes(2);
200
+
201
+ Object.defineProperty(window, 'location', {
202
+ value: undefined,
203
+ writable: true,
204
+ });
205
+
206
+ const {
207
+ result: { current: noHostnameValue },
208
+ } = renderHook(() => useTheme());
209
+
210
+ expect(noHostnameValue).toStrictEqual({
211
+ theme: DEFAULT_BASE_THEME,
212
+ screenMode: DEFAULT_SCREEN_MODE,
213
+ isModern: false,
214
+ isForestGreenTheme: false,
215
+ isScreenModeDark: false,
216
+ className: 'np-theme-light',
217
+ });
218
+
219
+ // eslint-disable-next-line no-console
220
+ expect(console.warn).toHaveBeenCalledTimes(2);
221
+
222
+ jest.clearAllMocks();
223
+ });
224
+ });
@@ -0,0 +1,53 @@
1
+ import { useContext, useMemo } from 'react';
2
+
3
+ import { ThemeContext } from './ThemeProvider';
4
+ import type { ComponentTheme, BaseTheme, ScreenMode, ExtraTheme } from './const';
5
+ import { DEFAULT_BASE_THEME, DEFAULT_SCREEN_MODE } from './const';
6
+ import { isThemeModern, isForestGreenTheme, isScreenModeDark, getThemeClassName } from './helpers';
7
+
8
+ interface ThemeHookValue {
9
+ theme: ComponentTheme | BaseTheme | ExtraTheme;
10
+ screenMode: ScreenMode;
11
+ isModern: boolean;
12
+ isForestGreenTheme: boolean;
13
+ isScreenModeDark: boolean;
14
+ className: string;
15
+ }
16
+
17
+ const FALLBACK_VALUES = {
18
+ theme: DEFAULT_BASE_THEME,
19
+ screenMode: DEFAULT_SCREEN_MODE,
20
+ };
21
+
22
+ const isNotProduction = () => {
23
+ try {
24
+ return ['localhost', 'dev-wi.se'].includes(window.location.hostname);
25
+ } catch {
26
+ return false;
27
+ }
28
+ };
29
+
30
+ export const useTheme = (): ThemeHookValue => {
31
+ const theming = useContext(ThemeContext);
32
+
33
+ if (!theming && isNotProduction()) {
34
+ // eslint-disable-next-line no-console
35
+ console.warn('Call to useTheme outside a ThemeProvider');
36
+ }
37
+
38
+ const { theme, screenMode: contextScreenMode } = theming ?? FALLBACK_VALUES;
39
+
40
+ const screenMode = theme === DEFAULT_BASE_THEME ? DEFAULT_SCREEN_MODE : contextScreenMode;
41
+
42
+ return useMemo(
43
+ () => ({
44
+ theme,
45
+ screenMode,
46
+ isModern: isThemeModern(theme),
47
+ isForestGreenTheme: isForestGreenTheme(theme),
48
+ isScreenModeDark: isScreenModeDark(theme, screenMode),
49
+ className: getThemeClassName(theme, screenMode),
50
+ }),
51
+ [theme, screenMode],
52
+ );
53
+ };