-
Notifications
You must be signed in to change notification settings - Fork 87
Expand file tree
/
Copy pathSwitchableFluentThemeProvider.tsx
More file actions
148 lines (134 loc) · 4.49 KB
/
SwitchableFluentThemeProvider.tsx
File metadata and controls
148 lines (134 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useState, useMemo, createContext, useContext } from 'react';
import { FluentThemeProvider, lightTheme, darkTheme } from '@azure/communication-react';
import { Theme, PartialTheme } from '@fluentui/react';
import { getThemeFromLocalStorage, saveThemeToLocalStorage } from '../utils/localStorage';
/**
* A theme with an associated name.
*/
type NamedTheme = {
/** assigned name of theme */
name: string;
/** theme used for applying to components */
theme: PartialTheme | Theme;
};
/**
* Collection of NamedThemes
*/
type ThemeCollection = Record<string, NamedTheme>;
const defaultThemes: ThemeCollection = {
Light: {
name: 'Light',
theme: lightTheme
},
Dark: {
name: 'Dark',
theme: darkTheme
}
};
/**
* Interface for React useContext hook containing the FluentTheme and a setter to switch themes
*/
interface SwitchableFluentThemeContext {
/**
* Currently chosen theme.
* @defaultValue lightTheme
*/
currentTheme: NamedTheme;
/**
* Whether to display components right-to-left
* @defaultValue false
*/
currentRtl: boolean;
/**
* Setter for the current theme.
* If this the doesn't already exist in the theme context this will
* add that theme to the store.
*/
setCurrentTheme: (namedTheme: NamedTheme) => void;
/**
* Setter for the current rtl.
*/
setCurrentRtl: (rtl: boolean) => void;
/**
* All stored themes within the context
* @defaultValue defaultThemes
*/
themeStore: ThemeCollection;
}
const defaultTheme: NamedTheme = defaultThemes.Light as NamedTheme;
/**
* React useContext for FluentTheme state of SwitchableFluentThemeProvider
*/
const SwitchableFluentThemeContext = createContext<SwitchableFluentThemeContext>({
currentTheme: defaultTheme,
currentRtl: false,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
setCurrentTheme: (theme: NamedTheme) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
setCurrentRtl: (rtl: boolean) => {},
themeStore: defaultThemes
});
/**
* Props for SwitchableFluentThemeProvider
*/
export interface SwitchableFluentThemeProviderProps {
/** Children to be themed */
children: React.ReactNode;
/** Scope ID for context */
scopeId: string;
/**
* Initial set of themes to switch between.
* @defaultValue defaultThemes
*/
themes?: ThemeCollection;
}
/**
* @description Provider wrapped around FluentThemeProvider that stores themes in local storage
* to be switched via useContext hook.
* @param props - SwitchableFluentThemeProviderProps
* @remarks This makes use of the browser's local storage if available
*/
export const SwitchableFluentThemeProvider = (props: SwitchableFluentThemeProviderProps): JSX.Element => {
const { children, scopeId } = props;
const [themeStore, setThemeCollection] = useState<ThemeCollection>(props.themes ?? defaultThemes);
const themeFromStorage = getThemeFromLocalStorage(scopeId);
const initialTheme = themeStore[themeFromStorage || defaultTheme.name] ?? defaultTheme;
const [currentTheme, _setCurrentTheme] = useState<NamedTheme>(initialTheme);
const [currentRtl, _setCurrentRtl] = useState<boolean>(false);
const state = useMemo<SwitchableFluentThemeContext>(
() => ({
currentTheme,
setCurrentTheme: (namedTheme: NamedTheme): void => {
_setCurrentTheme(namedTheme);
// If this is a new theme, add to the theme store
if (!themeStore[namedTheme.name]) {
setThemeCollection({ ...themeStore, namedTheme });
}
// Save current selection to local storage. Note the theme itself
// is not saved to local storage, only the name.
if (typeof Storage !== 'undefined') {
saveThemeToLocalStorage(namedTheme.name, scopeId);
}
},
currentRtl,
setCurrentRtl: (rtl: boolean): void => {
_setCurrentRtl(rtl);
},
themeStore
}),
[currentTheme, currentRtl, scopeId, themeStore]
);
return (
<SwitchableFluentThemeContext.Provider value={state}>
<FluentThemeProvider fluentTheme={currentTheme.theme} rtl={currentRtl}>
{children}
</FluentThemeProvider>
</SwitchableFluentThemeContext.Provider>
);
};
/**
* React hook for programmatically accessing the switchable fluent theme.
*/
export const useSwitchableFluentTheme = (): SwitchableFluentThemeContext => useContext(SwitchableFluentThemeContext);