зеркало из
1
0
Форкнуть 0

Refactor SwitchableFluentThemeProvider: Remove THEMES singleton and refine exports (#186)

This commit is contained in:
James Burnside 2021-04-28 20:05:33 -07:00 коммит произвёл GitHub
Родитель 3488020641
Коммит 66820ff5cc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 158 добавлений и 141 удалений

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Remove THEMES singleton. Refine theme related exports.",
"packageName": "@azure/communication-ui",
"email": "mail@jamesburnside.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -75,15 +75,12 @@ export type CustomMessagePayload = {
content?: string;
};
// @public
export const DARK = "Dark";
// @public
export const darkTheme: PartialTheme;
// @public (undocumented)
export type DefaultMessageRendererType = (props: MessageProps) => JSX.Element;
// @public
export const defaultThemes: ThemeCollection;
// @public
export const ErrorBar: (props: ErrorBarProps) => JSX.Element | null;
@ -95,12 +92,6 @@ export type ErrorBarProps = {
styles?: BaseCustomStylesProps;
};
// @public
export type FluentTheme = {
name: string;
theme: PartialTheme | Theme;
};
// @public
export const FluentThemeProvider: (props: FluentThemeProviderProps) => JSX.Element;
@ -110,9 +101,6 @@ export interface FluentThemeProviderProps {
fluentTheme?: PartialTheme | Theme;
}
// @public
export const getThemeFromLocalStorage: (scopeId: string) => string | null;
// @public (undocumented)
export const GridLayout: (props: GridLayoutProps) => JSX.Element;
@ -158,12 +146,6 @@ export const labeledScreenShareButtonProps: IButtonProps;
// @public
export const labeledVideoButtonProps: IButtonProps;
// @public
export const LIGHT = "Light";
// @public
export const lightTheme: PartialTheme;
// @public (undocumented)
export type Message<T extends MessageTypes> = {
type: T;
@ -219,6 +201,12 @@ export interface MessageThreadStylesProps extends BaseCustomStylesProps {
// @public (undocumented)
export type MessageTypes = 'chat' | 'system' | 'custom';
// @public
export type NamedTheme = {
name: string;
theme: PartialTheme | Theme;
};
// @public
export const optionsButtonProps: IButtonProps;
@ -267,9 +255,6 @@ export interface ReadReceiptProps {
// @public
export const recordButtonProps: IButtonProps;
// @public
export const saveThemeToLocalStorage: (theme: string, scopeId: string) => void;
// @public
export const screenShareButtonProps: IButtonProps;
@ -308,8 +293,9 @@ export interface StreamMediaProps {
// @public
export interface SwitchableFluentThemeContext {
fluentTheme: FluentTheme;
setFluentTheme: (fluentTheme: FluentTheme) => void;
currentTheme: NamedTheme;
setCurrentTheme: (namedTheme: NamedTheme) => void;
themeStore: ThemeCollection;
}
// @public
@ -319,6 +305,7 @@ export const SwitchableFluentThemeProvider: (props: SwitchableFluentThemeProvide
export interface SwitchableFluentThemeProviderProps {
children: React_2.ReactNode;
scopeId: string;
themes?: ThemeCollection;
}
// @public (undocumented)
@ -332,12 +319,7 @@ export type SystemMessagePayload = {
};
// @public
export type ThemeMap = {
[key: string]: Theme | PartialTheme;
};
// @public
export const THEMES: ThemeMap;
export type ThemeCollection = Record<string, NamedTheme>;
// @public
export const ThemeSelector: (props: ThemeSelectorProps) => JSX.Element;
@ -346,7 +328,6 @@ export const ThemeSelector: (props: ThemeSelectorProps) => JSX.Element;
export interface ThemeSelectorProps {
horizontal?: boolean;
label?: string;
themeMap?: ThemeMap;
}
// @public
@ -356,8 +337,8 @@ export const ThemeToggler: (props: ThemeTogglerProps) => JSX.Element;
export interface ThemeTogglerProps {
label?: string;
layout?: string;
offTheme?: FluentTheme;
onTheme?: FluentTheme;
offTheme?: NamedTheme;
onTheme?: NamedTheme;
}
// @public

Просмотреть файл

@ -2,15 +2,12 @@
import React from 'react';
import { ChoiceGroup, IChoiceGroupOption, concatStyleSets } from '@fluentui/react';
import { THEMES, ThemeMap } from '../constants/themes';
import { useSwitchableFluentTheme } from '../providers/SwitchableFluentThemeProvider';
/**
* Props for ThemeSelector component
*/
export interface ThemeSelectorProps {
/** Optional map of themes with theme as the key. Defaults to map of light and dark themes. */
themeMap?: ThemeMap;
/** Optional label for selector component */
label?: string;
/** Optional boolean to arrange choices horizontally */
@ -20,12 +17,11 @@ export interface ThemeSelectorProps {
/**
* @description ChoiceGroup component for selecting the fluent theme context for SwitchableFluentThemeProvider
* @param props - ThemeSelectorProps
* @remarks - this must be a child of a SwitchableFluentThemeProvider
*/
export const ThemeSelector = (props: ThemeSelectorProps): JSX.Element => {
const { themeMap, label, horizontal } = props;
const { fluentTheme, setFluentTheme } = useSwitchableFluentTheme();
const themes = themeMap ? themeMap : THEMES;
const { label, horizontal } = props;
const { currentTheme, setCurrentTheme, themeStore } = useSwitchableFluentTheme();
const onChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement> | undefined,
@ -33,15 +29,15 @@ export const ThemeSelector = (props: ThemeSelectorProps): JSX.Element => {
): void => {
if (option) {
const themeName = option.key.toString();
const theme = THEMES[themeName];
setFluentTheme({ name: themeName, theme: theme });
const theme = themeStore[themeName];
setCurrentTheme(theme);
}
};
return (
<ChoiceGroup
label={label}
options={Object.keys(themes).map((theme) => ({
options={Object.keys(themeStore).map((theme) => ({
key: theme,
text: theme,
styles: concatStyleSets(
@ -50,7 +46,7 @@ export const ThemeSelector = (props: ThemeSelectorProps): JSX.Element => {
)
}))}
onChange={onChange}
selectedKey={fluentTheme.name}
selectedKey={currentTheme.name}
styles={concatStyleSets(
{ label: { padding: '0' } },
horizontal ? { flexContainer: { display: 'flex' } } : undefined

Просмотреть файл

@ -2,8 +2,9 @@
import React from 'react';
import { Toggle, mergeStyles } from '@fluentui/react';
import { LIGHT, DARK, lightTheme, darkTheme } from '../constants/themes';
import { useSwitchableFluentTheme, FluentTheme } from '../providers/SwitchableFluentThemeProvider';
import { defaultThemes } from '../constants';
import { NamedTheme } from '../types';
import { useSwitchableFluentTheme } from '../providers/SwitchableFluentThemeProvider';
import { themeTogglerStyles } from './styles/ThemeToggler.styles';
/**
@ -11,9 +12,9 @@ import { themeTogglerStyles } from './styles/ThemeToggler.styles';
*/
export interface ThemeTogglerProps {
/** Optional theme name for the on state of the toggler. Default is 'dark' theme. */
onTheme?: FluentTheme;
onTheme?: NamedTheme;
/** Optional theme name of off state of the toggler. Default is 'light' theme. */
offTheme?: FluentTheme;
offTheme?: NamedTheme;
/** Optional label for toggler component */
label?: string;
/** Optional layout styling for toggler component */
@ -23,19 +24,20 @@ export interface ThemeTogglerProps {
/**
* @description Toggler component for toggling the fluent theme context for SwitchableFluentThemeProvider
* @param props - ThemeTogglerProps
* @remarks - this must be a child of a SwitchableFluentThemeProvider
*/
export const ThemeToggler = (props: ThemeTogglerProps): JSX.Element => {
const { onTheme, offTheme, label, layout } = props;
const { fluentTheme, setFluentTheme } = useSwitchableFluentTheme();
const { currentTheme, setCurrentTheme } = useSwitchableFluentTheme();
const _onTheme: FluentTheme = onTheme ? onTheme : { name: DARK, theme: darkTheme };
const _offTheme: FluentTheme = offTheme ? offTheme : { name: LIGHT, theme: lightTheme };
const _onTheme: NamedTheme = onTheme ? onTheme : defaultThemes.dark;
const _offTheme: NamedTheme = offTheme ? offTheme : defaultThemes.light;
const onChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
if (checked) {
setFluentTheme(_onTheme);
setCurrentTheme(_onTheme);
} else {
setFluentTheme(_offTheme);
setCurrentTheme(_offTheme);
}
};
@ -48,7 +50,7 @@ export const ThemeToggler = (props: ThemeTogglerProps): JSX.Element => {
onText={_onTheme.name}
offText={_offTheme.name}
onChange={onChange}
checked={fluentTheme.name === _onTheme.name}
checked={currentTheme.name === _onTheme.name}
/>
</div>
);

Просмотреть файл

@ -1,11 +1,12 @@
// © Microsoft Corporation. All rights reserved.
import { Theme, PartialTheme } from '@fluentui/react-theme-provider';
import { ThemeCollection } from '../types';
import { PartialTheme } from '@fluentui/react-theme-provider';
/**
* Light theme designed ACS UI SDK components
*/
export const lightTheme: PartialTheme = {
const lightTheme: PartialTheme = {
palette: {
themePrimary: '#0078d4',
themeLighterAlt: '#eff6fc',
@ -35,7 +36,7 @@ export const lightTheme: PartialTheme = {
/**
* Dark theme designed ACS UI SDK components
*/
export const darkTheme: PartialTheme = {
const darkTheme: PartialTheme = {
palette: {
themePrimary: '#2899f5',
themeLighterAlt: '#02060a',
@ -63,39 +64,15 @@ export const darkTheme: PartialTheme = {
};
/**
* Light theme name for ACS UI SDK components
* Light and Dark default themes used throughout components.
*/
export const LIGHT = 'Light';
/**
* Dark theme name for ACS UI SDK components
*/
export const DARK = 'Dark';
/**
* Map of themes
*/
export type ThemeMap = {
[key: string]: Theme | PartialTheme;
export const defaultThemes: ThemeCollection = {
light: {
name: 'light',
theme: lightTheme
},
dark: {
name: 'dark',
theme: darkTheme
}
};
/**
* Default themes map for ACS UI SDK components
*/
export const THEMES: ThemeMap = {
[LIGHT]: lightTheme,
[DARK]: darkTheme
};
const LocalStorageKey_Theme = 'AzureCommunicationUI_Theme';
/**
* Function to get theme for ACS UI SDK components from LocalStorage
*/
export const getThemeFromLocalStorage = (scopeId: string): string | null =>
window.localStorage.getItem(LocalStorageKey_Theme + '_' + scopeId);
/**
* Function to save theme for ACS UI SDK components from LocalStorage
*/
export const saveThemeToLocalStorage = (theme: string, scopeId: string): void =>
window.localStorage.setItem(LocalStorageKey_Theme + '_' + scopeId, theme);

Просмотреть файл

@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
import { mergeStyles } from '@fluentui/react';
import { ThemeProvider, Theme, PartialTheme } from '@fluentui/react-theme-provider';
import { mergeThemes, Provider, teamsTheme, ThemeInput } from '@fluentui/react-northstar';
import { lightTheme } from '../constants/themes';
import { defaultThemes } from '../constants';
/**
* Props for FluentThemeProvider
@ -41,7 +41,7 @@ const initialFluentNorthstarTheme = mergeThemes(teamsTheme, {
export const FluentThemeProvider = (props: FluentThemeProviderProps): JSX.Element => {
const { fluentTheme, children } = props;
// if fluentTheme is not provided, default to light theme
const fluentUITheme = fluentTheme ?? lightTheme;
const fluentUITheme = fluentTheme ?? defaultThemes.light.theme;
const [fluentNorthstarTheme, setFluentNorthstarTheme] = useState<ThemeInput<any>>(initialFluentNorthstarTheme);
useEffect(() => {

Просмотреть файл

@ -1,39 +1,56 @@
// © Microsoft Corporation. All rights reserved.
import React, { useState, useMemo, createContext, useContext } from 'react';
import { Theme, PartialTheme } from '@fluentui/react-theme-provider';
import { THEMES, LIGHT, lightTheme, getThemeFromLocalStorage, saveThemeToLocalStorage } from '../constants/themes';
import { NamedTheme, ThemeCollection } from '../types';
import { defaultThemes } from '../constants';
import { FluentThemeProvider } from './FluentThemeProvider';
/**
* type for theme state of SwitchableFluentThemeProvider. Simply contains \@fluentui/react PartialTheme | Theme and an assigned name
*/
export type FluentTheme = {
/** assigned name of theme */
name: string;
/** theme used for applying to all ACS UI SDK components */
theme: PartialTheme | Theme;
};
const defaultTheme: NamedTheme = defaultThemes.light;
/**
* interface for React useContext hook containing the FluentTheme and a setter to switch themes
* Interface for React useContext hook containing the FluentTheme and a setter to switch themes
*/
export interface SwitchableFluentThemeContext {
/** FluentTheme state context used for FluentThemeProvider */
fluentTheme: FluentTheme;
/** setter for FluentTheme */
setFluentTheme: (fluentTheme: FluentTheme) => void;
/**
* Currently chosen theme.
* @defaultValue lightTheme
*/
currentTheme: NamedTheme;
/**
* 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;
/**
* All stored themes within the context
* @defaultValue defaultThemes
*/
themeStore: ThemeCollection;
}
const defaultTheme: FluentTheme = { name: LIGHT, theme: lightTheme };
const LocalStorageKey_Theme = 'AzureCommunicationUI_Theme';
/**
* Function to get theme from LocalStorage
*/
const getThemeFromLocalStorage = (scopeId: string): string | null =>
window.localStorage.getItem(LocalStorageKey_Theme + '_' + scopeId);
/**
* Function to save theme to LocalStorage
*/
const saveThemeToLocalStorage = (theme: string, scopeId: string): void =>
window.localStorage.setItem(LocalStorageKey_Theme + '_' + scopeId, theme);
/**
* React useContext for FluentTheme state of SwitchableFluentThemeProvider
*/
const SwitchableFluentThemeContext = createContext<SwitchableFluentThemeContext>({
fluentTheme: defaultTheme,
currentTheme: defaultTheme,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
setFluentTheme: (fluentTheme: FluentTheme) => {}
setCurrentTheme: (theme: NamedTheme) => {},
themeStore: { ...defaultThemes }
});
/**
@ -44,38 +61,52 @@ export interface SwitchableFluentThemeProviderProps {
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 themeFromStorage = getThemeFromLocalStorage(scopeId);
const [fluentTheme, _setFluentTheme] = useState<FluentTheme>(
themeFromStorage && THEMES[themeFromStorage]
? { name: themeFromStorage, theme: THEMES[themeFromStorage] }
: defaultTheme
);
const [themeStore, setThemeCollection] = useState<ThemeCollection>(props.themes ?? defaultThemes);
const themeMemo = useMemo<SwitchableFluentThemeContext>(
const themeFromStorage = getThemeFromLocalStorage(scopeId);
const initialTheme = themeStore[themeFromStorage || 'light'] ?? defaultTheme;
const [currentTheme, _setCurrentTheme] = useState<NamedTheme>(initialTheme);
const state = useMemo<SwitchableFluentThemeContext>(
() => ({
fluentTheme: fluentTheme,
setFluentTheme: (fluentTheme: FluentTheme): void => {
_setFluentTheme(fluentTheme);
if (typeof Storage !== 'undefined') {
saveThemeToLocalStorage(fluentTheme.name, scopeId);
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);
}
},
themeStore
}),
[fluentTheme, scopeId]
[currentTheme, scopeId, themeStore]
);
return (
<SwitchableFluentThemeContext.Provider value={themeMemo}>
<FluentThemeProvider fluentTheme={fluentTheme.theme}>{children}</FluentThemeProvider>
<SwitchableFluentThemeContext.Provider value={state}>
<FluentThemeProvider fluentTheme={currentTheme.theme}>{children}</FluentThemeProvider>
</SwitchableFluentThemeContext.Provider>
);
};

Просмотреть файл

@ -25,5 +25,7 @@ export type {
CustomMessage,
SystemMessagePayload,
ChatMessagePayload,
CustomMessagePayload
CustomMessagePayload,
NamedTheme,
ThemeCollection
} from './types';

Просмотреть файл

@ -0,0 +1,18 @@
// © Microsoft Corporation. All rights reserved.
import { Theme, PartialTheme } from '@fluentui/react-theme-provider';
/**
* A theme with an associated name.
*/
export type NamedTheme = {
/** assigned name of theme */
name: string;
/** theme used for applying to components */
theme: PartialTheme | Theme;
};
/**
* Collection of NamedThemes
*/
export type ThemeCollection = Record<string, NamedTheme>;

Просмотреть файл

@ -2,10 +2,11 @@
export * from './ChatConfig';
export * from './ChatMessage';
export * from './WebUiChatParticipant';
export * from './GalleryParticipant';
export * from './ListParticipant';
export * from './ParticipantStream';
export * from './CommunicationUiError';
export * from './CustomStylesProps';
export * from './DevicePermission';
export * from './GalleryParticipant';
export * from './ListParticipant';
export * from './ParticipantStream';
export * from './ThemeTypes';
export * from './WebUiChatParticipant';

Просмотреть файл

@ -2,7 +2,7 @@
import React from 'react';
import { withKnobs } from '@storybook/addon-knobs';
import { FluentThemeProvider, LIGHT, THEMES } from '@azure/communication-ui';
import { FluentThemeProvider, defaultThemes } from '@azure/communication-ui';
import { initializeIcons, loadTheme, mergeStyles } from '@fluentui/react';
import { DocsContainer } from '@storybook/addon-docs/blocks';
import { BackToTop, TableOfContents } from 'storybook-docs-toc';
@ -17,6 +17,8 @@ import {
loadTheme({});
initializeIcons();
const THEMES = defaultThemes;
export const parameters = {
layout: 'fullscreen',
docs: {
@ -51,8 +53,8 @@ export const parameters = {
};
const withThemeProvider = (Story: any, context: any) => {
const themeName = context.globals.theme;
let theme = THEMES[themeName];
const themeName = (context.globals.theme as string).toLowerCase();
let theme = THEMES[themeName]?.theme;
if (context.globals.customTheme !== '') {
try {
theme = JSON.parse(context.globals.customTheme);
@ -87,7 +89,7 @@ export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: LIGHT
defaultValue: defaultThemes.light.name
},
customTheme: {
name: 'Custom theme',

Просмотреть файл

@ -1,7 +1,7 @@
import {
audioButtonProps,
ControlBar,
darkTheme,
defaultThemes,
FluentThemeProvider,
hangupButtonProps,
optionsButtonProps,
@ -13,7 +13,7 @@ import React from 'react';
export const DarkControlBar = (): JSX.Element => {
return (
<FluentThemeProvider fluentTheme={darkTheme}>
<FluentThemeProvider fluentTheme={defaultThemes.dark.theme}>
<ControlBar>
<DefaultButton {...videoButtonProps} />
<DefaultButton {...audioButtonProps} />